Custom Validator Functions
Custom validator functions allow you to extend the core validation logic by injecting Python functions at different stages of the validation process. You can use these to transform input, post-process results, or completely override validation for a specific field.
Implement a Before Validator
Use a "before" validator to sanitize or transform raw input before it is passed to the inner schema for validation.
from pydantic_core import SchemaValidator, core_schema
def sanitize_input(v: bytes) -> str:
# Transform raw bytes to string before core validation
return v.decode().strip() + ' world'
# Define a schema that calls sanitize_input BEFORE the string schema
func_schema = core_schema.no_info_before_validator_function(
function=sanitize_input,
schema=core_schema.str_schema()
)
v = SchemaValidator(func_schema)
assert v.validate_python(b' hello ') == 'hello world'
In this example, no_info_before_validator_function ensures that sanitize_input runs first. The result of this function is then passed to core_schema.str_schema().
Implement an After Validator
Use an "after" validator to perform complex validation or post-processing on a value that has already been validated by the inner schema.
from pydantic_core import SchemaValidator, core_schema
def append_suffix(v: str) -> str:
# v is already a validated string here
return v + '!!!'
# Define a schema that calls append_suffix AFTER the string schema
func_schema = core_schema.no_info_after_validator_function(
function=append_suffix,
schema=core_schema.str_schema()
)
v = SchemaValidator(func_schema)
assert v.validate_python('hello') == 'hello!!!'
Control Validation with Wrap Validators
Wrap validators provide the most control by passing a handler (of type ValidatorFunctionWrapHandler) to your function. This allows you to trigger the inner validation at any point, catch its errors, or skip it entirely.
from pydantic_core import SchemaValidator, core_schema
def wrap_validator(
v: Any,
handler: core_schema.ValidatorFunctionWrapHandler,
) -> str:
try:
# Call the inner schema validation
result = handler(input_value=v)
return f"Success: {result}"
except Exception:
# Fallback or custom error handling
return "Validation failed, but I caught it"
schema = core_schema.no_info_wrap_validator_function(
function=wrap_validator,
schema=core_schema.str_schema()
)
v = SchemaValidator(schema)
assert v.validate_python('hello') == 'Success: hello'
assert v.validate_python(123) == 'Validation failed, but I caught it'
The handler can also accept an outer_location to customize the error path if validation fails:
handler(input_value=v, outer_location='my_custom_loc').
Access Validation Metadata
If your validator needs access to the validation context, configuration, or other field data, use the with_info_* variants. These pass a ValidationInfo object as the last argument.
from pydantic_core import SchemaValidator, core_schema
def check_with_context(v: int, info: core_schema.ValidationInfo) -> int:
# Access context passed during validate_python
min_val = info.context.get('min_val', 0)
if v < min_val:
raise ValueError(f"Value must be at least {min_val}")
return v
schema = core_schema.with_info_plain_validator_function(function=check_with_context)
v = SchemaValidator(schema)
# Pass context to the validator
assert v.validate_python(10, context={'min_val': 5}) == 10
The ValidationInfo object provides:
context: The context dictionary passed to the validator.config: TheCoreConfigapplied to the validation.mode: Either'python'or'json'.data: A dictionary of other fields being validated (available when used within a model/TypedDict).field_name: The name of the field being validated.
Replace Validation Entirely
Use a "plain" validator when you want to bypass core validation logic and handle everything in your Python function.
from pydantic_core import SchemaValidator, core_schema
def custom_logic(v: Any) -> str:
if not isinstance(v, str):
raise ValueError("Expected a string")
return v.upper()
schema = core_schema.no_info_plain_validator_function(function=custom_logic)
v = SchemaValidator(schema)
assert v.validate_python('hello') == 'HELLO'
Troubleshooting
Error Handling
If your validator function raises a ValueError or AssertionError, pydantic-core automatically catches it and converts it into a ValidationError. Other exceptions will bubble up normally.
Function Signatures
Ensure your function signature matches the expected type:
- No Info:
def fn(value: Any) -> Any(ordef fn(value, handler)for wrap) - With Info:
def fn(value: Any, info: ValidationInfo) -> Any(ordef fn(value, handler, info)for wrap)
Using the wrong signature will result in a TypeError at runtime.
Deprecated field_name Argument
When creating schemas using functions like with_info_before_validator_function, the field_name keyword argument is deprecated. The field name is now automatically populated in the ValidationInfo object when the validator is used within a model or TypedDict.