Claude
Skills
Sign in
Back

pydantic

Included with Lifetime
$97 forever

Python data validation using type hints and runtime type checking with Pydantic v2's Rust-powered core for high-performance validation in FastAPI, Django, and configuration management.

Backend & APIs

What this skill does


# Pydantic Validation Skill

## Summary
Python data validation using type hints and runtime type checking with Pydantic v2's Rust-powered core for high-performance validation.

## When to Use
- API request/response validation (FastAPI, Django)
- Settings and configuration management (env variables, config files)
- ORM model validation (SQLAlchemy integration)
- Data parsing and serialization (JSON, dict, custom formats)
- Type-safe data classes with automatic validation
- CLI argument parsing with type safety

## Quick Start

```python
from pydantic import BaseModel, Field, EmailStr
from datetime import datetime

class User(BaseModel):
    id: int
    name: str = Field(..., min_length=1, max_length=100)
    email: EmailStr
    created_at: datetime = Field(default_factory=datetime.now)
    is_active: bool = True

# Validate data
user = User(id=1, name="Alice", email="[email protected]")
print(user.model_dump())  # {'id': 1, 'name': 'Alice', ...}

# Automatic type coercion
user2 = User(id="2", name="Bob", email="[email protected]")
assert user2.id == 2  # String "2" coerced to int

# Validation error
try:
    User(id=3, name="", email="invalid")
except ValidationError as e:
    print(e.errors())
```

---

## Core Concepts

### BaseModel Foundation

```python
from pydantic import BaseModel, ConfigDict

class Product(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
        use_enum_values=True,
        arbitrary_types_allowed=False
    )

    name: str
    price: float
    quantity: int = 0

# Usage
product = Product(name="  Widget  ", price=19.99)
assert product.name == "Widget"  # Whitespace stripped

# Validate on assignment
product.price = "29.99"  # Auto-converts to float
```

### Field Configuration

```python
from pydantic import Field, field_validator
from typing import Annotated

class Item(BaseModel):
    # Field constraints
    sku: str = Field(pattern=r'^[A-Z]{3}-\d{4}$')
    price: float = Field(gt=0, le=10000)
    stock: int = Field(ge=0, default=0)

    # Annotated types (Pydantic v2)
    quantity: Annotated[int, Field(ge=1, le=100)]

    # Descriptions and examples
    description: str = Field(
        ...,
        description="Product description",
        examples=["High-quality widget"]
    )

    # Deprecated fields
    old_field: str | None = Field(None, deprecated=True)

    @field_validator('sku')
    @classmethod
    def validate_sku(cls, v: str) -> str:
        if not v.startswith('ABC'):
            raise ValueError('SKU must start with ABC')
        return v
```

## Pydantic v2 Improvements

### Migration from v1

```python
# Pydantic v1
class OldModel(BaseModel):
    class Config:
        validate_assignment = True
        json_encoders = {datetime: lambda v: v.isoformat()}

# Pydantic v2
class NewModel(BaseModel):
    model_config = ConfigDict(
        validate_assignment=True,
        # json_encoders replaced by serializers
    )

    @model_serializer
    def ser_model(self) -> dict:
        return {...}

# Key changes:
# - .dict() → .model_dump()
# - .json() → .model_dump_json()
# - .parse_obj() → .model_validate()
# - .parse_raw() → .model_validate_json()
# - @validator → @field_validator
# - @root_validator → @model_validator
```

### Performance Improvements

```python
# v2 uses Rust core (pydantic-core) for 5-50x speedup
from pydantic import BaseModel
import time

class Data(BaseModel):
    values: list[int]
    names: list[str]

# Benchmark
data = {'values': list(range(10000)), 'names': ['item'] * 10000}
start = time.perf_counter()
for _ in range(1000):
    Data.model_validate(data)
elapsed = time.perf_counter() - start
print(f"Validated 1000 iterations in {elapsed:.2f}s")
```

## Field Types

### Built-in Types

```python
from pydantic import (
    BaseModel, EmailStr, HttpUrl, UUID4,
    FilePath, DirectoryPath, Json, SecretStr,
    PositiveInt, NegativeFloat, conint, constr
)
from typing import Literal
from pathlib import Path

class Example(BaseModel):
    # Email validation
    email: EmailStr

    # URL validation
    website: HttpUrl

    # UUID
    id: UUID4

    # File system paths
    config_file: FilePath
    data_dir: DirectoryPath

    # JSON string → parsed object
    metadata: Json[dict[str, str]]

    # Secret (won't print in logs)
    api_key: SecretStr

    # Constrained types
    age: PositiveInt
    balance: NegativeFloat
    username: constr(min_length=3, max_length=20, pattern=r'^[a-z]+$')
    code: conint(ge=1000, le=9999)

    # Literal types
    status: Literal['pending', 'approved', 'rejected']
```

### Custom Types

```python
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
from pydantic_core import core_schema
from typing import Any

class Color:
    def __init__(self, r: int, g: int, b: int):
        self.r, self.g, self.b = r, g, b

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> core_schema.CoreSchema:
        return core_schema.no_info_after_validator_function(
            cls.validate,
            core_schema.str_schema()
        )

    @classmethod
    def validate(cls, v: str) -> 'Color':
        if not v.startswith('#') or len(v) != 7:
            raise ValueError('Invalid hex color')
        r = int(v[1:3], 16)
        g = int(v[3:5], 16)
        b = int(v[5:7], 16)
        return cls(r, g, b)

class Design(BaseModel):
    primary_color: Color

# Usage
design = Design(primary_color='#FF5733')
assert design.primary_color.r == 255
```

## Validators

### Field Validators

```python
from pydantic import field_validator, model_validator

class Account(BaseModel):
    username: str
    password: str
    password_confirm: str

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

    @field_validator('password')
    @classmethod
    def password_strong(cls, v: str) -> str:
        if len(v) < 8:
            raise ValueError('must be at least 8 characters')
        if not any(c.isupper() for c in v):
            raise ValueError('must contain uppercase letter')
        return v

    # Validate multiple fields
    @field_validator('username', 'password')
    @classmethod
    def not_empty(cls, v: str) -> str:
        if not v or not v.strip():
            raise ValueError('must not be empty')
        return v.strip()
```

### Model Validators

```python
from pydantic import model_validator
from typing import Self

class DateRange(BaseModel):
    start_date: datetime
    end_date: datetime

    @model_validator(mode='after')
    def check_dates(self) -> Self:
        if self.end_date < self.start_date:
            raise ValueError('end_date must be after start_date')
        return self

class Order(BaseModel):
    items: list[str]
    total: float
    discount: float = 0

    @model_validator(mode='before')
    @classmethod
    def calculate_total(cls, data: dict) -> dict:
        # Pre-processing before validation
        if isinstance(data, dict) and 'total' not in data:
            data['total'] = len(data.get('items', [])) * 10.0
        return data
```

### Root Validators (Wrap)

```python
from pydantic import model_validator, ValidationInfo

class Config(BaseModel):
    env: Literal['dev', 'prod']
    debug: bool = False

    @model_validator(mode='wrap')
    @classmethod
    def validate_config(cls, values: Any, handler, info: ValidationInfo):
        # Call default validation
        result = handler(values)

        # Post-validation logic
        if result.env == 'prod' and result.debug:
            raise ValueError('debug cannot be True in production')

        return result
```

## Type Coercion and Strict Mode

```python
from pydantic import BaseModel, ConfigDict, ValidationError

# Coercive mode (default)
class CoerciveModel(BaseModel):
    count: int
    price: float

data = Co

Related in Backend & APIs