|
1 | | -# Copilot Instructions for python-samples-fastapi-restful |
| 1 | +# GitHub Copilot Instructions |
2 | 2 |
|
3 | | -## Project Overview |
| 3 | +> **⚡ Token Efficiency Note**: This is a minimal pointer file (~500 tokens, auto-loaded by Copilot). |
| 4 | +> For complete operational details, reference: `#file:AGENTS.md` (~2,500 tokens, loaded on-demand) |
| 5 | +> For specialized knowledge, use: `#file:SKILLS/<skill-name>/SKILL.md` (loaded on-demand when needed) |
4 | 6 |
|
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. |
| 7 | +## 🎯 Quick Context |
6 | 8 |
|
7 | | -## Tech Stack |
| 9 | +**Project**: FastAPI REST API demonstrating modern Python async patterns |
| 10 | +**Stack**: Python 3.13 • FastAPI • SQLAlchemy (async) • SQLite • Docker • pytest |
| 11 | +**Pattern**: Routes → Services → Database (layered architecture) |
| 12 | +**Philosophy**: Learning-focused PoC emphasizing async/await and type safety |
8 | 13 |
|
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 |
| 14 | +## 📐 Core Conventions |
18 | 15 |
|
19 | | -## Project Structure |
| 16 | +- **Naming**: snake_case for functions/variables, PascalCase for classes |
| 17 | +- **Type Hints**: Mandatory throughout (enforced by mypy if enabled) |
| 18 | +- **Async**: All I/O operations use `async`/`await` |
| 19 | +- **Testing**: pytest with fixtures and async support |
| 20 | +- **Formatting**: black (opinionated), flake8 (linting) |
20 | 21 |
|
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 |
| 22 | +## 🏗️ Architecture at a Glance |
55 | 23 |
|
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 |
| 24 | +```text |
| 25 | +Route → Service → Database |
| 26 | + ↓ ↓ |
| 27 | +Cache Session |
| 28 | +``` |
64 | 29 |
|
65 | | -### File Exclusions |
| 30 | +- **Routes**: FastAPI endpoints with dependency injection |
| 31 | +- **Services**: Async database operations via SQLAlchemy |
| 32 | +- **Database**: SQLite with async support (`aiosqlite`) |
| 33 | +- **Models**: Pydantic for validation, SQLAlchemy for ORM |
| 34 | +- **Cache**: aiocache SimpleMemoryCache (TTL: 600s / 10 min) |
66 | 35 |
|
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) |
| 36 | +## ✅ Copilot Should |
71 | 37 |
|
72 | | -### Commit Conventions |
| 38 | +- Generate idiomatic async FastAPI code with proper type hints |
| 39 | +- Use SQLAlchemy async APIs (`select()`, `scalars()`, `session.commit()`) |
| 40 | +- Follow dependency injection pattern with `Depends()` |
| 41 | +- Write tests with pytest async fixtures |
| 42 | +- Apply Pydantic models for request/response validation |
| 43 | +- Use structured logging (avoid print statements) |
| 44 | +- Implement proper HTTP status codes and responses |
73 | 45 |
|
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 |
| 46 | +## 🚫 Copilot Should Avoid |
80 | 47 |
|
81 | | -## Common Commands |
| 48 | +- Synchronous database operations |
| 49 | +- Mixing sync and async code |
| 50 | +- Missing type hints on functions |
| 51 | +- Using `print()` instead of logging |
| 52 | +- Creating routes without caching consideration |
| 53 | +- Ignoring Pydantic validation |
82 | 54 |
|
83 | | -### Local Development |
| 55 | +## ⚡ Quick Commands |
84 | 56 |
|
85 | 57 | ```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 |
| 58 | +# Run with hot reload |
| 59 | +uvicorn main:app --reload --host 0.0.0.0 --port 9000 |
99 | 60 |
|
100 | | -# Format code (must run from venv) |
101 | | -black . |
| 61 | +# Test with coverage |
| 62 | +pytest --cov=. --cov-report=term-missing |
102 | 63 |
|
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) |
| 64 | +# Docker |
120 | 65 | docker compose up |
121 | 66 |
|
122 | | -# Stop app |
123 | | -docker compose down |
124 | | - |
125 | | -# Reset database (removes volume) |
126 | | -docker compose down -v |
| 67 | +# Swagger: http://localhost:9000/docs |
127 | 68 | ``` |
128 | 69 |
|
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` |
| 70 | +## 📚 Need More Detail? |
209 | 71 |
|
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 |
| 72 | +**For operational procedures**: Load `#file:AGENTS.md` |
| 73 | +**For Docker expertise**: *(Planned)* `#file:SKILLS/docker-containerization/SKILL.md` |
| 74 | +**For testing patterns**: *(Planned)* `#file:SKILLS/testing-patterns/SKILL.md` |
215 | 75 |
|
216 | | -## Additional Resources |
| 76 | +--- |
217 | 77 |
|
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/ |
| 78 | +💡 **Why this structure?** Copilot auto-loads this file on every chat (~500 tokens). Loading `AGENTS.md` or `SKILLS/` explicitly gives you deep context only when needed, saving 80% of your token budget! |
0 commit comments