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

validators

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

Four types of field validators

Field validators can be one of four types: after validators run after Pydantic's internal validation and are generally more type safe; before validators run before Pydantic's internal parsing and validation but handle raw input; plain validators terminate validation immediately after returning with no further validators called and no Pydantic internal validation; wrap validators are the most flexible and can run code before or after Pydantic validation or terminate immediately.

Field validator definition patterns

Field validators can be defined using two patterns: the annotated pattern with AfterValidator, BeforeValidator, PlainValidator, or WrapValidator in a type annotation, or the decorator pattern using the @field_validator decorator applied as a class method.

After validator returns validated value

After validators must return the validated value. They run after Pydantic's internal validation, check for specific conditions, and can perform coercion or mutation on the validated value.

Before validator receives raw input

Before validators receive raw input which can be any arbitrary object. The value returned from a before validator is then validated against the provided type annotation by Pydantic. Before validators run before Pydantic's internal parsing and coercion.

Plain validator terminates validation

Plain validators act similarly to before validators but terminate validation immediately after returning. No further validators are called and Pydantic does not perform any of its internal validation against the field type.

Wrap validator has mandatory handler parameter

Wrap validators must be defined with a mandatory extra handler parameter, which is a callable taking the value to be validated. The handler delegates validation of the value to Pydantic. The handler can be wrapped in a try-except block or not called at all.

Field validator decorator mode parameter

The @field_validator decorator accepts a mode parameter which can be 'after' (the default), 'before', 'plain', or 'wrap' to specify which type of field validator to use.

Field validator after mode example

Example of an after validator using the decorator pattern that checks if a number is even: ```python 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 try: Model(number=1) except ValidationError as err: print(err) ```

Field validator before mode example

Example of a before validator using the decorator pattern that ensures input is a list: ```python 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 print(Model(numbers=2)) #> numbers=[2] ```

Field validator plain mode example

Example of a plain validator using the decorator pattern that doubles integers: ```python 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 print(Model(number=4)) #> number=8 print(Model(number='invalid')) #> number='invalid' ``` Note that although 'invalid' should not validate against the int type, Pydantic accepts the input because plain validators terminate validation immediately.

Field validator wrap mode example

Example of a wrap validator using the decorator pattern that truncates strings exceeding max_length: ```python 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 print(Model(my_string='abcde')) #> my_string='abcde' print(Model(my_string='abcdef')) #> my_string='abcde' ```

Annotated pattern validator reusability

A key benefit of using the annotated pattern for validators is to make validators reusable by defining a type alias with validators, which can then be used across multiple model fields and even nested within collection types.

Annotated pattern validator example

Example of creating a reusable EvenNumber type using the annotated pattern: ```python 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 multi-field validator

A key benefit of using the decorator pattern for validators is to apply a single validator function to multiple fields by passing multiple field names to the @field_validator decorator. Example: ```python 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() ```

Field validator wildcard and check_fields

When using the @field_validator decorator, passing '*' as the field name applies the validator to all fields including ones defined in subclasses. The check_fields parameter can be set to False to disable the check that provided field names are defined on the model, which is useful when the field validator is defined on a base class and the field is expected on subclasses.

Three types of model validators

Model validators can be one of three types: after validators run after the whole model has been validated and are defined as instance methods returning the validated instance; before validators run before the model is instantiated and handle raw input; wrap validators are the most flexible and can run code before or after Pydantic validation or terminate immediately.

Model after validator example

Example of a model after validator that checks two fields match: ```python 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

Example of a model before validator that checks for presence of a field: ```python 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

Example of a model wrap validator that logs failed validation: ```python 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 ```

Model validator must return instance

After model validators are defined as instance methods and must return the validated instance of type Self.

Model before validator handles raw input

Model before validators handle raw input which can be any arbitrary object. Most commonly the input data will be a dictionary when calling Model(field=value), but it could also be an arbitrary class instance if from_attributes configuration is set.

Model before validator mutation pitfall

When using model before validators, avoid mutating the value directly if raising a validation error later in the validator function, as the mutated value may be passed to other validators if using unions.

Model validator inheritance

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 will be called.

Validation error exceptions

Three types of exceptions can be raised inside validators: ValueError which is the most common; AssertionError using the assert statement, but note these are skipped when Python runs with the -O optimization flag; PydanticCustomError which is more verbose but provides extra flexibility.

PydanticCustomError example

Example of raising a PydanticCustomError in a field validator: ```python 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 try: Model(x=42 * 2) except ValidationError as e: print(e) ```

ValidationInfo provides extra validator information

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

ValidationInfo data property for field validators

For field validators, the already validated data can be accessed using the data property of ValidationInfo. This provides access to previously validated fields in the order they were defined. Note that you must ensure you are not accessing a field that hasn't been validated yet.

ValidationInfo data example

Example of using ValidationInfo.data to check if password fields match: ```python 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 ```

ValidationInfo data is None for model validators

The data property of ValidationInfo is None when using model validators, unlike field validators where it contains already validated field data.

ValidationInfo context from model_validate

A context object can be passed to model validation methods like model_validate(), which can be accessed inside validator functions using the context property of ValidationInfo.

ValidationInfo context example

Example of passing context to model_validate and using it in a field validator: ```python 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'} print(Model.model_validate(data)) #> text='This is an example document' print(Model.model_validate(data, context={'stopwords': ['this', 'is', 'an']})) #> text='example document' ```

Context not available in direct model instantiation

It is currently not possible to provide a context when directly instantiating a model by calling Model(...). A workaround is to use a ContextVar and a custom __init__ method.

Validator ordering in annotated pattern

When using the annotated pattern, the order of validator application is: before and wrap validators run from right to left, then after validators run from left to right.

Decorator validators converted to annotated form

Validators defined using the @field_validator decorator are internally converted to their annotated form counterpart and added last after the existing metadata for the field. The same validator ordering logic applies.

InstanceOf validator usage

InstanceOf can be used to validate that a value is an instance of a given class. Example: ```python 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]] print(Basket(fruits=[Banana(), Apple()])) #> fruits=[Banana, Apple] ```

SkipValidation utility

SkipValidation can be used to skip validation on a field. When using SkipValidation, validation of that field is not performed. Example: ```python from pydantic import BaseModel, SkipValidation class Model(BaseModel): names: list[SkipValidation[str]] m = Model(names=['foo', 'bar']) print(m) #> names=['foo', 'bar'] m = Model(names=['foo', 123]) print(m) #> names=['foo', 123] ``` Note that if a field with SkipValidation has the wrong type, it will emit a warning during serialization.

ValidateAs validator for custom types

ValidateAs can be used to validate a custom type from a type natively supported by Pydantic. This is particularly useful when using custom types with multiple fields.

ValidateAs example

Example of using ValidateAs to validate a custom class from a dictionary: ```python 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))] ) print(ta.validate_python({'a': 1})) #> MyCls(a=1) ```

PydanticUseDefault exception for default values

PydanticUseDefault can be used to notify Pydantic that the default value should be used instead of the provided input value.

PydanticUseDefault example

Example of using PydanticUseDefault to use the default value when None is passed: ```python 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' print(Model(name=None)) #> name='default_name' ```

Default values 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 default values by default.

Before/plain/wrap validators and JSON Schema input type

When using before, plain, or wrap field validators, the accepted input type may be different from the field annotation. The json_schema_input_type argument can be provided to specify the correct input type for JSON Schema generation.

json_schema_input_type example

Example of using json_schema_input_type to specify that a string field also accepts integers: ```python from typing import Any from pydantic import BaseModel, field_validator class Model(BaseModel): value: str @field_validator('value', mode='before', json_schema_input_type=int | str) @classmethod def cast_ints(cls, value: Any) -> Any: if isinstance(value, int): return str(value) else: return value print(Model.model_json_schema()['properties']['value']) #> {'anyOf': [{'type': 'integer'}, {'type': 'string'}], 'title': 'Value'} ```

json_schema_input_type defaults to field type or Any

If json_schema_input_type is not provided, Pydantic will use the field type by default. However, for plain validators, json_schema_input_type defaults to Any since the field type is completely discarded by plain validators.

Use built-in validation constraints instead of custom validators

Use built-in validation constraints from Field() or annotated_types instead of defining custom validators whenever possible. For example, use Annotated[int, Gt(1)] instead of writing a field_validator that checks if value > 1.

Prefer after validators over before validators

Use after validators because they run after Pydantic validation, guaranteeing you work with the correct field type. Before validators are more error-prone because input data can be anything, especially for model validators where input might not be a dict but an arbitrary object.

Annotated pattern for validators over decorator pattern

Prefer using the Annotated pattern with validators like AfterValidator placed next to the field definition, making it easy to understand. The decorator pattern with field_validator can lead to unclear behavior, especially when considering execution order with subclasses.

AfterValidator and field_validator example

Example comparing Annotated validator pattern with decorator pattern: from pydantic import BaseModel, field_validator, AfterValidator from typing import Annotated def is_even(value: int) -> int: if value % 2 == 1: raise ValueError(f'{value} is not an even number') return value class Model(BaseModel): even: Annotated[int, AfterValidator(is_even)] odd: int @field_validator('odd', mode='after') @classmethod def is_odd(cls, value: int) -> int: if value % 2 == 0: raise ValueError(f'{value} is not an odd number') return value Prefer the Annotated form as it places the validator next to the field.

Give your agent this brain