Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
da7047b
Add --validators option for Pydantic v2 field validators
koxudaxi Jan 2, 2026
4146987
Add validators documentation page
koxudaxi Jan 2, 2026
8dff2c7
Merge main and resolve conflicts
koxudaxi Jan 2, 2026
a116d14
docs: update CLI reference documentation and prompt data
github-actions[bot] Jan 2, 2026
d41d68c
Replace hash suffix with increment for validator method names
koxudaxi Jan 2, 2026
101317c
docs: update CLI reference documentation and prompt data
github-actions[bot] Jan 2, 2026
a80a5ca
Use ModelResolver for validator method name uniqueness
koxudaxi Jan 2, 2026
bcd6efd
Use ValidatorsConfigType for strict typing and _load_json_config helper
koxudaxi Jan 2, 2026
5752611
Use Pydantic models for validators config validation
koxudaxi Jan 2, 2026
3fb4649
Fix validators enum serialization and Pydantic v1 compatibility
koxudaxi Jan 2, 2026
c6c5b9c
Use ModelValidators type instead of Any for validators config
koxudaxi Jan 2, 2026
5c22227
Remove unnecessary pyright ignore comment
koxudaxi Jan 2, 2026
95ebd3a
Move function-internal imports to top level
koxudaxi Jan 2, 2026
26a13fb
Refactor validators loading to use helper function pattern
koxudaxi Jan 2, 2026
c30eee3
Remove inline comment
koxudaxi Jan 2, 2026
d9e776f
Add tests for validators edge cases and improve coverage
koxudaxi Jan 2, 2026
9d8793d
Merge main into feature/validators-option
koxudaxi Jan 2, 2026
db58280
Fix PR review comments: add import to docs and use model_dump helper
koxudaxi Jan 2, 2026
233159c
Add tests for plain mode and all validators skipped branches
koxudaxi Jan 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/cli-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This documentation is auto-generated from test cases.
| 🔧 [Typing Customization](typing-customization.md) | 27 | Type annotation and import behavior |
| 🏷️ [Field Customization](field-customization.md) | 24 | Field naming and docstring behavior |
| 🏗️ [Model Customization](model-customization.md) | 39 | Model generation behavior |
| 🎨 [Template Customization](template-customization.md) | 18 | Output formatting and custom rendering |
| 🎨 [Template Customization](template-customization.md) | 19 | Output formatting and custom rendering |
| 📘 [OpenAPI-only Options](openapi-only-options.md) | 7 | OpenAPI-specific features |
| 📋 [GraphQL-only Options](graphql-only-options.md) | 1 | |
| ⚙️ [General Options](general-options.md) | 15 | Utilities and meta options |
Expand Down Expand Up @@ -219,6 +219,7 @@ This documentation is auto-generated from test cases.
### V {#v}

- [`--validation`](openapi-only-options.md#validation)
- [`--validators`](template-customization.md#validators)
- [`--version`](utility-options.md#version)

### W {#w}
Expand Down
2 changes: 2 additions & 0 deletions docs/cli-reference/quick-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ datamodel-codegen [OPTIONS]
| [`--treat-dot-as-module`](template-customization.md#treat-dot-as-module) | Treat dots in schema names as module separators. |
| [`--use-double-quotes`](template-customization.md#use-double-quotes) | Use double quotes for string literals in generated code. |
| [`--use-exact-imports`](template-customization.md#use-exact-imports) | Import exact types instead of modules. |
| [`--validators`](template-customization.md#validators) | Add custom field validators to generated Pydantic v2 models. |
| [`--wrap-string-literal`](template-customization.md#wrap-string-literal) | Wrap long string literals across multiple lines. |

### 📘 OpenAPI-only Options
Expand Down Expand Up @@ -347,6 +348,7 @@ All options sorted alphabetically:
- [`--use-union-operator`](typing-customization.md#use-union-operator) - Use | operator for Union types (PEP 604).
- [`--use-unique-items-as-set`](typing-customization.md#use-unique-items-as-set) - Generate set types for arrays with uniqueItems constraint.
- [`--validation`](openapi-only-options.md#validation) - Enable validation constraints (deprecated, use --field-const...
- [`--validators`](template-customization.md#validators) - Add custom field validators to generated Pydantic v2 models.
- [`--version`](utility-options.md#version) - Show program version and exit
- [`--watch`](general-options.md#watch) - Watch input file(s) for changes and regenerate output automa...
- [`--watch-delay`](general-options.md#watch-delay) - Set debounce delay in seconds for watch mode.
Expand Down
78 changes: 78 additions & 0 deletions docs/cli-reference/template-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
| [`--treat-dot-as-module`](#treat-dot-as-module) | Treat dots in schema names as module separators. |
| [`--use-double-quotes`](#use-double-quotes) | Use double quotes for string literals in generated code. |
| [`--use-exact-imports`](#use-exact-imports) | Import exact types instead of modules. |
| [`--validators`](#validators) | Add custom field validators to generated Pydantic v2 models.... |
| [`--wrap-string-literal`](#wrap-string-literal) | Wrap long string literals across multiple lines. |

---
Expand Down Expand Up @@ -2692,6 +2693,83 @@ modules are generated. For single-file output, the difference is minimal.

---

## `--validators` {#validators}

Add custom field validators to generated Pydantic v2 models.

The `--validators` option takes a JSON file defining validators per model.
Each validator specifies the field(s) to validate, the validation function
to import, and optionally the mode (before/after/wrap/plain).
This allows injecting custom validation logic into generated models.

**See also:** [Field Validators](../validators.md)

!!! tip "Usage"

```bash
datamodel-codegen --input schema.json --validators tests/data/jsonschema/field_validators_config.json --output-model-type pydantic_v2.BaseModel --disable-timestamp # (1)!
```

1. :material-arrow-left: `--validators` - the option documented here

??? example "Examples"

**Input Schema:**

```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "User",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string",
"format": "email"
},
"age": {
"type": "integer",
"minimum": 0
}
},
"required": ["name", "email"]
}
```

**Output:**

```python
# generated by datamodel-codegen:
# filename: field_validators.json

from __future__ import annotations

from typing import Any

from myapp.validators import 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)
```

---

## `--wrap-string-literal` {#wrap-string-literal}

Wrap long string literals across multiple lines.
Expand Down
199 changes: 199 additions & 0 deletions docs/validators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<!-- related-cli-options: --validators -->

# Field Validators

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.

## Basic Usage

```bash
datamodel-codegen --input schema.json --output model.py \
--validators validators.json \
--output-model-type pydantic_v2.BaseModel
```

## Validators File Format

The validators file is a JSON file that maps model names to their validator definitions.

### Structure

```json
{
"ModelName": {
"validators": [
{
"field": "field_name",
"function": "module.path.to.validator_function",
"mode": "after"
}
]
}
}
```

### Fields

| 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`) |

## Validator Modes

Pydantic v2 supports different validator modes, each with its own signature:

### `before` / `after` Mode

Standard validators that run before or after Pydantic's own validation:

```python
def validate_name(v: Any, info: ValidationInfo) -> Any:
if not v:
raise ValueError("Name cannot be empty")
return v.strip()
```

### `wrap` Mode

Wrap validators receive a handler to call the next validator in the chain:

```python
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()
```

Comment thread
coderabbitai[bot] marked this conversation as resolved.
### `plain` Mode

Plain validators replace Pydantic's validation entirely:

```python
def plain_validate_name(v: Any) -> str:
if not isinstance(v, str):
raise TypeError("Expected string")
return v
```

## Example

### Input Schema

```json
{
"type": "object",
"title": "User",
"properties": {
"name": {"type": "string"},
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 0}
},
"required": ["name", "email"]
}
```

### Validators File

```json
{
"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"
}
]
}
}
```

### Validator Functions (myapp/validators.py)

```python
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 v
```

### Generated Output

```python
from __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)
```

## Notes

- This feature only supports Pydantic v2 (`--output-model-type pydantic_v2.BaseModel`)
- The `ModelName` in the validators file must match the generated Python class name
- Validator functions are imported automatically based on the `function` path
- When the same validator function is used multiple times, an incrementing suffix (`_1`, `_2`, etc.) is added to ensure method name uniqueness

---

## See Also

- [CLI Reference: `--validators`](cli-reference/general-options.md) - CLI option documentation
- [Pydantic v2 Validators Documentation](https://docs.pydantic.dev/latest/concepts/validators/) - Official Pydantic documentation
Loading
Loading