Files
atelier/languages/py-tooling.md
T
Jon Chery 4e433158cd docs(P03): complete language-derived extension — v0.4
---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---
2026-08-05 16:07:21 +00:00

5.0 KiB
Raw Blame History

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.
# 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.
# 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, 10100x 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.
# 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.
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).
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.