Generate REST API and OpenAPI documentation for your Flask project with automatic request/response validation using Pydantic models.
—
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.
The core validation decorator that automatically validates requests based on function parameter type hints.
def validate_request():
"""
Decorator for automatic request validation against Pydantic models.
Applied automatically when using typed parameters in route handlers.
Validates path, query, header, cookie, form, and body parameters.
Raises:
ValidationError: When request data doesn't match the expected schema
"""Usage Example:
from flask_openapi3 import OpenAPI, Info
from pydantic import BaseModel
app = OpenAPI(__name__, info=Info(title="API", version="1.0.0"))
class UserQuery(BaseModel):
name: str
age: int = None
class UserBody(BaseModel):
email: str
password: str
# Validation applied automatically based on parameter types
@app.post("/users")
def create_user(query: UserQuery, body: UserBody):
# query and body are automatically validated
return {"message": f"User {query.name} created with email {body.email}"}Core validation logic for different parameter types.
def _validate_request(
header: Optional[Type[BaseModel]] = None,
cookie: Optional[Type[BaseModel]] = None,
path: Optional[Type[BaseModel]] = None,
query: Optional[Type[BaseModel]] = None,
form: Optional[Type[BaseModel]] = None,
body: Optional[Type[BaseModel]] = None,
raw: Optional[Type[RawModel]] = None,
path_kwargs: Optional[dict[Any, Any]] = None,
) -> dict:
"""
Core request validation logic.
Args:
header: Pydantic model for header parameters
cookie: Pydantic model for cookie parameters
path: Pydantic model for path parameters
query: Pydantic model for query parameters
form: Pydantic model for form data
body: Pydantic model for request body
raw: RawModel for raw request data
path_kwargs: Path parameters extracted from URL
Returns:
Dictionary of validated request parameters
"""Validates URL path parameters against Pydantic models.
def _validate_path(path: Type[BaseModel], path_kwargs: dict, func_kwargs: dict):
"""
Validate path parameters from URL route.
Args:
path: Pydantic model defining expected path parameters
path_kwargs: Extracted path parameters from URL
func_kwargs: Function arguments to populate
Raises:
ValidationError: When path parameters don't match model
"""Usage Example:
from pydantic import BaseModel
class UserPath(BaseModel):
user_id: int
@app.get("/users/<int:user_id>")
def get_user(path: UserPath):
# path.user_id is validated as integer
return {"user_id": path.user_id}Validates URL query parameters against Pydantic models.
def _validate_query(query: Type[BaseModel], func_kwargs: dict):
"""
Validate query parameters from URL query string.
Args:
query: Pydantic model defining expected query parameters
func_kwargs: Function arguments to populate
Raises:
ValidationError: When query parameters don't match model
"""Usage Example:
from pydantic import BaseModel
from typing import Optional
class SearchQuery(BaseModel):
q: str
limit: int = 10
offset: int = 0
category: Optional[str] = None
@app.get("/search")
def search(query: SearchQuery):
# Query parameters automatically validated and converted
return {
"query": query.q,
"limit": query.limit,
"offset": query.offset,
"category": query.category
}Validates HTTP headers against Pydantic models.
def _validate_header(header: Type[BaseModel], func_kwargs: dict):
"""
Validate HTTP headers.
Args:
header: Pydantic model defining expected headers
func_kwargs: Function arguments to populate
Raises:
ValidationError: When headers don't match model
"""Usage Example:
from pydantic import BaseModel, Field
from typing import Optional
class AuthHeaders(BaseModel):
authorization: str = Field(alias="Authorization")
content_type: str = Field(alias="Content-Type", default="application/json")
user_agent: Optional[str] = Field(alias="User-Agent", default=None)
@app.post("/protected")
def protected_endpoint(header: AuthHeaders, body: dict):
# Headers automatically validated and available
auth_token = header.authorization
return {"authorized": True}Validates HTTP cookies against Pydantic models.
def _validate_cookie(cookie: Type[BaseModel], func_kwargs: dict):
"""
Validate HTTP cookies.
Args:
cookie: Pydantic model defining expected cookies
func_kwargs: Function arguments to populate
Raises:
ValidationError: When cookies don't match model
"""Usage Example:
from pydantic import BaseModel
from typing import Optional
class SessionCookies(BaseModel):
session_id: str
preferences: Optional[str] = None
@app.get("/profile")
def get_profile(cookie: SessionCookies):
# Cookies automatically validated
return {"session": cookie.session_id}Validates form data (application/x-www-form-urlencoded or multipart/form-data) against Pydantic models.
def _validate_form(form: Type[BaseModel], func_kwargs: dict):
"""
Validate form data from request.
Args:
form: Pydantic model defining expected form fields
func_kwargs: Function arguments to populate
Raises:
ValidationError: When form data doesn't match model
"""Usage Example:
from pydantic import BaseModel
from flask_openapi3 import FileStorage
class UserForm(BaseModel):
name: str
email: str
age: int
avatar: Optional[FileStorage] = None
@app.post("/users/form")
def create_user_form(form: UserForm):
# Form data including file uploads validated
return {
"name": form.name,
"email": form.email,
"age": form.age,
"has_avatar": form.avatar is not None
}Validates JSON request bodies against Pydantic models.
def _validate_body(body: Type[BaseModel], func_kwargs: dict):
"""
Validate JSON request body.
Args:
body: Pydantic model defining expected body structure
func_kwargs: Function arguments to populate
Raises:
ValidationError: When body doesn't match model
"""Usage Example:
from pydantic import BaseModel, EmailStr, validator
from typing import Optional
class CreateUserBody(BaseModel):
name: str
email: EmailStr
password: str
age: Optional[int] = None
@validator('password')
def validate_password(cls, v):
if len(v) < 8:
raise ValueError('Password must be at least 8 characters')
return v
@app.post("/users")
def create_user(body: CreateUserBody):
# JSON body automatically validated with custom validators
return {"id": 1, "name": body.name, "email": body.email}Validates raw request data for custom content types.
class RawModel(Request):
"""Raw request data handling"""
mimetypes: list[str] = ["application/json"]Usage Example:
from flask_openapi3 import RawModel
class CSVRawModel(RawModel):
mimetypes = ["text/csv", "application/csv"]
@app.post("/upload-csv")
def upload_csv(raw: CSVRawModel):
# Raw CSV data accessible via raw.data
csv_content = raw.data.decode('utf-8')
return {"rows": len(csv_content.split('\n'))}Built-in models for validation error responses.
class ValidationErrorModel(BaseModel):
"""Default validation error response format"""
detail: list[dict[str, Any]]
class UnprocessableEntity(BaseModel):
"""422 error response format"""
detail: list[dict[str, Any]]You can customize validation error responses by providing custom error models and callbacks:
from flask_openapi3 import OpenAPI, Info
from pydantic import BaseModel
class CustomErrorModel(BaseModel):
error: str
field_errors: dict[str, list[str]]
status_code: int
def custom_error_callback(e):
errors = {}
for error in e.errors():
field = ".".join(str(x) for x in error["loc"])
if field not in errors:
errors[field] = []
errors[field].append(error["msg"])
return {
"error": "Validation failed",
"field_errors": errors,
"status_code": 422
}
app = OpenAPI(
__name__,
info=Info(title="API", version="1.0.0"),
validation_error_model=CustomErrorModel,
validation_error_callback=custom_error_callback,
validation_error_status=422
)Use Pydantic field aliases to map between different parameter names:
from pydantic import BaseModel, Field
class UserQuery(BaseModel):
user_id: int = Field(alias="userId")
full_name: str = Field(alias="fullName")
@app.get("/users")
def get_users(query: UserQuery):
# URL: /users?userId=123&fullName=John
return {"user_id": query.user_id, "name": query.full_name}Use Pydantic validators for complex validation logic:
from pydantic import BaseModel, validator
import re
class UserBody(BaseModel):
username: str
email: str
@validator('username')
def validate_username(cls, v):
if not re.match(r'^[a-zA-Z0-9_]+$', v):
raise ValueError('Username can only contain letters, numbers, and underscores')
return v
@validator('email')
def validate_email(cls, v):
if '@' not in v:
raise ValueError('Invalid email format')
return v.lower()Support for complex nested validation:
from pydantic import BaseModel
from typing import List, Optional
class Address(BaseModel):
street: str
city: str
country: str
postal_code: str
class User(BaseModel):
name: str
email: str
addresses: List[Address]
primary_address: Optional[Address] = None
@app.post("/users")
def create_user(body: User):
# Nested validation automatically applied
return {"user_created": True, "address_count": len(body.addresses)}Install with Tessl CLI
npx tessl i tessl/pypi-flask-openapi3