Not everything deserves a model class, but lists, dicts and TypedDicts at boundaries still need validation.
BaseModel gives validation methods to classes. TypeAdapter gives the same powers to any type:
ta = TypeAdapter(list[int])
ta.validate_python(["1", 2]) # [1, 2]
ta.validate_json(b"[1, 2]")
ta.dump_json([1, 2])
ta.json_schema()Typical uses:
- top-level collections: a JSON array of objects,
TypeAdapter(list[User]) TypedDicts, when you want plain dicts (no attribute access, no model overhead) but validated keys and types- standard library
dataclasses,NamedTuples andAnnotatedconstraint types - unions that are not wrapped in a model
Creating a TypeAdapter builds a validator, which costs time (it compiles a core schema). Create adapters once, at module level, and reuse them; building one inside a hot loop or per request is a common performance bug.
Pydantic also has its own @pydantic.dataclasses.dataclass, a drop-in for the standard decorator that validates on __init__. It suits code that wants dataclass semantics; BaseModel has more features (config inheritance, model_* methods), so prefer models for API schemas.
validate_call is the function equivalent: decorate a function and its arguments are validated against their annotations on every call, with an optional validate_return=True.
Example
from typing import TypedDict
from pydantic import TypeAdapter, validate_call
class Row(TypedDict):
id: int
score: float
ROWS = TypeAdapter(list[Row]) # build once, reuse
rows = ROWS.validate_json(b'[{"id": "1", "score": 9}, {"id": 2, "score": "7.5"}]')
print(rows) # [{'id': 1, 'score': 9.0}, {'id': 2, 'score': 7.5}]
print(type(rows[0])) # <class 'dict'>
print(ROWS.json_schema()["$defs"]["Row"]["required"]) # ['id', 'score']
@validate_call
def repeat(text: str, times: int) -> str:
return text * times
print(repeat("ab", "3")) # abababRemember
- TypeAdapter(T) gives validate_python/validate_json/dump/json_schema for any type
- Good for top-level lists, TypedDicts and unions
- Build adapters once; construction compiles a validator
- @validate_call validates function arguments
Watch out for
- Constructing a TypeAdapter inside a request handler or loop.