|
1 | | -# GitHub Copilot Instructions |
| 1 | +# Copilot Instructions |
2 | 2 |
|
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) |
| 3 | +## Project Summary |
6 | 4 |
|
7 | | -## 🎯 Quick Context |
| 5 | +RESTful API with Python 3.13 + FastAPI demonstrating modern async patterns. Player registry with CRUD operations, SQLite + SQLAlchemy 2.0 (async), Pydantic validation, containerization. Part of multi-language comparison study (Java, .NET, TypeScript, Python, Go, Rust). Target: 80%+ test coverage. |
8 | 6 |
|
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 |
| 7 | +## Quick Start |
13 | 8 |
|
14 | | -## 📐 Core Conventions |
| 9 | +```bash |
| 10 | +# Install dependencies |
| 11 | +pip install -r requirements.txt |
| 12 | +pip install -r requirements-lint.txt |
| 13 | +pip install -r requirements-test.txt |
| 14 | + |
| 15 | +# Run development server |
| 16 | +uvicorn main:app --reload --port 9000 |
| 17 | +# Access: http://localhost:9000/docs |
| 18 | + |
| 19 | +# Run tests with coverage |
| 20 | +pytest --cov=./ --cov-report=html |
| 21 | + |
| 22 | +# Lint and format |
| 23 | +flake8 . |
| 24 | +black --check . # or: black . (to auto-format) |
| 25 | + |
| 26 | +# Docker |
| 27 | +docker compose up |
| 28 | +docker compose down -v # Reset database |
| 29 | +``` |
15 | 30 |
|
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) |
| 31 | +## Stack |
21 | 32 |
|
22 | | -## 🏗️ Architecture at a Glance |
| 33 | +- Python 3.13.3 (`.python-version` - auto-detected by pyenv/asdf/mise) |
| 34 | +- FastAPI 0.128.6, Uvicorn |
| 35 | +- SQLite + SQLAlchemy 2.0 (async) + aiosqlite |
| 36 | +- pytest + pytest-cov + httpx |
| 37 | +- Flake8 + Black |
| 38 | +- aiocache (in-memory, 10min TTL) |
| 39 | + |
| 40 | +## Architecture |
23 | 41 |
|
24 | 42 | ```text |
25 | | -Route → Service → Database |
26 | | - ↓ ↓ |
27 | | -Cache Session |
| 43 | +Request → Routes → Services → SQLAlchemy → SQLite |
| 44 | + (API) (Logic) (Async ORM) (Storage) |
| 45 | + ↓ |
| 46 | + Pydantic (Validation) |
28 | 47 | ``` |
29 | 48 |
|
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) |
| 49 | +**Key Directories:** |
| 50 | +- `routes/` - API endpoints (player_route.py, health_route.py) |
| 51 | +- `services/` - Business logic (player_service.py) |
| 52 | +- `models/` - Pydantic validation (camelCase JSON API) |
| 53 | +- `schemas/` - SQLAlchemy ORM models |
| 54 | +- `databases/` - Async DB setup, session factory |
| 55 | +- `storage/` - SQLite file (pre-seeded, 26 players) |
| 56 | +- `tests/` - pytest suite (test_main.py, conftest.py) |
| 57 | + |
| 58 | +**Config Files:** |
| 59 | +- `.flake8` - Linter (max-line-length=88, complexity=10) |
| 60 | +- `pyproject.toml` - Black formatter (line-length=88) |
| 61 | +- `.coveragerc` - Coverage config (80% target) |
| 62 | +- `compose.yaml` - Docker orchestration |
| 63 | +- `Dockerfile` - Multi-stage build |
| 64 | + |
| 65 | +## API Endpoints |
| 66 | + |
| 67 | +All async with `AsyncSession` injection: |
| 68 | +- `POST /players/` → 201|409|422 |
| 69 | +- `GET /players/` → 200 (cached 10min) |
| 70 | +- `GET /players/{player_id}` → 200|404 |
| 71 | +- `GET /players/squadnumber/{squad_number}` → 200|404 |
| 72 | +- `PUT /players/{player_id}` → 200|404|422 |
| 73 | +- `DELETE /players/{player_id}` → 200|404 |
| 74 | +- `GET /health` → 200 |
| 75 | + |
| 76 | +JSON: camelCase (e.g., `squadNumber`, `firstName`) |
| 77 | + |
| 78 | +## CI/CD |
| 79 | + |
| 80 | +**python-ci.yml** (push/PR to master): |
| 81 | +1. Lint: commitlint → `flake8 .` → `black --check .` |
| 82 | +2. Test: `pytest -v` → coverage |
| 83 | +3. Upload to Codecov |
| 84 | + |
| 85 | +**python-cd.yml** (tags `v*.*.*-*`): |
| 86 | +1. Validate semver + coach name |
| 87 | +2. Run tests |
| 88 | +3. Build Docker (amd64/arm64) |
| 89 | +4. Push to GHCR (3 tags: semver/coach/latest) |
| 90 | +5. Create GitHub release |
| 91 | + |
| 92 | +## Critical Patterns |
| 93 | + |
| 94 | +### Async Everywhere |
| 95 | +```python |
| 96 | +# Always use async/await |
| 97 | +async def get_player(async_session: AsyncSession, player_id: int): |
| 98 | + stmt = select(Player).where(Player.id == player_id) |
| 99 | + result = await async_session.execute(stmt) |
| 100 | + return result.scalar_one_or_none() |
| 101 | +``` |
| 102 | +- All routes: `async def` |
| 103 | +- Database: `AsyncSession` (never `Session`) |
| 104 | +- Driver: `aiosqlite` (not `sqlite3`) |
| 105 | +- SQLAlchemy 2.0: `select()` (not `session.query()`) |
| 106 | + |
| 107 | +### camelCase API Contract |
| 108 | +```python |
| 109 | +class PlayerModel(BaseModel): |
| 110 | + model_config = ConfigDict(alias_generator=to_camel) |
| 111 | + squad_number: int # Python: snake_case |
| 112 | + # JSON API: "squadNumber" (camelCase) |
| 113 | +``` |
35 | 114 |
|
36 | | -## ✅ Copilot Should |
| 115 | +### Database Schema Changes |
| 116 | +⚠️ No Alembic yet - manual process: |
| 117 | +1. Update `schemas/player_schema.py` |
| 118 | +2. Manually update `storage/players-sqlite3.db` (SQLite CLI/DB Browser) |
| 119 | +3. Preserve 26 players |
| 120 | +4. Update `models/player_model.py` if API changes |
| 121 | +5. Update services + tests |
37 | 122 |
|
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 |
| 123 | +### Caching |
| 124 | +- Key: `"players"` (hardcoded) |
| 125 | +- TTL: 600s (10min) |
| 126 | +- Cleared on POST/PUT/DELETE |
| 127 | +- Header: `X-Cache` (HIT/MISS) |
45 | 128 |
|
46 | | -## 🚫 Copilot Should Avoid |
| 129 | +## Common Issues |
47 | 130 |
|
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 |
| 131 | +1. **SQLAlchemy errors** → Always catch + rollback in services |
| 132 | +2. **Test file** → `test_main.py` excluded from Black (preserves long names) |
| 133 | +3. **Database location** → Local: `./storage/`, Docker: `/storage/` (volume) |
| 134 | +4. **Pydantic validation** → Returns 422 (not 400) |
| 135 | +5. **Import order** → stdlib → third-party → local |
54 | 136 |
|
55 | | -## ⚡ Quick Commands |
| 137 | +## Validation Checklist |
56 | 138 |
|
57 | 139 | ```bash |
58 | | -# Run with hot reload |
59 | | -uvicorn main:app --reload --host 0.0.0.0 --port 9000 |
| 140 | +flake8 . # Must pass |
| 141 | +black --check . # Must pass |
| 142 | +pytest # All pass |
| 143 | +pytest --cov=./ --cov-report=term # ≥80% |
| 144 | +curl http://localhost:9000/players # 200 OK |
| 145 | +``` |
60 | 146 |
|
61 | | -# Test with coverage |
62 | | -pytest --cov=. --cov-report=term-missing |
| 147 | +## Code Conventions |
63 | 148 |
|
64 | | -# Docker |
65 | | -docker compose up |
| 149 | +- Files: snake_case |
| 150 | +- Functions/vars: snake_case |
| 151 | +- Classes: PascalCase |
| 152 | +- Type hints: Required everywhere |
| 153 | +- Logging: `logging` module (never `print()`) |
| 154 | +- Errors: Catch specific exceptions |
| 155 | +- Line length: 88 |
| 156 | +- Complexity: ≤10 |
66 | 157 |
|
67 | | -# Swagger: http://localhost:9000/docs |
68 | | -``` |
| 158 | +## Commit Messages |
69 | 159 |
|
70 | | -## 📚 Need More Detail? |
| 160 | +Follow Conventional Commits format (enforced by commitlint in CI): |
71 | 161 |
|
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` |
| 162 | +**Format:** `type(scope): description (#issue)` |
| 163 | + |
| 164 | +**Rules:** |
| 165 | +- Max 80 characters |
| 166 | +- Types: `feat`, `fix`, `docs`, `test`, `refactor`, `chore`, `ci`, `perf`, `style`, `build` |
| 167 | +- Scope: Optional (e.g., `api`, `db`, `service`, `route`) |
| 168 | +- Issue number: Required suffix |
| 169 | + |
| 170 | +**Examples:** |
| 171 | +```text |
| 172 | +feat(api): add player stats endpoint (#42) |
| 173 | +fix(db): resolve async session leak (#88) |
| 174 | +``` |
75 | 175 |
|
76 | | ---- |
| 176 | +**CI Check:** First step in python-ci.yml validates all commit messages |
77 | 177 |
|
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! |
| 178 | +Trust these instructions. Search codebase only if info is incomplete/incorrect. |
0 commit comments