---
description: Python Web and APIs
alwaysApply: false
---

# Python Web and APIs

Patterns for production-grade web services and APIs in Python.

## FastAPI Dependency Injection

```python
from typing import Annotated

async def get_db(request: Request) -> AsyncGenerator[Database, None]:
    async with request.app.state.db.acquire() as conn:
        yield Database(conn)

CurrentUser = Annotated[User, Depends(get_current_user)]
DB = Annotated[Database, Depends(get_db)]

@app.get("/users/me")
async def get_me(user: CurrentUser) -> UserResponse:
    return UserResponse.model_validate(user)
```

## Request/Response Models

```python
class CreateUserRequest(BaseModel):
    model_config = ConfigDict(strict=True)
    name: str = Field(min_length=1, max_length=200)
    email: EmailStr

class PaginatedResponse(BaseModel, Generic[T]):
    items: list[T]
    total: int
    page: int
```

## Error Handling

```python
class AppError(Exception):
    def __init__(self, message: str, status_code: int = 500) -> None:
        self.message = message
        self.status_code = status_code

class NotFoundError(AppError):
    def __init__(self, entity: str, id: str) -> None:
        super().__init__(f"{entity} not found: {id}", status_code=404)

@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError) -> JSONResponse:
    return JSONResponse(status_code=exc.status_code, content={"error": exc.message})
```

## Configuration

```python
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    model_config = ConfigDict(env_file=".env")
    database_url: str
    debug: bool = False
    log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO"

settings = Settings()  # Validate at import time — fail fast
```

## Anti-Patterns

- Never put business logic in views/routes — delegate to services
- Never use raw SQL without parameterization — SQL injection risk
- Never use synchronous HTTP calls (`requests`) in async handlers — use `httpx`
