# Pydantic Models

> Automatically applies when creating data models for API responses and validation. Uses Pydantic BaseModel with validators, field definitions, and proper serialization.

- Skill: `majiayu000/pydantic-models` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add majiayu000/pydantic-models`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/pydantic-models/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/pydantic-models

---


# Pydantic Model Pattern Enforcer

When creating data models for API responses, validation, or configuration, follow Pydantic best practices.

## ✅ Correct Pattern

```python
from pydantic import BaseModel, Field, field_validator, model_validator
from typing import Optional, List
from datetime import datetime

class User(BaseModel):
    """User model with validation."""

    id: str = Field(..., description="User unique identifier")
    email: str = Field(..., description="User email address")
    name: str = Field(..., min_length=1, max_length=100)
    age: Optional[int] = Field(None, ge=0, le=150)
    created_at: datetime = Field(default_factory=datetime.now)
    tags: List[str] = Field(default_factory=list)

    @field_validator('email')
    @classmethod
    def validate_email(cls, v: str) -> str:
        """Validate email format."""
        if '@' not in v:
            raise ValueError('Invalid email format')
        return v.lower()

    @field_validator('name')
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Validate and clean name."""
        return v.strip()

    @model_validator(mode='after')
    def validate_model(self) -> 'User':
        """Cross-field validation."""
        if self.age and self.age < 13:
            if '@' in self.email and not self.email.endswith('@parent.com'):
                raise ValueError('Users under 13 must use parent email')
        return self

    class Config:
        """Pydantic configuration."""
        json_schema_extra = {
            "example": {
                "id": "usr_123",
                "email": "user@example.com",
                "name": "John Doe",
                "age": 30,
                "tags": ["premium", "verified"]
            }
        }
```

## Field Definitions

```python
from pydantic import BaseModel, Field, HttpUrl, EmailStr
from typing import Annotated
from decimal import Decimal

class Product(BaseModel):
    """Product with comprehensive field validation."""

    # Required field
    name: str = Field(..., min_length=1, max_length=200)

    # Optional with default
    description: str = Field("", max_length=1000)

    # Numeric constraints
    price: Decimal = Field(..., ge=0, decimal_places=2)
    quantity: int = Field(0, ge=0)

    # String patterns
    sku: str = Field(..., pattern=r'^[A-Z0-9-]+$')

    # URL validation
    image_url: Optional[HttpUrl] = None

    # Email validation (requires email-validator package)
    contact_email: Optional[EmailStr] = None

    # Annotated types
    weight: Annotated[float, Field(gt=0, description="Weight in kg")]

    # Enum field
    status: Literal["active", "inactive", "discontinued"] = "active"
```

## Field Validators

```python
from pydantic import BaseModel, field_validator
import re

class PhoneContact(BaseModel):
    phone: str
    country_code: str = "US"

    @field_validator('phone')
    @classmethod
    def validate_phone(cls, v: str) -> str:
        """Validate and normalize phone number."""
        # Remove non-digits
        digits = re.sub(r'\D', '', v)

        if len(digits) != 10:
            raise ValueError('Phone must be 10 digits')

        return digits

    @field_validator('country_code')
    @classmethod
    def validate_country(cls, v: str) -> str:
        """Validate country code."""
        allowed = ['US', 'CA', 'UK']
        if v not in allowed:
            raise ValueError(f'Country must be one of {allowed}')
        return v
```

## Model Validators

```python
from pydantic import BaseModel, model_validator
from datetime import datetime

class Booking(BaseModel):
    start_date: datetime
    end_date: datetime
    guests: int

    @model_validator(mode='after')
    def validate_dates(self) -> 'Booking':
        """Validate date range."""
        if self.end_date <= self.start_date:
            raise ValueError('end_date must be after start_date')

        duration = (self.end_date - self.start_date).days
        if duration > 30:
            raise ValueError('Booking cannot exceed 30 days')

        return self

    @model_validator(mode='after')
    def validate_capacity(self) -> 'Booking':
        """Validate guest count."""
        if self.guests < 1:
            raise ValueError('At least 1 guest required')
        if self.guests > 10:
            raise ValueError('Maximum 10 guests allowed')
        return self
```

## Nested Models

```python
from pydantic import BaseModel
from typing import List

class Address(BaseModel):
    """Address component."""
    street: str
    city: str
    state: str
    zip_code: str

class Customer(BaseModel):
    """Customer with nested address."""
    name: str
    email: str
    billing_address: Address
    shipping_address: Optional[Address] = None
    orders: List['Order'] = []

class Order(BaseModel):
    """Order model."""
    id: str
    total: Decimal
    items: List['OrderItem']

class OrderItem(BaseModel):
    """Order item."""
    product_id: str
    quantity: int
    price: Decimal

# Update forward references
Customer.model_rebuild()
```

## Model Configuration

```python
from pydantic import BaseModel, ConfigDict

class StrictModel(BaseModel):
    """Model with strict validation."""

    model_config = ConfigDict(
        # Validation settings
        str_strip_whitespace=True,      # Strip whitespace from strings
        validate_assignment=True,        # Validate on field assignment
        validate_default=True,           # Validate default values
        strict=True,                     # Strict type checking

        # Serialization settings
        use_enum_values=True,            # Serialize enums as values
        populate_by_name=True,           # Allow field population by alias

        # Extra fields
        extra='forbid',                  # Forbid extra fields (default: 'ignore')

        # JSON schema
        json_schema_extra={
            "example": {...}
        }
    )
```

## Computed Fields

```python
from pydantic import BaseModel, computed_field

class Rectangle(BaseModel):
    width: float
    height: float

    @computed_field
    @property
    def area(self) -> float:
        """Computed area."""
        return self.width * self.height

    @computed_field
    @property
    def perimeter(self) -> float:
        """Computed perimeter."""
        return 2 * (self.width + self.height)

# Usage
rect = Rectangle(width=10, height=5)
print(rect.area)       # 50.0
print(rect.perimeter)  # 30.0
print(rect.model_dump())  # Includes computed fields
```

## JSON Schema

```python
from pydantic import BaseModel

class APIResponse(BaseModel):
    """API response model."""
    data: dict
    message: str
    status: int = 200

# Generate JSON schema
schema = APIResponse.model_json_schema()

# Use in FastAPI (automatic)
@app.post("/api/endpoint")
async def endpoint(data: APIResponse) -> APIResponse:
    return data

# Custom schema
class CustomModel(BaseModel):
    name: str

    model_config = {
        "json_schema_extra": {
            "examples": [
                {"name": "Example 1"},
                {"name": "Example 2"}
            ]
        }
    }
```

## Serialization

```python
from pydantic import BaseModel, field_serializer
from datetime import datetime

class Event(BaseModel):
    name: str
    timestamp: datetime
    metadata: dict

    @field_serializer('timestamp')
    def serialize_timestamp(self, value: datetime) -> str:
        """Serialize timestamp as ISO string."""
        return value.isoformat()

    def model_dump_json(self, **kwargs) -> str:
        """Serialize to JSON string."""
        return super().model_dump_json(**kwargs)

# Usage
event = Event(name="test", timestamp=datetime.now(), metadata={})
json_str = event.model_dump_json()  # JSON string
dict_data = event.model_dump()       # Python dict
```

## ❌ Anti-Patterns

```python
# ❌ Using dict instead of model
def process_user(user: dict):  # No validation!
    pass

# ✅ Better: use Pydantic model
def process_user(user: User):  # Validated!
    pass

# ❌ Manual validation
def validate_email(email: str):
    if '@' not in email:
        raise ValueError("Invalid email")

# ✅ Better: use Pydantic validator
class User(BaseModel):
    email: EmailStr  # Built-in validation

# ❌ Mutable defaults
class Config(BaseModel):
    tags: List[str] = []  # ❌ Shared across instances!

# ✅ Better: use Field with default_factory
class Config(BaseModel):
    tags: List[str] = Field(default_factory=list)

# ❌ Not using Optional
class User(BaseModel):
    middle_name: str  # Required, but should be optional!

# ✅ Better
class User(BaseModel):
    middle_name: Optional[str] = None
```

## Best Practices Checklist

- ✅ Use `BaseModel` for all data models
- ✅ Add field descriptions with `Field(..., description="...")`
- ✅ Use appropriate validators for business logic
- ✅ Use `Optional` for nullable fields
- ✅ Use `Field(default_factory=list)` for mutable defaults
- ✅ Add JSON schema examples
- ✅ Use computed fields for derived data
- ✅ Configure strict validation when needed
- ✅ Document models with docstrings
- ✅ Use nested models for complex structures

## Auto-Apply

When creating models:
1. Use `BaseModel` as base class
2. Add type hints for all fields
3. Use `Field()` for constraints and descriptions
4. Add validators for business logic
5. Add JSON schema examples
6. Use nested models for complex data

## Related Skills

- structured-errors - For error response models
- docstring-format - For model documentation
- pytest-patterns - For testing models

