# Pydantic Schema Examples **Complete Pydantic schema patterns for request/response validation.** ## Request/Response Schemas ```python # app/schemas/user.py from pydantic import BaseModel, EmailStr, Field, ConfigDict, field_validator, model_validator from datetime import datetime from typing import Optional class UserBase(BaseModel): """Shared fields for User schemas.""" email: EmailStr full_name: str = Field(..., min_length=1, max_length=255) is_active: bool = True class UserCreate(UserBase): """Schema for creating a new user.""" password: str = Field(..., min_length=8, max_length=100) password_confirm: str = Field(..., min_length=8, max_length=100) @field_validator("password") @classmethod def validate_password_strength(cls, v: str) -> str: """Ensure password meets complexity requirements.""" if len(v) < 8: raise ValueError("Password must be at least 8 characters") if not any(char.isdigit() for char in v): raise ValueError("Password must contain at least one digit") if not any(char.isupper() for char in v): raise ValueError("Password must contain at least one uppercase letter") if not any(char.islower() for char in v): raise ValueError("Password must contain at least one lowercase letter") return v @model_validator(mode="after") def passwords_match(self) -> "UserCreate": """Ensure password and password_confirm match.""" if self.password != self.password_confirm: raise ValueError("Passwords do not match") return self class UserUpdate(BaseModel): """Schema for updating an existing user (all fields optional).""" email: Optional[EmailStr] = None full_name: Optional[str] = Field(None, min_length=1, max_length=255) is_active: Optional[bool] = None class UserRead(UserBase): """Schema for reading user data (public fields only).""" id: str tenant_id: str created_at: datetime updated_at: datetime model_config = ConfigDict(from_attributes=True) class PaginatedResponse[T](BaseModel): """Generic paginated response.""" items: list[T] total: int skip: int limit: int has_more: bool ``` ## Nested Schemas ```python # app/schemas/organization.py from pydantic import BaseModel, ConfigDict, Field from datetime import datetime from typing import Optional from app.schemas.team import TeamRead class OrganizationBase(BaseModel): """Shared fields for Organization schemas.""" name: str = Field(..., min_length=1, max_length=255) description: Optional[str] = None class OrganizationCreate(OrganizationBase): """Schema for creating a new organization.""" pass class OrganizationRead(OrganizationBase): """Organization with nested teams.""" id: str tenant_id: str teams: list[TeamRead] = [] # Nested teams created_at: datetime updated_at: datetime model_config = ConfigDict(from_attributes=True) ``` ## Custom Validation ```python # app/schemas/user.py from pydantic import field_validator, model_validator import re class UserCreate(BaseModel): email: EmailStr username: str password: str @field_validator("username") @classmethod def validate_username(cls, v: str) -> str: """Ensure username is alphanumeric.""" if not re.match(r"^[a-zA-Z0-9_-]+$", v): raise ValueError("Username must contain only letters, numbers, hyphens, and underscores") if len(v) < 3: raise ValueError("Username must be at least 3 characters") return v @field_validator("email") @classmethod def validate_email_domain(cls, v: str) -> str: """Ensure email is from allowed domain.""" allowed_domains = ["example.com", "greyhaven.studio"] domain = v.split("@")[1] if domain not in allowed_domains: raise ValueError(f"Email must be from {', '.join(allowed_domains)}") return v @model_validator(mode="after") def validate_username_not_in_email(self) -> "UserCreate": """Ensure username is not part of email.""" if self.username.lower() in self.email.lower(): raise ValueError("Username cannot be part of email address") return self ``` **See also:** - [fastapi-crud.md](fastapi-crud.md) - CRUD endpoints using these schemas - [../templates/pydantic-schemas.py](../templates/pydantic-schemas.py) - Schema template