Skip to content

Getting Started

Prerequisites

  • Python 3.11+
  • pip and venv

Setup

cd appmecca-api

# Install runtime + dev dependencies
make dev

# Verify installation
make test

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.