Setup
The project requires Python 3.13 (>=3.13,<3.14) and uses uv as the only supported package manager — never pip, pip3, or python3 -m pip. uv resolves everything against the committed uv.lock, which guarantees a deterministic, reproducible environment across machines and CI; bare pip bypasses that lock file and causes environment drift.
Install dependencies
dev dependency group (pytest, bandit, and friends) from uv.lock.Everyday commands
Run everything throughuv run so it executes inside the project virtual environment.
uv run pytest uses --import-mode=importlib and excludes integration tests by default — both are configured in pyproject.toml under [tool.pytest.ini_options] via addopts = "--import-mode=importlib -m 'not integration'".Linting
All linters and formatters run through trunk, never as bare binaries. Tools likeruff, bandit, yamllint, shellcheck, prettier, and actionlint live inside trunk’s sandbox — invoking them directly either fails with command not found or picks up the wrong configuration.
trunk check --filter=<TOOL> <PATH> with the tool name from the CI output.
Layer rules
The repository enforces a strict layered architecture:libs/<service>/wraps one external SDK or API with idiomatic Python types and functions. Nolibs/<x>module may import fromlibs/<y>. If two adapters need to coordinate, that coordination belongs insrc/.src/is the orchestration layer: multi-step workflows, side effects, and Modal@app.function/@modal.fastapi_endpointdecorators. Adapter modules inlibs/must stay callable in isolation — no orchestration insidelibs/.cli/is Typer-only: parse arguments → preflight → call intosrc/→ render output. No business logic incli/.
Where new code goes
- External SDK call? New file in
libs/<service>/. Wrap one SDK only, no cross-lib imports. - Multi-step flow or Modal endpoint?
src/<service>/. If the module defines Modal endpoints, add its import to_ENDPOINT_MODULESinsrc/app.pyso the decorators register. - User-facing command?
cli/<group>/as a Typer subapp that calls intosrc/. Wire it intocli/main.pyviaapp.add_typer(...). - Standalone data product?
data-gen/<product>/. Self-contained and independent of other data products. - Webhook handler?
webhooks/<name>.py. Each handler is an independent Modal app — do not register it insrc/app.py.
Testing
- Tests mirror the source layout:
tests/cli/,tests/libs/,tests/src/, andtests/integration/. Put new tests in the directory that mirrors the code under test. - Tests that hit live external APIs carry the
integrationmarker, defined inpyproject.toml. The defaultaddoptsfilter (-m 'not integration') keeps them out of ordinary runs, souv run pytestnever needs credentials. - Plain
assertstatements are allowed in tests — ruff’sS101rule is ignored fortests/**via a per-file ignore inpyproject.toml.
Conventions
-
Temporary files go in
tmp/only. The directory is gitignored. Never write scratch output to the repo root or next to source code. -
Anchor script file I/O on the script’s own directory, not the CWD.
uv run path/to/script.pydoes not change the working directory, so relative paths resolve from wherever the command was invoked and can silently write files to the wrong place: -
Scripts under
scripts/are directly executable withuvshebangs where practical. Scripts that need Infisical secrets must show the full flag form in their usage text (--projectId,--token,--env) rather than assuminginfisical inithas run: - Docstrings explain why, not what. Document decisions and gotchas inline, next to the code they affect.
-
No summary or investigation
.mdfiles. Live documentation belongs in code (docstrings, per-module READMEs); the docs site underdocs/is the only place for prose documentation.
Docs site
Documentation pages live indocs/ and are built with Mintlify. Local preview requires Node 24 (pinned in docs/.node-version): install the CLI with npm i -g mint, then run mint dev from inside docs/. Every page needs title and description frontmatter — the description becomes the page’s llms.txt entry.