Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
8239fd9
Fix NameError & reformat everything
johnslavik Nov 29, 2025
272cd11
Apply suggestions from code review
johnslavik Nov 30, 2025
e951084
Run migration script
johnslavik Nov 30, 2025
b2dad08
Initial plan
Copilot Nov 30, 2025
72b1bbd
Fix griffelib/griffecli separation and add proper exports
Copilot Nov 30, 2025
6b829c8
Fix test imports and paths for new package structure
Copilot Nov 30, 2025
bb98dca
Address code review feedback on comments and documentation
Copilot Nov 30, 2025
95c310e
Add version locking for griffe's dependencies on griffelib and griffecli
Copilot Nov 30, 2025
538c553
Use double brackets for version interpolation and re-export griffelib…
Copilot Nov 30, 2025
3b28334
Initial plan
Copilot Nov 30, 2025
e080e57
Fix missing pyproject.toml config and CLI program name
Copilot Nov 30, 2025
b310df2
Fix griffe._internal references to griffelib._internal in docs
Copilot Nov 30, 2025
e647fed
reapply yore fix -b2
pawamoy Dec 2, 2025
7be9780
access members from griffe in tests
pawamoy Dec 2, 2025
697e2ea
casing and order in pyproject.toml
pawamoy Dec 2, 2025
e2bdab5
Add info about griffelib to installation
johnslavik Jan 16, 2026
bfdd1be
Fix reference docs
johnslavik Jan 16, 2026
2b39ee3
Add more info on griffelib
johnslavik Jan 16, 2026
2814aa7
Fix documention build warnings
johnslavik Jan 18, 2026
b6bb973
Don't introduce the new `griffelib` namespace
johnslavik Jan 18, 2026
c7d2fe9
Fix packaging
johnslavik Jan 18, 2026
8a6b14a
Additional cleanups/fixes
johnslavik Jan 18, 2026
7dd30c3
Fix coverage config
johnslavik Jan 18, 2026
e2ea8b8
Remove pointless option in ruff config
johnslavik Jan 18, 2026
0571dda
Remove unnecessary change
johnslavik Jan 18, 2026
77b44fc
Fix cross-ref
johnslavik Jan 18, 2026
e0f3490
Fix warning
johnslavik Jan 18, 2026
4886bdb
Fix quality checks
johnslavik Jan 18, 2026
ebca858
Fix CLI glue code
johnslavik Jan 18, 2026
38abea4
Fix lint problems
johnslavik Jan 18, 2026
8a1e655
Fix typing problems
johnslavik Jan 18, 2026
a02b3ed
Update paths to inventories
johnslavik Jan 18, 2026
4e53073
Use double quoted strings
johnslavik Jan 18, 2026
09d0864
Fix inventory paths again
johnslavik Jan 18, 2026
f50fd18
Make API tests generic over packages
johnslavik Jan 18, 2026
a4ce6fb
Workaround single locations test
johnslavik Jan 18, 2026
c71d034
More descriptive duty names
johnslavik Jan 18, 2026
f37ffdc
Fix lint problems
johnslavik Jan 18, 2026
0c5610c
Fix test rewrite
johnslavik Jan 18, 2026
0b1ca26
Remove `public_objects` fixture reference
johnslavik Jan 18, 2026
449ff54
Fix tests to new layout
johnslavik Jan 18, 2026
e90f3c1
Fix import
johnslavik Jan 18, 2026
01d4d7c
Improve test structure
johnslavik Jan 18, 2026
8d91988
Workaround pytest moodiness
johnslavik Jan 18, 2026
333a1b6
Fix problems with building
johnslavik Jan 18, 2026
b455adf
Fix comment
johnslavik Jan 18, 2026
e44a595
Update the documentation
johnslavik Jan 18, 2026
ce2c62e
Apply suggestions from code review
johnslavik Jan 18, 2026
87ce03f
Apply suggestions from code review
johnslavik Jan 19, 2026
b550102
Update example comment in Griffe finder
johnslavik Jan 19, 2026
6f3b755
Keep original descriptions
johnslavik Jan 19, 2026
b3cf2f3
Revert one suggestion
johnslavik Jan 19, 2026
524f534
Add `"griffe"` to version debug check candidates
johnslavik Jan 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/coverage.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
branch = true
parallel = true
source =
src/griffe
packages/griffelib/src/griffe
packages/griffecli/src/griffecli
tests/

[coverage:paths]
Expand Down
14 changes: 7 additions & 7 deletions config/ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,24 @@ ignore = [
logger-objects = ["griffe.logger"]

[lint.per-file-ignores]
"src/griffe/__main__.py" = [
"packages/griffecli/src/griffecli/__main__.py" = [
"D100", # Missing module docstring
]
"src/griffe/_internal/cli.py" = [
"packages/griffecli/src/griffecli/_internal/cli.py" = [
"T201", # Print statement
]
"src/griffe/_internal/git.py" = [
"packages/griffelib/src/griffe/_internal/git.py" = [
"S603", # `subprocess` call: check for execution of untrusted input
"S607", # Starting a process with a partial executable path
]
"src/griffe/_internal/agents/nodes/*.py" = [
"packages/griffelib/src/griffe/_internal/agents/nodes/*.py" = [
"ARG001", # Unused function argument
"N812", # Lowercase `keyword` imported as non-lowercase `NodeKeyword`
]
"src/griffe/_internal/debug.py" = [
"packages/griffelib/src/griffe/_internal/debug.py" = [
"T201", # Print statement
]
"src/griffe/_internal/**.py" = [
"packages/griffelib/src/griffe/_internal/**.py" = [
"D100", # Missing docstring in public module
]
"scripts/*.py" = [
Expand Down Expand Up @@ -84,7 +84,7 @@ docstring-quotes = "double"
ban-relative-imports = "all"

[lint.isort]
known-first-party = ["griffe"]
known-first-party = ["griffe", "griffecli"]

[lint.pydocstyle]
convention = "google"
Expand Down
24 changes: 18 additions & 6 deletions docs/guide/contributors/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ descriptions = {
"site": "Documentation site, built with `make run mkdocs build` (git-ignored).",
"src": "The source of our Python package(s). See [Sources](#sources) and [Program structure](#program-structure).",
"src/griffe": "Our public API, exposed to users. See [Program structure](#program-structure).",
"src/griffe/_internal": "Our internal API, hidden from users. See [Program structure](#program-structure).",
"packages/griffelib/src/griffe/_internal": "Our internal API, hidden from users. See [Program structure](#program-structure).",
"tests": "Our test suite. See [Tests](#tests).",
".copier-answers.yml": "The answers file generated by [Copier](https://copier.readthedocs.io/en/stable/). See [Boilerplate](#boilerplate).",
"devdeps.txt": "Our development dependencies specification. See [`make setup`][command-setup] command.",
Expand Down Expand Up @@ -98,19 +98,31 @@ The tools used in tasks have their configuration files stored in the `config` fo

## Sources

Sources are located in the `src` folder, following the [src-layout](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/). We use [PDM-Backend](https://backend.pdm-project.org/) to build source and wheel distributions, and configure it in `pyproject.toml` to search for packages in the `src` folder.
Sources are located in the `packages/` subfolders, following the [src-layout](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/).
We use [Hatch](https://hatch.pypa.io/latest/) to build source and wheel distributions, and configure it in `pyproject.toml`.

## Tests

Our test suite is located in the `tests` folder. It is located outside of the sources as to not pollute distributions (it would be very wrong to publish a `tests` package as part of our distributions, since this name is extremely common), or worse, the public API. The `tests` folder is however included in our source distributions (`.tar.gz`), alongside most of our metadata and configuration files. Check out `pyproject.toml` to get the full list of files included in our source distributions.
Our test suite is located in the `tests` folder. It is located outside of the sources as to not pollute distributions (it would be very wrong to publish a `tests` package as part of our distributions, since this name is extremely common), or worse, the public API. The `tests` folder is however included in our source distributions (`.tar.gz`), alongside most of our metadata and configuration files. Check out our `pyproject.toml` files to get the full list of files included in our source distributions for every individual package within the `packages/` folder.

The test suite is based on [pytest](https://docs.pytest.org/en/8.2.x/). Test modules reflect our internal API structure, and except for a few test modules that test specific aspects of our API, each test module tests the logic from the corresponding module in the internal API. For example, `test_finder.py` tests code of the `griffe._internal.finder` internal module, while `test_functions` tests our ability to extract correct information from function signatures, statically. The general rule of thumb when writing new tests is to mirror the internal API. If a test touches to many aspects of the loading process, it can be added to the `test_loader` test module.

## Program structure

The internal API is contained within the `src/griffe/_internal` folder. The top-level `griffe/__init__.py` module exposes all the public API, by importing the internal objects from various submodules of `griffe._internal`.
Griffe is split into two pieces: the library and the CLI.

Users then import `griffe` directly, or import objects from it.
Each of them has an internal API contained within an `_internal` folder:

- `packages/griffelib/src/griffe/_internal` for the library,
- `packages/griffecli/src/griffecli/_internal` for the CLI.

Griffe can be installed in library-only mode, which means that the CLI package from `packages/griffecli` is not present.
Library-only mode can be preferred if the user does not utilize the CLI functionality of Griffe and does not want to incorporate its dependencies.

The top-level `packages/griffelib/src/griffe/__init__.py` module exposes all the public API available: it always re-exports internal objects from various submodules of `griffe._internal` and, if the CLI is installed, it re-exports the public API of `griffecli` as well.

Users then import `griffe` directly, or import objects from it. If they don't have `griffecli` installed, they cannot import the CLI-related functionality,
such as [`griffecli.check`][].

We'll be honest: our code organization is not the most elegant, but it works :shrug: Have a look at the following module dependency graph, which will basically tell you nothing except that we have a lot of inter-module dependencies. Arrows read as "imports from". The code base is generally pleasant to work with though.

Expand All @@ -122,7 +134,7 @@ if os.getenv("DEPLOY") == "true":
from pydeps.target import Target

cli.verbose = cli._not_verbose
options = cli.parse_args(["src/griffe", "--noshow", "--reverse"])
options = cli.parse_args(["packages/griffelib/src/griffe", "--noshow", "--reverse"])
colors.START_COLOR = 128
target = Target(options["fname"])
with target.chdir_work():
Expand Down
6 changes: 5 additions & 1 deletion docs/guide/contributors/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ Deprecated code should also be marked as legacy code. We use [Yore](https://pawa

Examples:

```python title="Remove function when we bump to 2.0"
```python title="Remove function when we bump to 5.0"
# YORE: Bump 5: Remove block.
def deprecated_function():
...
```

```python title="Simplify imports when Python 3.15 is EOL"
# YORE: EOL 3.15: Replace block with line 4.
Expand Down
75 changes: 75 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,67 @@ Griffe is a Python package, so you can install it with your favorite Python pack

</div>

## Install as a library only

If you only need the library for API introspection and analysis without the CLI tool, you can install `griffelib`:

=== ":simple-python: pip"
```bash
pip install griffelib
```

<div class="result" markdown>

[pip](https://pip.pypa.io/en/stable/) is the main package installer for Python.

</div>

=== ":simple-pdm: pdm"
```bash
pdm add griffelib
```

<div class="result" markdown>

[PDM](https://pdm-project.org/en/latest/) is an all-in-one solution for Python project management.

</div>

=== ":simple-poetry: poetry"
```bash
poetry add griffelib
```

<div class="result" markdown>

[Poetry](https://python-poetry.org/) is an all-in-one solution for Python project management.

</div>

=== ":simple-rye: rye"
```bash
rye add griffelib
```

<div class="result" markdown>

[Rye](https://rye.astral.sh/) is an all-in-one solution for Python project management, written in Rust.

</div>

=== ":simple-astral: uv"
```bash
uv add griffelib
```

<div class="result" markdown>

[uv](https://docs.astral.sh/uv/) is an extremely fast Python package and project manager, written in Rust.

</div>

This installs the `griffe` package as usual, but without the CLI program and its dependencies.

## Install as a tool only

=== ":simple-python: pip"
Expand Down Expand Up @@ -104,3 +165,17 @@ Griffe is a Python package, so you can install it with your favorite Python pack
[uv](https://docs.astral.sh/uv/) is an extremely fast Python package and project manager, written in Rust.

</div>

## Running Griffe

Once installed, you can run Griffe using the `griffe` command:

```console
$ griffe check mypackage
```

Or as a Python module:

```console
$ python -m griffe check mypackage
```
8 changes: 4 additions & 4 deletions docs/reference/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

## **Main API**

::: griffe.main
::: griffecli.main

::: griffe.check
::: griffecli.check

::: griffe.dump
::: griffecli.dump

## **Advanced API**

::: griffe.get_parser
::: griffecli.get_parser
2 changes: 1 addition & 1 deletion docs/reference/api/loggers.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

::: griffe.LogLevel

::: griffe.DEFAULT_LOG_LEVEL
::: griffecli.DEFAULT_LOG_LEVEL
options:
annotations_path: full

Expand Down
19 changes: 16 additions & 3 deletions duties.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from duty.context import Context


PY_SRC_PATHS = (Path(_) for _ in ("src", "tests", "duties.py", "scripts"))
PY_SRC_PATHS = (Path(_) for _ in ("packages/griffecli/src", "packages/griffelib/src", "tests", "duties.py", "scripts"))
PY_SRC_LIST = tuple(str(_) for _ in PY_SRC_PATHS)
PY_SRC = " ".join(PY_SRC_LIST)
CI = os.environ.get("CI", "0") in {"1", "true", "yes", ""}
Expand Down Expand Up @@ -287,14 +287,27 @@ def check_api(ctx: Context, *cli_args: str) -> None:
ctx.run(
tools.griffe.check(
"griffe",
search=["src"],
search=["packages/griffelib/src"],
color=True,
extensions=[
"griffe_inherited_docstrings",
"unpack_typeddict",
],
).add_args(*cli_args),
title="Checking for API breaking changes",
title="Checking for API breaking changes in Griffe library",
nofail=True,
)
ctx.run(
tools.griffe.check(
"griffecli",
search=["packages/griffecli/src"],
color=True,
extensions=[
"griffe_inherited_docstrings",
"unpack_typeddict",
],
).add_args(*cli_args),
title="Checking for API breaking changes in Griffe CLI",
nofail=True,
)

Expand Down
4 changes: 2 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ site_url: "https://mkdocstrings.github.io/griffe"
repo_url: "https://github.com/mkdocstrings/griffe"
repo_name: "mkdocstrings/griffe"
site_dir: "site"
watch: [mkdocs.yml, README.md, CONTRIBUTING.md, CHANGELOG.md, src]
watch: [mkdocs.yml, README.md, CONTRIBUTING.md, CHANGELOG.md, packages]
copyright: Copyright &copy; 2021 Timothée Mazzucotelli
edit_uri: edit/main/docs/

Expand Down Expand Up @@ -218,7 +218,7 @@ plugins:
- url: https://docs.python.org/3/objects.inv
domains: [std, py]
- https://typing-extensions.readthedocs.io/en/latest/objects.inv
paths: [src, scripts, .]
paths: [packages/griffelib/src, packages/griffecli/src, scripts, .]
options:
backlinks: tree
docstring_options:
Expand Down
60 changes: 60 additions & 0 deletions packages/griffecli/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
[build-system]
# pdm-backend is left here as a dependency of the version discovery script currently in use.
# It may be removed in the future. See mkdocstrings/griffe#430
requires = ["hatchling", "pdm-backend", "uv-dynamic-versioning>=0.7.0"]
build-backend = "hatchling.build"

[project]
name = "griffecli"
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
authors = [{name = "Timothée Mazzucotelli", email = "dev@pawamoy.fr"}]
license = "ISC"
license-files = ["LICENSE"]
requires-python = ">=3.10"
keywords = ["api", "signature", "breaking-changes", "static-analysis", "dynamic-analysis"]
dynamic = ["version", "dependencies"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
# YORE: EOL 3.10: Remove line.
"Programming Language :: Python :: 3.10",
# YORE: EOL 3.11: Remove line.
"Programming Language :: Python :: 3.11",
# YORE: EOL 3.12: Remove line.
"Programming Language :: Python :: 3.12",
# YORE: EOL 3.13: Remove line.
"Programming Language :: Python :: 3.13",
# YORE: EOL 3.14: Remove line.
"Programming Language :: Python :: 3.14",
"Topic :: Documentation",
"Topic :: Software Development",
"Topic :: Software Development :: Documentation",
"Topic :: Utilities",
"Typing :: Typed",
]

[project.scripts]
griffecli = "griffecli:main"

[tool.hatch.version]
source = "code"
path = "../../scripts/get_version.py"
expression = "get_version()"

[tool.hatch.build.targets.sdist.force-include]
"../../CHANGELOG.md" = "CHANGELOG.md"
"../../LICENSE" = "LICENSE"
"../../README.md" = "README.md"

[tool.hatch.metadata.hooks.uv-dynamic-versioning]
# Dependencies are dynamically versioned; {{version}} is substituted at build time.
dependencies = ["griffelib=={{version}}", "colorama>=0.4"]

[tool.hatch.build.targets.wheel]
packages = ["src/griffecli"]

[tool.uv.sources]
griffelib = { workspace = true }
27 changes: 27 additions & 0 deletions packages/griffecli/src/griffecli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# This top-level module imports all public names from the CLI package,
# and exposes them as public objects.

"""Griffe CLI package.
The CLI (Command Line Interface) for the griffe library.
This package provides command-line tools for interacting with griffe.
## CLI entrypoints
- [`griffecli.main`][]: Run the main program.
- [`griffecli.check`][]: Check for API breaking changes in two versions of the same package.
- [`griffecli.dump`][]: Load packages data and dump it as JSON.
- [`griffecli.get_parser`][]: Get the argument parser for the CLI.
"""

from __future__ import annotations

from griffecli._internal.cli import DEFAULT_LOG_LEVEL, check, dump, get_parser, main

__all__ = [
"DEFAULT_LOG_LEVEL",
"check",
"dump",
"get_parser",
"main",
]
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Entry-point module, in case you use `python -m griffe`.
# Entry-point module, in case you use `python -m griffecli`.
#
# Why does this file exist, and why `__main__`? For more info, read:
#
Expand All @@ -7,7 +7,7 @@

import sys

from griffe._internal.cli import main
from griffecli._internal.cli import main

if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
1 change: 1 addition & 0 deletions packages/griffecli/src/griffecli/_internal/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Internal modules for the griffecli package.
Loading
Loading