4e433158cd
---ci--- project: atelier phase: 3 milestone: v0.4 status: complete phase_role: execution phase_tag: v0.3.3 requirements: covered: [ATELIER-102, ATELIER-103, ATELIER-104, ATELIER-105] partial: [] ---/ci---
99 lines
5.0 KiB
Markdown
99 lines
5.0 KiB
Markdown
# Python Tooling — Derived Application
|
||
|
||
> Applies Atelier's domain principles to Python tooling specifically.
|
||
> Derives from `domains/` docs; introduces no new P-rules (D-063).
|
||
> See `languages/python.md` for the language first-principles stub.
|
||
|
||
## ruff for Lint and Format (DevOps P2 Automation, C2 Clarity)
|
||
|
||
- **`ruff` replaces flake8 + black + isort + pyupgrade:** one tool, one config, one order of magnitude faster. Format is not debated in review (Clarity C2).
|
||
- **Rule selection is principled, not "everything":** `select = ["E", "F", "I", "UP", "B", "SIM"]` — each rule group has a one-line `# reason:` in `pyproject.toml`. Rules without a rationale are noise (Documentation P1 — docs are code).
|
||
- **`ruff format` is the formatter, `ruff check` is the linter:** run both in CI; the formatter is deterministic, the linter surfaces smells.
|
||
- **Applies `devops/P2`:** the format/lint gate runs on every push; a developer never waits for a reviewer to comment on style.
|
||
|
||
```toml
|
||
# pyproject.toml
|
||
[tool.ruff]
|
||
target-version = "py311"
|
||
line-length = 100
|
||
|
||
[tool.ruff.lint]
|
||
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
||
# reason: E/F = pyflakes+pycodestyle; I = isort; UP = pyupgrade; B = bugbear; SIM = simplification
|
||
|
||
[tool.ruff.format]
|
||
quote-style = "double"
|
||
```
|
||
|
||
## mypy and Type-Check Gate (DevOps P2 Automation, Data P7 Type Fidelity)
|
||
|
||
- **`mypy --strict` in CI, not in the editor:** strict flags (`disallow_untyped_defs`, `no_implicit_optional`, `warn_return_any`) are the floor. The editor runs a relaxed mypy for speed; CI runs strict as the gate.
|
||
- **`pyright` for stricter/async-aware checking:** pyright understands `async` better and reports faster; mypy is the standard. Pick one as the gate, run the other as informational.
|
||
- **Per-module overrides only with a tracked reason:** `[[tool.mypy.overrides]] module = "legacy.*" ignore_errors = true` — each override block links to a ticket. Untracked overrides accumulate into a permanently untyped core.
|
||
- **`py.typed` marker for libraries:** ships the type info to consumers. Without it, downstream mypy treats the library as `Any`.
|
||
|
||
```bash
|
||
# CI gate
|
||
mypy --strict src/
|
||
pyright src/ || true # informational
|
||
```
|
||
|
||
## Dependency Management: poetry and uv (DevOps P1 Reproducibility)
|
||
|
||
- **`poetry` or `uv` for lockfile discipline:** both produce a deterministic lock (`poetry.lock` / `uv.lock`). `pip install` alone does not — it resolves at install time, producing different trees across machines.
|
||
- **`uv` for speed (Rust-based, 10–100x faster):** newer tool, same lockfile semantics. Either is acceptable; do not mix within a repo.
|
||
- **Lockfile committed for applications:** for libraries, commit the lock for CI reproducibility even though consumers resolve their own tree.
|
||
- **`--frozen` install in CI:** `poetry install --no-dev --frozen` fails if the lock is out of sync. Prevents a "works on my machine" drift.
|
||
|
||
```bash
|
||
# CI install — deterministic
|
||
uv sync --frozen --no-dev
|
||
# or
|
||
poetry install --no-dev --frozen
|
||
```
|
||
|
||
## Virtualenv Discipline (DevOps P1 Reproducibility, C3 Simplicity)
|
||
|
||
- **One virtualenv per project, never the system Python:** `uv venv` or `python -m venv .venv`. System Python drift breaks reproducibility.
|
||
- **`uv` creates and pins the Python version:** `uv venv --python 3.12` ensures the same interpreter across machines. A pinned Python is part of the reproducibility contract, not just the lockfile.
|
||
- **No `pip install` into the system Python in CI:** use `uv`/`poetry`'s venv. A CI step that mutates system Python makes the next job non-hermetic.
|
||
|
||
```bash
|
||
uv venv --python 3.12
|
||
source .venv/bin/activate
|
||
uv pip install -r requirements.txt
|
||
```
|
||
|
||
## Documentation in the Pipeline (Documentation P1 Documentation is Code, DevOps P9 Documentation in the Pipeline)
|
||
|
||
- **`mkdocs` + `mkdocstrings` from docstrings:** API docs are generated from `google`- or `numpy`-style docstrings; the build fails on missing docstrings for public symbols (Documentation P1).
|
||
- **`doctest` blocks in docstrings are run by pytest:** a `>>>` example is a tested artifact; a stale example fails the build (Documentation P1, Testing P1).
|
||
- **`pyproject.toml` is the single source of tool config:** ruff, mypy, pytest, poetry all read from it. Do not scatter `.flake8`, `setup.cfg`, `mypy.ini`. One config file is one place to look (Clarity C2).
|
||
|
||
```python
|
||
def get_user(id: UUID) -> User:
|
||
"""Fetch a user by id.
|
||
|
||
Args:
|
||
id: the user's UUID.
|
||
|
||
Returns:
|
||
The User.
|
||
|
||
Raises:
|
||
NotFoundError: if the user does not exist.
|
||
|
||
Example:
|
||
>>> get_user(UUID('intentional-example-uuid'))
|
||
User(...)
|
||
"""
|
||
...
|
||
```
|
||
|
||
## Cross-References
|
||
|
||
- `domains/devops/ci-cd.md` — the pipeline gates that host ruff/mypy/poetry.
|
||
- `domains/devops/first-principles.md` — DevOps P1 Reproducibility, P2 Automation.
|
||
- `domains/documentation/first-principles.md` — Documentation P1 Documentation is Code.
|
||
- `languages/py-types.md` — the type rules mypy enforces reference this doc.
|
||
- `languages/py-testing.md` — the pytest config (`pyproject.toml [tool.pytest]`) detailed here. |