Skip to content

Pydantic Schema Standards

Schema Hierarchy

Every resource follows a four-schema pattern:

from datetime import datetime
from uuid import UUID

from pydantic import BaseModel, ConfigDict, EmailStr, Field


# Shared fields
class UserBase(BaseModel):
    email: EmailStr
    name: str = Field(min_length=1, max_length=200)


# Input for creation
class UserCreate(UserBase):
    password: str = Field(min_length=8, max_length=128)


# Input for updates — all fields optional
class UserUpdate(BaseModel):
    email: EmailStr | None = None
    name: str | None = Field(None, min_length=1, max_length=200)


# Output — includes DB-generated fields, reads from ORM objects
class UserResponse(UserBase):
    id: UUID
    is_active: bool
    created_at: datetime

    model_config = ConfigDict(from_attributes=True)

Field Validators

Use @field_validator for single-field rules and @model_validator for cross-field rules:

from pydantic import field_validator, model_validator


class PasswordResetRequest(BaseModel):
    new_password: str = Field(min_length=8)
    confirm_password: str

    @field_validator("new_password")
    @classmethod
    def password_strength(cls, v: str) -> str:
        if not any(c.isupper() for c in v):
            raise ValueError("Password must contain an uppercase letter")
        if not any(c.isdigit() for c in v):
            raise ValueError("Password must contain a digit")
        return v

    @model_validator(mode="after")
    def passwords_match(self) -> "PasswordResetRequest":
        if self.new_password != self.confirm_password:
            raise ValueError("Passwords do not match")
        return self

Nested Relationships

class OrganizationResponse(BaseModel):
    id: UUID
    name: str
    members: list[UserResponse]

    model_config = ConfigDict(from_attributes=True)

Reusable Schema Components

Extract shared patterns into composable pieces:

class TimestampMixin(BaseModel):
    created_at: datetime
    updated_at: datetime

    model_config = ConfigDict(from_attributes=True)


class PaginationParams(BaseModel):
    page: int = Field(1, ge=1)
    size: int = Field(20, ge=1, le=100)

    @property
    def offset(self) -> int:
        return (self.page - 1) * self.size

See Also