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. Part of a cross-language comparison study (.NET, Go, Java, Rust, TypeScript).
- Language: Python 3.13
- Framework: FastAPI + Uvicorn
- ORM: SQLAlchemy 2.0 (async) + aiosqlite
- Database: SQLite
- 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
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
models/ — Pydantic models for request/response validation
storage/ — SQLite database file (players-sqlite3.db, pre-seeded)
scripts/ — shell scripts for Docker (entrypoint.sh, healthcheck.sh)
tools/ — standalone seed scripts (run manually, not via Alembic)
tests/ — pytest integration tests
Layer rule: Routes → Services → SQLAlchemy → SQLite. Routes handle HTTP concerns only; business logic belongs in services.
- 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); never use the removedPlayerModel - 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)
- Logging:
loggingmodule only; neverprint() - Line length: 88; complexity ≤ 10
- Import order: stdlib → third-party → local
- Tests: naming pattern
test_request_{method}_{resource}_{context}_response_{outcome}; docstrings single-line, concise;tests/player_stub.pyfor test data;tests/test_main.pyexcluded from Black - 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
# 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 .
# 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)
- Add/modify routes and endpoints
- Service layer logic and cache management
- Tests (maintain async patterns and naming convention)
- Documentation and docstring updates
- Lint/format fixes
- Refactoring within existing architectural patterns
- Database schema (
schemas/player_schema.py— no Alembic, use tools/ seed scripts manually) - Dependencies (
pyproject.tomlwith PEP 735 dependency groups) - CI/CD configuration (
.github/workflows/) - Docker setup
- API contracts (breaking Pydantic model changes)
- Global error handling
.envfiles (secrets)- Production configurations
- Async/await patterns (mandatory throughout)
- Type hints (mandatory throughout)
- Core layered architecture
Add an endpoint: Add Pydantic model in models/ → add async service method in services/ with error handling → add route in routes/ with Depends(generate_async_session) → add tests following naming pattern → run pre-commit checks.
Modify schema: Update schemas/player_schema.py → manually update storage/players-sqlite3.db (preserve 26 players) → update models/player_model.py if API changes → update services and tests → run pytest.
After completing work: Suggest a branch name (e.g. feat/add-player-stats) and a commit message following Conventional Commits including co-author line:
feat(scope): description (#issue)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>