REST API for managing football players built with Python and FastAPI. Implements async CRUD operations with SQLAlchemy 2.0 (async), SQLite, Pydantic validation, and in-memory caching.
- Language: Python 3.13
- Framework: FastAPI + Uvicorn
- ORM: SQLAlchemy 2.0 (async) + aiosqlite
- Database: SQLite (local/test), PostgreSQL-compatible
- Migrations: Alembic (async,
render_as_batch=True) - Validation: Pydantic
- Caching: aiocache (in-memory, 10-minute TTL)
- Testing: pytest + pytest-cov + httpx
- Linting/Formatting: Flake8 + Black
- Containerization: Docker
main.py — application entry point: FastAPI setup, router registration
alembic.ini — Alembic configuration (sqlalchemy.url set dynamically)
alembic/ — Alembic migration environment and version scripts
routes/ — HTTP route definitions + dependency injection [HTTP layer]
services/ — async business logic + cache management [business layer]
schemas/ — SQLAlchemy ORM models (database schema) [data layer]
databases/ — async SQLAlchemy session setup + get_database_url()
models/ — Pydantic models for request/response validation
scripts/ — shell scripts for Docker (entrypoint.sh, healthcheck.sh)
tools/ — legacy standalone seed scripts (superseded by Alembic migrations)
tests/ — pytest integration tests
Layer rule: Routes → Services → SQLAlchemy → SQLite. Routes handle HTTP
concerns only; business logic belongs in services. Never skip a layer.
- Naming: snake_case (files, functions, variables), PascalCase (classes)
- Type hints: Required everywhere — functions, variables, return types
- Async: All routes and service functions must be
async def; useAsyncSession(neverSession); useaiosqlite(neversqlite3); use SQLAlchemy 2.0select()(neversession.query()) - API contract: camelCase JSON via Pydantic
alias_generator=to_camel; Python internals stay snake_case - Models:
PlayerRequestModel(noid, used for POST/PUT) andPlayerResponseModel(includesid: UUID, used for GET/POST responses). One request model intentionally covers both POST and PUT — per-operation differences (conflict check on POST, mismatch guard on PUT) are handled at the route layer, not by duplicating the model. Never reintroduce the removedPlayerModel; it was removed because a single flat model conflated ORM, request, and response concerns. - Primary key: UUID surrogate key (
id) — opaque, internal, used for GET by id only. UUID v4 for API-created records; UUID v5 (deterministic) for migration-seeded records.squad_numberis the natural key — human-readable, domain-meaningful, used for all mutation endpoints (PUT, DELETE) and preferred for all external consumers - Caching: cache key
"players"(hardcoded); clear on POST/PUT/DELETE;X-Cacheheader (HIT/MISS) - Errors: Catch specific exceptions with rollback in services; Pydantic validation returns 422 (not 400); squad number mismatch on PUT returns 400 (not 422 — it is a semantic error, not a validation failure)
- Logging:
loggingmodule only; neverprint() - Line length: 88; complexity ≤ 10
- Import order: stdlib → third-party → local
- Tests: integration tests against the real SQLite DB (seeded via
Alembic migrations) via
TestClient— no mocking. Naming patterntest_request_{method}_{resource}_{context}_response_{outcome}; docstrings single-line, concise;tests/player_fake.pyfor test data;tests/conftest.pyprovides afunction-scopedclientfixture for isolation;tests/test_main.pyexcluded from Black;tests/test_migrations.pycovers Alembic downgrade paths - Decisions: justify every decision on its own technical merits; never use "another project does it this way" as a reason — that explains nothing and may mean replicating a mistake
- Avoid: sync DB access, mixing sync/async,
print(), missing type hints, unhandled exceptions
# Setup (using uv)
uv venv
source .venv/bin/activate # Linux/macOS; use .venv\Scripts\activate on Windows
uv pip install --group dev
# Apply migrations (required once before first run, and after down -v)
uv run alembic upgrade head
# Run application
uv run uvicorn main:app --reload --port 9000 # http://localhost:9000/docs
# Run tests
uv run pytest # run tests
uv run pytest --cov=./ --cov-report=term # with coverage (target >=80%)
# Linting and formatting
uv run flake8 .
uv run black --check .
# Migration workflow
uv run alembic upgrade head # apply all pending migrations
uv run alembic downgrade -1 # roll back last migration
uv run alembic revision --autogenerate -m "desc" # generate migration from schema
# Docker
docker compose up
docker compose down -v- Update
CHANGELOG.md[Unreleased]section (Added / Changed / Fixed / Removed) uv run flake8 .— must passuv run black --check .— must passuv run pytest— all tests must passuv run pytest --cov=./ --cov-report=term— coverage must be ≥80%- Commit message follows Conventional Commits format (enforced by commitlint)
Format: type(scope): description (#issue) — max 80 chars
Types: feat fix chore docs test refactor ci perf
Example: feat(api): add player stats endpoint (#42)
Tags follow the format v{MAJOR}.{MINOR}.{PATCH}-{COACH} (e.g.
v2.0.0-capello). The CD pipeline validates the coach name against a fixed
list (A–Z):
ancelotti bielsa capello delbosque eriksson ferguson guardiola heynckes
inzaghi klopp kovac low mourinho nagelsmann ottmar pochettino queiroz
ranieri simeone tuchel unai vangaal wenger xavi yozhef zeman
Never suggest a release tag with a coach name not on this list.
- Add/modify routes in
routes/player_route.pyandroutes/health_route.py - Add/modify service methods in
services/player_service.py - Add/modify Pydantic models in
models/player_model.py(field additions or docstring updates that don't change the API contract) - Tests in
tests/— maintain async patterns, naming convention, and integration-test approach (no mocking) - Documentation and docstring updates
- Lint/format fixes
- Refactoring within existing architectural patterns
- Database schema (
schemas/player_schema.py) and Alembic migrations (alembic/versions/) — schema changes require a new migration file; seed data changes require updating the relevant migration and any test fixtures that reference specific UUIDs models/player_model.pydesign decisions — especially splitting or merging request/response models; discuss the rationale before restructuring- Dependencies (
pyproject.tomlwith PEP 735 dependency groups) - CI/CD configuration (
.github/workflows/) - Docker setup (
Dockerfile,docker-compose.yml,scripts/) - Breaking API contract changes (field renames, type changes, removing fields)
- Global error handling middleware
- HTTP status codes assigned to existing error conditions
.envfiles (secrets)alembic/versions/migration files once merged tomaster— migrations are append-only; fix forward with a new migration, never edit history- Production configurations
This project uses Spec-Driven Development (SDD): discuss in Plan mode first, create a GitHub Issue as the spec artifact, then implement. Always offer to draft an issue before writing code.
Feature request (enhancement label):
- Problem: the pain point being solved
- Proposed Solution: expected behavior and functionality
- Suggested Approach (optional): implementation plan if known
- Acceptance Criteria: at minimum — behaves as proposed, tests added/updated, no regressions
- References: related issues, docs, or examples
Bug report (bug label):
- Description: clear summary of the bug
- Steps to Reproduce: numbered, minimal steps
- Expected / Actual Behavior: one section each
- Environment: runtime versions + OS
- Additional Context: logs, screenshots, stack traces
- Possible Solution (optional): suggested fix or workaround
Add an endpoint: Add Pydantic model in models/ if the request/response
shape is new → add async service method in services/ with error handling and
rollback → add route in routes/ with Depends(generate_async_session) →
add tests following the naming pattern → run pre-commit checks.
Modify schema: Update schemas/player_schema.py → run
uv run alembic revision --autogenerate -m "description" to generate a
migration → review and adjust the generated file in alembic/versions/ →
run uv run alembic upgrade head → update models/player_model.py if the
API shape changes → update services and tests → run pytest.
After completing work: Propose a branch name and commit message for user approval. Do not create a branch, commit, or push until the user explicitly confirms. Commit message format:
feat(scope): description (#issue)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>