PydanticUseDefault exception in partial JSON parsing
pydantic_core.PydanticUseDefault is an exception that can be raised in validators to signal that a default value should be used. This is useful when handling missing fields from partial JSON parsing.
__get_pydantic_core_schema__ for custom validators on Annotated types
To create a custom validator attached to an Annotated type, implement the __get_pydantic_core_schema__ method that takes source_type and a GetCoreSchemaHandler, and returns a CoreSchema. This method allows you to customize the schema of the annotated type and add custom validation logic.
ValidatorFunctionWrapHandler in wrap validators
A wrap validator receives a ValidatorFunctionWrapHandler as the handler parameter. The handler function is called to perform standard pydantic validation of the input. Wrap validators can perform operations both before and after calling the handler to validate the input.
core_schema.no_info_wrap_validator_function
The core_schema.no_info_wrap_validator_function is used to create a wrap validator schema. It takes two parameters: the validator function and the schema to wrap (typically obtained from handler(source_type)).
Custom datetime validator with timezone constraint via Annotated metadata
This example shows how to create a custom validator using __get_pydantic_core_schema__ that enforces timezone constraints on datetime objects. The validator ensures a datetime object has the correct timezone, supports string specification of the timezone, and raises an error if the timezone does not match the constraint.
Custom datetime validator with UTC offset bounds
This example demonstrates a wrap validator that checks datetime UTC offset is within inclusive bounds. It calls handler(value) to validate the input, then verifies the utcoffset().total_seconds() / 3600 value falls between lower_bound and upper_bound parameters.
model_validator with mode='after' for nested model validation
A model_validator with mode='after' can be placed on the outer model to validate fields of nested models using data from the parent model. The validator function receives self with all fields already validated and must return Self.
field_validator with ValidationInfo context for nested validation
A field_validator can access validation context via the ValidationInfo parameter to receive data from parent models. Use info.context.get() to retrieve values passed via the context parameter in model_validate().
Passing validation context to model_validate
The model_validate() method accepts a context parameter (a dictionary) that is passed to all validators. If context is not included, info.context will be None in field validators.
Mutating context within validators for nested validation
Validation context can be mutated within validators using info.context.update() to pass data from parent to child validators. This approach adds power to nested validation but can make code harder to debug.
Custom validator error messages matter for debugging
Error messages written in raise ValueError() calls in custom validators are displayed when validation fails and are consumed by tooling that processes structured errors. Craft these messages carefully as they will be read during troubleshooting and debugging of real data.
Custom datetime validator with timezone constraint example code
```python
import datetime as dt
from dataclasses import dataclass
from typing import Annotated, Any
import pytz
from pydantic_core import CoreSchema, core_schema
from pydantic import (
GetCoreSchemaHandler,
PydanticUserError,
TypeAdapter,
ValidationError,
ValidatorFunctionWrapHandler,
)
@dataclass(frozen=True)
class MyDatetimeValidator:
tz_constraint: str | None = None
def tz_constraint_validator(
self,
value: dt.datetime,
handler: ValidatorFunctionWrapHandler,
):
"""Validate tz_constraint and tz_info."""
if self.tz_constraint is None:
assert (
value.tzinfo is None
), 'tz_constraint is None, but provided value is tz-aware.'
return handler(value)
if self.tz_constraint not in pytz.all_timezones:
raise PydanticUserError(
f'Invalid tz_constraint: {self.tz_constraint}',
code='unevaluable-type-annotation',
)
result = handler(value)
assert self.tz_constraint == str(
result.tzinfo
), f'Invalid tzinfo: {str(result.tzinfo)}, expected: {self.tz_constraint}'
return result
def __get_pydantic_core_schema__(
self,
source_type: Any,
handler: GetCoreSchemaHandler,
) -> CoreSchema:
return core_schema.no_info_wrap_validator_function(
self.tz_constraint_validator,
handler(source_type),
)
LA = 'America/Los_Angeles'
ta = TypeAdapter(Annotated[dt.datetime, MyDatetimeValidator(LA)])
print(
ta.validate_python(dt.datetime(2023, 1, 1, 0, 0, tzinfo=pytz.timezone(LA)))
)
```
This example creates a custom validator via Annotated metadata that enforces a specific timezone constraint on datetime objects.
Nested model validation with model_validator example
```python
from typing_extensions import Self
from pydantic import BaseModel, ValidationError, model_validator
class User(BaseModel):
username: str
password: str
class Organization(BaseModel):
forbidden_passwords: list[str]
users: list[User]
@model_validator(mode='after')
def validate_user_passwords(self) -> Self:
for user in self.users:
current_pw = user.password
if current_pw in self.forbidden_passwords:
raise ValueError(
f'Password {current_pw} is forbidden. Please choose another password for user {user.username}.'
)
return self
data = {
'forbidden_passwords': ['123'],
'users': [
{'username': 'Spartacat', 'password': '123'},
{'username': 'Iceburgh', 'password': '87'},
],
}
try:
org = Organization(**data)
except ValidationError as e:
print(e)
```
This example shows how to use a model_validator on the outer model to validate nested model fields using parent model data.
Nested model validation with field_validator and context example
```python
from pydantic import BaseModel, ValidationError, ValidationInfo, field_validator
class User(BaseModel):
username: str
password: str
@field_validator('password', mode='after')
@classmethod
def validate_user_passwords(
cls, password: str, info: ValidationInfo
) -> str:
forbidden_passwords = (
info.context.get('forbidden_passwords', []) if info.context else []
)
if password in forbidden_passwords:
raise ValueError(f'Password {password} is forbidden.')
return password
class Organization(BaseModel):
forbidden_passwords: list[str]
users: list[User]
@field_validator('forbidden_passwords', mode='after')
@classmethod
def add_context(cls, v: list[str], info: ValidationInfo) -> list[str]:
if info.context is not None:
info.context.update({'forbidden_passwords': v})
return v
data = {
'forbidden_passwords': ['123'],
'users': [
{'username': 'Spartacat', 'password': '123'},
{'username': 'Iceburgh', 'password': '87'},
],
}
try:
org = Organization.model_validate(data, context={})
except ValidationError as e:
print(e)
```
This example demonstrates passing forbidden passwords from parent model to nested model validators via validation context.
Example: detecting cyclic references with field_validator
```python
from __future__ import annotations
from collections.abc import Generator
from contextlib import contextmanager
from dataclasses import field
from pydantic import BaseModel, ValidationError, field_validator
def is_recursion_validation_error(exc: ValidationError) -> bool:
errors = exc.errors()
return len(errors) == 1 and errors[0]['type'] == 'recursion_loop'
@contextmanager
def suppress_recursion_validation_error() -> Generator[None]:
try:
yield
except ValidationError as exc:
if not is_recursion_validation_error(exc):
raise exc
class Node(BaseModel):
id: int
children: list[Node] = field(default_factory=list)
@field_validator('children', mode='wrap')
@classmethod
def drop_cyclic_references(cls, children, h):
try:
return h(children)
except ValidationError as exc:
if not (
is_recursion_validation_error(exc)
and isinstance(children, list)
):
raise exc
value_without_cyclic_refs = []
for child in children:
with suppress_recursion_validation_error():
value_without_cyclic_refs.extend(h([child]))
return h(value_without_cyclic_refs)
```
This example shows how to handle cyclic references in validation by using a wrap mode field_validator to catch recursion_loop errors and filter out cyclic references.