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 · all subjects

custom_validators

58 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Custom datetime validator with timezone constraint via Annotated

A custom datetime validator can be implemented using the `__get_pydantic_core_schema__` method on an Annotated metadata class. The validator can enforce timezone constraints by validating that a datetime object has the correct timezone. Use a wrap validator function with `ValidatorFunctionWrapHandler` to perform operations both before and after default Pydantic validation. The `handler` function is called to validate the input with standard Pydantic validation. Use `core_schema.no_info_wrap_validator_function()` to attach the validator to the schema.

Custom datetime validator with UTC offset bounds

A custom datetime validator can enforce UTC offset constraints by checking that a datetime object's UTC offset falls within defined lower and upper bounds in hours. Implement the validator using `__get_pydantic_core_schema__` with a wrap validator that calls `handler(value)` to validate with standard Pydantic validation, then asserts the UTC offset is within bounds using `value.utcoffset().total_seconds() / 3600` to convert to hours.

Validating nested model fields with model_validator

Nested model validation can be performed using a `@model_validator(mode='after')` decorator on the parent model class. This validator receives the fully validated parent instance (via `Self` type hint) and can access nested model fields to perform cross-field validation. Raise `ValueError` with a descriptive message if validation fails. This approach is simpler than passing context to nested validators.

Validating nested model fields with field_validator and validation context

Nested model validation can alternatively be performed using `@field_validator` on the nested model class with access to validation context from the parent. The nested validator receives `info: ValidationInfo` and accesses forbidden data via `info.context.get()`. The parent model must update the context in its own validator to make data available to nested validators. This approach requires passing `context={}` to `model_validate()`, otherwise `info.context` will be `None` and context updates will not occur.

Validation error messages are used by Logfire

The error messages written in custom validator `raise ValueError()` calls are important: they appear in validation error output and are also consumed by external tooling like Logfire to generate explanations of failed validations. Craft error messages carefully as they will be read by users and developers when validation rules reject data.

ValidatorFunctionWrapHandler in wrap validators

The `ValidatorFunctionWrapHandler` type is used as the handler parameter in wrap validator functions. Call `handler(value)` within the wrap validator to perform standard Pydantic validation, allowing custom validation logic to wrap around the default validation.

Context mutation in nested validators can lead to confusing code

While validation context can be mutated within a validator to enable nested validation, this approach adds complexity and can lead to code that is difficult to debug. Use validation context mutation at your own risk.

Pydantic AI validation context and retry behavior

When an LLM returns data that fails Pydantic validation, Pydantic AI automatically feeds the validation errors back to the model and asks it to retry. Validation context passed to the agent is used for validation only and is not sent to the model itself.

Wrap validators for customization

Wrap validators are a powerful way to customize validation in Pydantic V2. They allow you to intercept and modify the validation process on a per-field basis.

Wrap validator example

Example showing wrap validator usage: from datetime import datetime, timezone from typing import Any from pydantic_core.core_schema import ValidatorFunctionWrapHandler from pydantic import BaseModel, field_validator class Meeting(BaseModel): when: datetime @field_validator('when', mode='wrap') def when_now( cls, input_value: Any, handler: ValidatorFunctionWrapHandler ) -> datetime: if input_value == 'now': return datetime.now() when = handler(input_value) if when.tzinfo is None: when = when.replace(tzinfo=timezone.utc) return when print(Meeting(when='2020-01-01T12:00+01:00')) #> when=datetime.datetime(2020, 1, 1, 12, 0, tzinfo=TzInfo(3600)) print(Meeting(when='now')) #> when=datetime.datetime(2032, 1, 2, 3, 4, 5, 6) print(Meeting(when='2020-01-01T12:00')) #> when=datetime.datetime(2020, 1, 1, 12, 0, tzinfo=datetime.timezone.utc)

@validator deprecated, use @field_validator

In Pydantic V2, `@validator` is deprecated and should be replaced with `@field_validator`, which provides new features and improvements. The `@validator` decorator no longer supports the `each_item` keyword argument; instead, annotate the type argument using `Annotated`.

@field_validator no longer accepts field and config arguments

In Pydantic V2, `@field_validator` functions can no longer add `field` or `config` arguments to their signature. To access configuration, use `info.config`. To access field information, use `info.field_name` to index into `cls.model_fields`.

always=True in validators applies standard validators to defaults

In Pydantic V2, if you use `always=True` in a validator, standard validators for the annotated type will also be applied to defaults, not just custom validators. Use `validate_default=True` in `Field` instead for similar behavior with more flexibility.

@root_validator deprecated, use @model_validator

In Pydantic V2, `@root_validator` is deprecated and should be replaced with `@model_validator`, which provides new features and improvements. Note that allowed signatures have changed. In some circumstances (like assignment when `validate_assignment` is True), `@model_validator` receives a model instance instead of a dict.

@root_validator requires skip_on_failure=True explicitly

In Pydantic V2, the deprecated `@root_validator` decorator requires `skip_on_failure=True` to be set explicitly due to refactors in validation logic. The default value can no longer be used.

TypeError no longer converted to ValidationError in validators

In Pydantic V2, when a `TypeError` is raised in a validator function, it is no longer converted into a `ValidationError`. It remains a `TypeError`. This applies to all validation decorators.

allow_reuse keyword argument no longer necessary

In Pydantic V2, the `allow_reuse` keyword argument for validators is no longer necessary. The approach to detecting repeatedly defined functions has been overhauled to only error for redefinition within a single class, reducing false positives.

@validate_arguments renamed to @validate_call

In Pydantic V2, the `@validate_arguments` decorator has been renamed to `@validate_call`. The decorated function no longer has attributes like `raw_function` or `validate` methods added to it.

Four types of field validators

Pydantic supports four types of field validators: After validators run after Pydantic's internal validation; Before validators run before Pydantic's internal parsing and validation; Plain validators terminate validation immediately after returning, skipping further validators and Pydantic's internal validation; Wrap validators are the most flexible, allowing code before or after Pydantic processes input, or immediate termination.

After validators defined with AfterValidator or @field_validator

After validators can be defined using the Annotated pattern with AfterValidator(callable) or using the @field_validator('field_name', mode='after') decorator as a classmethod. They run after Pydantic's internal validation and are generally more type safe. The 'after' mode is the default for the decorator and can be omitted. The validator must return the validated value.

Before validators defined with BeforeValidator or @field_validator

Before validators can be defined using the Annotated pattern with BeforeValidator(callable) or using the @field_validator('field_name', mode='before') decorator as a classmethod. They run before Pydantic's internal parsing and validation. Before validators take the raw input which can be any arbitrary object, and should use Any as the type hint. The value returned is then validated against the field's type annotation by Pydantic. Avoid mutating the value directly if raising a validation error later, as the mutated value may be passed to other validators in unions.

Plain validators terminate validation immediately

Plain validators can be defined using the Annotated pattern with PlainValidator(callable) or using the @field_validator('field_name', mode='plain') decorator as a classmethod. They act similarly to before validators but terminate validation immediately after returning, so no further validators are called and Pydantic does not perform any internal validation against the field type. The field will accept any value the validator returns, regardless of the field's type annotation.

Wrap validators with ValidatorFunctionWrapHandler

Wrap validators are the most flexible of all validator types. They must be defined with a mandatory extra 'handler' parameter of type ValidatorFunctionWrapHandler, which is a callable taking the value to be validated. Wrap validators can run code before or after Pydantic and other validators process the input, or terminate validation immediately by returning early or raising an error. The handler delegates validation to Pydantic and can be wrapped in try..except or not called at all. They can be defined using the Annotated pattern with WrapValidator(callable) or using the @field_validator('field_name', mode='wrap') decorator as a classmethod.

ValidatorFunctionWrapHandler import and usage

ValidatorFunctionWrapHandler is imported from pydantic and is used as the type for the handler parameter in wrap validators. It is a callable that takes the value to be validated and delegates validation to Pydantic. You can wrap the handler call in try..except blocks to handle validation errors.

Three types of model validators

Pydantic supports three types of model validators: After validators run after the whole model has been validated and are defined as instance methods; Before validators run before the model is instantiated and are defined as classmethods; Wrap validators are the most flexible and run code before or after Pydantic processes input data.

Model after validators must return Self

Model after validators run after the whole model has been validated and are defined as instance methods. They must return the validated instance (Self). They can be seen as post-initialization hooks.

Model before validators are classmethods taking Any

Model before validators run before the model is instantiated and are defined as classmethods. They take the raw input data (type hinted as Any) which can be any arbitrary object, not necessarily a dictionary. They should avoid mutating the value directly if raising a validation error later, as the mutated value may be passed to other validators.

Model wrap validators take ModelWrapValidatorHandler

Model wrap validators take a handler parameter of type ModelWrapValidatorHandler[Self] and can run code before or after Pydantic processes the input data, or terminate validation immediately. They are defined as classmethods and return Self.

Raising validation errors with ValueError, AssertionError, or PydanticCustomError

Three types of exceptions can be used to raise validation errors inside validators: ValueError is the most common; AssertionError using the assert statement also works but is skipped when Python is run with the -O optimization flag; PydanticCustomError from pydantic_core is more verbose but provides extra flexibility with custom error types and context.

ValidationInfo argument in validators

Both field and model validators (in all modes) can optionally take an extra ValidationInfo argument providing useful extra information including already validated data (via the data property, None for model validators), user-defined context (via the context property), the current validation mode ('python', 'json', or 'strings'), and the current field name for field validators (via the field_name property).

Accessing already validated data with ValidationInfo.data

For field validators, the already validated data can be accessed using the ValidationInfo.data property, which is a dictionary of field names to validated values. The data property is None for model validators. Validation is performed in the order fields are defined, so you must not access a field that hasn't been validated yet.

Passing and accessing validation context

A context object can be passed to validation methods like model_validate(data, context={...}), which can be accessed inside validator functions using the ValidationInfo.context property. It is currently not possible to provide context when directly instantiating a model with Model(...). The context can be a dictionary or any other object.

Validator ordering in annotated pattern

When using the annotated pattern, the order validators are applied is: Before and Wrap validators run from right to left, then After validators run from left to right. Validators defined using the decorator pattern are converted to their annotated form counterpart and added last, so the same ordering logic applies.

Using annotated pattern for reusable validators

One key benefit of using the annotated pattern is to make validators reusable. You can create a type alias like EvenNumber = Annotated[int, AfterValidator(is_even)] and then use it across multiple models or nested in collections like list[EvenNumber]. Validators can also be composed by creating Annotated[EvenNumber, AfterValidator(another_validator)].

Using decorator pattern for validators on multiple fields

One key benefit of using the @field_validator decorator is to apply the validator to multiple fields in a single decorator. Pass multiple field names as arguments: @field_validator('f1', 'f2', mode='before'). To apply a validator to all fields including those in subclasses, pass '*' as the field name. The check_fields argument can be set to False to disable the check that provided field names are defined on the model, useful when the validator is on a base class.

Default values are not validated by default

Default values of fields are not validated unless configured to do so, and thus custom validators will not be applied to them. This is documented in the fields documentation under validate-default-values.

Model validators inherit in subclasses

A model validator defined in a base class will be called during the validation of a subclass instance. Overriding a model validator in a subclass will override the base class' validator, and thus only the subclass' version of the validator will be called.

InstanceOf special type for instance validation

InstanceOf is a special utility that can be used to validate that a value is an instance of a given class. It is imported from pydantic.functional_validators and used like InstanceOf[ClassName]. When validation fails, the error type is 'is_instance_of'.

SkipValidation special type skips field validation

SkipValidation is a special utility that can be used to skip validation on a field. It is used like SkipValidation[FieldType]. When a value has the wrong type, it will emit a warning during serialization.

ValidateAs special type for custom type validation

ValidateAs is a special utility that can be used to validate a custom type from a type natively supported by Pydantic. It takes two arguments: a validator model (like a BaseModel or TypeAdapter target) and a conversion function that transforms the validated result into the custom type. It is particularly useful when using custom types with multiple fields.

PydanticUseDefault signals to use field default

PydanticUseDefault is imported from pydantic_core and can be raised inside validators to notify Pydantic that the default value should be used instead of the provided value. It is typically used in BeforeValidator to replace None or other sentinel values with the field default.

Field validator example: validating even numbers

Example of an after validator using the decorator pattern: from pydantic import BaseModel, ValidationError, field_validator class Model(BaseModel): number: int @field_validator('number', mode='after') @classmethod def is_even(cls, value: int) -> int: if value % 2 == 1: raise ValueError(f'{value} is not an even number') return value Model(number=1) raises ValidationError with error type 'value_error'

Before validator example: ensure list

Example of a before validator using the decorator pattern: from typing import Any from pydantic import BaseModel, ValidationError, field_validator class Model(BaseModel): numbers: list[int] @field_validator('numbers', mode='before') @classmethod def ensure_list(cls, value: Any) -> Any: if not isinstance(value, list): return [value] else: return value Model(numbers=2) produces numbers=[2]; Model(numbers='str') raises ValidationError

Plain validator example: double or pass through

Example of a plain validator using the decorator pattern: from typing import Any from pydantic import BaseModel, field_validator class Model(BaseModel): number: int @field_validator('number', mode='plain') @classmethod def val_number(cls, value: Any) -> Any: if isinstance(value, int): return value * 2 else: return value Model(number=4) produces number=8; Model(number='invalid') produces number='invalid' despite str not matching int type

Wrap validator example: truncate string on error

Example of a wrap validator using the decorator pattern: from typing import Any, Annotated from pydantic import BaseModel, Field, ValidationError, ValidatorFunctionWrapHandler, field_validator class Model(BaseModel): my_string: Annotated[str, Field(max_length=5)] @field_validator('my_string', mode='wrap') @classmethod def truncate(cls, value: Any, handler: ValidatorFunctionWrapHandler) -> str: try: return handler(value) except ValidationError as err: if err.errors()[0]['type'] == 'string_too_long': return handler(value[:5]) else: raise Model(my_string='abcde') produces my_string='abcde'; Model(my_string='abcdef') produces my_string='abcde'

Model after validator example: check passwords match

Example of a model after validator: from typing_extensions import Self from pydantic import BaseModel, model_validator class UserModel(BaseModel): username: str password: str password_repeat: str @model_validator(mode='after') def check_passwords_match(self) -> Self: if self.password != self.password_repeat: raise ValueError('Passwords do not match') return self

Model before validator example: reject card number

Example of a model before validator: from typing import Any from pydantic import BaseModel, model_validator class UserModel(BaseModel): username: str @model_validator(mode='before') @classmethod def check_card_number_not_present(cls, data: Any) -> Any: if isinstance(data, dict): if 'card_number' in data: raise ValueError("'card_number' should not be included") return data

Model wrap validator example: log failed validation

Example of a model wrap validator: import logging from typing import Any from typing_extensions import Self from pydantic import BaseModel, ModelWrapValidatorHandler, ValidationError, model_validator class UserModel(BaseModel): username: str @model_validator(mode='wrap') @classmethod def log_failed_validation(cls, data: Any, handler: ModelWrapValidatorHandler[Self]) -> Self: try: return handler(data) except ValidationError: logging.error('Model %s failed to validate with data %s', cls, data) raise

PydanticCustomError example with custom error type

Example of raising PydanticCustomError: from pydantic_core import PydanticCustomError from pydantic import BaseModel, ValidationError, field_validator class Model(BaseModel): x: int @field_validator('x', mode='after') @classmethod def validate_x(cls, v: int) -> int: if v % 42 == 0: raise PydanticCustomError( 'the_answer_error', '{number} is the answer!', {'number': v}, ) return v Model(x=84) raises ValidationError with type='the_answer_error' and message '84 is the answer!'

ValidationInfo.data example for field validators

Example of accessing already validated data with ValidationInfo.data: from pydantic import BaseModel, ValidationInfo, field_validator class UserModel(BaseModel): password: str password_repeat: str username: str @field_validator('password_repeat', mode='after') @classmethod def check_passwords_match(cls, value: str, info: ValidationInfo) -> str: if value != info.data['password']: raise ValueError('Passwords do not match') return value Note: username is not accessible in ValidationInfo.data because it is defined after password_repeat

Validation context example with model_validate

Example of passing and using validation context: from pydantic import BaseModel, ValidationInfo, field_validator class Model(BaseModel): text: str @field_validator('text', mode='after') @classmethod def remove_stopwords(cls, v: str, info: ValidationInfo) -> str: if isinstance(info.context, dict): stopwords = info.context.get('stopwords', set()) v = ' '.join(w for w in v.split() if w.lower() not in stopwords) return v data = {'text': 'This is an example document'} Model.model_validate(data) Model.model_validate(data, context={'stopwords': ['this', 'is', 'an']}) produces text='example document'

InstanceOf example for type validation

Example of using InstanceOf: from pydantic import BaseModel, InstanceOf, ValidationError class Fruit: def __repr__(self): return self.__class__.__name__ class Banana(Fruit): ... class Apple(Fruit): ... class Basket(BaseModel): fruits: list[InstanceOf[Fruit]] Basket(fruits=[Banana(), Apple()]) succeeds Basket(fruits=[Banana(), 'Apple']) raises ValidationError with type='is_instance_of'

SkipValidation example

Example of using SkipValidation: from pydantic import BaseModel, SkipValidation class Model(BaseModel): names: list[SkipValidation[str]] m = Model(names=['foo', 'bar']) m = Model(names=['foo', 123]) The validation of the second item (123) is skipped. If it has the wrong type it will emit a warning during serialization.

ValidateAs example for custom type

Example of using ValidateAs: from typing import Annotated from pydantic import BaseModel, TypeAdapter, ValidateAs class MyCls: def __init__(self, a: int) -> None: self.a = a def __repr__(self) -> str: return f"MyCls(a={self.a})" class ValModel(BaseModel): a: int ta = TypeAdapter( Annotated[MyCls, ValidateAs(ValModel, lambda v: MyCls(a=v.a))] ) ta.validate_python({'a': 1}) produces MyCls(a=1)

PydanticUseDefault example

Example of using PydanticUseDefault: from typing import Annotated, Any from pydantic_core import PydanticUseDefault from pydantic import BaseModel, BeforeValidator def default_if_none(value: Any) -> Any: if value is None: raise PydanticUseDefault() return value class Model(BaseModel): name: Annotated[str, BeforeValidator(default_if_none)] = 'default_name' Model(name=None) produces name='default_name'

Reusable annotated validator type

Example of creating reusable validator types with Annotated: from typing import Annotated from pydantic import AfterValidator, BaseModel def is_even(value: int) -> int: if value % 2 == 1: raise ValueError(f'{value} is not an even number') return value EvenNumber = Annotated[int, AfterValidator(is_even)] class Model1(BaseModel): my_number: EvenNumber class Model2(BaseModel): other_number: Annotated[EvenNumber, AfterValidator(lambda v: v + 2)] class Model3(BaseModel): list_of_even_numbers: list[EvenNumber]

Decorator pattern for validators on multiple fields

Example of applying a field validator to multiple fields: from pydantic import BaseModel, field_validator class Model(BaseModel): f1: str f2: str @field_validator('f1', 'f2', mode='before') @classmethod def capitalize(cls, value: str) -> str: return value.capitalize()

Context variable approach for __init__ with context

Example of providing context when directly instantiating a model using ContextVar: from __future__ import annotations from collections.abc import Generator from contextlib import contextmanager from contextvars import ContextVar from typing import Any from pydantic import BaseModel, ValidationInfo, field_validator _init_context_var = ContextVar('_init_context_var', default=None) @contextmanager def init_context(value: dict[str, Any]) -> Generator[None]: token = _init_context_var.set(value) try: yield finally: _init_context_var.reset(token) class Model(BaseModel): my_number: int def __init__(self, /, **data: Any) -> None: self.__pydantic_validator__.validate_python( data, self_instance=self, context=_init_context_var.get(), ) @field_validator('my_number') @classmethod def multiply_with_context(cls, value: int, info: ValidationInfo) -> int: if isinstance(info.context, dict): multiplier = info.context.get('multiplier', 1) value = value * multiplier return value with init_context({'multiplier': 3}): print(Model(my_number=2))

Give your agent this brain