pydantic#
pydantic makes python's type hints real at runtime. define a model with annotations, and pydantic validates and coerces data to match.
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:
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 —
BaseSettings, env loading, splitting config by concern - validation —
Annotatedtypes, validators, custom types - serialization —
model_dump, computed fields, JSON round-trips
sources#
- how to use pydantic-settings
- coping with python's type system
- prefect/src/prefect/types/ —
Annotatedtype library pattern - pdsx/_internal/config.py