Интеграция с Pydantic, валидация запросов и ответов, DTO, сериализация данных, кастомные валидаторы
Интеграция с Pydantic, DTO, сериализация данных, кастомные валидаторы
Starlite использует Pydantic для валидации и сериализации данных. Это обеспечивает:
Для валидации тела запроса используйте Pydantic-модели:
from pydantic import BaseModel, EmailStr
from starlite import post
class UserCreate(BaseModel):
name: str
email: EmailStr
age: int
@post("/users")
def create_user(data: UserCreate) -> User:
# data уже валидирована
return User(
id=1,
name=data.name,
email=data.email,
age=data.age
)Starlite автоматически:
Аннотация возвращаемого типа обеспечивает сериализацию:
from pydantic import BaseModel
from starlite import get
class User(BaseModel):
id: int
name: str
email: str
@get("/users/{user_id:int}")
def get_user(user_id: int) -> User:
return User(id=user_id, name="John", email="john@example.com")from pydantic import BaseModel, Field, validator
class UserCreate(BaseModel):
name: str = Field(min_length=2, max_length=50)
email: EmailStr
age: int = Field(ge=0, le=150)
password: str = Field(min_length=8)
@validator("name")
def name_must_not_contain_numbers(cls, v):
if any(c.isdigit() for c in v):
raise ValueError("Name cannot contain numbers")
return v
@validator("password")
def password_must_be_strong(cls, v):
if not any(c.isupper() for c in v):
raise ValueError("Password must contain uppercase letter")
if not any(c.isdigit() for c in v):
raise ValueError("Password must contain digit")
return vfrom pydantic import BaseModel, root_validator
class UserCreate(BaseModel):
password: str
password_confirm: str
@root_validator
def passwords_match(cls, values):
if values["password"] != values["password_confirm"]:
raise ValueError("Passwords do not match")
return valuesStarlite поддерживает DTO для гибкого контроля над данными:
from starlite import DTOConfig
from starlite.dto import DataclassDTO
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: str
password_hash: str
class UserDTO(DataclassDTO[User]):
config = DTOConfig(
exclude={"password_hash"}, # Исключить поле
max_nested_depth=2,
rename_strategy="camel", # camelCase для JSON
)
@post("/users", dto=UserDTO)
def create_user(data: User) -> User:
...from starlite import DTOConfig
from starlite.dto import DataclassDTO
class UserReadDTO(DataclassDTO[User]):
config = DTOConfig(
include={"id", "name", "email"}, # Только эти поля
)
@get("/users/{user_id:int}", return_dto=UserReadDTO)
def get_user(user_id: int) -> User:
...from dataclasses import dataclass
from typing import list
@dataclass
class Address:
street: str
city: str
zip_code: str
@dataclass
class User:
id: int
name: str
addresses: list[Address]
class UserDTO(DataclassDTO[User]):
config = DTOConfig(
exclude={"addresses.zip_code"}, # Исключить вложенное поле
)Starlite автоматически сериализует:
from datetime import datetime
from uuid import UUID, uuid4
from starlite import get
@get("/data")
def get_data() -> dict:
return {
"id": uuid4(), # Сериализуется в строку
"created_at": datetime.now(), # Сериализуется в ISO-формат
"value": 42,
}from starlite.serialization import SerializationConfig
app = Starlite(
route_handlers=[...],
serialization_config=SerializationConfig(
exclude_none=True, # Исключить None-значения
exclude_empty_dicts=True,
),
)from starlite import Query
@get("/items")
def list_items(
page: int = Query(ge=1, le=1000, default=1),
page_size: int = Query(ge=1, le=100, default=20),
search: str | None = Query(min_length=3, max_length=100, default=None),
sort_by: str = Query(
pattern="^(name|created_at|price)$",
default="created_at"
),
) -> list[Item]:
...from starlite import Query
@get("/items")
def list_items(
page_num: int = Query(query="page", default=1),
items_per_page: int = Query(query="size", default=20),
) -> list[Item]:
...Запрос: GET /items?page=2&size=50
from starlite import Header
@get("/protected")
def protected_route(
x_api_key: str = Header(header="X-API-Key", min_length=32),
user_agent: str | None = Header(default=None),
) -> dict:
...from starlite import Cookie
@get("/dashboard")
def dashboard(
session_id: str = Cookie(min_length=32),
remember_me: bool = Cookie(default=False),
) -> dict:
...from starlite import RequestEncoding, post
from msgspec import Struct
class UploadForm(Struct):
title: str
description: str | None = None
@post("/upload", request_encoding=RequestEncoding.MULTI_PART)
def upload_file(
data: UploadForm,
file: bytes, # или UploadFile
) -> dict:
# file содержит бинарные данные
return {"size": len(file)}from starlite import UploadFile
@post("/upload-multiple")
def upload_multiple(
files: list[UploadFile],
) -> dict:
uploaded = []
for file in files:
content = await file.read()
uploaded.append({
"filename": file.filename,
"size": len(content),
})
return {"files": uploaded}from starlite import RequestEncoding
from pydantic import BaseModel
class LoginForm(BaseModel):
username: str
password: str
@post("/login", request_encoding=RequestEncoding.URL_ENCODED)
def login(data: LoginForm) -> dict:
...Можно зарегистрировать свои парсеры для типов:
from starlite import TypeEncoders
from bson import ObjectId
def object_id_encoder(obj: ObjectId) -> str:
return str(obj)
app = Starlite(
route_handlers=[...],
type_encoders={ObjectId: object_id_encoder},
)from starlite.exceptions import ValidationException
from starlite import ExceptionResponseContent
@exception_handler(ValidationException)
def handle_validation_error(
request: Request,
exc: ValidationException,
) -> Response:
return Response(
content=ExceptionResponseContent(
status_code=400,
detail="Validation failed",
extra=exc.extra,
),
status_code=400,
)
app = Starlite(
route_handlers=[...],
exception_handlers={
ValidationException: handle_validation_error,
},
)# ✅ Хорошо — Pydantic v2
from pydantic import BaseModel, Field, field_validator
class User(BaseModel):
name: str = Field(min_length=2)
@field_validator("name")
@classmethod
def validate_name(cls, v):
...
# ❌ Устарело — Pydantic v1
from pydantic import BaseModel, validator# ✅ Хорошо
class UserCreateDTO(DataclassDTO[User]):
config = DTOConfig(exclude={"id", "created_at"})
class UserReadDTO(DataclassDTO[User]):
config = DTOConfig(exclude={"password_hash"})
# ❌ Плохо — один DTO для всего# ✅ Хорошо — валидация в модели
class UserCreate(BaseModel):
email: EmailStr
password: str = Field(min_length=8)
# ❌ Плохо — валидация в хендлере
@post("/users")
def create_user(data: dict) -> User:
if "@" not in data["email"]:
raise ValidationException(...)# ✅ Хорошо
serialization_config=SerializationConfig(exclude_none=True)
# ❌ Плохо — None в JSON
{"name": "John", "email": null, "phone": null}Валидация данных в Starlite предлагает:
В следующей теме мы изучим обработку исключений.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.