# pydantic pydantic makes python's type hints real at runtime. define a model with annotations, and pydantic validates and coerces data to match. ```python from pydantic import BaseModel class User(BaseModel): name: str age: int user = User(name="alice", age="25") # age coerced to int user = User(name="alice", age="not a number") # raises ValidationError ``` this is why pydantic shows up everywhere — it bridges python's dynamic runtime and the desire for validated, typed data. ## when to use what | tool | use when | |------|----------| | `BaseModel` | API boundaries, external data, anything you serialize | | `BaseSettings` | configuration from env vars / .env files | | `Annotated[T, ...]` types | reusable validation bound to the type, not the model | | `dataclasses` | internal data you control — no validation overhead | | `TypedDict` | typed dict unpacking (`**kwargs`) with per-key types | pydantic models are heavier than they look — they do real work on instantiation. for internal data: ```python from dataclasses import dataclass @dataclass class BatchResult: successful: list[str] failed: list[tuple[str, Exception]] @property def total(self) -> int: return len(self.successful) + len(self.failed) ``` use pydantic at boundaries. use dataclasses for internal structures. ## contents - [settings](./settings.md) — `BaseSettings`, env loading, splitting config by concern - [validation](./validation.md) — `Annotated` types, validators, custom types - [serialization](./serialization.md) — `model_dump`, computed fields, JSON round-trips ## sources - [how to use pydantic-settings](https://blog.zzstoatzz.io/how-to-use-pydantic-settings/) - [coping with python's type system](https://blog.zzstoatzz.io/coping-with-python-type-system/) - [prefect/src/prefect/types/](https://github.com/prefecthq/prefect/tree/main/src/prefect/types) — `Annotated` type library pattern - [pdsx/_internal/config.py](https://github.com/zzstoatzz/pdsx/blob/main/src/pdsx/_internal/config.py)