pyprojkit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyprojkit-0.1.0/LICENSE +21 -0
- pyprojkit-0.1.0/PKG-INFO +310 -0
- pyprojkit-0.1.0/README.md +267 -0
- pyprojkit-0.1.0/pyproject.toml +143 -0
- pyprojkit-0.1.0/src/pyprojkit/__init__.py +104 -0
- pyprojkit-0.1.0/src/pyprojkit/_paths.py +43 -0
- pyprojkit-0.1.0/src/pyprojkit/_run.py +36 -0
- pyprojkit-0.1.0/src/pyprojkit/cli.py +58 -0
- pyprojkit-0.1.0/src/pyprojkit/config/__init__.py +79 -0
- pyprojkit-0.1.0/src/pyprojkit/config/base.py +84 -0
- pyprojkit-0.1.0/src/pyprojkit/config/profiles.py +97 -0
- pyprojkit-0.1.0/src/pyprojkit/config/project.py +194 -0
- pyprojkit-0.1.0/src/pyprojkit/config/tools.py +170 -0
- pyprojkit-0.1.0/src/pyprojkit/discovery.py +51 -0
- pyprojkit-0.1.0/src/pyprojkit/sessions.py +71 -0
- pyprojkit-0.1.0/src/pyprojkit/sync.py +243 -0
- pyprojkit-0.1.0/src/pyprojkit/tasks.py +375 -0
- pyprojkit-0.1.0/src/pyprojkit/versions.py +114 -0
pyprojkit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Matt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pyprojkit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyprojkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Development workflow toolkit for Python projects: pyproject.toml sync, doit tasks, nox sessions
|
|
5
|
+
Author: mm21
|
|
6
|
+
Author-email: mm21 <mm21.dev@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Natural Language :: English
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Dist: doit>=0.36.0,<0.37
|
|
20
|
+
Requires-Dist: nox>=2026.4.10
|
|
21
|
+
Requires-Dist: python-dotenv>=1.0.1,<2
|
|
22
|
+
Requires-Dist: toml-sort>=0.24.2,<0.25
|
|
23
|
+
Requires-Dist: tomlkit>=0.13.3,<0.14
|
|
24
|
+
Requires-Dist: pyprojkit[analysis,doc,format,test] ; extra == 'all'
|
|
25
|
+
Requires-Dist: mypy[reports]>=1.5.1,<2 ; extra == 'analysis'
|
|
26
|
+
Requires-Dist: pyright>=1.1.329,<2 ; extra == 'analysis'
|
|
27
|
+
Requires-Dist: mkinit ; extra == 'doc'
|
|
28
|
+
Requires-Dist: autoflake>=2.3.1,<3 ; extra == 'format'
|
|
29
|
+
Requires-Dist: black>=25.1.0,<26 ; extra == 'format'
|
|
30
|
+
Requires-Dist: docformatter>=1.7.7,<2 ; extra == 'format'
|
|
31
|
+
Requires-Dist: isort>=6.0.1,<7 ; extra == 'format'
|
|
32
|
+
Requires-Dist: genbadge[tests,coverage]>=1.1.0,<2 ; extra == 'test'
|
|
33
|
+
Requires-Dist: pytest>=8.4.2,<9 ; extra == 'test'
|
|
34
|
+
Requires-Dist: pytest-cov>=7.1.0 ; extra == 'test'
|
|
35
|
+
Requires-Python: >=3.12, <3.15
|
|
36
|
+
Project-URL: homepage, https://github.com/mm21/pyprojkit
|
|
37
|
+
Provides-Extra: all
|
|
38
|
+
Provides-Extra: analysis
|
|
39
|
+
Provides-Extra: doc
|
|
40
|
+
Provides-Extra: format
|
|
41
|
+
Provides-Extra: test
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# PyProjKit
|
|
45
|
+
|
|
46
|
+
Development workflow toolkit for Python projects
|
|
47
|
+
|
|
48
|
+
[](https://pypi.org/project/pyprojkit)
|
|
49
|
+
[](https://pypi.org/project/pyprojkit)
|
|
50
|
+
[]()
|
|
51
|
+
[]()
|
|
52
|
+
[](https://github.com/psf/black)
|
|
53
|
+
|
|
54
|
+
- [PyProjKit](#pyprojkit)
|
|
55
|
+
- [Motivation](#motivation)
|
|
56
|
+
- [Getting started](#getting-started)
|
|
57
|
+
- [Declaring configuration: `pyprojconf.py`](#declaring-configuration-pyprojconfpy)
|
|
58
|
+
- [Python versions](#python-versions)
|
|
59
|
+
- [Tools and profiles](#tools-and-profiles)
|
|
60
|
+
- [Syncing `pyproject.toml`](#syncing-pyprojecttoml)
|
|
61
|
+
- [Managed content](#managed-content)
|
|
62
|
+
- [Check mode](#check-mode)
|
|
63
|
+
- [doit tasks](#doit-tasks)
|
|
64
|
+
- [nox sessions](#nox-sessions)
|
|
65
|
+
- [Path conventions](#path-conventions)
|
|
66
|
+
- [Custom profiles](#custom-profiles)
|
|
67
|
+
|
|
68
|
+
## Motivation
|
|
69
|
+
|
|
70
|
+
Every Python project accumulates the same development workflow boilerplate: formatter settings in `pyproject.toml`, supported Python versions repeated across classifiers, `requires-python`, black's `target-version`, and the testing matrix, plus a `dodo.py` and `noxfile.py` that look nearly identical from project to project. Keeping all of it consistent — and keeping it up to date as tools and Python versions evolve — is tedious and error-prone.
|
|
71
|
+
|
|
72
|
+
PyProjKit centralizes all of this. Each project declares its configuration exactly once in a `pyprojconf.py` at the project root; PyProjKit then:
|
|
73
|
+
|
|
74
|
+
1. **Syncs `pyproject.toml`**: writes the managed parts (version classifiers, `requires-python`, `[tool.*]` tables) while preserving everything else, with a `--check` mode for CI.
|
|
75
|
+
2. **Provides `doit` task factories**: format, test, badges, publish, and more, pre-wired to sensible conventions.
|
|
76
|
+
3. **Provides `nox` session factories**: a test session across all supported Python versions, pinned to exact patch releases.
|
|
77
|
+
|
|
78
|
+
Supported Python versions are declared once and drive everything: classifiers, `requires-python`, black's `target-version`, mypy's `python_version`, and the nox interpreter matrix.
|
|
79
|
+
|
|
80
|
+
## Getting started
|
|
81
|
+
|
|
82
|
+
Install from PyPI:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install pyprojkit
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Or add it to your project's dev dependencies (formatter, test, and analysis tools are available as extras):
|
|
89
|
+
|
|
90
|
+
```toml
|
|
91
|
+
[dependency-groups]
|
|
92
|
+
dev = [
|
|
93
|
+
"pyprojkit[all]"
|
|
94
|
+
]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Then drop three small files into your project root.
|
|
98
|
+
|
|
99
|
+
`pyprojconf.py`:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from pyprojkit import ProjectConfig, PythonVersions
|
|
103
|
+
|
|
104
|
+
config = ProjectConfig(package="my_package", python=PythonVersions(3, (12, 14)))
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`dodo.py`:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from pyprojkit import TaskFactory
|
|
111
|
+
|
|
112
|
+
globals().update(TaskFactory().create_all_tasks())
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`noxfile.py`:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from pyprojkit import NoxFactory
|
|
119
|
+
|
|
120
|
+
test = NoxFactory().create_test_session()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Then sync your `pyproject.toml` and run your workflow:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
doit sync # or: pyprojkit sync
|
|
127
|
+
doit format
|
|
128
|
+
doit test
|
|
129
|
+
doit badges
|
|
130
|
+
nox
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Declaring configuration: `pyprojconf.py`
|
|
134
|
+
|
|
135
|
+
`pyprojconf.py` must define a module-level `config: ProjectConfig`. `TaskFactory`, `NoxFactory`, and the `pyprojkit` CLI load it automatically from the current directory.
|
|
136
|
+
|
|
137
|
+
### Python versions
|
|
138
|
+
|
|
139
|
+
`PythonVersions` takes the major version and an inclusive range of minor versions — only versions you explicitly support (i.e. test against):
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
python = PythonVersions(3, (12, 14))
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This single declaration drives:
|
|
146
|
+
|
|
147
|
+
- `requires-python` (`>=3.12,<3.15`)
|
|
148
|
+
- Trove classifiers (`Programming Language :: Python :: 3.12`, ...)
|
|
149
|
+
- black's `target-version` (`["py312", "py313", "py314"]`)
|
|
150
|
+
- mypy's `python_version` (`3.12`)
|
|
151
|
+
- nox interpreter pins (`3.12.13`, `3.13.13`, `3.14.6`)
|
|
152
|
+
|
|
153
|
+
The exact patch releases used for nox come from a mapping bundled with PyProjKit, updated with each release — so updating PyProjKit refreshes the pins across all your projects. Individual projects can override:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
python = PythonVersions(3, (12, 14), patch_overrides={(3, 14): "3.14.7"})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Tools and profiles
|
|
160
|
+
|
|
161
|
+
Tool configurations are grouped by category under `ToolsConfig`, whose defaults constitute the default *profile*:
|
|
162
|
+
|
|
163
|
+
- `formatting`: ordered chain of formatters; by default autoflake → isort → black → docformatter → toml-sort
|
|
164
|
+
- `test`: pytest + coverage settings
|
|
165
|
+
- `doit`, `nox`: cache locations and backends
|
|
166
|
+
- `doc`: documentation tools — mkinit (enabled by default) and sphinx (opt-in)
|
|
167
|
+
- `analysis`: mypy + pyright (opt-in)
|
|
168
|
+
- `publish`: build/publish output directory
|
|
169
|
+
|
|
170
|
+
Customize with `dataclasses.replace`:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from dataclasses import replace
|
|
174
|
+
|
|
175
|
+
from pyprojkit import (
|
|
176
|
+
AnalysisConfig,
|
|
177
|
+
DocConfig,
|
|
178
|
+
ProjectConfig,
|
|
179
|
+
PythonVersions,
|
|
180
|
+
SphinxConfig,
|
|
181
|
+
ToolsConfig,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
config = ProjectConfig(
|
|
185
|
+
package="my_package",
|
|
186
|
+
python=PythonVersions(3, (12, 14)),
|
|
187
|
+
tools=replace(
|
|
188
|
+
ToolsConfig.default(),
|
|
189
|
+
doc=DocConfig(sphinx=SphinxConfig(copy_env_var="MY_PACKAGE_DOCS_DIR")),
|
|
190
|
+
analysis=AnalysisConfig(),
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
For one-off tweaks to managed tables there's an escape hatch, merged last into the synced output:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
tools=replace(
|
|
199
|
+
ToolsConfig.default(),
|
|
200
|
+
tool_overrides={"tool.pytest.ini_options": {"addopts": "-x"}},
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Syncing `pyproject.toml`
|
|
205
|
+
|
|
206
|
+
Formatters and other tools read their settings from `pyproject.toml`, so PyProjKit writes them there — `pyproject.toml` remains the single source of truth for the tools themselves, while `pyprojconf.py` is the single source of truth for you.
|
|
207
|
+
|
|
208
|
+
### Managed content
|
|
209
|
+
|
|
210
|
+
The sync engine owns:
|
|
211
|
+
|
|
212
|
+
- `project.requires-python`
|
|
213
|
+
- Python version classifiers (other classifiers are untouched)
|
|
214
|
+
- One `[tool.X]` table per enabled tool (fully owned; hand edits inside are overwritten)
|
|
215
|
+
- `[tool.pyprojkit].managed`: bookkeeping list of owned tables, so a tool dropped from your configuration gets its table cleanly removed on the next sync
|
|
216
|
+
|
|
217
|
+
Everything else — dependencies, `[tool.uv.sources]`, build system, unmanaged tool tables — is preserved. Output is normalized with toml-sort using the same settings as the managed `[tool.tomlsort]` table, so syncing and formatting never fight.
|
|
218
|
+
|
|
219
|
+
### Check mode
|
|
220
|
+
|
|
221
|
+
Verify a project is in sync without writing (e.g. in CI):
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
pyprojkit sync --check
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Prints a diff and exits nonzero if `pyproject.toml` is out of date.
|
|
228
|
+
|
|
229
|
+
## doit tasks
|
|
230
|
+
|
|
231
|
+
`TaskFactory` creates `doit` tasks pre-wired to the configuration. Create them selectively:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from pyprojkit import TaskFactory
|
|
235
|
+
|
|
236
|
+
factory = TaskFactory()
|
|
237
|
+
|
|
238
|
+
task_sync = factory.create_sync_task()
|
|
239
|
+
task_format = factory.create_format_task()
|
|
240
|
+
task_test = factory.create_test_task()
|
|
241
|
+
task_badges = factory.create_badges_task()
|
|
242
|
+
task_publish = factory.create_publish_task()
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
or all at once: `globals().update(factory.create_all_tasks())`, which creates every task enabled by the configuration.
|
|
246
|
+
|
|
247
|
+
| Task | Description |
|
|
248
|
+
| --- | --- |
|
|
249
|
+
| `sync` | Update `pyproject.toml` from `pyprojconf.py` |
|
|
250
|
+
| `format` | Run the configured formatter chain over the project |
|
|
251
|
+
| `test` | Run pytest with coverage (HTML + XML) and JUnit output |
|
|
252
|
+
| `badges` | Generate test/coverage badges via genbadge |
|
|
253
|
+
| `publish` | Build and publish via uv (output dir is cleaned first, so stale artifacts are never published) |
|
|
254
|
+
| `init` | Generate `__init__.py` files via mkinit (enabled by default) |
|
|
255
|
+
| `doc` | Build documentation via sphinx; `--copy` deploys to a directory given by an env var (opt-in) |
|
|
256
|
+
| `analysis` | Run mypy (with HTML/cobertura reports) and pyright (opt-in) |
|
|
257
|
+
|
|
258
|
+
The `format` task passes an explicit list of paths to each formatter — root `*.py`/`*.toml` files plus the configured `format_paths` directories that exist (`src`, `test`, `doc`, `examples` by default) — so formatters never wander into `.venv` or other unrelated trees.
|
|
259
|
+
|
|
260
|
+
## nox sessions
|
|
261
|
+
|
|
262
|
+
`NoxFactory.create_test_session()` registers a `test` session which, for each pinned interpreter, syncs the project's dev dependency group via uv into the session environment and runs pytest:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
$ nox -l
|
|
266
|
+
* test-3.12.13
|
|
267
|
+
* test-3.13.13
|
|
268
|
+
* test-3.14.6
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Extra arguments pass through to pytest: `nox -s test-3.12.13 -- -k my_test`.
|
|
272
|
+
|
|
273
|
+
## Path conventions
|
|
274
|
+
|
|
275
|
+
All tasks share the same layout:
|
|
276
|
+
|
|
277
|
+
- `__cache__/`: caches (doit db, pytest cache, coverage data, nox envs)
|
|
278
|
+
- `__out__/`: generated artifacts (`test/` coverage + JUnit results, `doc/html`, `analysis/`, `uv/` build artifacts)
|
|
279
|
+
- `badges/`: generated badge SVGs
|
|
280
|
+
|
|
281
|
+
## Custom profiles
|
|
282
|
+
|
|
283
|
+
A profile is a named factory for a pre-canned configuration, at two levels: the overall tools profile and the formatting profile it contains. The `"default"` profiles ship with PyProjKit; register your own once and reuse it across projects:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
# in your shared module, e.g. mycompany_profiles.py
|
|
287
|
+
from dataclasses import replace
|
|
288
|
+
|
|
289
|
+
from pyprojkit import AnalysisConfig, ToolsConfig, register_tools_profile
|
|
290
|
+
|
|
291
|
+
register_tools_profile(
|
|
292
|
+
"mycompany",
|
|
293
|
+
lambda: replace(ToolsConfig.default(), analysis=AnalysisConfig()),
|
|
294
|
+
)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
# in each project's pyprojconf.py
|
|
299
|
+
import mycompany_profiles # noqa: F401 -- registers the profile
|
|
300
|
+
|
|
301
|
+
from pyprojkit import ProjectConfig, PythonVersions, get_tools_profile
|
|
302
|
+
|
|
303
|
+
config = ProjectConfig(
|
|
304
|
+
package="my_package",
|
|
305
|
+
python=PythonVersions(3, (12, 14)),
|
|
306
|
+
tools=get_tools_profile("mycompany"),
|
|
307
|
+
)
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
New formatters are equally pluggable: subclass `BaseFormatterConfig` (declaring the tool's `[tool.X]` table and command line) and include it in a `FormattingConfig` — the sync engine and format task pick it up automatically.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# PyProjKit
|
|
2
|
+
|
|
3
|
+
Development workflow toolkit for Python projects
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/pyprojkit)
|
|
6
|
+
[](https://pypi.org/project/pyprojkit)
|
|
7
|
+
[]()
|
|
8
|
+
[]()
|
|
9
|
+
[](https://github.com/psf/black)
|
|
10
|
+
|
|
11
|
+
- [PyProjKit](#pyprojkit)
|
|
12
|
+
- [Motivation](#motivation)
|
|
13
|
+
- [Getting started](#getting-started)
|
|
14
|
+
- [Declaring configuration: `pyprojconf.py`](#declaring-configuration-pyprojconfpy)
|
|
15
|
+
- [Python versions](#python-versions)
|
|
16
|
+
- [Tools and profiles](#tools-and-profiles)
|
|
17
|
+
- [Syncing `pyproject.toml`](#syncing-pyprojecttoml)
|
|
18
|
+
- [Managed content](#managed-content)
|
|
19
|
+
- [Check mode](#check-mode)
|
|
20
|
+
- [doit tasks](#doit-tasks)
|
|
21
|
+
- [nox sessions](#nox-sessions)
|
|
22
|
+
- [Path conventions](#path-conventions)
|
|
23
|
+
- [Custom profiles](#custom-profiles)
|
|
24
|
+
|
|
25
|
+
## Motivation
|
|
26
|
+
|
|
27
|
+
Every Python project accumulates the same development workflow boilerplate: formatter settings in `pyproject.toml`, supported Python versions repeated across classifiers, `requires-python`, black's `target-version`, and the testing matrix, plus a `dodo.py` and `noxfile.py` that look nearly identical from project to project. Keeping all of it consistent — and keeping it up to date as tools and Python versions evolve — is tedious and error-prone.
|
|
28
|
+
|
|
29
|
+
PyProjKit centralizes all of this. Each project declares its configuration exactly once in a `pyprojconf.py` at the project root; PyProjKit then:
|
|
30
|
+
|
|
31
|
+
1. **Syncs `pyproject.toml`**: writes the managed parts (version classifiers, `requires-python`, `[tool.*]` tables) while preserving everything else, with a `--check` mode for CI.
|
|
32
|
+
2. **Provides `doit` task factories**: format, test, badges, publish, and more, pre-wired to sensible conventions.
|
|
33
|
+
3. **Provides `nox` session factories**: a test session across all supported Python versions, pinned to exact patch releases.
|
|
34
|
+
|
|
35
|
+
Supported Python versions are declared once and drive everything: classifiers, `requires-python`, black's `target-version`, mypy's `python_version`, and the nox interpreter matrix.
|
|
36
|
+
|
|
37
|
+
## Getting started
|
|
38
|
+
|
|
39
|
+
Install from PyPI:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install pyprojkit
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or add it to your project's dev dependencies (formatter, test, and analysis tools are available as extras):
|
|
46
|
+
|
|
47
|
+
```toml
|
|
48
|
+
[dependency-groups]
|
|
49
|
+
dev = [
|
|
50
|
+
"pyprojkit[all]"
|
|
51
|
+
]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then drop three small files into your project root.
|
|
55
|
+
|
|
56
|
+
`pyprojconf.py`:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from pyprojkit import ProjectConfig, PythonVersions
|
|
60
|
+
|
|
61
|
+
config = ProjectConfig(package="my_package", python=PythonVersions(3, (12, 14)))
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`dodo.py`:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from pyprojkit import TaskFactory
|
|
68
|
+
|
|
69
|
+
globals().update(TaskFactory().create_all_tasks())
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`noxfile.py`:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from pyprojkit import NoxFactory
|
|
76
|
+
|
|
77
|
+
test = NoxFactory().create_test_session()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Then sync your `pyproject.toml` and run your workflow:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
doit sync # or: pyprojkit sync
|
|
84
|
+
doit format
|
|
85
|
+
doit test
|
|
86
|
+
doit badges
|
|
87
|
+
nox
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Declaring configuration: `pyprojconf.py`
|
|
91
|
+
|
|
92
|
+
`pyprojconf.py` must define a module-level `config: ProjectConfig`. `TaskFactory`, `NoxFactory`, and the `pyprojkit` CLI load it automatically from the current directory.
|
|
93
|
+
|
|
94
|
+
### Python versions
|
|
95
|
+
|
|
96
|
+
`PythonVersions` takes the major version and an inclusive range of minor versions — only versions you explicitly support (i.e. test against):
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
python = PythonVersions(3, (12, 14))
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This single declaration drives:
|
|
103
|
+
|
|
104
|
+
- `requires-python` (`>=3.12,<3.15`)
|
|
105
|
+
- Trove classifiers (`Programming Language :: Python :: 3.12`, ...)
|
|
106
|
+
- black's `target-version` (`["py312", "py313", "py314"]`)
|
|
107
|
+
- mypy's `python_version` (`3.12`)
|
|
108
|
+
- nox interpreter pins (`3.12.13`, `3.13.13`, `3.14.6`)
|
|
109
|
+
|
|
110
|
+
The exact patch releases used for nox come from a mapping bundled with PyProjKit, updated with each release — so updating PyProjKit refreshes the pins across all your projects. Individual projects can override:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
python = PythonVersions(3, (12, 14), patch_overrides={(3, 14): "3.14.7"})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Tools and profiles
|
|
117
|
+
|
|
118
|
+
Tool configurations are grouped by category under `ToolsConfig`, whose defaults constitute the default *profile*:
|
|
119
|
+
|
|
120
|
+
- `formatting`: ordered chain of formatters; by default autoflake → isort → black → docformatter → toml-sort
|
|
121
|
+
- `test`: pytest + coverage settings
|
|
122
|
+
- `doit`, `nox`: cache locations and backends
|
|
123
|
+
- `doc`: documentation tools — mkinit (enabled by default) and sphinx (opt-in)
|
|
124
|
+
- `analysis`: mypy + pyright (opt-in)
|
|
125
|
+
- `publish`: build/publish output directory
|
|
126
|
+
|
|
127
|
+
Customize with `dataclasses.replace`:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from dataclasses import replace
|
|
131
|
+
|
|
132
|
+
from pyprojkit import (
|
|
133
|
+
AnalysisConfig,
|
|
134
|
+
DocConfig,
|
|
135
|
+
ProjectConfig,
|
|
136
|
+
PythonVersions,
|
|
137
|
+
SphinxConfig,
|
|
138
|
+
ToolsConfig,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
config = ProjectConfig(
|
|
142
|
+
package="my_package",
|
|
143
|
+
python=PythonVersions(3, (12, 14)),
|
|
144
|
+
tools=replace(
|
|
145
|
+
ToolsConfig.default(),
|
|
146
|
+
doc=DocConfig(sphinx=SphinxConfig(copy_env_var="MY_PACKAGE_DOCS_DIR")),
|
|
147
|
+
analysis=AnalysisConfig(),
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
For one-off tweaks to managed tables there's an escape hatch, merged last into the synced output:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
tools=replace(
|
|
156
|
+
ToolsConfig.default(),
|
|
157
|
+
tool_overrides={"tool.pytest.ini_options": {"addopts": "-x"}},
|
|
158
|
+
)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Syncing `pyproject.toml`
|
|
162
|
+
|
|
163
|
+
Formatters and other tools read their settings from `pyproject.toml`, so PyProjKit writes them there — `pyproject.toml` remains the single source of truth for the tools themselves, while `pyprojconf.py` is the single source of truth for you.
|
|
164
|
+
|
|
165
|
+
### Managed content
|
|
166
|
+
|
|
167
|
+
The sync engine owns:
|
|
168
|
+
|
|
169
|
+
- `project.requires-python`
|
|
170
|
+
- Python version classifiers (other classifiers are untouched)
|
|
171
|
+
- One `[tool.X]` table per enabled tool (fully owned; hand edits inside are overwritten)
|
|
172
|
+
- `[tool.pyprojkit].managed`: bookkeeping list of owned tables, so a tool dropped from your configuration gets its table cleanly removed on the next sync
|
|
173
|
+
|
|
174
|
+
Everything else — dependencies, `[tool.uv.sources]`, build system, unmanaged tool tables — is preserved. Output is normalized with toml-sort using the same settings as the managed `[tool.tomlsort]` table, so syncing and formatting never fight.
|
|
175
|
+
|
|
176
|
+
### Check mode
|
|
177
|
+
|
|
178
|
+
Verify a project is in sync without writing (e.g. in CI):
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
pyprojkit sync --check
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Prints a diff and exits nonzero if `pyproject.toml` is out of date.
|
|
185
|
+
|
|
186
|
+
## doit tasks
|
|
187
|
+
|
|
188
|
+
`TaskFactory` creates `doit` tasks pre-wired to the configuration. Create them selectively:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from pyprojkit import TaskFactory
|
|
192
|
+
|
|
193
|
+
factory = TaskFactory()
|
|
194
|
+
|
|
195
|
+
task_sync = factory.create_sync_task()
|
|
196
|
+
task_format = factory.create_format_task()
|
|
197
|
+
task_test = factory.create_test_task()
|
|
198
|
+
task_badges = factory.create_badges_task()
|
|
199
|
+
task_publish = factory.create_publish_task()
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
or all at once: `globals().update(factory.create_all_tasks())`, which creates every task enabled by the configuration.
|
|
203
|
+
|
|
204
|
+
| Task | Description |
|
|
205
|
+
| --- | --- |
|
|
206
|
+
| `sync` | Update `pyproject.toml` from `pyprojconf.py` |
|
|
207
|
+
| `format` | Run the configured formatter chain over the project |
|
|
208
|
+
| `test` | Run pytest with coverage (HTML + XML) and JUnit output |
|
|
209
|
+
| `badges` | Generate test/coverage badges via genbadge |
|
|
210
|
+
| `publish` | Build and publish via uv (output dir is cleaned first, so stale artifacts are never published) |
|
|
211
|
+
| `init` | Generate `__init__.py` files via mkinit (enabled by default) |
|
|
212
|
+
| `doc` | Build documentation via sphinx; `--copy` deploys to a directory given by an env var (opt-in) |
|
|
213
|
+
| `analysis` | Run mypy (with HTML/cobertura reports) and pyright (opt-in) |
|
|
214
|
+
|
|
215
|
+
The `format` task passes an explicit list of paths to each formatter — root `*.py`/`*.toml` files plus the configured `format_paths` directories that exist (`src`, `test`, `doc`, `examples` by default) — so formatters never wander into `.venv` or other unrelated trees.
|
|
216
|
+
|
|
217
|
+
## nox sessions
|
|
218
|
+
|
|
219
|
+
`NoxFactory.create_test_session()` registers a `test` session which, for each pinned interpreter, syncs the project's dev dependency group via uv into the session environment and runs pytest:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
$ nox -l
|
|
223
|
+
* test-3.12.13
|
|
224
|
+
* test-3.13.13
|
|
225
|
+
* test-3.14.6
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Extra arguments pass through to pytest: `nox -s test-3.12.13 -- -k my_test`.
|
|
229
|
+
|
|
230
|
+
## Path conventions
|
|
231
|
+
|
|
232
|
+
All tasks share the same layout:
|
|
233
|
+
|
|
234
|
+
- `__cache__/`: caches (doit db, pytest cache, coverage data, nox envs)
|
|
235
|
+
- `__out__/`: generated artifacts (`test/` coverage + JUnit results, `doc/html`, `analysis/`, `uv/` build artifacts)
|
|
236
|
+
- `badges/`: generated badge SVGs
|
|
237
|
+
|
|
238
|
+
## Custom profiles
|
|
239
|
+
|
|
240
|
+
A profile is a named factory for a pre-canned configuration, at two levels: the overall tools profile and the formatting profile it contains. The `"default"` profiles ship with PyProjKit; register your own once and reuse it across projects:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
# in your shared module, e.g. mycompany_profiles.py
|
|
244
|
+
from dataclasses import replace
|
|
245
|
+
|
|
246
|
+
from pyprojkit import AnalysisConfig, ToolsConfig, register_tools_profile
|
|
247
|
+
|
|
248
|
+
register_tools_profile(
|
|
249
|
+
"mycompany",
|
|
250
|
+
lambda: replace(ToolsConfig.default(), analysis=AnalysisConfig()),
|
|
251
|
+
)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
# in each project's pyprojconf.py
|
|
256
|
+
import mycompany_profiles # noqa: F401 -- registers the profile
|
|
257
|
+
|
|
258
|
+
from pyprojkit import ProjectConfig, PythonVersions, get_tools_profile
|
|
259
|
+
|
|
260
|
+
config = ProjectConfig(
|
|
261
|
+
package="my_package",
|
|
262
|
+
python=PythonVersions(3, (12, 14)),
|
|
263
|
+
tools=get_tools_profile("mycompany"),
|
|
264
|
+
)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
New formatters are equally pluggable: subclass `BaseFormatterConfig` (declaring the tool's `[tool.X]` table and command line) and include it in a `FormattingConfig` — the sync engine and format task pick it up automatically.
|