pydantic

SKILL.md

Pydantic v2

Pydantic v2 is the standard library for data validation, serialization, and settings management in Python. It uses type annotations to define data schemas and validates data at runtime with a high-performance Rust core.

Model Basics

Define models by subclassing BaseModel. Use standard type annotations for fields and Field() for metadata and constraints.

from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field


class User(BaseModel):
    id: int
    name: str = Field(min_length=1, max_length=100, description="Full name")
    email: str
    age: Optional[int] = Field(default=None, ge=0, le=150)
    is_active: bool = True
    created_at: datetime = Field(default_factory=datetime.utcnow)

Field Basics

  • Required fields: declare with a type annotation and no default.
  • Optional fields: use Optional[T] (or T | None) with a default of None.
  • Default values: assign a literal or use Field(default=...).
  • Default factories: use Field(default_factory=callable) for mutable defaults.

Common Field() Parameters

Parameter Purpose
default Static default value
default_factory Callable producing a default
alias Alternative name for parsing input
title Human-readable title for JSON Schema
description Field description for JSON Schema
ge, gt, le, lt Numeric constraints
min_length, max_length String/collection length
pattern Regex pattern for strings
exclude Exclude from serialization
frozen Make individual field immutable

Common Field Types and Constraints

Pydantic ships constrained types and special types for common patterns.

from pydantic import (
    BaseModel,
    EmailStr,
    HttpUrl,
    Field,
    constr,
    conint,
    confloat,
    conlist,
)


class Profile(BaseModel):
    username: constr(min_length=3, max_length=30, pattern=r"^[a-zA-Z0-9_]+$")
    email: EmailStr
    website: HttpUrl | None = None
    score: conint(ge=0, le=100) = 0
    rating: confloat(ge=0.0, le=5.0) = 0.0
    tags: conlist(str, min_length=1, max_length=10) = ["general"]

Install pydantic[email] to use EmailStr.

Frequently Used Types

  • EmailStr -- validated email address.
  • HttpUrl, AnyUrl -- validated URLs.
  • IPvAnyAddress -- IPv4 or IPv6 address.
  • SecretStr -- string hidden in repr and serialization.
  • FilePath, DirectoryPath -- validated filesystem paths.
  • PastDatetime, FutureDatetime -- temporal constraints.
  • PositiveInt, NegativeInt, NonNegativeInt -- numeric shortcuts.
  • UUID4 -- UUID version 4.
  • Json[T] -- parse a JSON string into type T.

Model Configuration

Control model behavior with model_config using ConfigDict.

from pydantic import BaseModel, ConfigDict


class Item(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,
        frozen=False,
        from_attributes=True,
        populate_by_name=True,
        use_enum_values=True,
        validate_default=True,
        strict=False,
        json_schema_extra={"examples": [{"item_name": "Widget", "price": 9.99}]},
    )

    item_name: str = Field(alias="itemName")
    price: float

Key Configuration Options

Option Effect
str_strip_whitespace Strip leading/trailing whitespace from strings
frozen Make all fields immutable (model becomes hashable)
from_attributes Allow initialization from ORM objects via attribute access
populate_by_name Accept both field name and alias during parsing
use_enum_values Store enum values instead of enum members
validate_default Run validators on default values
strict Disable type coercion globally
extra "forbid", "allow", or "ignore" extra fields
json_schema_extra Extend generated JSON Schema

Validators

Field Validators

Apply validation logic to individual fields with @field_validator.

from pydantic import BaseModel, field_validator


class Signup(BaseModel):
    username: str
    password: str

    @field_validator("username")
    @classmethod
    def username_must_be_alphanumeric(cls, v: str) -> str:
        if not v.isalnum():
            raise ValueError("must be alphanumeric")
        return v.lower()

    @field_validator("password", mode="after")
    @classmethod
    def password_strength(cls, v: str) -> str:
        if len(v) < 8:
            raise ValueError("must be at least 8 characters")
        return v
  • mode="before" -- run before Pydantic's own parsing (receives raw input).
  • mode="after" (default) -- run after Pydantic parses and coerces the value.
  • Apply to multiple fields: @field_validator("field_a", "field_b").

Model Validators

Validate the entire model for cross-field logic with @model_validator.

from pydantic import BaseModel, model_validator


class DateRange(BaseModel):
    start: datetime
    end: datetime

    @model_validator(mode="after")
    def check_date_order(self) -> "DateRange":
        if self.start >= self.end:
            raise ValueError("start must be before end")
        return self
  • mode="before" -- receives raw input dict; useful for restructuring data.
  • mode="after" -- receives the fully constructed model instance.

For advanced validator patterns including BeforeValidator, AfterValidator, WrapValidator, custom types, and discriminated unions, consult the validators reference.

Computed Fields

Derive fields from other values without storing them. Computed fields appear in serialization and JSON Schema.

from pydantic import BaseModel, computed_field


class Rectangle(BaseModel):
    width: float
    height: float

    @computed_field
    @property
    def area(self) -> float:
        return self.width * self.height

Serialization

model_dump()

Convert a model instance to a dictionary.

user = User(id=1, name="Ada", email="ada@example.com")
data = user.model_dump()                        # full dict
data = user.model_dump(exclude_none=True)        # drop None values
data = user.model_dump(include={"id", "name"})   # only selected fields
data = user.model_dump(exclude={"created_at"})   # exclude fields
data = user.model_dump(by_alias=True)            # use alias names as keys
data = user.model_dump(mode="json")              # JSON-compatible types

model_dump_json()

Serialize directly to a JSON string (faster than json.dumps(model.model_dump())).

json_str = user.model_dump_json(indent=2, exclude_none=True)

Serialization Aliases

Use serialization_alias to control output key names independently from parsing aliases.

class ApiResponse(BaseModel):
    internal_id: int = Field(serialization_alias="id")
    status_code: int = Field(serialization_alias="status")

Custom Serializers

from pydantic import field_serializer

class Event(BaseModel):
    timestamp: datetime

    @field_serializer("timestamp")
    def serialize_ts(self, v: datetime, _info) -> str:
        return v.isoformat()

Deserialization

From a Dictionary

user = User.model_validate({"id": 1, "name": "Ada", "email": "ada@example.com"})

From a JSON String

user = User.model_validate_json('{"id": 1, "name": "Ada", "email": "ada@example.com"}')

From ORM / Arbitrary Objects

With from_attributes=True in model_config, initialize from objects with matching attributes.

class UserRead(BaseModel):
    model_config = ConfigDict(from_attributes=True)
    id: int
    name: str

# sqlalchemy_user is an ORM instance
user_read = UserRead.model_validate(sqlalchemy_user)

Settings Management

Use BaseSettings to load configuration from environment variables, .env files, and other sources.

pip install pydantic-settings
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        env_prefix="APP_",
        env_nested_delimiter="__",
        case_sensitive=False,
    )

    app_name: str = "My App"
    debug: bool = False
    database_url: str
    redis_url: str = "redis://localhost:6379"
    allowed_origins: list[str] = ["http://localhost:3000"]


settings = Settings()

Environment Variable Mapping

With env_prefix="APP_", the field database_url maps to APP_DATABASE_URL.

Nested Settings

Use env_nested_delimiter to map nested models from flat environment variables.

class DatabaseSettings(BaseModel):
    host: str = "localhost"
    port: int = 5432
    name: str = "mydb"


class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_nested_delimiter="__")

    db: DatabaseSettings = DatabaseSettings()

Set DB__HOST=prod-db and DB__PORT=5433 in the environment to override nested values.

Secrets Directory

Load secrets from files (e.g., Docker secrets):

class Settings(BaseSettings):
    model_config = SettingsConfigDict(secrets_dir="/run/secrets")

    db_password: str

Model Inheritance and Composition

Inheritance

Share common fields across models with standard class inheritance.

class TimestampMixin(BaseModel):
    created_at: datetime = Field(default_factory=datetime.utcnow)
    updated_at: datetime | None = None


class UserCreate(TimestampMixin):
    name: str
    email: str


class UserUpdate(BaseModel):
    name: str | None = None
    email: str | None = None

Composition (Nested Models)

Nest models for structured data. Pydantic validates nested objects recursively.

class Address(BaseModel):
    street: str
    city: str
    country: str = "US"


class Company(BaseModel):
    name: str
    address: Address
    employees: list["Employee"] = []


class Employee(BaseModel):
    name: str
    role: str

Company.model_rebuild()  # resolve forward references

Generic Models

Create reusable, type-parameterized models with Generic[T].

from typing import Generic, TypeVar
from pydantic import BaseModel

T = TypeVar("T")


class PaginatedResponse(BaseModel, Generic[T]):
    items: list[T]
    total: int
    page: int
    per_page: int
    has_next: bool

    @computed_field
    @property
    def total_pages(self) -> int:
        return (self.total + self.per_page - 1) // self.per_page


# Concrete usage
class UserList(PaginatedResponse[User]):
    pass

# Or inline
response = PaginatedResponse[User].model_validate(data)

Generic models generate accurate JSON Schemas per specialization, making them ideal for API response wrappers.

JSON Schema Generation

Generate JSON Schema from any model for documentation, API specs, or frontend validation.

schema = User.model_json_schema()

Customize with json_schema_extra in model_config or Field():

class Product(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "examples": [{"name": "Widget", "price": 9.99}]
        }
    )
    name: str
    price: float = Field(json_schema_extra={"examples": [9.99, 19.99]})

For FastAPI request/response models, consult the fastapi skill.

For advanced validator patterns, discriminated unions, recursive models, and custom types, consult the validators reference.

Weekly Installs
2
First Seen
Feb 7, 2026
Installed on
mcpjam2
openhands2
replit2
junie2
windsurf2
zencoder2