0
# Validation System
1
2
Decorators and functions for custom validation logic, including field validators, model validators, and functional validation utilities.
3
4
## Capabilities
5
6
### Field Validators
7
8
Decorators for creating custom field validation logic that runs during model validation.
9
10
```python { .api }
11
def field_validator(*fields, mode='before', check_fields=None):
12
"""
13
Decorator for field validation methods.
14
15
Args:
16
*fields (str): Field names to validate
17
mode (str): Validation mode ('before', 'after', 'wrap', 'plain')
18
check_fields (bool, optional): Whether to check if fields exist
19
20
Returns:
21
Decorator function
22
"""
23
24
@field_validator('field_name')
25
@classmethod
26
def validate_field(cls, v):
27
"""
28
Template for field validator method.
29
30
Args:
31
v: Field value to validate
32
33
Returns:
34
Validated value
35
36
Raises:
37
ValueError: If validation fails
38
"""
39
```
40
41
### Model Validators
42
43
Decorators for creating validation logic that operates on the entire model or multiple fields.
44
45
```python { .api }
46
def model_validator(*, mode):
47
"""
48
Decorator for model validation methods.
49
50
Args:
51
mode (str): Validation mode ('before', 'after', 'wrap')
52
53
Returns:
54
Decorator function
55
"""
56
57
@model_validator(mode='after')
58
@classmethod
59
def validate_model(cls, values):
60
"""
61
Template for model validator method.
62
63
Args:
64
values: Model values (dict for 'before', model instance for 'after')
65
66
Returns:
67
Validated values or model instance
68
69
Raises:
70
ValueError: If validation fails
71
"""
72
```
73
74
### Function Validation
75
76
Decorator to add pydantic validation to regular functions.
77
78
```python { .api }
79
def validate_call(*, config=None, validate_return=False):
80
"""
81
Decorator to validate function arguments and optionally return values.
82
83
Args:
84
config: Validation configuration
85
validate_return (bool): Whether to validate return values
86
87
Returns:
88
Decorated function with validation
89
"""
90
```
91
92
### BeforeValidator and AfterValidator
93
94
Functional validators that can be used with Annotated types.
95
96
```python { .api }
97
class BeforeValidator:
98
"""
99
Validator that runs before pydantic's internal validation.
100
"""
101
102
def __init__(self, func):
103
"""
104
Initialize validator.
105
106
Args:
107
func: Validation function
108
"""
109
110
class AfterValidator:
111
"""
112
Validator that runs after pydantic's internal validation.
113
"""
114
115
def __init__(self, func):
116
"""
117
Initialize validator.
118
119
Args:
120
func: Validation function
121
"""
122
123
class WrapValidator:
124
"""
125
Validator that wraps pydantic's internal validation.
126
"""
127
128
def __init__(self, func):
129
"""
130
Initialize validator.
131
132
Args:
133
func: Validation function
134
"""
135
136
class PlainValidator:
137
"""
138
Validator that completely replaces pydantic's internal validation.
139
"""
140
141
def __init__(self, func):
142
"""
143
Initialize validator.
144
145
Args:
146
func: Validation function
147
"""
148
```
149
150
### Core Schema Classes
151
152
Core classes from pydantic-core that are part of the validation API.
153
154
```python { .api }
155
class ValidationInfo:
156
"""
157
Information available during validation.
158
"""
159
160
@property
161
def config(self):
162
"""ConfigDict: Current model configuration"""
163
164
@property
165
def context(self):
166
"""dict | None: Validation context"""
167
168
@property
169
def data(self):
170
"""dict: Raw input data"""
171
172
@property
173
def field_name(self):
174
"""str | None: Current field name"""
175
176
@property
177
def mode(self):
178
"""str: Validation mode ('python' or 'json')"""
179
180
class ValidatorFunctionWrapHandler:
181
"""
182
Handler for wrap validators.
183
"""
184
185
def __call__(self, value):
186
"""
187
Call the wrapped validator.
188
189
Args:
190
value: Value to validate
191
192
Returns:
193
Validated value
194
"""
195
```
196
197
## Usage Examples
198
199
### Field Validators
200
201
```python
202
from pydantic import BaseModel, field_validator
203
import re
204
205
class User(BaseModel):
206
name: str
207
email: str
208
age: int
209
210
@field_validator('name')
211
@classmethod
212
def validate_name(cls, v):
213
if not v.strip():
214
raise ValueError('Name cannot be empty')
215
return v.title()
216
217
@field_validator('email')
218
@classmethod
219
def validate_email(cls, v):
220
pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
221
if not re.match(pattern, v):
222
raise ValueError('Invalid email format')
223
return v.lower()
224
225
@field_validator('age')
226
@classmethod
227
def validate_age(cls, v):
228
if v < 0:
229
raise ValueError('Age cannot be negative')
230
if v > 150:
231
raise ValueError('Age seems unrealistic')
232
return v
233
234
# Usage
235
user = User(name="john doe", email="JOHN@EXAMPLE.COM", age=30)
236
print(user.name) # "John Doe"
237
print(user.email) # "john@example.com"
238
```
239
240
### Model Validators
241
242
```python
243
from pydantic import BaseModel, model_validator
244
from typing import Optional
245
246
class DateRange(BaseModel):
247
start_date: str
248
end_date: str
249
duration_days: Optional[int] = None
250
251
@model_validator(mode='after')
252
def validate_date_range(self):
253
from datetime import datetime
254
255
start = datetime.fromisoformat(self.start_date)
256
end = datetime.fromisoformat(self.end_date)
257
258
if start >= end:
259
raise ValueError('start_date must be before end_date')
260
261
# Calculate duration if not provided
262
if self.duration_days is None:
263
self.duration_days = (end - start).days
264
265
return self
266
267
# Usage
268
date_range = DateRange(
269
start_date="2023-01-01",
270
end_date="2023-01-10"
271
)
272
print(date_range.duration_days) # 9
273
```
274
275
### Function Validation
276
277
```python
278
from pydantic import validate_call
279
from typing import List
280
281
@validate_call
282
def process_data(
283
data: List[int],
284
multiplier: float = 1.0,
285
max_value: int = 100
286
) -> List[int]:
287
"""
288
Process list of integers with validation.
289
"""
290
result = []
291
for item in data:
292
processed = int(item * multiplier)
293
if processed > max_value:
294
processed = max_value
295
result.append(processed)
296
return result
297
298
# Usage - arguments are validated automatically
299
result = process_data([1, 2, 3], multiplier=2.5, max_value=50)
300
print(result) # [2, 5, 7]
301
302
# This would raise ValidationError
303
# process_data("not a list", multiplier=2.5)
304
```
305
306
### Functional Validators with Annotated
307
308
```python
309
from pydantic import BaseModel, BeforeValidator, AfterValidator
310
from typing import Annotated
311
312
def normalize_string(v):
313
"""Normalize string by stripping and converting to lowercase."""
314
if isinstance(v, str):
315
return v.strip().lower()
316
return v
317
318
def validate_positive(v):
319
"""Ensure value is positive."""
320
if v <= 0:
321
raise ValueError('Value must be positive')
322
return v
323
324
class Product(BaseModel):
325
name: Annotated[str, BeforeValidator(normalize_string)]
326
price: Annotated[float, AfterValidator(validate_positive)]
327
328
# Usage
329
product = Product(name=" LAPTOP ", price=999.99)
330
print(product.name) # "laptop"
331
print(product.price) # 999.99
332
```
333
334
### Validation with Context
335
336
```python
337
from pydantic import BaseModel, field_validator, ValidationInfo
338
339
class SecurityModel(BaseModel):
340
username: str
341
role: str
342
343
@field_validator('role')
344
@classmethod
345
def validate_role(cls, v, info: ValidationInfo):
346
# Access validation context
347
if info.context and 'allowed_roles' in info.context:
348
allowed = info.context['allowed_roles']
349
if v not in allowed:
350
raise ValueError(f'Role must be one of: {allowed}')
351
return v
352
353
# Usage with context
354
context = {'allowed_roles': ['admin', 'user', 'guest']}
355
user = SecurityModel.model_validate(
356
{'username': 'john', 'role': 'admin'},
357
context=context
358
)
359
```