Accessing Config in Custom Validators
Custom validators in pydantic-core often need to adapt their behavior based on global settings or specific field context. By using the ValidationInfo protocol, you can access the active CoreConfig and other metadata during the validation process.
In this tutorial, you will build a string validator that enforces uppercase constraints only when the validator is running in "strict" mode, as defined in the CoreConfig.
Prerequisites
To follow this tutorial, you need pydantic-core installed. You will primarily use the core_schema module to define your validation logic.
1. Define a Config-Aware Validator Function
First, you need to create a validator function that accepts two arguments: the value to be validated and an info object of type ValidationInfo.
from typing import Any
from pydantic_core import core_schema
def check_uppercase_if_strict(value: Any, info: core_schema.ValidationInfo) -> str:
# Ensure we are dealing with a string
v = str(value)
# Access the active configuration
config = info.config
# Check if 'strict' mode is enabled in the CoreConfig
if config and config.get('strict'):
if v != v.upper():
raise ValueError("In strict mode, the value must be uppercase")
return v
The ValidationInfo object provides a config property which returns a CoreConfig dictionary (or None if no config is provided). By checking config.get('strict'), your validator can dynamically change its requirements based on how the SchemaValidator was initialized.
2. Wrap the Validator in a Core Schema
To use your function, you must wrap it using one of the "with-info" helper functions. Since we want to check the value after it has been processed as a string, we use with_info_after_validator_function.
# Define the base schema (a simple string)
base_str_schema = core_schema.str_schema()
# Wrap it with our custom validator
schema = core_schema.with_info_after_validator_function(
function=check_uppercase_if_strict,
schema=base_str_schema
)
Using with_info_after_validator_function ensures that pydantic-core passes the ValidationInfo object to your function. If you used a "no-info" variant, your function would only receive the value.
3. Execute Validation with Different Configurations
Now, you can see the validator in action by creating a SchemaValidator and passing different CoreConfig options.
from pydantic_core import SchemaValidator, ValidationError
# Case 1: Validation without strict mode (default)
v1 = SchemaValidator(schema)
print(v1.validate_python("hello")) # Output: "hello"
# Case 2: Validation with strict mode enabled in CoreConfig
config = core_schema.CoreConfig(strict=True)
v2 = SchemaValidator(schema, config=config)
try:
v2.validate_python("hello")
except ValidationError as e:
print(e)
# Output will include: "Value error, In strict mode, the value must be uppercase"
print(v2.validate_python("HELLO")) # Output: "HELLO"
When v2 is initialized with strict=True, the info.config property inside your validator reflects this setting, triggering the uppercase check.
4. Accessing Additional Context
Beyond config, the ValidationInfo protocol provides other useful properties for complex validation logic:
info.data: A dictionary of other fields being validated. This is available when the validator is part of aTypedDictorModeland is validating a specific field.info.field_name: The name of the field currently being validated.info.mode: Whether the input is'python'or'json'.
Here is how you might use field_name and data in a model-like structure:
def field_logger_validator(value: Any, info: core_schema.ValidationInfo) -> Any:
print(f"Validating field: {info.field_name}")
if info.data:
print(f"Sibling data available: {list(info.data.keys())}")
return value
# Using it within a TypedDict
complex_schema = core_schema.typed_dict_schema({
'username': core_schema.typed_dict_field(core_schema.str_schema()),
'profile_slug': core_schema.typed_dict_field(
core_schema.with_info_after_validator_function(
field_logger_validator,
core_schema.str_schema()
)
)
})
validator = SchemaValidator(complex_schema)
validator.validate_python({'username': 'jdoe', 'profile_slug': 'jdoe-123'})
# Output:
# Validating field: profile_slug
# Sibling data available: ['username']
Summary
By utilizing ValidationInfo, you have built a validator that is aware of its execution environment. You learned how to:
- Define a validator that accepts the
ValidationInfoprotocol. - Safely access
CoreConfigsettings likestrict. - Register the validator using
with_info_after_validator_function. - Leverage
field_nameanddatafor context-aware validation.
This approach allows you to write highly reusable validation logic that respects the global configuration of your SchemaValidator.