- Python 99.3%
- Mako 0.3%
- Makefile 0.2%
- Dockerfile 0.2%
| src/app | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| .python-version | ||
| alembic.ini | ||
| Dockerfile | ||
| Makefile | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
api-service
FastAPI backend for MedPartners. Domain-driven (feature-first) layout: each business
domain is a vertical slice router -> service -> repository + schemas; infrastructure
(api/, core/, db/, events/, storage/, audit/) is kept separate.
Quick start
cp .env.example .env # optional - sane local defaults are baked in
uv sync # install deps into .venv from the lockfile
make dev # uvicorn with --reload on :8080
- Swagger UI: http://localhost:8080/docs
- ReDoc: http://localhost:8080/redoc
- Liveness: http://localhost:8080/healthz
- Readiness: http://localhost:8080/readyz
Business endpoints live under /api and require Authorization: Bearer <API__ADMIN_TOKEN>
(default dev-admin-token). Health endpoints are open.
Layout
src/app/ # import root is `app` (src-layout)
main.py # create_app() factory + lifespan
config.py # typed Settings (pydantic-settings), get_settings()
logging.py # structlog JSON config
api/ # transport: router, deps, middleware, errors, pagination, responses
core/ # exceptions, correlation context, bearer-token security
db/ # async engine/session, Core models, alembic migrations (schema owner)
events/ # event envelope + Redis Streams publisher
storage/ # MinIO / S3 client
audit/ # audit_log writer (cross-cutting)
upload/ documents/ catalog/ partners/ review/ search/ dashboard/ # business domains
tests/
unit/ integration/ api/ fixtures/
Each domain: router.py (thin), service.py (business logic + transaction boundary),
repository.py (SQL only), schemas.py (Pydantic DTOs). Add models.py, exceptions.py
or helpers (zip.py, importer.py, dedup.py) per domain as needed.
How to add a domain endpoint
- Define request/response DTOs in
<domain>/schemas.py. - Add SQL in
<domain>/repository.py(no business rules here). - Put orchestration, transaction boundary, events and audit in
<domain>/service.py. - Wire a thin route in
<domain>/router.py; depend on the service, never the session. - The router is already aggregated in
app/api/router.py.
Rules: routes depend on services; services get collaborators via constructor; SQL only in
repositories; below the API layer raise app.core.exceptions.AppError subclasses (never
HTTPException); all list endpoints return Page[T]; mutations write audit_log inside
the same transaction.
Configuration
All config is typed in app/config.py. Nested groups map env vars via __:
DB__DSN, QUEUE__URL, STORAGE__ENDPOINT, API__ADMIN_TOKEN, MATCHING__AUTO_THRESHOLD.
Local defaults let the app boot without infra; override via .env or the environment.
.env is git-ignored - secrets come from the environment.
Database / migrations
SQLAlchemy Core (async, psycopg v3). Tables in app/db/models.py; alembic owns the schema.
make revision m="create core tables" # autogenerate a migration
make migrate # alembic upgrade head
Commands
make dev # run with reload
make run # run without reload
make test # pytest
make lint # ruff check
make fmt # ruff format
make typecheck # mypy
make openapi # export openapi/openapi.json
Infra for full run
make migrate and /readyz green need PostgreSQL, Redis and MinIO reachable (see
.env.example). For the monorepo deploy (api-service + workers + postgres/redis/minio)
see infra/docs/tech/repository-layout.md.