Cheat sheet — Modern Python Toolchain & Spec-Driven Skeleton¶
Companion to Module 01 — Modern Toolchain & Spec-Driven Skeleton · CC BY 4.0 — print it, pin it, share it.
Last reviewed: 2026-07
uv — projects, deps, and the lockfile¶
uv init sift # scaffold a new project (pyproject.toml, src/, .python-version)
uv init --lib mylib # scaffold a library layout instead of an app
uv python install 3.12 # install a managed Python; uv pins it via .python-version
uv add pydantic # add a dependency AND update uv.lock
uv add --dev ruff pyright # add dev-only deps (dependency-group "dev")
uv remove requests # drop a dependency and relock
uv lock # resolve + write uv.lock (pinned versions AND hashes)
uv sync # install exactly what uv.lock says into .venv
uv sync --frozen # install from the lock WITHOUT re-resolving (CI: fail if stale)
uv sync --locked # verify uv.lock is up to date, then sync (CI gate)
uv run python alert_parse.py # run a command in the project env (auto-syncs first)
uv run -- sift --help # run your entry point; -- ends uv's own flag parsing
uv tree # show the resolved dependency tree
uv export --format requirements-txt > requirements.txt # interop shim if something needs pip
uv.lock is the security artifact: it records the exact version and hash of every transitive
dependency. Commit it. uv sync --frozen / --locked in CI is what makes installs reproducible.
ruff — lint + format in one tool¶
ruff check # lint the project (replaces flake8/isort/pyupgrade/…)
ruff check --fix # apply autofixes
ruff check --fix --unsafe-fixes # include fixes that may change behavior — review these
ruff check --select E,F,I # run only chosen rule groups (E/F=pyflakes, I=import sort)
ruff check --statistics # count violations by rule — good for triaging a legacy file
ruff format # format code (a black-compatible formatter)
ruff format --check # report but don't write — the CI mode
ruff check --diff # preview lint fixes without writing
pyright — static type gate¶
pyright # type-check the whole project
pyright src/sift # check a path
pyright --stats # show inferred vs. declared coverage
pyright --outputjson # machine-readable output for tooling
pyright --warnings # treat warnings as failures (stricter gate)
Run it through the project env so it sees your deps: uv run pyright.
Minimal pyproject.toml¶
[project]
name = "sift"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["pydantic>=2"]
[project.scripts]
sift = "sift.cli:main" # console entry point -> uv run sift
[dependency-groups]
dev = ["ruff", "pyright"] # installed by uv sync, excluded from the built package
[tool.ruff]
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"] # errors, pyflakes, imports, pyupgrade, bugbear
[tool.pyright]
include = ["src"]
typeCheckingMode = "standard" # "basic" | "standard" | "strict"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
CI gate — fail the build, not on goodwill¶
# .github/workflows/ci.yml
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5 # installs uv, caches the resolver
- run: uv sync --locked # reproducible install; fails if uv.lock is stale
- run: uv run ruff check # lint
- run: uv run ruff format --check # format drift
- run: uv run pyright # types
- run: uv run python alert_parse.py sample.json # behavior unchanged
Spec-driven workflow (openspec)¶
npx openspec init # add openspec/ to the repo (specs live in version control)
npx openspec list # list active change specs
# 1. Write the change spec: what it does, the typed contract, acceptance checks.
# 2. Hand the spec to the copilot; let it implement.
# 3. Review the DIFF against the spec — sharper than reading code cold.
npx openspec validate <id> # check a change spec is well-formed
npx openspec archive <id> # move a completed change into the applied history
Alternative — GitHub spec-kit (Python-native; the pattern is tool-agnostic):
uvx specify init # scaffold spec-kit in the repo
# loop: /specify (what & why) -> /plan (how) -> /tasks (break down) -> implement
Pick one and stay consistent; the method — spec first, AI implements, you review against the spec — is what carries through the track, not the specific tool.
Gotchas worth remembering¶
- Pinned version ≠ pinned bytes.
torch==2.0.1still installs whatever is at that version now; a hash-lockeduv.lockrecords the artifact digest and refuses a swapped byte. Hashes are the actual defense against thetorchtritondependency-confusion class. - Commit
uv.lock. It is not a build artifact — it is the reproducibility guarantee. An uncommitted lock means CI resolves fresh and "works on my machine" comes right back. - Use
--frozen/--lockedin CI, not a plainuv sync. A bare sync will silently re-resolve and mask a stale lock; the flags make a drifted lockfile a build failure. - Migrate strangler-fig, don't big-bang. Wrap the working script unchanged, get the gate green around it, then refactor one slice behind the passing build — with the prior commit as rollback. Deleting and regenerating loses the behavior you can't see.
- Review AI output against the spec, not for vibes. The high-value catch is the copilot's
defaults: it reaches for
pip/venv/requirements.txt, skips lockfile hashes, and leaves types off. The spec is the lens that makes those omissions obvious. uv runauto-syncs. It reconciles.venvto the lock before running, so you rarely calluv syncby hand locally — but CI should sync explicitly so the failure is legible.
Comments
Sign in with GitHub to comment. Choose the type: Feedback (errors or suggestions on this page) · Hints (help for fellow learners — no spoilers) · General (anything else).