<< All versions
Skill v1.0.0
currentAutomated scan100/100izangi2714/claude-code-python-stack/pydantic-patterns
──Details
PublishedSeptember 28, 2026 at 06:20 PM
Content Hashsha256:4874b021e4094b41...
Git SHA
──Files
Files (1 file, 8.3 KB)
SKILL.md8.3 KBactive
SKILL.md · 350 lines · 8.3 KB
version: "1.0.0" name: pydantic-patterns description: Pydantic v2 patterns — models, validators, serialization, computed fields, discriminated unions, settings management, and integration with FastAPI/SQLAlchemy. origin: custom
Pydantic v2 Patterns
Data validation and serialization patterns using Pydantic v2.
When to Activate
- Defining request/response schemas for APIs
- Validating external data (user input, API responses, file content)
- Managing application settings with environment variables
- Converting between domain models and DTOs
Basic Models
python
from pydantic import BaseModel, Field, EmailStrfrom datetime import datetimeclass UserCreate(BaseModel):email: EmailStrname: str = Field(min_length=1, max_length=255)password: str = Field(min_length=8, max_length=128)age: int | None = Field(None, ge=0, le=150)class UserResponse(BaseModel):id: intemail: strname: strcreated_at: datetimemodel_config = {"from_attributes": True} # Enable ORM mode
Validators
Field Validators
python
from pydantic import BaseModel, field_validator, model_validatorimport reclass UserCreate(BaseModel):username: strpassword: strpassword_confirm: str@field_validator("username")@classmethoddef username_alphanumeric(cls, v: str) -> str:if not re.match(r"^[a-zA-Z0-9_]+$", v):raise ValueError("Username must be alphanumeric")return v.lower()@field_validator("password")@classmethoddef password_strength(cls, v: str) -> str:if not re.search(r"[A-Z]", v):raise ValueError("Password must contain uppercase letter")if not re.search(r"[0-9]", v):raise ValueError("Password must contain a digit")return v@model_validator(mode="after")def passwords_match(self) -> "UserCreate":if self.password != self.password_confirm:raise ValueError("Passwords do not match")return self
Before and After Validators
python
from pydantic import field_validatorclass Product(BaseModel):name: strprice: floattags: list[str] = []@field_validator("name", mode="before")@classmethoddef strip_name(cls, v):if isinstance(v, str):return v.strip()return v@field_validator("tags", mode="before")@classmethoddef parse_tags(cls, v):if isinstance(v, str):return [t.strip() for t in v.split(",")]return v@field_validator("price")@classmethoddef round_price(cls, v: float) -> float:return round(v, 2)
Computed Fields
python
from pydantic import BaseModel, computed_fieldclass OrderItem(BaseModel):product_name: strquantity: intunit_price: float@computed_field@propertydef total_price(self) -> float:return round(self.quantity * self.unit_price, 2)class Order(BaseModel):items: list[OrderItem]@computed_field@propertydef grand_total(self) -> float:return round(sum(item.total_price for item in self.items), 2)
Discriminated Unions
python
from pydantic import BaseModel, Discriminator, Tagfrom typing import Annotated, Literalclass EmailNotification(BaseModel):type: Literal["email"] = "email"to: strsubject: strbody: strclass SMSNotification(BaseModel):type: Literal["sms"] = "sms"phone: strmessage: strclass PushNotification(BaseModel):type: Literal["push"] = "push"device_token: strtitle: strbody: strNotification = Annotated[EmailNotification | SMSNotification | PushNotification,Discriminator("type"),]class NotificationRequest(BaseModel):notifications: list[Notification]
Serialization Control
python
from pydantic import BaseModel, field_serializer, model_serializerfrom datetime import datetimeclass Event(BaseModel):name: strstart_time: datetimemetadata: dict@field_serializer("start_time")def serialize_time(self, v: datetime, _info) -> str:return v.strftime("%Y-%m-%d %H:%M")# Exclude None valuesmodel_config = {"json_schema_extra": {"examples": [{"name": "Conference", "start_time": "2025-01-15 09:00"}]}}# Usageevent = Event(name="Conf", start_time=datetime.now(), metadata={"key": "val"})event.model_dump(exclude_none=True) # dict without None valuesevent.model_dump(include={"name", "start_time"}) # only specific fieldsevent.model_dump_json() # JSON string
Nested Models
python
class Address(BaseModel):street: strcity: strcountry: str = "US"zip_code: strclass Company(BaseModel):name: straddress: Addressemployees: list["Employee"] = []class Employee(BaseModel):name: stremail: EmailStrdepartment: strcompany: Company | None = None# Parsing nested datadata = {"name": "Acme Inc","address": {"street": "123 Main St", "city": "NYC", "zip_code": "10001"},"employees": [{"name": "Alice", "email": "alice@acme.com", "department": "Engineering"},],}company = Company.model_validate(data)
Settings Management
python
from pydantic_settings import BaseSettings, SettingsConfigDictfrom pydantic import SecretStrclass Settings(BaseSettings):model_config = SettingsConfigDict(env_file=".env",env_file_encoding="utf-8",env_prefix="APP_",case_sensitive=False,)# Requireddatabase_url: strsecret_key: SecretStrredis_url: str# Optional with defaultsdebug: bool = Falselog_level: str = "INFO"port: int = 8000allowed_origins: list[str] = ["http://localhost:3000"]# Nested settingsclass DBConfig(BaseSettings):pool_size: int = 20max_overflow: int = 10db: DBConfig = DBConfig()# .env file:# APP_DATABASE_URL=postgres://user:pass@localhost/db# APP_SECRET_KEY=my-secret-key# APP_DEBUG=true# APP_ALLOWED_ORIGINS=["http://localhost:3000","http://localhost:8080"]settings = Settings()
Generic Response Models
python
from pydantic import BaseModelfrom typing import TypeVar, GenericT = TypeVar("T")class APIResponse(BaseModel, Generic[T]):success: bool = Truedata: T | None = Noneerror: str | None = Noneclass PaginatedData(BaseModel, Generic[T]):items: list[T]total: intpage: intper_page: int@computed_field@propertydef pages(self) -> int:return (self.total + self.per_page - 1) // self.per_page# Usage in FastAPI@router.get("/users", response_model=APIResponse[PaginatedData[UserResponse]])async def list_users(): ...
Integration with SQLAlchemy
python
from pydantic import BaseModelclass UserResponse(BaseModel):id: intemail: strname: strmodel_config = {"from_attributes": True}# Converts SQLAlchemy model to Pydanticuser_orm = await session.get(User, 1)user_schema = UserResponse.model_validate(user_orm)
Custom Types
python
from pydantic import GetCoreSchemaHandlerfrom pydantic_core import core_schemaclass PhoneNumber(str):@classmethoddef __get_pydantic_core_schema__(cls, source, handler: GetCoreSchemaHandler):return core_schema.no_info_after_validator_function(cls._validate,core_schema.str_schema(min_length=10, max_length=15),)@classmethoddef _validate(cls, v: str) -> "PhoneNumber":cleaned = "".join(c for c in v if c.isdigit() or c == "+")if not cleaned.startswith("+"):cleaned = f"+{cleaned}"return cls(cleaned)class Contact(BaseModel):name: strphone: PhoneNumber
Quick Reference
| Feature | Usage | |
|---|---|---|
model_validate(data) | Parse dict/ORM to model | |
model_dump() | Convert to dict | |
model_dump_json() | Convert to JSON string | |
model_json_schema() | Generate JSON Schema | |
@field_validator | Validate single field | |
@model_validator | Validate across fields | |
@computed_field | Derived field in output | |
from_attributes=True | Enable ORM mode | |
Field(ge=0, le=100) | Numeric constraints | |
SecretStr | Hide sensitive values | |
Discriminator("field") | Tagged unions |
Remember: Pydantic v2 uses model_validate instead of parse_obj, model_dump instead of dict(). Always use the v2 API.