Structured Objects
Pydantic provides several ways to define and validate structured data, ranging from full-featured classes to lightweight dictionary-like structures. The core of this functionality is built upon the pydantic-core Rust engine, which handles the heavy lifting of validation and serialization.
Base Models
The BaseModel class in pydantic/main.py is the primary interface for creating structured objects. When you inherit from BaseModel, Pydantic uses a metaclass to process your type annotations and build a validation schema.
Core Functionality
A BaseModel provides automatic validation on initialization and several methods for data manipulation:
__init__: Validates input data against the model's fields.model_dump(): Converts the model instance into a Python dictionary.model_dump_json(): Serializes the model instance to a JSON string.model_construct(): A class method to create a model without validation (useful for trusted data).
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str = 'Jane Doe'
# Validation happens on initialization
user = User(id='123')
assert user.id == 123
assert user.name == 'Jane Doe'
# Serialization
print(user.model_dump())
#> {'id': 123, 'name': 'Jane Doe'}
Internal Metadata
Models store their structural information in several "dunder" attributes managed by Pydantic:
__pydantic_fields__: A dictionary mapping field names toFieldInfoobjects.__pydantic_validator__: Thepydantic-corevalidator instance.__pydantic_serializer__: Thepydantic-coreserializer instance.
Pydantic Dataclasses
For developers who prefer the standard library's dataclass pattern, Pydantic provides an enhanced version in pydantic/dataclasses.py. The @pydantic.dataclasses.dataclass decorator is a drop-in replacement for dataclasses.dataclass that adds Pydantic's validation engine.
Unlike BaseModel, Pydantic dataclasses do not inherit from a base class. Instead, the decorator wraps the class and injects validation logic into the __init__ method.
import pydantic.dataclasses
@pydantic.dataclasses.dataclass
class Item:
name: str
price: float
item = Item(name='Widget', price='10.50')
assert item.price == 10.5
If you apply @pydantic.dataclasses.dataclass to an existing standard library dataclass, Pydantic will create a subclass to add validation without modifying the original class.
TypedDicts and TypeAdapter
When working with structures that are not classes—such as TypedDict or primitive types—Pydantic uses the TypeAdapter class found in pydantic/type_adapter.py.
TypeAdapter provides an interface similar to BaseModel for types that don't support methods. This is particularly useful for validating TypedDict structures, which are treated as plain dictionaries at runtime.
from typing import Annotated
from typing_extensions import TypedDict
from pydantic import TypeAdapter, Field
class UserDict(TypedDict):
id: int
username: Annotated[str, Field(min_length=3)]
ta = TypeAdapter(UserDict)
validated = ta.validate_python({'id': '1', 'username': 'jdoe'})
assert validated == {'id': 1, 'username': 'jdoe'}
Note: On Python versions earlier than 3.12, you should use
typing_extensions.TypedDictinstead of the standard library version to ensure full compatibility with Pydantic's type resolution.
Configuration with ConfigDict
All structured objects in Pydantic can be configured using ConfigDict (defined in pydantic/config.py). This allows you to control behaviors like:
extra: Whether to'ignore','allow', or'forbid'extra fields.frozen: Whether the object should be immutable.strict: Whether to use strict type checking instead of coercion.validate_assignment: Whether to re-validate data when a field is changed.
Applying Configuration
The method for applying configuration depends on the object type:
- BaseModel: Set the
model_configclass attribute. - Dataclass: Pass the
configargument to the decorator. - TypeAdapter: Pass the
configargument to the constructor.
from pydantic import BaseModel, ConfigDict
class StrictModel(BaseModel):
model_config = ConfigDict(strict=True, extra='forbid')
value: int
# This would raise a ValidationError because '1' is a string and strict=True
# model = StrictModel(value='1')
Comparison of Structured Objects
| Feature | BaseModel | pydantic.dataclasses | TypeAdapter (TypedDict) |
|---|---|---|---|
| Inheritance | Required (BaseModel) | None | None |
| Validation | On __init__ | On __init__ | Via validate_python |
| Methods | model_dump, etc. | None (use RootModel or TypeAdapter) | validate_python, dump_python |
| Runtime Type | Class instance | Class instance | dict |
| Config | model_config attr | config parameter | config parameter |
While BaseModel is the most common choice for application logic, TypeAdapter with TypedDict is often preferred for API responses or legacy data structures where maintaining a plain dictionary is required.