Getting Started¶
Prerequisites¶
- Python 3.11+
pipandvenv
Setup¶
This creates a .venv in the appmecca-api/ directory (separate from the main project's venv).
Run Locally¶
# Set required environment variable
export APPMECCA_API_DATABASE_URL="postgresql://appmecca:password@localhost:5432/appmecca"
# Start uvicorn with hot reload
make run
make run starts uvicorn with --reload on port 8000. The API is then available at http://localhost:8000, with interactive docs at http://localhost:8000/api/docs and the OpenAPI schema at /api/openapi.json.
Port 8000 locally, 8080 in the container
make run uses 8000 for local convenience. The Docker image exposes and
serves on 8080 — that is the port the ALB health check and the
appmecca.yaml service definition both target.
No Database? No Problem.
GET /api/health works even without a database — it only touches PostgreSQL
when you pass ?deep=true, and reports degraded when that check fails.
For full local development, use the bundled Docker stack (below), a local
PostgreSQL instance, or an SSH tunnel to RDS.
Local Docker Stack¶
appmecca-api/dev/ contains a self-contained Postgres + API stack with the source bind-mounted and hot reload, so you can iterate without touching a deployed environment:
docker compose -f dev/docker-compose.yml up -d # API on 127.0.0.1:8080, PG on 127.0.0.1:5433
./dev/smoke.sh # quick verification
docker compose -f dev/docker-compose.yml down # keeps the PG volume
docker compose -f dev/docker-compose.yml down -v # drops the data too
First boot takes ~30 s — the entrypoint editable-installs the API and platform packages against the mounted source, waits for Postgres, runs alembic migrations, then starts uvicorn with --reload.
The platform repo also ships docker-compose.auth-flow.yml, which brings up PG + Redis + the API for exercising the full auth flow without AWS.
Project Layout¶
appmecca-api/
├── src/appmecca_api/ # Source code
│ ├── main.py # FastAPI app factory, middleware, /api/health
│ ├── handler.py # Thin `from appmecca_api.main import app` alias
│ ├── config.py # Pydantic Settings
│ ├── celery.py # Celery app + Beat schedule
│ ├── scheduler.py # DatabaseScheduler — Beat backed by cron_jobs
│ ├── hostnames.py # Wraps the canonical hostname formula
│ ├── aws_safety.py # Validated boto3 client factory
│ ├── auth/ # API keys, sessions, identity, scopes, OAuth
│ ├── db/ # Database layer
│ │ ├── connection.py # asyncpg pool management
│ │ ├── logs_connection.py # Pool for the appmecca_logs database
│ │ ├── notify.py # LISTEN/NOTIFY waiter for agent round-trips
│ │ └── repositories/ # Data access (base.py + one per table group)
│ ├── services/ # Business logic
│ ├── routes/ # REST endpoints
│ ├── models/ # Pydantic request/response models
│ ├── middleware/ # cache_headers.py
│ ├── tasks/ # Celery tasks
│ ├── canary/ # Conformance-probe service
│ ├── email/ # Email providers + rendering
│ └── data/ # api_cache_behaviours.yaml, hostname-aliases.yaml
├── tests/ # Test suite (unit/ + integration/)
├── dev/ # Local docker-compose stack
├── Dockerfile # Multi-stage build (requires PG_MAJOR build arg)
├── entrypoint.sh # Runs migrations, then execs the service command
├── appmecca.yaml # Deployment shape: api, worker, beat, canary
├── status-checks.yaml # Seeded into status_checks at container start
├── pyproject.toml # Package config
└── Makefile # Build targets
There are no alembic migrations in this repo
The migration chain lives in appmecca-common/src/appmecca/db/migrations/
and ships in the appmecca-common wheel. The API only calls
appmecca.db.migrate.ensure_database() from its entrypoint.
template.yaml and samconfig.toml are dead
Both files remain in the repository from the retired AWS SAM / Lambda
deployment. Nothing reads them — the API is deployed as a container,
with the rest of the platform, by appmecca publish <ref>.
Code Style¶
| Tool | Rule | Command |
|---|---|---|
| Black | 88 char line length, py311 target |
make format |
| isort | Sorted imports, black profile, appmecca_api first-party |
make format |
| flake8 | PEP 8 | make lint |
| mypy | disallow_untyped_defs, warn_return_any, check_untyped_defs |
make lint |
All functions must have type hints. Public methods must have Google-style docstrings. Use structlog for logging with event names and key-value pairs.
# Good
logger.info("build_triggered", app_name="myapp", branch="main")
# Bad
logger.info(f"Triggered build for myapp on main")
Broad exception handlers must justify themselves. An except Exception: pass (or any falsy-sentinel return) needs either a logger.* call in the handler or a comment saying what error class is absorbed, why silence is safe, and what the fallback produces. A bare # noqa: BLE001 is not a justification, and the platform's make check-exceptions gate runs strict.
Makefile Targets¶
| Target | Description |
|---|---|
make help |
Show all targets |
make install |
Create .venv and install runtime dependencies |
make dev |
install + dev dependencies |
make run |
Start local uvicorn with --reload on port 8000 |
make test |
Run all tests with coverage |
make test-unit |
Run unit tests only |
make lint |
flake8 + mypy |
make format |
black + isort |
make format-check |
Check formatting without changes |
make clean |
Remove build artifacts |
make migrate |
Run appmecca.db.migrate.ensure_database() against $APPMECCA_DATABASE_URL |
make migrate-revision |
Error stub — prints where to actually create a revision |
make build and make deploy ENV=x are dead targets
They still exist in the Makefile and still shell out to sam build /
sam deploy, but SAM is not part of the deployment path. Use
appmecca publish <ref> -e <env> instead, which ships the API with the
rest of the platform. make migrate-revision is likewise
a stub that exits non-zero and tells you to run alembic from the platform
repo (its printed path is out of date — the chain is at
appmecca-common/src/appmecca/db/migrations/).
Relationship to the Platform¶
appmecca-api/ is its own repository, checked out as a submodule of the platform meta-repo. It has its own pyproject.toml, .venv, .flake8, Makefile and test suite, so day-to-day API work needs no other repo checked out.
It is not dependency-free, though — it installs appmecca-common and imports from it at runtime:
| Imported module | Used for |
|---|---|
appmecca.db.migrate |
The entrypoint's migration run |
appmecca.deploy.hostname |
The one canonical service-hostname formula |
appmecca.db.tls |
Shared datastore-TLS policy |
appmecca.email.service |
SES send path |
appmecca.deploy.cloudfront, .discovery, .git_preflight, .ns_scan, .storage_entitlement |
Shared deploy-side helpers |
Nothing in appmecca-common imports back from appmecca_api — the dependency runs one way.
For local work, make dev installs appmecca-common from the platform checkout. For CodeBuild, the build stages its wheel into the Docker context; without it the container crash-loops on ModuleNotFoundError: No module named 'appmecca'.
Never construct boto3 clients directly
Use appmecca_api.aws_safety.get_validated_client(...). A direct
boto3.client(...) fails the platform's make lint-boto3 CI gate, which
runs with an empty baseline — no grandfathering.
Cross-Repo Parity Gates¶
Some invariants span repositories and are enforced in CI and by every appmecca publish* command, which refuses to ship on drift:
| Invariant | Where it lives |
|---|---|
TERMINAL_STATUSES / SUCCESS_TERMINAL_STATUSES |
db/repositories/queue.py here, plus the agent's queue_client.py and deploy/service.py in appmecca-common |
The static_resolver shape logic |
services/static_resolver.py here mirrors appmecca/config/static_resolver.py |
| Hostname construction | Both wrappers call appmecca.deploy.hostname.canonical_hostname |
Adding a new terminal status means updating all three declarations in the same change.