Local development
Requirements
-
uv (Python 3.12 is installed by uv if missing)
-
GNU make
-
Read access to the private repositories pinned in
pyproject.toml:pybFlaskUtils,EventLoggerPySDKandMessagingEmail -
For SAM targets: the AWS SAM CLI. For
make run-local: Docker as well.
Quick start
make install # .venv from uv.lock, plus pre-commit hooks (ruff format, lint, complexity)
make check # lint (incl. complexity), typecheck, name-clash guard, tests
Every command CI runs is a make target, so a green make check locally means a green CI job, apart from
sam-validate and the lockfile check (uv lock --check).
Make targets
Usage: make <target> [ENV=dev|prod] [AWS_PROFILE=...]
help Show this help
install Create .venv with runtime and dev dependencies (uv sync) and install the git hooks
lock Re-resolve uv.lock after editing pyproject.toml
upgrade Upgrade every dependency to the newest allowed version
hooks Install the pre-commit git hooks (skipped in CI and outside a git checkout)
format Format code and apply safe lint autofixes (rewrites files)
lint Check formatting, lint rules and complexity (changes nothing)
typecheck Type-check src with mypy
test Run the test suite
test-cov Run the test suite with a coverage report (coverage.xml + terminal)
check-names Fail if a src/ top-level package name is shadowed by an installed distribution
check Run lint, typecheck, the name-clash guard and tests
docs-help Refresh the make target list included in the docs
clean Remove build output, caches and coverage files
sam-validate Validate template.yaml (sam validate --lint)
sam-build Build all functions into .aws-sam/
run-local Serve the API locally on :3000 (sam local start-api, needs Docker)
deploy Build and deploy the stack for ENV (dev or prod) using samconfig.toml
build-ApiFunction (sam) Build ApiFunction
build-WorkerFunction (sam) Build WorkerFunction
ENV (dev or prod) selects the samconfig.toml environment for SAM targets, and AWS_PROFILE is
passed to SAM as --profile.
Complexity limits
make lint fails when a function in src/, tests/ or scripts/ is too complex. Two measures are checked,
both configured in pyproject.toml:
| Measure | Limit | Tool and what it counts |
|---|---|---|
Cyclomatic complexity |
10 |
ruff |
Cognitive complexity |
15 |
complexipy ( |
To see the scores, run uv run complexipy --sort desc. When a function goes over, flatten it (early
returns, a lookup table instead of an if/elif chain, small helpers) rather than raising the limit.
Docstrings
make lint also fails when a module, class or public function in src/ or scripts/ has no docstring
(ruff D1; tests, magic methods and __init__ are exempt). Write what the code is responsible for and what
callers need to know, such as what it raises or returns when something is missing, rather than restating
the name.
Dependencies
-
Add or change a dependency in
pyproject.toml, then runmake lockand commituv.lock. -
make upgradere-resolves everything to the newest allowed versions. -
The private libraries are pinned to git tags in
[tool.uv.sources].
Tests
make test # pytest
make test-cov # with coverage (terminal + coverage.xml)
uv run --frozen pytest tests/integration/test_handlers.py # a subset
Tests need no AWS access or network. tests/conftest.py sets fake AWS credentials and
AWS_SECRETS_MANAGER=False before anything is imported, moto’s mock_aws provides DynamoDB, SNS and Kinesis,
and HTTP calls to internal APIs are mocked with requests-mock.
Running the API locally
make run-local AWS_PROFILE=dev
This builds the functions and serves ApiFunction on http://127.0.0.1:3000 with sam local start-api,
using the staging settings with triggers off. It reads common/crm and the _staging tables of the
staging account through AWS_PROFILE, so treat it as staging.
-
The first request takes a while: SAM pulls the Lambda image and starts a container.
-
SAM logs
pyb-api-gateway-authorizer not foundon each request. The authorizer lives in another stack, so it is skipped locally and requests go straight to the app. -
Run
make sam-buildagain after code changes.