BaseModel inheritance for schema definition
Models are defined by creating classes that inherit from pydantic.main.BaseModel. Fields are defined as annotated attributes on the class. Models are conceptually similar to structs in C or the requirements of a single API endpoint.
Models differ from Python dataclasses
Pydantic models share similarities with Python dataclasses but have been designed with important differences that streamline workflows related to validation, serialization, and JSON schema generation.
Pydantic validation guarantees output types
Pydantic's validation process instantiates models that adhere to specified types and constraints. Pydantic guarantees the types and constraints of the output, not the input data. The term 'validation' in Pydantic refers to the process of instantiating a model that adheres to specified types and constraints, which may include copying and coercing data to new types without mutating the original input.
Basic model example with default values and config
Example: from pydantic import BaseModel, ConfigDict
class User(BaseModel):
id: int
name: str = 'Jane Doe'
model_config = ConfigDict(str_max_length=10)
In this example, id is required (no default), name is optional with default 'Jane Doe', and the model has configuration setting str_max_length=10.
Field access and type coercion in models
Fields of a model instance are accessed as normal attributes. Pydantic automatically coerces input data to match declared field types. For example, the string '123' passed to an int field will be coerced to integer 123. The model_fields_set attribute can be inspected to check which field names were explicitly set during instantiation.
model_dump() for serialization
The model_dump() method returns a dictionary of the model's fields and values. Calling dict() on a model instance provides a dictionary but nested fields are not recursively converted. model_dump() provides numerous arguments to customize serialization.
Models are mutable by default
By default, Pydantic models are mutable and field values can be changed through attribute assignment after instantiation.
Naming collision pitfall with field names
When defining models, avoid naming collisions between field names and their type annotations. For example, defining 'int: int | None = None' is invalid because Python's annotated assignment evaluation makes it equivalent to 'int: None = None', leading to validation errors.
BaseModel methods and attributes list
BaseModel provides these methods: model_validate() for validating objects, model_validate_json() for JSON data, model_construct() to create without validation, model_dump() for dictionaries, model_dump_json() for JSON strings, model_copy() for copying models, model_json_schema() for JSON Schema, model_rebuild() for rebuilding schema. Attributes: model_fields (mapping of field names to FieldInfo), model_computed_fields (mapping of computed field names to ComputedFieldInfo), model_extra (extra fields set during validation), model_fields_set (fields explicitly provided during initialization), model_parametrized_name() for generic class names, model_post_init() for post-instantiation actions.
Nested models example
Complex hierarchical data structures can use models as types in annotations. Example: class Foo(BaseModel): count: int; size: float | None = None
class Bar(BaseModel): apple: str = 'x'; banana: str = 'y'
class Spam(BaseModel): foo: Foo; bars: list[Bar]
Nested models are automatically validated and can be instantiated from dictionaries.
model_rebuild() for forward annotations
The model_rebuild() method rebuilds the model schema to handle forward annotations where types are not yet defined when the model class is created. In V2, model_rebuild() replaced update_forward_refs() from V1. When calling model_rebuild() on the outermost model, it builds a core schema for the whole model including nested models, so all types at all levels must be ready before calling model_rebuild().
Three validation modes: Python, JSON, and strings
Pydantic validates data in three modes: Python mode (using __init__() constructor with keyword arguments or model_validate()), JSON mode (using model_validate_json() with JSON strings or bytes), strings mode (using model_validate_strings() with dictionaries of string keys and values). Validation methods allow control over strictness, extra data behavior, and validation context.
model_validate() method details
model_validate() validates the given object against the Pydantic model. Data can be provided as a dictionary or as a model instance (instances assumed valid by default unless revalidate_instances setting is changed). Arbitrary objects can also be provided if explicitly enabled with from_attributes configuration.
model_validate_json() for JSON input
model_validate_json() validates data provided as a JSON string or bytes object. This is generally considered faster than manually parsing JSON data as a dictionary.
model_validate_strings() for string coercion
model_validate_strings() validates data as a dictionary with string keys and values, validating in JSON mode so strings are coerced into correct types. This is useful when non-JSON data needs JSON mode validation behavior.
model_construct() creates models without validation
model_construct() creates models without running validation. Useful for complex data already known to be valid, non-idempotent validators, or avoiding side effects. However, model_construct() does not do any validation, so it can create invalid models. Only use with already-validated data or data you definitely trust. In V2, the performance gap between validation and model_construct() has narrowed considerably.
model_construct() behavior notes
model_construct() does not convert dictionaries to model instances for fields referring to model types - you must do this yourself. Default values are still used for fields not passed as keyword arguments. For models with private attributes, the __pydantic_private__ dictionary is populated the same as with validation. No __init__ method from the model or parent classes is called.
Custom __init__() not recommended
While possible to define custom __init__() on models, this is not recommended because validation parameters (strictness, extra data behavior, validation context) will be lost. Instead, use after field validators, after model validators, or model_post_init().
model_post_init() for post-instantiation actions
model_post_init() is called after model instantiation and all field validators are applied. It receives a context argument and can be used to perform actions after the model is initialized. Example: def model_post_init(self, context: Any) -> None: logging.info('Model initialized with id %d', self.id)
ValidationError contains all validation errors
Pydantic raises a single ValidationError exception regardless of how many errors are found. The ValidationError contains information about all errors and how they occurred.
model_copy() for model duplication
model_copy() duplicates models with optional updates, useful with frozen models. Syntax: m.model_copy(update={'field': value}, deep=False). By default performs shallow copy (nested model instances share references). Pass deep=True for deep copy (new object references for nested models).
Generic models with TypeVar
Pydantic supports generic models using TypeVar and Generic from typing. Example: class Response(BaseModel, Generic[DataT]): data: DataT. Use Response[int] or Response[str] to parametrize. Configuration, validation, and serialization logic on the generic model applies to parametrized classes. Generic models create subclasses at runtime which are cached.
Python 3.12 type parameter syntax for generics
Python 3.12 introduces new type parameter syntax: class Response[DataT](BaseModel): data: DataT. This is equivalent to the TypeVar and Generic approach but with cleaner syntax.
Generic model parametrization caching
Internally, Pydantic creates subclasses of generic models at runtime when parametrized. These classes are cached, so minimal overhead is introduced by using generic models.
Inheriting from generic models
To inherit from a generic model and preserve that it is generic, the subclass must also inherit from Generic. Example: class ChildClass(BaseClass[TypeX], Generic[TypeX]): pass
Generic model with partial type replacement
Generic subclasses can partially or fully replace type variables from the superclass. Example: class ChildClass(BaseClass[int, TypeY], Generic[TypeY, TypeZ]): replaces TypeX with int but keeps TypeY generic and adds TypeZ.
model_parametrized_name() customization
Override model_parametrized_name() classmethod to customize the name generation for parametrized generic models. Example: @classmethod def model_parametrized_name(cls, params: tuple[type[Any], ...]) -> str: return f'{params[0].__name__.title()}Response'
Type variable bounds and constraints in unparametrized generics
When a generic model is not parametrized, Pydantic uses type variable bounds or constraints if specified. If the type variable is bound or constrained to a specific type, it will be used. If it has a default type (PEP 696), it will be used. For unbound or unconstrained type variables, Any is used.
Unparametrized generic data loss warning
Validation against unparametrized generic models can lead to data loss when a subtype of the type variable upper bound, constraints, or default is provided without explicit parametrization. The resulting type will be the upper bound, not the provided subtype. Example: ItemHolder(item_data) validates against ItemBase, losing IntItem fields.
Parametrized generics in isinstance() not recommended
Strongly avoid using parametrized generics in isinstance() checks like isinstance(my_model, MyGenericModel[int]). Use isinstance(my_model, MyGenericModel) without parameters instead. If needed, subclass the parametrized generic: class MyIntModel(MyGenericModel[int]): pass, then use isinstance(my_model, MyIntModel).
create_model() for dynamic model creation
The create_model() function creates models dynamically using runtime information. Field definitions are specified as keyword arguments as either: a single element (type annotation), or a two-tuple (type, default value or Field()). Example: DynamicModel = create_model('DynamicModel', foo=str, bar=(int, 123))
create_model() with Field and annotations
Advanced create_model() example: DynamicModel = create_model('DynamicModel', foo=(str, Field(alias='FOO')), bar=Annotated[str, Field(description='Bar field')], _private=(int, PrivateAttr(default=1)))
create_model() with __config__ and __base__ arguments
create_model() supports __config__ and __base__ keyword arguments to customize the model. __base__ extends a base model with extra fields. Example: BarModel = create_model('BarModel', apple=(str, 'russet'), banana=(str, 'yellow'), __base__=FooModel)
create_model() with validators
Validators can be added to dynamically created models by passing a dictionary to __validators__. Example: validators = {'username_validator': field_validator('username')(alphanum)}; UserModel = create_model('UserModel', username=(str, ...), __validators__=validators)
RootModel for custom root types
Pydantic models can be defined with custom root types by subclassing RootModel. The root type is specified as a generic parameter. The root value is passed to __init__ or model_validate() via the first and only argument. Example: Pets = RootModel[list[str]]; Pets(['dog', 'cat'])
RootModel subclassing with methods
RootModel can be subclassed to add custom methods. Example: class Pets(RootModel[list[str]]): def describe(self) -> str: return f'Pets: {", ".join(self.root)}'
Models with Abstract Base Classes
Pydantic models can be used with Python's Abstract Base Classes (ABCs). Example: class FooBarModel(BaseModel, abc.ABC): a: str; b: int; @abc.abstractmethod def my_abstract_method(self): pass
Field ordering preservation
Field order in Pydantic models is preserved in JSON Schema, validation errors, and serialization. The order of field definitions in the model class determines the order in these outputs.
ClassVar for class variables
Attributes annotated with ClassVar are treated as class variables and do not become fields on model instances. Example: class Model(BaseModel): x: ClassVar[int] = 1; y: int = 2. Only y is a field.
Private attributes with underscore prefix
Attributes with a leading underscore are not treated as fields and are not included in the model schema. These are converted to private attributes which are not validated or set during __init__, model_validate(), etc. Example: _secret_value: str
Private attributes with PrivateAttr
Private attributes can be defined using PrivateAttr with optional default values or default factories. Example: _processed_at: datetime = PrivateAttr(default_factory=datetime.now). Dunder names like __attr__ are not supported.
Private attribute default factories with model data
Since v2.13, private attribute default factories can take the validated model data as an argument, allowing factories to access model field values.
Model signature generation
All Pydantic models have their signature generated based on their fields. The signature reflects field names (or aliases), types, and defaults. This is useful for introspection and libraries like FastAPI. The signature respects custom __init__() functions if defined.
Model signature with field aliases
When generating model signatures, Pydantic prioritizes field aliases over field names. If a field has an alias, the alias appears in the signature parameter name. If neither alias nor name is a valid Python identifier, a **data argument is added.
Structural pattern matching for models
Pydantic supports structural pattern matching (PEP 636) in Python 3.10+. Example: match pet: case Pet(species='dog', name=dog_name): print(f'{dog_name} is a dog')
Attribute copies during validation
Arguments passed to the constructor are often copied to perform validation and coercion. The copied objects have different IDs than the originals. Exception: model instances are used as-is unless revalidate_instances='always' is set.
validate_call decorator basic usage
The validate_call() decorator allows arguments passed to a function to be parsed and validated using the function's type annotations before the function is called. It uses the same approach as model creation and initialization internally, providing an easy way to apply validation with minimal boilerplate.
validate_call return value validation default
By default, the return value of a function decorated with validate_call is not validated. To validate the return value, the validate_return argument of the decorator must be set to True.
validate_call parameter configurations supported
The validate_call decorator is designed to work with all possible parameter configurations: positional or keyword parameters with or without defaults, keyword-only parameters after *, positional-only parameters before /, variable positional parameters via *, and variable keyword parameters via **.
validate_call with async functions
The validate_call decorator can also be used on async functions, validating arguments before the async function is executed. Validation errors are raised as normal ValidationError exceptions.
validate_call Unpack for keyword parameters
Unpack and typed dictionaries can be used to annotate variable keyword parameters of a function decorated with validate_call. This allows structured validation of **kwargs using a TypedDict definition, available since v2.10.
validate_call validation failure raises ValidationError
Upon validation failure, validate_call raises a standard Pydantic ValidationError, not TypeError as Python normally does for missing required arguments. The error identifies the argument and value that were rejected.
validate_call type checker compatibility
The validate_call decorator preserves the decorated function's signature and should be compatible with type checkers like mypy and pyright. However, attributes like raw_function won't be recognized by type checkers and require suppression with # type: ignore comments.
validate_call example with async function
Example using validate_call with async function:
from pydantic import PositiveInt, validate_call
@validate_call
async def get_user_email(user_id: PositiveInt):
email = await conn.execute('select email from users where id=$1', user_id)
return email
The decorator validates PositiveInt argument before the async function executes.
validate_call all parameter types example
Example showing validate_call supporting all parameter configurations:
@validate_call
def armageddon(
a: int,
/,
b: int,
*c: int,
d: int,
e: int = None,
**f: int,
) -> str:
return f'a={a} b={b} c={c} d={d} e={e} f={f}'
Supports positional-only (a, b before /), variable positional (*c), keyword-only (d, e after *), and variable keyword (**f).
Pydantic purpose and use cases
Pydantic is a Python data validation and serialization library based on type hints. It is dataclasses with runtime validation and is mostly useful when dealing with external untrusted data, such as when defining an HTTP API. It is generally not recommended to use Pydantic to define classes that are instantiated within user code, as this reduces flexibility and makes it harder to use types not supported by Pydantic.
Avoid from __future__ import annotations in Pydantic modules
Do not use from __future__ import annotations in modules defining Pydantic models if possible, as it stringifies all annotations by default and can cause challenges for Pydantic to evaluate them. Only add explicit quotes to annotations that aren't defined yet, such as self-references (self_ref: 'Model'). In Python >= 3.14, annotations evaluation is deferred, so string annotations should not be used at all.
Recursive type aliases with TypeAliasType
For recursive type aliases, do not use quoted TypeAlias strings like 'list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None' as Pydantic will generally not be able to evaluate them. Instead, use an explicit type alias: type JsonValue = list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None (Python >= 3.12), or from typing_extensions import TypeAliasType; JsonValue = TypeAliasType('JsonValue', 'list[JsonValue] | dict[str, JsonValue] | str | bool | int | float | None')
Subclass serialization uses defined type not runtime value
When subclassing Pydantic models, serialization uses the defined type annotation rather than the runtime value type. For example, if Main has model: Base but an instance is actually Sub1, calling model_dump() will serialize according to Base, not Sub1, causing fields from Sub1 to be missing.
Use discriminated unions instead of model subclasses in fields
Instead of defining a field with a base class type and assigning subclass instances, use discriminated unions with a discriminator field. Example: class Sub1(Base): type: Literal['sub1']; class Sub2(Base): type: Literal['sub2']; Subs = Annotated[Sub1 | Sub2, Field(discriminator='type')]; class Main(BaseModel): model: Subs. This ensures correct validation and serialization.