0
# Request Validation
1
2
Automatic request validation using Pydantic models for different parameter types. Flask-OpenAPI3 automatically validates incoming requests against Pydantic models and handles validation errors with customizable responses.
3
4
## Capabilities
5
6
### Automatic Request Validation
7
8
The core validation decorator that automatically validates requests based on function parameter type hints.
9
10
```python { .api }
11
def validate_request():
12
"""
13
Decorator for automatic request validation against Pydantic models.
14
15
Applied automatically when using typed parameters in route handlers.
16
Validates path, query, header, cookie, form, and body parameters.
17
18
Raises:
19
ValidationError: When request data doesn't match the expected schema
20
"""
21
```
22
23
**Usage Example:**
24
25
```python
26
from flask_openapi3 import OpenAPI, Info
27
from pydantic import BaseModel
28
29
app = OpenAPI(__name__, info=Info(title="API", version="1.0.0"))
30
31
class UserQuery(BaseModel):
32
name: str
33
age: int = None
34
35
class UserBody(BaseModel):
36
email: str
37
password: str
38
39
# Validation applied automatically based on parameter types
40
@app.post("/users")
41
def create_user(query: UserQuery, body: UserBody):
42
# query and body are automatically validated
43
return {"message": f"User {query.name} created with email {body.email}"}
44
```
45
46
### Internal Validation Functions
47
48
Core validation logic for different parameter types.
49
50
```python { .api }
51
def _validate_request(
52
header: Optional[Type[BaseModel]] = None,
53
cookie: Optional[Type[BaseModel]] = None,
54
path: Optional[Type[BaseModel]] = None,
55
query: Optional[Type[BaseModel]] = None,
56
form: Optional[Type[BaseModel]] = None,
57
body: Optional[Type[BaseModel]] = None,
58
raw: Optional[Type[RawModel]] = None,
59
path_kwargs: Optional[dict[Any, Any]] = None,
60
) -> dict:
61
"""
62
Core request validation logic.
63
64
Args:
65
header: Pydantic model for header parameters
66
cookie: Pydantic model for cookie parameters
67
path: Pydantic model for path parameters
68
query: Pydantic model for query parameters
69
form: Pydantic model for form data
70
body: Pydantic model for request body
71
raw: RawModel for raw request data
72
path_kwargs: Path parameters extracted from URL
73
74
Returns:
75
Dictionary of validated request parameters
76
"""
77
```
78
79
### Path Parameter Validation
80
81
Validates URL path parameters against Pydantic models.
82
83
```python { .api }
84
def _validate_path(path: Type[BaseModel], path_kwargs: dict, func_kwargs: dict):
85
"""
86
Validate path parameters from URL route.
87
88
Args:
89
path: Pydantic model defining expected path parameters
90
path_kwargs: Extracted path parameters from URL
91
func_kwargs: Function arguments to populate
92
93
Raises:
94
ValidationError: When path parameters don't match model
95
"""
96
```
97
98
**Usage Example:**
99
100
```python
101
from pydantic import BaseModel
102
103
class UserPath(BaseModel):
104
user_id: int
105
106
@app.get("/users/<int:user_id>")
107
def get_user(path: UserPath):
108
# path.user_id is validated as integer
109
return {"user_id": path.user_id}
110
```
111
112
### Query Parameter Validation
113
114
Validates URL query parameters against Pydantic models.
115
116
```python { .api }
117
def _validate_query(query: Type[BaseModel], func_kwargs: dict):
118
"""
119
Validate query parameters from URL query string.
120
121
Args:
122
query: Pydantic model defining expected query parameters
123
func_kwargs: Function arguments to populate
124
125
Raises:
126
ValidationError: When query parameters don't match model
127
"""
128
```
129
130
**Usage Example:**
131
132
```python
133
from pydantic import BaseModel
134
from typing import Optional
135
136
class SearchQuery(BaseModel):
137
q: str
138
limit: int = 10
139
offset: int = 0
140
category: Optional[str] = None
141
142
@app.get("/search")
143
def search(query: SearchQuery):
144
# Query parameters automatically validated and converted
145
return {
146
"query": query.q,
147
"limit": query.limit,
148
"offset": query.offset,
149
"category": query.category
150
}
151
```
152
153
### Header Parameter Validation
154
155
Validates HTTP headers against Pydantic models.
156
157
```python { .api }
158
def _validate_header(header: Type[BaseModel], func_kwargs: dict):
159
"""
160
Validate HTTP headers.
161
162
Args:
163
header: Pydantic model defining expected headers
164
func_kwargs: Function arguments to populate
165
166
Raises:
167
ValidationError: When headers don't match model
168
"""
169
```
170
171
**Usage Example:**
172
173
```python
174
from pydantic import BaseModel, Field
175
from typing import Optional
176
177
class AuthHeaders(BaseModel):
178
authorization: str = Field(alias="Authorization")
179
content_type: str = Field(alias="Content-Type", default="application/json")
180
user_agent: Optional[str] = Field(alias="User-Agent", default=None)
181
182
@app.post("/protected")
183
def protected_endpoint(header: AuthHeaders, body: dict):
184
# Headers automatically validated and available
185
auth_token = header.authorization
186
return {"authorized": True}
187
```
188
189
### Cookie Parameter Validation
190
191
Validates HTTP cookies against Pydantic models.
192
193
```python { .api }
194
def _validate_cookie(cookie: Type[BaseModel], func_kwargs: dict):
195
"""
196
Validate HTTP cookies.
197
198
Args:
199
cookie: Pydantic model defining expected cookies
200
func_kwargs: Function arguments to populate
201
202
Raises:
203
ValidationError: When cookies don't match model
204
"""
205
```
206
207
**Usage Example:**
208
209
```python
210
from pydantic import BaseModel
211
from typing import Optional
212
213
class SessionCookies(BaseModel):
214
session_id: str
215
preferences: Optional[str] = None
216
217
@app.get("/profile")
218
def get_profile(cookie: SessionCookies):
219
# Cookies automatically validated
220
return {"session": cookie.session_id}
221
```
222
223
### Form Data Validation
224
225
Validates form data (application/x-www-form-urlencoded or multipart/form-data) against Pydantic models.
226
227
```python { .api }
228
def _validate_form(form: Type[BaseModel], func_kwargs: dict):
229
"""
230
Validate form data from request.
231
232
Args:
233
form: Pydantic model defining expected form fields
234
func_kwargs: Function arguments to populate
235
236
Raises:
237
ValidationError: When form data doesn't match model
238
"""
239
```
240
241
**Usage Example:**
242
243
```python
244
from pydantic import BaseModel
245
from flask_openapi3 import FileStorage
246
247
class UserForm(BaseModel):
248
name: str
249
email: str
250
age: int
251
avatar: Optional[FileStorage] = None
252
253
@app.post("/users/form")
254
def create_user_form(form: UserForm):
255
# Form data including file uploads validated
256
return {
257
"name": form.name,
258
"email": form.email,
259
"age": form.age,
260
"has_avatar": form.avatar is not None
261
}
262
```
263
264
### Request Body Validation
265
266
Validates JSON request bodies against Pydantic models.
267
268
```python { .api }
269
def _validate_body(body: Type[BaseModel], func_kwargs: dict):
270
"""
271
Validate JSON request body.
272
273
Args:
274
body: Pydantic model defining expected body structure
275
func_kwargs: Function arguments to populate
276
277
Raises:
278
ValidationError: When body doesn't match model
279
"""
280
```
281
282
**Usage Example:**
283
284
```python
285
from pydantic import BaseModel, EmailStr, validator
286
from typing import Optional
287
288
class CreateUserBody(BaseModel):
289
name: str
290
email: EmailStr
291
password: str
292
age: Optional[int] = None
293
294
@validator('password')
295
def validate_password(cls, v):
296
if len(v) < 8:
297
raise ValueError('Password must be at least 8 characters')
298
return v
299
300
@app.post("/users")
301
def create_user(body: CreateUserBody):
302
# JSON body automatically validated with custom validators
303
return {"id": 1, "name": body.name, "email": body.email}
304
```
305
306
### Raw Data Validation
307
308
Validates raw request data for custom content types.
309
310
```python { .api }
311
class RawModel(Request):
312
"""Raw request data handling"""
313
mimetypes: list[str] = ["application/json"]
314
```
315
316
**Usage Example:**
317
318
```python
319
from flask_openapi3 import RawModel
320
321
class CSVRawModel(RawModel):
322
mimetypes = ["text/csv", "application/csv"]
323
324
@app.post("/upload-csv")
325
def upload_csv(raw: CSVRawModel):
326
# Raw CSV data accessible via raw.data
327
csv_content = raw.data.decode('utf-8')
328
return {"rows": len(csv_content.split('\n'))}
329
```
330
331
## Validation Error Handling
332
333
### Default Error Models
334
335
Built-in models for validation error responses.
336
337
```python { .api }
338
class ValidationErrorModel(BaseModel):
339
"""Default validation error response format"""
340
detail: list[dict[str, Any]]
341
342
class UnprocessableEntity(BaseModel):
343
"""422 error response format"""
344
detail: list[dict[str, Any]]
345
```
346
347
### Custom Validation Error Handling
348
349
You can customize validation error responses by providing custom error models and callbacks:
350
351
```python
352
from flask_openapi3 import OpenAPI, Info
353
from pydantic import BaseModel
354
355
class CustomErrorModel(BaseModel):
356
error: str
357
field_errors: dict[str, list[str]]
358
status_code: int
359
360
def custom_error_callback(e):
361
errors = {}
362
for error in e.errors():
363
field = ".".join(str(x) for x in error["loc"])
364
if field not in errors:
365
errors[field] = []
366
errors[field].append(error["msg"])
367
368
return {
369
"error": "Validation failed",
370
"field_errors": errors,
371
"status_code": 422
372
}
373
374
app = OpenAPI(
375
__name__,
376
info=Info(title="API", version="1.0.0"),
377
validation_error_model=CustomErrorModel,
378
validation_error_callback=custom_error_callback,
379
validation_error_status=422
380
)
381
```
382
383
## Advanced Validation Features
384
385
### Field Aliases
386
387
Use Pydantic field aliases to map between different parameter names:
388
389
```python
390
from pydantic import BaseModel, Field
391
392
class UserQuery(BaseModel):
393
user_id: int = Field(alias="userId")
394
full_name: str = Field(alias="fullName")
395
396
@app.get("/users")
397
def get_users(query: UserQuery):
398
# URL: /users?userId=123&fullName=John
399
return {"user_id": query.user_id, "name": query.full_name}
400
```
401
402
### Custom Validators
403
404
Use Pydantic validators for complex validation logic:
405
406
```python
407
from pydantic import BaseModel, validator
408
import re
409
410
class UserBody(BaseModel):
411
username: str
412
email: str
413
414
@validator('username')
415
def validate_username(cls, v):
416
if not re.match(r'^[a-zA-Z0-9_]+$', v):
417
raise ValueError('Username can only contain letters, numbers, and underscores')
418
return v
419
420
@validator('email')
421
def validate_email(cls, v):
422
if '@' not in v:
423
raise ValueError('Invalid email format')
424
return v.lower()
425
```
426
427
### Nested Models
428
429
Support for complex nested validation:
430
431
```python
432
from pydantic import BaseModel
433
from typing import List, Optional
434
435
class Address(BaseModel):
436
street: str
437
city: str
438
country: str
439
postal_code: str
440
441
class User(BaseModel):
442
name: str
443
email: str
444
addresses: List[Address]
445
primary_address: Optional[Address] = None
446
447
@app.post("/users")
448
def create_user(body: User):
449
# Nested validation automatically applied
450
return {"user_created": True, "address_count": len(body.addresses)}
451
```