Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion config/coverage.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
branch = true
parallel = true
source =
src/griffe
packages/griffe/src/griffe
tests/

[coverage:paths]
Expand Down
16 changes: 8 additions & 8 deletions config/ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,27 @@ ignore = [
"TRY003", # Avoid specifying long messages outside the exception class
]

logger-objects = ["griffe.logger"]
logger-objects = ["griffe.logger", "griffelib.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/griffelib/_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/griffelib/_internal/agents/nodes/*.py" = [
"ARG001", # Unused function argument
"N812", # Lowercase `keyword` imported as non-lowercase `NodeKeyword`
]
"src/griffe/_internal/debug.py" = [
"packages/griffelib/src/griffelib/_internal/debug.py" = [
"T201", # Print statement
]
"src/griffe/_internal/**.py" = [
"packages/griffelib/src/griffelib/_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", "griffelib", "griffecli"]

[lint.pydocstyle]
convention = "google"
Expand Down
2 changes: 1 addition & 1 deletion docs/extensions/official/runtime-objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This extension stores runtime objects corresponding to each loaded Griffe object
>>> griffe_data["parse"].extra
defaultdict(<class 'dict'>, {'runtime-objects': {'object': <function parse at 0x78685c951260>}})
>>> griffe_data["Module"].extra
defaultdict(<class 'dict'>, {'runtime-objects': {'object': <class 'griffe._internal.models.Module'>}})
defaultdict(<class 'dict'>, {'runtime-objects': {'object': <class 'griffelib._internal.models.Module'>}})
```

It can be useful in combination with mkdocstrings-python and custom templates, to iterate over object values or their attributes that couldn't be loaded by Griffe itself (for example, objects built dynamically and loaded as attributes won't have "members" to iterate over).
8 changes: 4 additions & 4 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/griffelib/_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 @@ -104,11 +104,11 @@ Sources are located in the `src` folder, following the [src-layout](https://pack

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.

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.
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 `griffelib._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`.
The internal API is contained within the `packages/griffelib/src/griffelib/_internal` folder. The top-level `griffe/__init__.py` module exposes all the public API, by importing the internal objects from `griffelib`, which itself imports from various submodules of `griffelib._internal`.

Users then import `griffe` directly, or import objects from it.

Expand All @@ -122,7 +122,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/griffelib", "--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
14 changes: 7 additions & 7 deletions docs/guide/users/loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,10 @@ from package1 import X
False
>>> package2["X"].target
Traceback (most recent call last):
File "griffe/_internal/models.py", line 1375, in _resolve_target
File "griffelib/_internal/models.py", line 1375, in _resolve_target
resolved = self.modules_collection.get_member(self.target_path)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "griffe/_internal/mixins.py", line 84, in get_member
File "griffelib/_internal/mixins.py", line 84, in get_member
return self.members[parts[0]].get_member(parts[1:]) # type: ignore[attr-defined]
~~~~~~~~~~~~^^^^^^^^^^
KeyError: 'package1'
Expand All @@ -226,13 +226,13 @@ The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "griffe/_internal/dataclasses.py", line 1310, in target
File "griffelib/_internal/dataclasses.py", line 1310, in target
self.resolve_target()
File "griffe/_internal/dataclasses.py", line 1369, in resolve_target
File "griffelib/_internal/dataclasses.py", line 1369, in resolve_target
self._resolve_target()
File "griffe/_internal/dataclasses.py", line 1377, in _resolve_target
File "griffelib/_internal/dataclasses.py", line 1377, in _resolve_target
raise AliasResolutionError(self) from error
griffe._internal.exceptions.AliasResolutionError: Could not resolve alias package2.X pointing at package1.X (in package2/__init__.py:1)
griffelib._internal.exceptions.AliasResolutionError: Could not resolve alias package2.X pointing at package1.X (in package2/__init__.py:1)
```

As you can see in the interpreter session above, Griffe did not resolve the `X` alias. When we tried to access its target object anyway, it failed with a `KeyError`, which was raised again as an [`AliasResolutionError`][griffe.AliasResolutionError].
Expand All @@ -250,7 +250,7 @@ False # Hmm?
>>> package2["X"].target
Traceback (most recent call last):
...
griffe._internal.exceptions.AliasResolutionError: Could not resolve alias package2.X pointing at package1.X (in package2/__init__.py:1)
griffelib._internal.exceptions.AliasResolutionError: Could not resolve alias package2.X pointing at package1.X (in package2/__init__.py:1)
```

The same exception again? What happened here? We loaded both packages, but Griffe still failed to resolve the alias. That is expected; here is the explanation.
Expand Down
12 changes: 6 additions & 6 deletions docs/guide/users/navigating.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ When [loading an object](loading.md), Griffe will give you back an instance of o
```python
>>> import griffe
>>> type(griffe.load("markdown"))
<class 'griffe._internal.models.Module'>
<class 'griffelib._internal.models.Module'>
>>> type(griffe.load("markdown.core.Markdown"))
<class 'griffe._internal.models.Class'>
<class 'griffelib._internal.models.Class'>
>>> type(griffe.load("markdown.Markdown"))
<class 'griffe._internal.models.Alias'>
<class 'griffelib._internal.models.Alias'>
>>> type(griffe.load("markdown.core.markdown"))
<class 'griffe._internal.models.Function'>
<class 'griffelib._internal.models.Function'>
>>> type(griffe.load("markdown.markdown"))
<class 'griffe._internal.models.Alias'>
<class 'griffelib._internal.models.Alias'>
>>> type(griffe.load("markdown.Markdown.references"))
<class 'griffe._internal.models.Attribute'>
<class 'griffelib._internal.models.Attribute'>
```

However deep the object is, Griffe loads the entire package. It means that in all the cases above, Griffe loaded the whole `markdown` package. The model instance Griffe gives you back is therefore part of a tree that you can navigate.
Expand Down
76 changes: 76 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,65 @@ 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 just `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>

## Install as a tool only

=== ":simple-python: pip"
Expand Down Expand Up @@ -104,3 +163,20 @@ 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
```

TIP: **Alternative CLI Package**
If you need to run Griffe without the full library, you can also use `python -m griffecli` instead of `python -m griffe`. This runs the CLI directly from the `griffecli` package.
4 changes: 2 additions & 2 deletions docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ Both commands accept a `-h`, `--help` argument to show all the available options

## Python library

As a library, Griffe exposes all its public API directly in the top-level module. It means you can simply import `griffe` to access all its API.
As a library, Griffe exposes all its public API directly in the top-level module. It means you can simply import `griffe` or `griffelib` to access all its API.

```python
import griffe
import griffe # or griffelib

griffe.load(...)
griffe.find_breaking_changes(...)
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/api/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Griffe is able to analyze code both statically and dynamically.

## **Main API**

::: griffe.visit
::: griffelib.visit

::: griffe.inspect

Expand Down
2 changes: 1 addition & 1 deletion docs/reference/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## **Main API**

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

::: griffe.check

Expand Down
2 changes: 1 addition & 1 deletion docs/reference/api/expressions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Expressions

::: griffe._internal.expressions
::: griffelib._internal.expressions
options:
members: false
show_root_heading: false
Expand Down
2 changes: 1 addition & 1 deletion duties.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ def check_api(ctx: Context, *cli_args: str) -> None:
*cli_args: Additional Griffe CLI arguments.
"""
ctx.run(
tools.griffe.check(
tools.griffecli.check(
"griffe",
search=["src"],
color=True,
Expand Down
57 changes: 57 additions & 0 deletions packages/griffecli/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[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 = "The Griffe CLI"
authors = [{name = "Timothée Mazzucotelli", email = "[email protected]"}]
license = "ISC"
license-files = ["LICENSE"]
requires-python = ">=3.10"
keywords = ["api", "signature", "breaking-changes", "static-analysis", "dynamic-analysis"]
dynamic = ["version", "dependencies", "optional-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.metadata.hooks.uv-dynamic-versioning]
# Dependencies are dynamically versioned; {{version}} is substituted at build time.
dependencies = ["griffelib=={{version}}", "colorama>=0.4"]

[tool.hatch.metadata.hooks.uv-dynamic-versioning.optional-dependencies]
# No optional dependencies for the CLI package

[tool.hatch.build.targets.sdist]

[tool.hatch.build.targets.wheel]
sources = ["src/"]
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.
Comment on lines +11 to +14
Copy link
Member

@pawamoy pawamoy Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see a mix of cross-ref, some using griffe.*, some using griffecli.* or griffelib.*, we should make them consistent.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes -- I'd opt for griffe where possible, as in #434 (comment)

"""

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",
]
Loading
Loading