uv sync
cp .env.example .env
docker compose up -d
uv run prek install # or: pre-commit installSee the README for environment variables and running the
pipeline end to end, and docs/architecture.md
for how the codebase is organized.
Manage dependencies through uv, not by hand-editing pyproject.toml:
uv add "some-package" # runtime dependency
uv add --dev "some-package" # dev-only dependency (linting, tests, tooling)
uv remove some-packageThese update pyproject.toml and uv.lock together and keep them
consistent; the pre-commit hooks also re-export requirements.txt /
requirements-dev.txt from the lockfile, so those shouldn't be edited by
hand either.
Always run Python through uv run (uv run <script.py>, uv run pytest,
uv run opa-database ...) rather than invoking python/python3
directly, so the command runs against this project's synced environment
and pinned Python version instead of whatever interpreter happens to be
on PATH.
uv run ruff format --check .
uv run ruff check .
uv run ty check
uv run pytest
uv run prek run --all-filesThe first four are the same checks CI runs (.github/workflows/ci.yml).
prek run --all-files runs every configured pre-commit hook
(.pre-commit-config.yaml) against the whole repo, which is the reliable
way to know ahead of time whether pre-commit will pass on commit, rather
than finding out hook-by-hook as you commit.
Every public module, class, and function gets a
Google-style
docstring: an imperative one-line summary, optionally a blank line and
more detail, then Args:/Returns:/Raises: sections as needed. For
example:
"""Do something (imperative).
Longer description if needed.
Args:
param_name (type): What it is.
Returns:
type: What it is.
Raises:
SomeError: When this happens.
"""See src/opa_database/config.py or
src/opa_database/loaders/silver.py::replace_period for real examples in
this codebase. ruff's pydocstyle rules (D, part of lint.extend-select = ["ALL"] in pyproject.toml) enforce that public objects have a
docstring at all; following the Google section layout consistently is a
project convention on top of that.
main: production. Every commit onmainis a released version.develop: integration branch. All feature/fix work branches offdevelopand comes back via PR intodevelop, never directly intomain.
The end-to-end flow:
- Branch off
develop, do the work, open a PR back intodevelop. CI (.github/workflows/ci.yml) runs on the PR:check-pr-titlevalidates the title against Conventional Commits (feat:,fix:,chore:,docs:,refactor:,perf:,test:,ci:,build:,style:,revert:, optionally scoped likefeat(silver): ...),validateruns formatting/lint/type checks, andtestruns the pytest suite. All three must pass before merging. - Once merged into
develop, that push triggersprep-release-pr: CI automatically opens (or updates, if one is already open) achore: release vX.Y.ZPR fromdevelopintomain, with a preview of the commits it would include. This release PR is never opened by hand and keeps accumulating/updating itself as more PRs land ondevelop, so nothing needs to be done to maintain it. - When it's actually time to ship, merge that release PR into
main. That merge triggersautomate-release(python-semantic-release), which bumps the version inpyproject.toml, updatesCHANGELOG.md, tags the release, and publishes a GitHub release, all derived from the Conventional Commit history rather than written by hand.developis then fast-forwarded to matchmainso both branches stay in sync.
So in short: PRs always target develop; develop -> main PRs are
release PRs, generated and updated automatically, and merging one of
those is the actual release action.
Use the PR template's checklist before requesting review on a feature/fix PR.