|
| 1 | +# Copilot Instructions for python-samples-fastapi-restful |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +This is a RESTful API proof of concept built with **Python 3.13** and **FastAPI**. The application manages football player data with full CRUD operations, featuring async SQLAlchemy ORM, in-memory caching, and SQLite database storage. |
| 6 | + |
| 7 | +## Tech Stack |
| 8 | + |
| 9 | +- **Framework**: FastAPI 0.123.0 with standard dependencies |
| 10 | +- **Database**: SQLite with async support (`aiosqlite 0.21.0`) |
| 11 | +- **ORM**: SQLAlchemy 2.0.44 (async) |
| 12 | +- **Caching**: aiocache 0.12.3 (SimpleMemoryCache) |
| 13 | +- **Testing**: pytest 9.0.1, pytest-cov 7.0.0, pytest-sugar 1.1.1, gevent 25.9.1 |
| 14 | +- **Linting**: flake8 7.3.0, black 25.11.0 |
| 15 | +- **Python Version**: 3.13.3 (see `.python-version`) |
| 16 | +- **Server**: uvicorn (included in FastAPI standard dependencies) |
| 17 | +- **Container**: Docker with multi-stage builds, Docker Compose |
| 18 | + |
| 19 | +## Project Structure |
| 20 | + |
| 21 | +``` |
| 22 | +├── main.py # FastAPI app entry point, lifespan handler, router registration |
| 23 | +├── databases/ |
| 24 | +│ └── player_database.py # Async engine, sessionmaker, Base, session generator |
| 25 | +├── models/ |
| 26 | +│ └── player_model.py # Pydantic models for API request/response validation |
| 27 | +├── schemas/ |
| 28 | +│ └── player_schema.py # SQLAlchemy ORM table schema definitions |
| 29 | +├── routes/ |
| 30 | +│ ├── player_route.py # Player CRUD endpoints with caching |
| 31 | +│ └── health_route.py # Health check endpoint |
| 32 | +├── services/ |
| 33 | +│ └── player_service.py # Async database CRUD operations |
| 34 | +├── tests/ |
| 35 | +│ ├── conftest.py # pytest fixtures (TestClient) |
| 36 | +│ ├── test_main.py # Test suite for all endpoints |
| 37 | +│ └── player_stub.py # Test data stubs |
| 38 | +├── storage/ # SQLite database file (seeded) |
| 39 | +├── scripts/ |
| 40 | +│ ├── entrypoint.sh # Docker entrypoint for DB initialization |
| 41 | +│ └── healthcheck.sh # Docker health check script |
| 42 | +└── postman_collections/ # Postman collection for API testing |
| 43 | +``` |
| 44 | + |
| 45 | +## Key Architectural Patterns |
| 46 | + |
| 47 | +1. **Layered Architecture**: Routes → Services → Database |
| 48 | +2. **Dependency Injection**: `AsyncSession` via `Depends(generate_async_session)` |
| 49 | +3. **Pydantic for Validation**: `PlayerModel` with camelCase aliasing (`to_camel`) |
| 50 | +4. **SQLAlchemy ORM**: `Player` schema mapped to `players` table |
| 51 | +5. **Caching**: In-memory cache (10 min TTL) with `X-Cache` headers (HIT/MISS) |
| 52 | +6. **Async/Await**: All database operations are async |
| 53 | + |
| 54 | +## Coding Guidelines |
| 55 | + |
| 56 | +### Python Style (Strict Enforcement) |
| 57 | + |
| 58 | +- **Formatter**: Black (line length: 88, target: Python 3.13) |
| 59 | +- **Linter**: flake8 (max-complexity: 10, ignores: E203, W503) |
| 60 | +- **Run Before Commit**: `black .` and `flake8` |
| 61 | +- **Imports**: SQLAlchemy 2.0+ style (use `select()` not legacy `Query`) |
| 62 | +- **Docstrings**: Google-style docstrings for all modules, classes, and functions |
| 63 | +- **Type Hints**: Use type annotations for function parameters and return values |
| 64 | + |
| 65 | +### File Exclusions |
| 66 | + |
| 67 | +Black and flake8 exclude: |
| 68 | +- `.venv`, `.git`, `.github`, `.pytest_cache`, `__pycache__` |
| 69 | +- `assets/`, `htmlcov/`, `postman_collections/`, `scripts/`, `storage/` |
| 70 | +- Exception: `tests/test_main.py` allows E501 (long lines for test names) |
| 71 | + |
| 72 | +### Commit Conventions |
| 73 | + |
| 74 | +Follow **Conventional Commits** (enforced by commitlint): |
| 75 | +- `feat:` for new features |
| 76 | +- `fix:` for bug fixes |
| 77 | +- `chore:` for maintenance/tooling |
| 78 | +- Max header length: 80 characters |
| 79 | +- Max body line length: 80 characters |
| 80 | + |
| 81 | +## Common Commands |
| 82 | + |
| 83 | +### Local Development |
| 84 | + |
| 85 | +```bash |
| 86 | +# Install dependencies |
| 87 | +pip install -r requirements.txt |
| 88 | +pip install -r requirements-lint.txt |
| 89 | +pip install -r requirements-test.txt |
| 90 | + |
| 91 | +# IMPORTANT: Activate virtual environment before running commands |
| 92 | +source .venv/bin/activate |
| 93 | + |
| 94 | +# Start server (auto-reload on port 9000) |
| 95 | +uvicorn main:app --reload --port 9000 |
| 96 | + |
| 97 | +# Access interactive API docs |
| 98 | +# http://localhost:9000/docs |
| 99 | + |
| 100 | +# Format code (must run from venv) |
| 101 | +black . |
| 102 | + |
| 103 | +# Lint code (must run from venv) |
| 104 | +flake8 . |
| 105 | + |
| 106 | +# Run tests |
| 107 | +pytest -v |
| 108 | + |
| 109 | +# Run tests with coverage |
| 110 | +pytest --cov=./ --cov-report=xml --cov-report=term |
| 111 | +``` |
| 112 | + |
| 113 | +### Docker |
| 114 | + |
| 115 | +```bash |
| 116 | +# Build image |
| 117 | +docker compose build |
| 118 | + |
| 119 | +# Start app (initializes DB from seed on first run) |
| 120 | +docker compose up |
| 121 | + |
| 122 | +# Stop app |
| 123 | +docker compose down |
| 124 | + |
| 125 | +# Reset database (removes volume) |
| 126 | +docker compose down -v |
| 127 | +``` |
| 128 | + |
| 129 | +## Database Details |
| 130 | + |
| 131 | +- **Path**: Controlled by `STORAGE_PATH` env var (default: `./storage/players-sqlite3.db`) |
| 132 | +- **Docker Volume**: Persistent volume at `/storage/` in container |
| 133 | +- **Initialization**: On first Docker run, `entrypoint.sh` copies seed DB from `/app/hold/` to `/storage/` |
| 134 | +- **Schema**: Single `players` table with columns: id (PK), firstName, middleName, lastName, dateOfBirth, squadNumber (unique), position, abbrPosition, team, league, starting11 |
| 135 | + |
| 136 | +## API Endpoints |
| 137 | + |
| 138 | +| Method | Path | Description | Cache | |
| 139 | +|--------|-------------------------------------|------------------------------|-------| |
| 140 | +| GET | `/health` | Health check | No | |
| 141 | +| GET | `/players/` | Get all players | Yes | |
| 142 | +| GET | `/players/{player_id}` | Get player by ID | No | |
| 143 | +| GET | `/players/squadnumber/{squad_number}` | Get player by squad number | No | |
| 144 | +| POST | `/players/` | Create new player | Clears| |
| 145 | +| PUT | `/players/{player_id}` | Update existing player | Clears| |
| 146 | +| DELETE | `/players/{player_id}` | Delete player | Clears| |
| 147 | + |
| 148 | +**Cache Notes**: |
| 149 | +- Cache key: `"players"`, TTL: 600s (10 min) |
| 150 | +- Cache is cleared on POST/PUT/DELETE operations |
| 151 | +- Response header `X-Cache: HIT` or `MISS` indicates cache status |
| 152 | + |
| 153 | +## Testing |
| 154 | + |
| 155 | +- **Framework**: pytest with `TestClient` from FastAPI |
| 156 | +- **Fixture**: `client` fixture in `conftest.py` (function scope for test isolation) |
| 157 | +- **Coverage Target**: 80% (configured in `codecov.yml`) |
| 158 | +- **Test Data**: Use stubs from `tests/player_stub.py` |
| 159 | +- **Warnings**: DeprecationWarning from httpx is suppressed in conftest |
| 160 | + |
| 161 | +## CI/CD Pipeline |
| 162 | + |
| 163 | +GitHub Actions workflow (`.github/workflows/python-app.yml`): |
| 164 | +1. **Lint Job**: Commitlint → Flake8 → Black (check mode) |
| 165 | +2. **Test Job**: pytest with coverage report generation |
| 166 | +3. **Coverage Job**: Upload to Codecov and Codacy (only for same-repo PRs) |
| 167 | + |
| 168 | +**All PRs must pass CI checks before review.** |
| 169 | + |
| 170 | +## Common Pitfalls & Solutions |
| 171 | + |
| 172 | +1. **Virtual Environment**: Always activate `.venv` before running black, flake8, or pytest: |
| 173 | + ```bash |
| 174 | + source .venv/bin/activate |
| 175 | + ``` |
| 176 | + |
| 177 | +2. **FastAPI Route Ordering**: Static routes MUST be defined before dynamic path parameters. Place `/players/statistics` before `/players/{player_id}`, or FastAPI will try to parse "statistics" as a player_id. |
| 178 | + ```python |
| 179 | + # CORRECT order: |
| 180 | + @api_router.get("/players/statistics") # Static route first |
| 181 | + @api_router.get("/players/{player_id}") # Dynamic route after |
| 182 | + ``` |
| 183 | + |
| 184 | +3. **SQLAlchemy 2.0 Migration**: Use `select()` not `session.query()`. Example: |
| 185 | + ```python |
| 186 | + statement = select(Player).where(Player.id == player_id) |
| 187 | + result = await async_session.execute(statement) |
| 188 | + ``` |
| 189 | + |
| 190 | +4. **Async Session Usage**: Always use `Depends(generate_async_session)` in routes, never create sessions manually. |
| 191 | + |
| 192 | +5. **Cache Invalidation**: Remember to call `await simple_memory_cache.clear(CACHE_KEY)` after mutations (POST/PUT/DELETE). |
| 193 | + |
| 194 | +6. **Pydantic Model Conversion**: Use `player_model.model_dump()` to convert Pydantic to dict for SQLAlchemy: |
| 195 | + ```python |
| 196 | + player = Player(**player_model.model_dump()) |
| 197 | + ``` |
| 198 | + |
| 199 | +7. **Database Path in Docker**: Use `STORAGE_PATH` env var, not hardcoded paths. |
| 200 | + |
| 201 | +8. **Port Conflicts**: Default port is 9000. If occupied, use `--port` flag with uvicorn. |
| 202 | + |
| 203 | +## VS Code Configuration |
| 204 | + |
| 205 | +Recommended extensions (`.vscode/extensions.json`): |
| 206 | +- `ms-python.python`, `ms-python.flake8`, `ms-python.black-formatter` |
| 207 | +- `github.vscode-pull-request-github`, `github.vscode-github-actions` |
| 208 | +- `ms-azuretools.vscode-containers`, `sonarsource.sonarlint-vscode` |
| 209 | + |
| 210 | +Settings (`.vscode/settings.json`): |
| 211 | +- Auto-format on save with Black |
| 212 | +- Pytest enabled (not unittest) |
| 213 | +- Flake8 integration with matching CLI args |
| 214 | +- Editor ruler at column 88 |
| 215 | + |
| 216 | +## Additional Resources |
| 217 | + |
| 218 | +- **Postman Collection**: `postman_collections/python-samples-fastapi-restful.postman_collection.json` |
| 219 | +- **Architecture Diagram**: `assets/images/structure.svg` |
| 220 | +- **FastAPI Docs**: https://fastapi.tiangolo.com/ |
| 221 | +- **SQLAlchemy 2.0**: https://docs.sqlalchemy.org/en/20/ |
| 222 | +- **Conventional Commits**: https://www.conventionalcommits.org/ |
0 commit comments