---
description: EEIK Python Standards — apply to all Python source files
globs: ["**/*.py"]
alwaysApply: true
---

# Python & FastAPI Standards

**Applies To:** All Python source files
**Enforced By:** `python-developer` agent, Ruff, mypy, CI gates
**Reference:** `.claude/standards/python.md`, `.claude/standards/fastapi.md`

---

## The 8 Python Non-Negotiables

### 1 — Type Annotations on Everything

Every function and method must be fully annotated. `mypy --strict` must pass.

```python
# ✅ CORRECT
def find_orders(customer_id: str, statuses: list[str]) -> list[Order]:
    ...

# ❌ VIOLATION — untyped
def find_orders(customer_id, statuses):
    ...
```

### 2 — `logging` Not `print()`

```python
# ✅ CORRECT
import logging
logger = logging.getLogger(__name__)
logger.info("Order placed: order_id=%s", order.id)

# ❌ VIOLATION
print(f"Order placed: {order.id}")
```

### 3 — No Bare `except:`

```python
# ✅ CORRECT
try:
    result = service.call()
except ServiceUnavailableError as exc:
    logger.error("Service call failed", exc_info=True)
    raise

# ❌ VIOLATION — catches SystemExit, KeyboardInterrupt
try:
    result = service.call()
except:
    pass
```

### 4 — No `import *`

```python
# ✅ CORRECT
from myapp.domain.orders import Order, OrderStatus

# ❌ VIOLATION
from myapp.domain.orders import *
```

### 5 — Pydantic / Dataclass for Domain Objects

```python
# ✅ CORRECT
from dataclasses import dataclass

@dataclass(frozen=True)
class OrderId:
    value: str

# ❌ VIOLATION — raw dict as domain object
def process(order: dict) -> dict:
    ...
```

### 6 — Constructor Injection (No Global State)

```python
# ✅ CORRECT — dependency injected via FastAPI Depends
from typing import Annotated
from fastapi import Depends

OrderServiceDep = Annotated[OrderService, Depends(get_order_service)]

@router.post("/orders")
async def create_order(service: OrderServiceDep) -> OrderResponse:
    ...

# ❌ VIOLATION — module-level instantiation
db = create_engine(DATABASE_URL)  # global state
```

### 7 — `async def` for All I/O

```python
# ✅ CORRECT
async def get_order(order_id: str) -> Order:
    return await order_repo.find(order_id)

# ❌ VIOLATION — blocking call in async context
async def get_order(order_id: str) -> Order:
    import requests
    resp = requests.get(f"/orders/{order_id}")  # blocks event loop
```

### 8 — Settings via `BaseSettings`

```python
# ✅ CORRECT
from pydantic_settings import BaseSettings

class AppSettings(BaseSettings):
    database_url: str
    secret_key: str

# ❌ VIOLATION — hardcoded configuration
DATABASE_URL = "postgresql://user:password@localhost/db"
```

---

## FastAPI Route Rules

- All routes `async def` with explicit `response_model=`
- Domain errors → global exception handler returning RFC 7807 `ProblemDetail`
- Dependency injection via `Annotated[T, Depends(factory)]`
- Never return raw `dict` from a route

```python
# ✅ CORRECT FastAPI route
@router.post("/v1/orders", response_model=OrderResponse, status_code=201)
async def create_order(
    body: CreateOrderRequest,
    service: OrderServiceDep,
) -> OrderResponse:
    result = await service.place_order(body.to_command())
    return OrderResponse.from_domain(result)
```

---

## Anti-Patterns Checklist

| Anti-Pattern | Fix |
|---|---|
| `print()` | `logger.info(...)` |
| Bare `except:` | `except SpecificError:` |
| `import *` | Explicit imports |
| Mutable default args | `None` sentinel |
| Raw `dict` domain objects | `@dataclass(frozen=True)` or Pydantic |
| Global DB/service instances | `Depends(factory)` per request |
| Blocking I/O in `async def` | `await` with async library |
| `time.sleep()` in tests | `asyncio.wait_for` or `tenacity` |
