pydantic

Pydantic Validation Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "pydantic" with this command: npx skills add bobmatnyc/claude-mpm-skills/bobmatnyc-claude-mpm-skills-pydantic

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

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="alice@example.com") print(user.model_dump()) # {'id': 1, 'name': 'Alice', ...}

Automatic type coercion

user2 = User(id="2", name="Bob", email="bob@example.com") 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

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

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

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

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

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

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

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

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)

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

from pydantic import BaseModel, ConfigDict, ValidationError

Coercive mode (default)

class CoerciveModel(BaseModel): count: int price: float

data = CoerciveModel(count="42", price="19.99") assert data.count == 42 # String → int assert data.price == 19.99 # String → float

Strict mode

class StrictModel(BaseModel): model_config = ConfigDict(strict=True)

count: int
price: float

try: StrictModel(count="42", price="19.99") # Raises ValidationError except ValidationError as e: print("Strict mode: no coercion allowed")

Per-field strict mode

class MixedModel(BaseModel): flexible: int # Allows coercion strict: Annotated[int, Field(strict=True)] # No coercion

MixedModel(flexible="1", strict=2) # OK

MixedModel(flexible="1", strict="2") # ValidationError

Nested Models and Recursive Types

from pydantic import BaseModel from typing import ForwardRef

Nested models

class Address(BaseModel): street: str city: str country: str

class Company(BaseModel): name: str address: Address

company = Company( name="ACME Corp", address={'street': '123 Main St', 'city': 'NYC', 'country': 'USA'} )

Recursive types (tree structure)

class TreeNode(BaseModel): value: int children: list['TreeNode'] = []

TreeNode.model_rebuild() # Required for forward references

tree = TreeNode( value=1, children=[ TreeNode(value=2, children=[TreeNode(value=4)]), TreeNode(value=3) ] )

Self-referencing with ForwardRef

class Category(BaseModel): name: str parent: 'Category | None' = None subcategories: list['Category'] = []

Category.model_rebuild()

Generic Models

from pydantic import BaseModel from typing import Generic, TypeVar

T = TypeVar('T')

class Response(BaseModel, Generic[T]): success: bool data: T message: str = ''

class User(BaseModel): id: int name: str

Usage with concrete type

user_response = Response[User]( success=True, data=User(id=1, name='Alice') )

List response

list_response = Response[list[User]]( success=True, data=[User(id=1, name='Alice'), User(id=2, name='Bob')] )

Generic repository pattern

class Repository(BaseModel, Generic[T]): items: list[T]

def add(self, item: T) -> None:
    self.items.append(item)

user_repo = RepositoryUser user_repo.add(User(id=1, name='Alice'))

Serialization

Model Dump

from pydantic import BaseModel, Field, field_serializer

class Article(BaseModel): title: str content: str tags: list[str] metadata: dict[str, Any] = {}

# Serialization customization
@field_serializer('tags')
def serialize_tags(self, tags: list[str]) -> str:
    return ','.join(tags)

article = Article( title='Pydantic Guide', content='...', tags=['python', 'validation'] )

Dump to dict

data = article.model_dump()

{'title': 'Pydantic Guide', 'tags': 'python,validation', ...}

Exclude fields

data = article.model_dump(exclude={'metadata'})

Include only specific fields

data = article.model_dump(include={'title', 'tags'})

Exclude unset fields

article2 = Article(title='Test', content='...', tags=[]) data = article2.model_dump(exclude_unset=True) # metadata excluded

By alias

class AliasModel(BaseModel): internal_name: str = Field(alias='externalName')

model = AliasModel(externalName='value') model.model_dump(by_alias=True) # {'externalName': 'value'}

JSON Serialization

from datetime import datetime from pydantic import BaseModel, field_serializer

class Event(BaseModel): name: str timestamp: datetime

@field_serializer('timestamp')
def serialize_dt(self, dt: datetime) -> str:
    return dt.isoformat()

event = Event(name='Deploy', timestamp=datetime.now())

Dump to JSON string

json_str = event.model_dump_json()

'{"name":"Deploy","timestamp":"2025-11-30T..."}'

Pretty print

json_str = event.model_dump_json(indent=2)

Parse from JSON

event2 = Event.model_validate_json(json_str)

Custom Serializers

from pydantic import model_serializer

class User(BaseModel): id: int username: str password: SecretStr

@model_serializer
def ser_model(self) -> dict[str, Any]:
    return {
        'id': self.id,
        'username': self.username,
        # Never serialize password
    }

user = User(id=1, username='alice', password='secret123') assert 'password' not in user.model_dump()

Settings Management

BaseSettings

from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import Field

class AppSettings(BaseSettings): model_config = SettingsConfigDict( env_file='.env', env_file_encoding='utf-8', env_prefix='APP_', case_sensitive=False )

# Environment variables
database_url: str
redis_url: str = 'redis://localhost:6379'
secret_key: SecretStr
debug: bool = False

# Nested settings
class SMTPSettings(BaseModel):
    host: str
    port: int = 587
    username: str
    password: SecretStr

smtp: SMTPSettings

Reads from environment variables:

APP_DATABASE_URL, APP_REDIS_URL, APP_SECRET_KEY, APP_DEBUG

APP_SMTP__HOST, APP_SMTP__PORT, etc.

settings = AppSettings()

Multi-Environment Settings

from functools import lru_cache

class Settings(BaseSettings): environment: Literal['dev', 'staging', 'prod'] = 'dev' database_url: str api_key: SecretStr

model_config = SettingsConfigDict(
    env_file='.env',
    extra='ignore'
)

@property
def is_production(self) -> bool:
    return self.environment == 'prod'

@lru_cache def get_settings() -> Settings: return Settings()

Usage in FastAPI

from fastapi import Depends

@app.get('/config') def get_config(settings: Settings = Depends(get_settings)): return {'env': settings.environment}

FastAPI Integration

Request/Response Models

from fastapi import FastAPI, HTTPException from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserCreate(BaseModel): username: str = Field(min_length=3, max_length=50) email: EmailStr password: str = Field(min_length=8)

class UserResponse(BaseModel): id: int username: str email: EmailStr

model_config = ConfigDict(from_attributes=True)

@app.post('/users', response_model=UserResponse) def create_user(user: UserCreate): # FastAPI auto-validates request body # Returns only fields in UserResponse (password excluded) return UserResponse( id=1, username=user.username, email=user.email )

Query Parameters

from pydantic import BaseModel, Field from fastapi import Query

class PaginationParams(BaseModel): skip: int = Field(0, ge=0) limit: int = Field(10, ge=1, le=100)

class SearchParams(BaseModel): q: str = Field(..., min_length=1) category: str | None = None sort_by: Literal['date', 'relevance'] = 'relevance'

@app.get('/search') def search(params: SearchParams = Query()): return {'query': params.q, 'sort': params.sort_by}

Response Model Customization

class DetailedUser(BaseModel): id: int username: str email: EmailStr created_at: datetime last_login: datetime | None

@app.get('/users/{user_id}', response_model=DetailedUser) def get_user(user_id: int, include_dates: bool = False): user = DetailedUser( id=user_id, username='alice', email='alice@example.com', created_at=datetime.now(), last_login=None )

if not include_dates:
    return user.model_dump(exclude={'created_at', 'last_login'})
return user

SQLAlchemy Integration

ORM Models with Pydantic

from sqlalchemy import Column, Integer, String, DateTime from sqlalchemy.orm import DeclarativeBase from pydantic import BaseModel, ConfigDict

class Base(DeclarativeBase): pass

SQLAlchemy ORM model

class UserDB(Base): tablename = 'users'

id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True)
email = Column(String(100))
created_at = Column(DateTime, default=datetime.utcnow)

Pydantic model for validation

class UserSchema(BaseModel): model_config = ConfigDict(from_attributes=True)

id: int
username: str
email: EmailStr
created_at: datetime

Usage

from sqlalchemy.orm import Session

def get_user(db: Session, user_id: int) -> UserSchema: user = db.query(UserDB).filter(UserDB.id == user_id).first() return UserSchema.model_validate(user) # ORM → Pydantic

Hybrid Approach

from pydantic import BaseModel

class UserBase(BaseModel): username: str email: EmailStr

class UserCreate(UserBase): password: str

class UserUpdate(BaseModel): username: str | None = None email: EmailStr | None = None password: str | None = None

class UserInDB(UserBase): model_config = ConfigDict(from_attributes=True)

id: int
created_at: datetime
password_hash: str

CRUD operations

def create_user(db: Session, user: UserCreate) -> UserInDB: db_user = UserDB( username=user.username, email=user.email, password_hash=hash_password(user.password) ) db.add(db_user) db.commit() db.refresh(db_user) return UserInDB.model_validate(db_user)

Django Integration

Django Model Validation

from django.db import models from pydantic import BaseModel, field_validator

Django model

class Article(models.Model): title = models.CharField(max_length=200) content = models.TextField() published = models.BooleanField(default=False)

Pydantic schema

class ArticleSchema(BaseModel): model_config = ConfigDict(from_attributes=True)

title: str = Field(max_length=200)
content: str
published: bool = False

@field_validator('content')
@classmethod
def validate_content(cls, v: str) -> str:
    if len(v) < 100:
        raise ValueError('Content too short')
    return v

Usage in Django views

from django.http import JsonResponse from django.views.decorators.http import require_http_methods

@require_http_methods(['POST']) def create_article(request): try: data = ArticleSchema.model_validate_json(request.body) article = Article.objects.create(**data.model_dump()) return JsonResponse({'id': article.id}) except ValidationError as e: return JsonResponse({'errors': e.errors()}, status=400)

Computed Fields

from pydantic import computed_field

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

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

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

rect = Rectangle(width=10, height=5) assert rect.area == 50 assert rect.perimeter == 30

Computed fields in serialization

data = rect.model_dump()

{'width': 10.0, 'height': 5.0, 'area': 50.0, 'perimeter': 30.0}

Custom Errors

from pydantic import BaseModel, field_validator, ValidationError from pydantic_core import PydanticCustomError

class StrictUser(BaseModel): username: str age: int

@field_validator('username')
@classmethod
def validate_username(cls, v: str) -> str:
    if len(v) < 3:
        raise PydanticCustomError(
            'username_too_short',
            'Username must be at least 3 characters',
            {'min_length': 3, 'actual_length': len(v)}
        )
    return v

@field_validator('age')
@classmethod
def validate_age(cls, v: int) -> int:
    if v < 18:
        raise PydanticCustomError(
            'underage',
            'User must be at least 18 years old',
            {'age': v, 'minimum_age': 18}
        )
    return v

Custom error handling

try: StrictUser(username='ab', age=16) except ValidationError as e: for error in e.errors(): print(f"{error['type']}: {error['msg']}") print(f"Context: {error.get('ctx')}")

Performance Optimization

V2 Rust Core Benefits

Pydantic v2 uses pydantic-core (Rust) for:

- 5-50x faster validation

- Lower memory usage

- Better error messages

- Improved JSON parsing

import timeit from pydantic import BaseModel

class Data(BaseModel): values: list[int] names: list[str] metadata: dict[str, Any]

Benchmark

data_dict = { 'values': list(range(1000)), 'names': ['item'] * 1000, 'metadata': {'key': 'value'} }

def validate(): Data.model_validate(data_dict)

time_taken = timeit.timeit(validate, number=10000) print(f"10000 validations: {time_taken:.2f}s")

Optimization Techniques

from pydantic import BaseModel, ConfigDict

class OptimizedModel(BaseModel): model_config = ConfigDict( # Validate assignment only when needed validate_assignment=False,

    # Disable validation for internal use
    validate_default=False,

    # Use slots for memory efficiency
    # (Not available in Pydantic v2 BaseModel directly)
)

data: list[int]

Reuse validators

from functools import lru_cache

@lru_cache(maxsize=128) def get_validator(model_class): return model_class.model_validate

Bulk validation

def validate_bulk(items: list[dict]) -> list[Data]: validator = get_validator(Data) return [validator(item) for item in items]

JSON Schema Generation

from pydantic import BaseModel, Field

class Product(BaseModel): """Product model for catalog"""

id: int = Field(description="Unique product identifier")
name: str = Field(description="Product name", examples=["Widget"])
price: float = Field(gt=0, description="Price in USD")
tags: list[str] = Field(default=[], description="Product tags")

Generate JSON Schema

schema = Product.model_json_schema() print(json.dumps(schema, indent=2))

{

"title": "Product",

"description": "Product model for catalog",

"type": "object",

"properties": {

"id": {"type": "integer", "description": "Unique product identifier"},

"name": {"type": "string", "description": "Product name"},

...

},

"required": ["id", "name", "price"]

}

OpenAPI compatible

from fastapi import FastAPI

app = FastAPI()

@app.post('/products') def create_product(product: Product): return product

FastAPI auto-generates OpenAPI schema from Pydantic models

Dataclass Integration

from pydantic.dataclasses import dataclass from pydantic import Field

@dataclass class User: id: int name: str = Field(min_length=1) email: str = Field(pattern=r'.+@.+..+')

Works like Pydantic BaseModel with validation

user = User(id=1, name='Alice', email='alice@example.com')

Validation on construction

try: User(id=2, name='', email='invalid') except ValidationError as e: print(e.errors())

Convert to Pydantic BaseModel

from pydantic import BaseModel

class UserModel(BaseModel): model_config = ConfigDict(from_attributes=True)

id: int
name: str
email: str

user_model = UserModel.model_validate(user)

Testing Strategies

Unit Testing Models

import pytest from pydantic import ValidationError

def test_user_validation(): # Valid data user = User(id=1, name='Alice', email='alice@example.com') assert user.name == 'Alice'

# Invalid data
with pytest.raises(ValidationError) as exc_info:
    User(id='invalid', name='Bob', email='bob@example.com')

errors = exc_info.value.errors()
assert errors[0]['type'] == 'int_parsing'

def test_user_serialization(): user = User(id=1, name='Alice', email='alice@example.com') data = user.model_dump()

assert data == {
    'id': 1,
    'name': 'Alice',
    'email': 'alice@example.com'
}

def test_nested_validation(): company = Company( name='ACME', address={'street': '123 Main', 'city': 'NYC', 'country': 'USA'} ) assert company.address.city == 'NYC'

Testing with Fixtures

@pytest.fixture def sample_user_data(): return { 'id': 1, 'name': 'Alice', 'email': 'alice@example.com' }

@pytest.fixture def sample_user(sample_user_data): return User(**sample_user_data)

def test_with_fixtures(sample_user): assert sample_user.name == 'Alice'

def test_invalid_email(sample_user_data): sample_user_data['email'] = 'invalid' with pytest.raises(ValidationError): User(**sample_user_data)

Property-Based Testing

from hypothesis import given, strategies as st

@given( id=st.integers(min_value=1), name=st.text(min_size=1, max_size=100), email=st.emails() ) def test_user_always_valid(id, name, email): user = User(id=id, name=name, email=email) assert user.id == id assert user.name == name assert user.email == email

Migration Guide (v1 → v2)

Key Changes

v1

from pydantic import BaseModel

class OldModel(BaseModel): class Config: validate_assignment = True arbitrary_types_allowed = True

# Validators
@validator('field')
def validate_field(cls, v):
    return v

@root_validator
def validate_model(cls, values):
    return values

# Serialization
data = model.dict()
json_str = model.json()

# Parsing
model = OldModel.parse_obj(data)
model = OldModel.parse_raw(json_str)

v2

from pydantic import BaseModel, ConfigDict, field_validator, model_validator

class NewModel(BaseModel): model_config = ConfigDict( validate_assignment=True, arbitrary_types_allowed=True )

# Field validators
@field_validator('field')
@classmethod
def validate_field(cls, v):
    return v

# Model validators
@model_validator(mode='after')
def validate_model(self):
    return self

# Serialization
data = model.model_dump()
json_str = model.model_dump_json()

# Parsing
model = NewModel.model_validate(data)
model = NewModel.model_validate_json(json_str)

Migration Checklist

  • Replace class Config with model_config = ConfigDict()

  • Update .dict() → .model_dump()

  • Update .json() → .model_dump_json()

  • Update .parse_obj() → .model_validate()

  • Update .parse_raw() → .model_validate_json()

  • Update @validator → @field_validator with @classmethod

  • Update @root_validator → @model_validator(mode='after')

  • Review json_encoders → use @field_serializer

  • Test strict mode behavior changes

  • Update custom types to use get_pydantic_core_schema

Best Practices

Model Organization

Separate schemas by use case

class UserBase(BaseModel): """Shared fields""" username: str email: EmailStr

class UserCreate(UserBase): """API request for creating user""" password: str

class UserUpdate(BaseModel): """API request for updating user (all optional)""" username: str | None = None email: EmailStr | None = None password: str | None = None

class UserInDB(UserBase): """Database representation""" model_config = ConfigDict(from_attributes=True)

id: int
password_hash: str
created_at: datetime

class UserResponse(UserBase): """API response (excludes sensitive data)""" id: int created_at: datetime

Validation Best Practices

Use Field for constraints, not validators

class Good(BaseModel): age: int = Field(ge=0, le=150) email: EmailStr

class Bad(BaseModel): age: int email: str

@field_validator('age')
@classmethod
def validate_age(cls, v):
    if v < 0 or v > 150:
        raise ValueError('invalid age')
    return v

Prefer composition over inheritance

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

class User(TimestampMixin): username: str email: EmailStr

Error Handling

from pydantic import ValidationError

def safe_validate(data: dict) -> User | None: try: return User.model_validate(data) except ValidationError as e: # Log validation errors logger.error(f"Validation failed: {e.errors()}") return None

def validate_with_details(data: dict): try: return User.model_validate(data) except ValidationError as e: # Return user-friendly errors return { 'success': False, 'errors': [ { 'field': '.'.join(str(loc) for loc in err['loc']), 'message': err['msg'], 'type': err['type'] } for err in e.errors() ] }

Common Patterns

API Response Wrapper

from typing import Generic, TypeVar

T = TypeVar('T')

class APIResponse(BaseModel, Generic[T]): success: bool data: T | None = None error: str | None = None metadata: dict[str, Any] = {}

Usage

user_response = APIResponse[User]( success=True, data=User(id=1, name='Alice', email='alice@example.com') )

error_response = APIResponse[User]( success=False, error='User not found' )

Pagination

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

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

users = PaginatedResponse[User]( items=[...], total=100, page=1, page_size=10 ) assert users.total_pages == 10

Audit Fields

class AuditMixin(BaseModel): created_at: datetime = Field(default_factory=datetime.utcnow) updated_at: datetime = Field(default_factory=datetime.utcnow) created_by: int | None = None updated_by: int | None = None

class Document(AuditMixin): title: str content: str

@model_validator(mode='before')
@classmethod
def update_timestamp(cls, data: dict) -> dict:
    if isinstance(data, dict):
        data['updated_at'] = datetime.utcnow()
    return data

Related Skills

When using Pydantic, consider these complementary skills:

  • fastapi-local-dev: FastAPI development server patterns with Pydantic integration

  • sqlalchemy: SQLAlchemy ORM patterns for database models with Pydantic validation

  • django: Django framework integration with Pydantic schemas

  • pytest: Testing strategies for Pydantic models and validation

Quick FastAPI Integration Reference (Inlined for Standalone Use)

FastAPI with Pydantic (basic pattern)

from fastapi import FastAPI, HTTPException from pydantic import BaseModel, EmailStr

app = FastAPI()

class UserCreate(BaseModel): username: str email: EmailStr password: str

class UserResponse(BaseModel): id: int username: str email: EmailStr

model_config = ConfigDict(from_attributes=True)

@app.post('/users', response_model=UserResponse) def create_user(user: UserCreate): # FastAPI auto-validates using Pydantic # response_model filters out password return UserResponse(id=1, username=user.username, email=user.email)

Quick SQLAlchemy Integration Reference (Inlined for Standalone Use)

SQLAlchemy 2.0 with Pydantic validation

from sqlalchemy import Column, Integer, String from sqlalchemy.orm import DeclarativeBase from pydantic import BaseModel, ConfigDict

class Base(DeclarativeBase): pass

class UserDB(Base): tablename = 'users' id = Column(Integer, primary_key=True) username = Column(String(50)) email = Column(String(100))

class UserSchema(BaseModel): model_config = ConfigDict(from_attributes=True) id: int username: str email: str

Convert ORM to Pydantic

user_orm = db.query(UserDB).first() user_validated = UserSchema.model_validate(user_orm)

Quick Pytest Testing Reference (Inlined for Standalone Use)

Testing Pydantic models with pytest

import pytest from pydantic import ValidationError

def test_user_validation(): user = User(id=1, name='Alice', email='alice@example.com') assert user.name == 'Alice'

def test_validation_error(): with pytest.raises(ValidationError) as exc_info: User(id='invalid', name='Bob', email='bob@example.com') errors = exc_info.value.errors() assert errors[0]['type'] == 'int_parsing'

@pytest.fixture def sample_user(): return User(id=1, name='Alice', email='alice@example.com')

[Full integration patterns available in respective skills if deployed together]

Additional Resources

  • Pydantic Documentation

  • Migration Guide v1→v2

  • Performance Benchmarks

  • JSON Schema Integration

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

drizzle-orm

No summary provided by upstream source.

Repository SourceNeeds Review
General

playwright-e2e-testing

No summary provided by upstream source.

Repository SourceNeeds Review
General

tailwind-css

No summary provided by upstream source.

Repository SourceNeeds Review
General

trpc-type-safety

No summary provided by upstream source.

Repository SourceNeeds Review