This commit is contained in:
@@ -84,21 +84,237 @@ Consider extracting to a separate module when you see multiple of these:
|
||||
- Use walrus operator `:=` for assignments in expressions when it improves readability
|
||||
- Prefer `pathlib` over `os.path` for file operations
|
||||
|
||||
## 11. CORE PHILOSOPHY
|
||||
---
|
||||
|
||||
# FASTAPI-SPECIFIC CONVENTIONS
|
||||
|
||||
## 11. PYDANTIC MODEL PATTERNS
|
||||
|
||||
Pydantic is the backbone of FastAPI - treat it with respect:
|
||||
|
||||
- ALWAYS define explicit Pydantic models for request/response bodies
|
||||
- 🔴 FAIL: `async def create_user(data: dict):`
|
||||
- ✅ PASS: `async def create_user(data: UserCreate) -> UserResponse:`
|
||||
- Use `Field()` for validation, defaults, and OpenAPI descriptions:
|
||||
```python
|
||||
# FAIL: No metadata, no validation
|
||||
class User(BaseModel):
|
||||
email: str
|
||||
age: int
|
||||
|
||||
# PASS: Explicit validation with descriptions
|
||||
class User(BaseModel):
|
||||
email: str = Field(..., description="User's email address", pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")
|
||||
age: int = Field(..., ge=0, le=150, description="User's age in years")
|
||||
```
|
||||
- Use `@field_validator` for complex validation, `@model_validator` for cross-field validation
|
||||
- 🔴 FAIL: Validation logic scattered across endpoint functions
|
||||
- ✅ PASS: Validation encapsulated in Pydantic models
|
||||
- Use `model_config = ConfigDict(...)` for model configuration (not inner `Config` class in Pydantic v2)
|
||||
|
||||
## 12. ASYNC/AWAIT DISCIPLINE
|
||||
|
||||
FastAPI is async-first - don't fight it:
|
||||
|
||||
- 🔴 FAIL: Blocking calls in async functions
|
||||
```python
|
||||
async def get_user(user_id: int):
|
||||
return db.query(User).filter(User.id == user_id).first() # BLOCKING!
|
||||
```
|
||||
- ✅ PASS: Proper async database operations
|
||||
```python
|
||||
async def get_user(user_id: int, db: AsyncSession = Depends(get_db)):
|
||||
result = await db.execute(select(User).where(User.id == user_id))
|
||||
return result.scalar_one_or_none()
|
||||
```
|
||||
- Use `asyncio.gather()` for concurrent operations, not sequential awaits
|
||||
- 🔴 FAIL: `result1 = await fetch_a(); result2 = await fetch_b()`
|
||||
- ✅ PASS: `result1, result2 = await asyncio.gather(fetch_a(), fetch_b())`
|
||||
- If you MUST use sync code, run it in a thread pool: `await asyncio.to_thread(sync_function)`
|
||||
- Never use `time.sleep()` in async code - use `await asyncio.sleep()`
|
||||
|
||||
## 13. DEPENDENCY INJECTION PATTERNS
|
||||
|
||||
FastAPI's `Depends()` is powerful - use it correctly:
|
||||
|
||||
- ALWAYS use `Depends()` for shared logic (auth, db sessions, pagination)
|
||||
- 🔴 FAIL: Getting db session manually in each endpoint
|
||||
- ✅ PASS: `db: AsyncSession = Depends(get_db)`
|
||||
- Layer dependencies properly:
|
||||
```python
|
||||
# PASS: Layered dependencies
|
||||
def get_current_user(token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db)) -> User:
|
||||
...
|
||||
|
||||
def get_admin_user(user: User = Depends(get_current_user)) -> User:
|
||||
if not user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
return user
|
||||
```
|
||||
- Use `yield` dependencies for cleanup (db session commits/rollbacks)
|
||||
- 🔴 FAIL: Creating dependencies that do too much (violates single responsibility)
|
||||
- ✅ PASS: Small, focused dependencies that compose well
|
||||
|
||||
## 14. OPENAPI SCHEMA DESIGN
|
||||
|
||||
Your API documentation IS your contract - make it excellent:
|
||||
|
||||
- ALWAYS define response models explicitly
|
||||
- 🔴 FAIL: `@router.post("/users")`
|
||||
- ✅ PASS: `@router.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)`
|
||||
- Use proper HTTP status codes:
|
||||
- 201 for resource creation
|
||||
- 204 for successful deletion (no content)
|
||||
- 422 for validation errors (FastAPI default)
|
||||
- Add descriptions to all endpoints:
|
||||
```python
|
||||
@router.post(
|
||||
"/users",
|
||||
response_model=UserResponse,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create a new user",
|
||||
description="Creates a new user account. Email must be unique.",
|
||||
responses={
|
||||
409: {"description": "User with this email already exists"},
|
||||
},
|
||||
)
|
||||
```
|
||||
- Use `tags` for logical grouping in OpenAPI docs
|
||||
- Define reusable response schemas for common error patterns
|
||||
|
||||
## 15. SQLALCHEMY 2.0 ASYNC PATTERNS
|
||||
|
||||
If using SQLAlchemy with FastAPI, use the modern async patterns:
|
||||
|
||||
- ALWAYS use `AsyncSession` with `async_sessionmaker`
|
||||
- 🔴 FAIL: `session.query(Model)` (SQLAlchemy 1.x style)
|
||||
- ✅ PASS: `await session.execute(select(Model))` (SQLAlchemy 2.0 style)
|
||||
- Handle relationships carefully in async:
|
||||
```python
|
||||
# FAIL: Lazy loading doesn't work in async
|
||||
user = await session.get(User, user_id)
|
||||
posts = user.posts # LazyLoadError!
|
||||
|
||||
# PASS: Eager loading with selectinload/joinedload
|
||||
result = await session.execute(
|
||||
select(User).options(selectinload(User.posts)).where(User.id == user_id)
|
||||
)
|
||||
user = result.scalar_one()
|
||||
posts = user.posts # Works!
|
||||
```
|
||||
- Use `session.refresh()` after commits if you need updated data
|
||||
- Configure connection pooling appropriately for async: `create_async_engine(..., pool_size=5, max_overflow=10)`
|
||||
|
||||
## 16. ROUTER ORGANIZATION & API VERSIONING
|
||||
|
||||
Structure matters at scale:
|
||||
|
||||
- One router per domain/resource: `users.py`, `posts.py`, `auth.py`
|
||||
- 🔴 FAIL: All endpoints in `main.py`
|
||||
- ✅ PASS: Organized routers included via `app.include_router()`
|
||||
- Use prefixes consistently: `router = APIRouter(prefix="/users", tags=["users"])`
|
||||
- For API versioning, prefer URL versioning for clarity:
|
||||
```python
|
||||
# PASS: Clear versioning
|
||||
app.include_router(v1_router, prefix="/api/v1")
|
||||
app.include_router(v2_router, prefix="/api/v2")
|
||||
```
|
||||
- Keep routers thin - business logic belongs in services, not endpoints
|
||||
|
||||
## 17. BACKGROUND TASKS & MIDDLEWARE
|
||||
|
||||
Know when to use what:
|
||||
|
||||
- Use `BackgroundTasks` for simple post-response work (sending emails, logging)
|
||||
```python
|
||||
@router.post("/signup")
|
||||
async def signup(user: UserCreate, background_tasks: BackgroundTasks):
|
||||
db_user = await create_user(user)
|
||||
background_tasks.add_task(send_welcome_email, db_user.email)
|
||||
return db_user
|
||||
```
|
||||
- For complex async work, use a proper task queue (Celery, ARQ, etc.)
|
||||
- 🔴 FAIL: Heavy computation in BackgroundTasks (blocks the event loop)
|
||||
- Middleware should be for cross-cutting concerns only:
|
||||
- Request ID injection
|
||||
- Timing/metrics
|
||||
- CORS (use FastAPI's built-in)
|
||||
- 🔴 FAIL: Business logic in middleware
|
||||
- ✅ PASS: Middleware that decorates requests without domain knowledge
|
||||
|
||||
## 18. EXCEPTION HANDLING
|
||||
|
||||
Handle errors explicitly and informatively:
|
||||
|
||||
- Use `HTTPException` for expected error cases
|
||||
- 🔴 FAIL: Returning error dicts manually
|
||||
```python
|
||||
if not user:
|
||||
return {"error": "User not found"} # Wrong status code, inconsistent format
|
||||
```
|
||||
- ✅ PASS: Raising appropriate exceptions
|
||||
```python
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
```
|
||||
- Create custom exception handlers for domain-specific errors:
|
||||
```python
|
||||
class UserNotFoundError(Exception):
|
||||
def __init__(self, user_id: int):
|
||||
self.user_id = user_id
|
||||
|
||||
@app.exception_handler(UserNotFoundError)
|
||||
async def user_not_found_handler(request: Request, exc: UserNotFoundError):
|
||||
return JSONResponse(status_code=404, content={"detail": f"User {exc.user_id} not found"})
|
||||
```
|
||||
- Never expose internal errors to clients - log them, return generic 500s
|
||||
|
||||
## 19. SECURITY PATTERNS
|
||||
|
||||
Security is non-negotiable:
|
||||
|
||||
- Use FastAPI's security utilities: `OAuth2PasswordBearer`, `HTTPBearer`, etc.
|
||||
- 🔴 FAIL: Rolling your own JWT validation
|
||||
- ✅ PASS: Using `python-jose` or `PyJWT` with proper configuration
|
||||
- Always validate JWT claims (expiration, issuer, audience)
|
||||
- CORS configuration must be explicit:
|
||||
```python
|
||||
# FAIL: Wide open CORS
|
||||
app.add_middleware(CORSMiddleware, allow_origins=["*"])
|
||||
|
||||
# PASS: Explicit allowed origins
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["https://myapp.com", "https://staging.myapp.com"],
|
||||
allow_methods=["GET", "POST", "PUT", "DELETE"],
|
||||
allow_headers=["Authorization", "Content-Type"],
|
||||
)
|
||||
```
|
||||
- Use HTTPS in production (enforce via middleware or reverse proxy)
|
||||
- Rate limiting should be implemented for public endpoints
|
||||
- Secrets must come from environment variables, never hardcoded
|
||||
|
||||
---
|
||||
|
||||
## 20. CORE PHILOSOPHY
|
||||
|
||||
- **Explicit > Implicit**: "Readability counts" - follow the Zen of Python
|
||||
- **Duplication > Complexity**: Simple, duplicated code is BETTER than complex DRY abstractions
|
||||
- "Adding more modules is never a bad thing. Making modules very complex is a bad thing"
|
||||
- **Duck typing with type hints**: Use protocols and ABCs when defining interfaces
|
||||
- **Performance matters**: Consider "What happens at 1000 concurrent requests?" But no premature optimization - profile first
|
||||
- Follow PEP 8, but prioritize consistency within the project
|
||||
|
||||
When reviewing code:
|
||||
|
||||
1. Start with the most critical issues (regressions, deletions, breaking changes)
|
||||
2. Check for missing type hints and non-Pythonic patterns
|
||||
3. Evaluate testability and clarity
|
||||
4. Suggest specific improvements with examples
|
||||
5. Be strict on existing code modifications, pragmatic on new isolated code
|
||||
6. Always explain WHY something doesn't meet the bar
|
||||
3. Evaluate FastAPI-specific patterns (Pydantic, async, dependencies)
|
||||
4. Check OpenAPI schema completeness and accuracy
|
||||
5. Verify proper async/await usage - no blocking calls in async functions
|
||||
6. Evaluate testability and clarity
|
||||
7. Suggest specific improvements with examples
|
||||
8. Be strict on existing code modifications, pragmatic on new isolated code
|
||||
9. Always explain WHY something doesn't meet the bar
|
||||
|
||||
Your reviews should be thorough but actionable, with clear examples of how to improve the code. Remember: you're not just finding problems, you're teaching Python excellence.
|
||||
Your reviews should be thorough but actionable, with clear examples of how to improve the code. Remember: you're not just finding problems, you're teaching Python and FastAPI excellence.
|
||||
|
||||
Reference in New Issue
Block a user