The --validators option allows you to add custom field validators to generated Pydantic v2 models. This enables you to inject validation logic into generated code without manually editing it.
datamodel-codegen --input schema.json --output model.py \
--validators validators.json \
--output-model-type pydantic_v2.BaseModelThe validators file is a JSON file that maps model names to their validator definitions.
{
"ModelName": {
"validators": [
{
"field": "field_name",
"function": "module.path.to.validator_function",
"mode": "after"
}
]
}
}| Field | Description | Required |
|---|---|---|
field |
Single field name to validate | One of field or fields |
fields |
List of field names (for multi-field validators) | One of field or fields |
function |
Fully qualified path to the validator function | Yes |
mode |
Validator mode: before, after, wrap, or plain |
No (default: after) |
Pydantic v2 supports different validator modes, each with its own signature:
Standard validators that run before or after Pydantic's own validation:
def validate_name(v: Any, info: ValidationInfo) -> Any:
if not v:
raise ValueError("Name cannot be empty")
return v.strip()Wrap validators receive a handler to call the next validator in the chain:
from pydantic import ValidationInfo, ValidatorFunctionWrapHandler
def wrap_validate_name(
v: Any,
handler: ValidatorFunctionWrapHandler,
info: ValidationInfo
) -> Any:
# Pre-processing
v = v.strip() if isinstance(v, str) else v
# Call next validator
result = handler(v)
# Post-processing
return result.upper()Plain validators replace Pydantic's validation entirely:
def plain_validate_name(v: Any) -> str:
if not isinstance(v, str):
raise TypeError("Expected string")
return v{
"type": "object",
"title": "User",
"properties": {
"name": {"type": "string"},
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 0}
},
"required": ["name", "email"]
}{
"User": {
"validators": [
{
"field": "name",
"function": "myapp.validators.validate_name",
"mode": "before"
},
{
"field": "email",
"function": "myapp.validators.validate_email",
"mode": "after"
},
{
"fields": ["name", "email"],
"function": "myapp.validators.validate_contact_info",
"mode": "after"
}
]
}
}from typing import Any
from pydantic import ValidationInfo
def validate_name(v: Any, info: ValidationInfo) -> Any:
if isinstance(v, str):
return v.strip()
return v
def validate_email(v: Any, info: ValidationInfo) -> Any:
if isinstance(v, str) and not v.endswith("@example.com"):
# Custom email domain validation
pass
return v
def validate_contact_info(v: Any, info: ValidationInfo) -> Any:
# This runs for both name and email fields
return vfrom __future__ import annotations
from typing import Any
from myapp.validators import validate_contact_info, validate_email, validate_name
from pydantic import BaseModel, EmailStr, ValidationInfo, conint, field_validator
class User(BaseModel):
name: str
email: EmailStr
age: conint(ge=0) | None = None
@field_validator('name', mode='before')
@classmethod
def validate_name_validator(cls, v: Any, info: ValidationInfo) -> Any:
return validate_name(v, info)
@field_validator('email', mode='after')
@classmethod
def validate_email_validator(cls, v: Any, info: ValidationInfo) -> Any:
return validate_email(v, info)
@field_validator('name', 'email', mode='after')
@classmethod
def validate_contact_info_validator(cls, v: Any, info: ValidationInfo) -> Any:
return validate_contact_info(v, info)- This feature only supports Pydantic v2 (
--output-model-type pydantic_v2.BaseModel) - The
ModelNamein the validators file must match the generated Python class name - Validator functions are imported automatically based on the
functionpath - When the same validator function is used multiple times, an incrementing suffix (
_1,_2, etc.) is added to ensure method name uniqueness
- CLI Reference:
--validators- CLI option documentation - Pydantic v2 Validators Documentation - Official Pydantic documentation