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---
5.0 KiB
5.0 KiB
Python Tooling — Derived Application
Applies Atelier's domain principles to Python tooling specifically. Derives from
domains/docs; introduces no new P-rules (D-063). Seelanguages/python.mdfor the language first-principles stub.
ruff for Lint and Format (DevOps P2 Automation, C2 Clarity)
ruffreplaces 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:inpyproject.toml. Rules without a rationale are noise (Documentation P1 — docs are code). ruff formatis the formatter,ruff checkis 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.
# 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 --strictin 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.pyrightfor stricter/async-aware checking: pyright understandsasyncbetter 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.typedmarker for libraries: ships the type info to consumers. Without it, downstream mypy treats the library asAny.
# CI gate
mypy --strict src/
pyright src/ || true # informational
Dependency Management: poetry and uv (DevOps P1 Reproducibility)
poetryoruvfor lockfile discipline: both produce a deterministic lock (poetry.lock/uv.lock).pip installalone does not — it resolves at install time, producing different trees across machines.uvfor 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.
--frozeninstall in CI:poetry install --no-dev --frozenfails if the lock is out of sync. Prevents a "works on my machine" drift.
# 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 venvorpython -m venv .venv. System Python drift breaks reproducibility. uvcreates and pins the Python version:uv venv --python 3.12ensures the same interpreter across machines. A pinned Python is part of the reproducibility contract, not just the lockfile.- No
pip installinto the system Python in CI: useuv/poetry's venv. A CI step that mutates system Python makes the next job non-hermetic.
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+mkdocstringsfrom docstrings: API docs are generated fromgoogle- ornumpy-style docstrings; the build fails on missing docstrings for public symbols (Documentation P1).doctestblocks in docstrings are run by pytest: a>>>example is a tested artifact; a stale example fails the build (Documentation P1, Testing P1).pyproject.tomlis 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).
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.