Skip to content

Commit 162c31c

Browse files
johnslavikpawamoy
andauthored
refactor: Split to griffe, griffecli and griffelib uv workspaces
Issue-408: #408 PR-434: #434 Co-authored-by: Timothée Mazzucotelli <dev@pawamoy.fr>
1 parent b4b502b commit 162c31c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+432
-121
lines changed

config/coverage.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
branch = true
33
parallel = true
44
source =
5-
src/griffe
5+
packages/griffelib/src/griffe
6+
packages/griffecli/src/griffecli
67
tests/
78

89
[coverage:paths]

config/ruff.toml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,30 @@ ignore = [
3333
"TD002", # Missing author in TODO
3434
"TD003", # Missing issue link on the line following this TODO
3535
"TRY003", # Avoid specifying long messages outside the exception class
36+
"UP007", # Use `X | Y` for type annotations
3637
]
3738

3839
logger-objects = ["griffe.logger"]
3940

4041
[lint.per-file-ignores]
41-
"src/griffe/__main__.py" = [
42+
"packages/griffecli/src/griffecli/__main__.py" = [
4243
"D100", # Missing module docstring
4344
]
44-
"src/griffe/_internal/cli.py" = [
45+
"packages/griffecli/src/griffecli/_internal/cli.py" = [
4546
"T201", # Print statement
4647
]
47-
"src/griffe/_internal/git.py" = [
48+
"packages/griffelib/src/griffe/_internal/git.py" = [
4849
"S603", # `subprocess` call: check for execution of untrusted input
4950
"S607", # Starting a process with a partial executable path
5051
]
51-
"src/griffe/_internal/agents/nodes/*.py" = [
52+
"packages/griffelib/src/griffe/_internal/agents/nodes/*.py" = [
5253
"ARG001", # Unused function argument
5354
"N812", # Lowercase `keyword` imported as non-lowercase `NodeKeyword`
5455
]
55-
"src/griffe/_internal/debug.py" = [
56+
"packages/griffelib/src/griffe/_internal/debug.py" = [
5657
"T201", # Print statement
5758
]
58-
"src/griffe/_internal/**.py" = [
59+
"packages/griffelib/src/griffe/_internal/**.py" = [
5960
"D100", # Missing docstring in public module
6061
]
6162
"scripts/*.py" = [
@@ -84,7 +85,7 @@ docstring-quotes = "double"
8485
ban-relative-imports = "all"
8586

8687
[lint.isort]
87-
known-first-party = ["griffe"]
88+
known-first-party = ["griffe", "griffecli"]
8889

8990
[lint.pydocstyle]
9091
convention = "google"

docs/guide/contributors/architecture.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ descriptions = {
2323
"site": "Documentation site, built with `make run mkdocs build` (git-ignored).",
2424
"src": "The source of our Python package(s). See [Sources](#sources) and [Program structure](#program-structure).",
2525
"src/griffe": "Our public API, exposed to users. See [Program structure](#program-structure).",
26-
"src/griffe/_internal": "Our internal API, hidden from users. See [Program structure](#program-structure).",
26+
"packages/griffelib/src/griffe/_internal": "Our internal API, hidden from users. See [Program structure](#program-structure).",
2727
"tests": "Our test suite. See [Tests](#tests).",
2828
".copier-answers.yml": "The answers file generated by [Copier](https://copier.readthedocs.io/en/stable/). See [Boilerplate](#boilerplate).",
2929
"devdeps.txt": "Our development dependencies specification. See [`make setup`][command-setup] command.",
@@ -98,19 +98,31 @@ The tools used in tasks have their configuration files stored in the `config` fo
9898

9999
## Sources
100100

101-
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.
101+
Sources are located in the `packages/` subfolders, following the [src-layout](https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout/).
102+
We use [Hatch](https://hatch.pypa.io/latest/) to build source and wheel distributions, and configure it in `pyproject.toml`.
102103

103104
## Tests
104105

105-
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.
106+
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.
106107

107108
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.
108109

109110
## Program structure
110111

111-
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`.
112+
Griffe is split into two pieces: the library and the CLI.
112113

113-
Users then import `griffe` directly, or import objects from it.
114+
Each of them has an internal API contained within an `_internal` folder:
115+
116+
- `packages/griffelib/src/griffe/_internal` for the library,
117+
- `packages/griffecli/src/griffecli/_internal` for the CLI.
118+
119+
Griffe can be installed in library-only mode, which means that the CLI package from `packages/griffecli` is not present.
120+
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.
121+
122+
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.
123+
124+
Users then import `griffe` directly, or import objects from it. If they don't have `griffecli` installed, they cannot import the CLI-related functionality,
125+
such as [`griffecli.check`][].
114126

115127
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.
116128

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

124136
cli.verbose = cli._not_verbose
125-
options = cli.parse_args(["src/griffe", "--noshow", "--reverse"])
137+
options = cli.parse_args(["packages/griffelib/src/griffe", "--noshow", "--reverse"])
126138
colors.START_COLOR = 128
127139
target = Target(options["fname"])
128140
with target.chdir_work():

docs/guide/contributors/workflow.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ Deprecated code should also be marked as legacy code. We use [Yore](https://pawa
5656

5757
Examples:
5858

59-
```python title="Remove function when we bump to 2.0"
59+
```python title="Remove function when we bump to 5.0"
60+
# YORE: Bump 5: Remove block.
61+
def deprecated_function():
62+
...
63+
```
6064

6165
```python title="Simplify imports when Python 3.15 is EOL"
6266
# YORE: EOL 3.15: Replace block with line 4.

docs/installation.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,67 @@ Griffe is a Python package, so you can install it with your favorite Python pack
5959

6060
</div>
6161

62+
## Install as a library only
63+
64+
If you only need the library for API introspection and analysis without the CLI tool, you can install `griffelib`:
65+
66+
=== ":simple-python: pip"
67+
```bash
68+
pip install griffelib
69+
```
70+
71+
<div class="result" markdown>
72+
73+
[pip](https://pip.pypa.io/en/stable/) is the main package installer for Python.
74+
75+
</div>
76+
77+
=== ":simple-pdm: pdm"
78+
```bash
79+
pdm add griffelib
80+
```
81+
82+
<div class="result" markdown>
83+
84+
[PDM](https://pdm-project.org/en/latest/) is an all-in-one solution for Python project management.
85+
86+
</div>
87+
88+
=== ":simple-poetry: poetry"
89+
```bash
90+
poetry add griffelib
91+
```
92+
93+
<div class="result" markdown>
94+
95+
[Poetry](https://python-poetry.org/) is an all-in-one solution for Python project management.
96+
97+
</div>
98+
99+
=== ":simple-rye: rye"
100+
```bash
101+
rye add griffelib
102+
```
103+
104+
<div class="result" markdown>
105+
106+
[Rye](https://rye.astral.sh/) is an all-in-one solution for Python project management, written in Rust.
107+
108+
</div>
109+
110+
=== ":simple-astral: uv"
111+
```bash
112+
uv add griffelib
113+
```
114+
115+
<div class="result" markdown>
116+
117+
[uv](https://docs.astral.sh/uv/) is an extremely fast Python package and project manager, written in Rust.
118+
119+
</div>
120+
121+
This installs the `griffe` package as usual, but without the CLI program and its dependencies.
122+
62123
## Install as a tool only
63124

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

106167
</div>
168+
169+
## Running Griffe
170+
171+
Once installed, you can run Griffe using the `griffe` command:
172+
173+
```console
174+
$ griffe check mypackage
175+
```
176+
177+
Or as a Python module:
178+
179+
```console
180+
$ python -m griffe check mypackage
181+
```

docs/reference/api/cli.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
## **Main API**
44

5-
::: griffe.main
5+
::: griffecli.main
66

7-
::: griffe.check
7+
::: griffecli.check
88

9-
::: griffe.dump
9+
::: griffecli.dump
1010

1111
## **Advanced API**
1212

13-
::: griffe.get_parser
13+
::: griffecli.get_parser

docs/reference/api/loggers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
::: griffe.LogLevel
1212

13-
::: griffe.DEFAULT_LOG_LEVEL
13+
::: griffecli.DEFAULT_LOG_LEVEL
1414
options:
1515
annotations_path: full
1616

duties.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from duty.context import Context
2525

2626

27-
PY_SRC_PATHS = (Path(_) for _ in ("src", "tests", "duties.py", "scripts"))
27+
PY_SRC_PATHS = (Path(_) for _ in ("packages/griffecli/src", "packages/griffelib/src", "tests", "duties.py", "scripts"))
2828
PY_SRC_LIST = tuple(str(_) for _ in PY_SRC_PATHS)
2929
PY_SRC = " ".join(PY_SRC_LIST)
3030
CI = os.environ.get("CI", "0") in {"1", "true", "yes", ""}
@@ -287,14 +287,27 @@ def check_api(ctx: Context, *cli_args: str) -> None:
287287
ctx.run(
288288
tools.griffe.check(
289289
"griffe",
290-
search=["src"],
290+
search=["packages/griffelib/src"],
291291
color=True,
292292
extensions=[
293293
"griffe_inherited_docstrings",
294294
"unpack_typeddict",
295295
],
296296
).add_args(*cli_args),
297-
title="Checking for API breaking changes",
297+
title="Checking for API breaking changes in Griffe library",
298+
nofail=True,
299+
)
300+
ctx.run(
301+
tools.griffe.check(
302+
"griffecli",
303+
search=["packages/griffecli/src"],
304+
color=True,
305+
extensions=[
306+
"griffe_inherited_docstrings",
307+
"unpack_typeddict",
308+
],
309+
).add_args(*cli_args),
310+
title="Checking for API breaking changes in Griffe CLI",
298311
nofail=True,
299312
)
300313

mkdocs.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ site_url: "https://mkdocstrings.github.io/griffe"
44
repo_url: "https://github.com/mkdocstrings/griffe"
55
repo_name: "mkdocstrings/griffe"
66
site_dir: "site"
7-
watch: [mkdocs.yml, README.md, CONTRIBUTING.md, CHANGELOG.md, src]
7+
watch: [mkdocs.yml, README.md, CONTRIBUTING.md, CHANGELOG.md, packages]
88
copyright: Copyright &copy; 2021 Timothée Mazzucotelli
99
edit_uri: edit/main/docs/
1010

@@ -218,7 +218,7 @@ plugins:
218218
- url: https://docs.python.org/3/objects.inv
219219
domains: [std, py]
220220
- https://typing-extensions.readthedocs.io/en/latest/objects.inv
221-
paths: [src, scripts, .]
221+
paths: [packages/griffelib/src, packages/griffecli/src, scripts, .]
222222
options:
223223
backlinks: tree
224224
docstring_options:

packages/griffecli/pyproject.toml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
[build-system]
2+
# pdm-backend is left here as a dependency of the version discovery script currently in use.
3+
# It may be removed in the future. See mkdocstrings/griffe#430
4+
requires = ["hatchling", "pdm-backend", "uv-dynamic-versioning>=0.7.0"]
5+
build-backend = "hatchling.build"
6+
7+
[project]
8+
name = "griffecli"
9+
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."
10+
authors = [{name = "Timothée Mazzucotelli", email = "dev@pawamoy.fr"}]
11+
license = "ISC"
12+
license-files = ["LICENSE"]
13+
requires-python = ">=3.10"
14+
keywords = ["api", "signature", "breaking-changes", "static-analysis", "dynamic-analysis"]
15+
dynamic = ["version", "dependencies"]
16+
classifiers = [
17+
"Development Status :: 5 - Production/Stable",
18+
"Intended Audience :: Developers",
19+
"Programming Language :: Python",
20+
"Programming Language :: Python :: 3",
21+
"Programming Language :: Python :: 3 :: Only",
22+
# YORE: EOL 3.10: Remove line.
23+
"Programming Language :: Python :: 3.10",
24+
# YORE: EOL 3.11: Remove line.
25+
"Programming Language :: Python :: 3.11",
26+
# YORE: EOL 3.12: Remove line.
27+
"Programming Language :: Python :: 3.12",
28+
# YORE: EOL 3.13: Remove line.
29+
"Programming Language :: Python :: 3.13",
30+
# YORE: EOL 3.14: Remove line.
31+
"Programming Language :: Python :: 3.14",
32+
"Topic :: Documentation",
33+
"Topic :: Software Development",
34+
"Topic :: Software Development :: Documentation",
35+
"Topic :: Utilities",
36+
"Typing :: Typed",
37+
]
38+
39+
[project.scripts]
40+
griffecli = "griffecli:main"
41+
42+
[tool.hatch.version]
43+
source = "code"
44+
path = "../../scripts/get_version.py"
45+
expression = "get_version()"
46+
47+
[tool.hatch.build.targets.sdist.force-include]
48+
"../../CHANGELOG.md" = "CHANGELOG.md"
49+
"../../LICENSE" = "LICENSE"
50+
"../../README.md" = "README.md"
51+
52+
[tool.hatch.metadata.hooks.uv-dynamic-versioning]
53+
# Dependencies are dynamically versioned; {{version}} is substituted at build time.
54+
dependencies = ["griffelib=={{version}}", "colorama>=0.4"]
55+
56+
[tool.hatch.build.targets.wheel]
57+
packages = ["src/griffecli"]
58+
59+
[tool.uv.sources]
60+
griffelib = { workspace = true }

0 commit comments

Comments
 (0)