new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Pydantic · Concepts · all subjects

models

63 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

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.

Give your agent this brain