# Python — Language Application > How Atelier's domain principles apply in Python specifically. Derives from `domains/` docs. ## Derived Docs - [py-types.md](py-types.md) — type hints + Pydantic, mypy/pyright, gradual typing. - [py-tooling.md](py-tooling.md) — ruff, mypy, poetry, uv, virtualenv discipline. - [py-async.md](py-async.md) — asyncio, anyio, cancellation, structured concurrency. - [py-testing.md](py-testing.md) — pytest, factory_boy, fixture discipline, parametrize. ## Type System (C1 Correctness, Data P7 Type Fidelity) - **Type hints on every function:** `def get_user(id: UUID) -> User | None:`. - **`mypy --strict` or `pyright` in CI:** type check is not optional. - **No `Any` without justification:** `Any` disables the type checker. Use `object` + narrowing. - **Pydantic for runtime validation:** schemas validate and type at the boundary. ```python from pydantic import BaseModel from uuid import UUID class UserCreate(BaseModel): email: str name: str # additionalProperties: false by default (extra='forbid') ``` ## Error Handling (Errors P1 Errors are Data) - **Exceptions for exceptional cases,** not control flow. `raise` not `return None` for errors. - **Custom exception hierarchy:** ```python class AppError(Exception): pass class ValidationError(AppError): pass class NotFoundError(AppError): pass ``` - **Never bare `except:`:** `except Exception as e:` (catch specific, not everything). - **Never `except: pass`:** log and re-raise or handle, never swallow (Errors P2). ## Async (Concurrency P7, P8) - **`asyncio` for I/O-bound:** `async def`, `await`. Not threads for I/O. - **`anyio` for portability** if you may switch runtimes (trio compatibility). - **Timeout on every `await`:** `asyncio.wait_for(coro, timeout=5)`, not bare `await`. - **Cancellation propagated:** `asyncio.CancelledError` is not caught; it propagates. ## Immutability (Concurrency P1) - **`frozen=True` dataclasses** for value objects: ```python from dataclasses import dataclass @dataclass(frozen=True) class UserId: value: str ``` - **Tuples over lists** for fixed-length, immutable sequences. - **No in-place mutation of shared state:** return new objects. ## Nullability (C1) - **`Optional[T]` is `T | None`:** explicit, must be checked. - **`None` is not "not found":** raise `NotFoundError` or return `Result`, not `None`. - **`assert` is for invariants,** not for runtime checks (stripped with `-O`). ## Testing (Testing) - **pytest** with fixtures (factories, not shared state). - **`pytest --randomly`** to catch order-dependent tests (P2 Independence). - **`freezegun` for time:** no `datetime.now()` in tests; inject the clock. - **`factory_boy` or `pytest-factoryboy`** for realistic factories. ## Observability (Observability P1) - **`structlog` or `python-json-logger`:** JSON logs, not `print`. - **`logging` with structured formatter:** every log has `request_id`, `user_id`, `event`. - **No secrets in logs:** `mask_secret()` helper, or `structlog` processors. ## Tooling (DevOps P2) - **`ruff` for lint + format:** replaces flake8 + black + isort. - **`mypy --strict` in CI:** type check. - **`pip-tools` or `poetry` for lockfile:** pinned dependencies. - **`pip install --no-deps -r requirements.txt`:** reproducible install.