Python Dataclasses and Pydantic: Modern Data Validation for Production APIs

Python Dataclasses and Pydantic: Modern Data Validation for Production APIs

Before dataclasses, Python developers wrote dozens of lines of boilerplate for simple data containers. __init__, __repr__, __eq__ — all by hand. Dataclasses generate them automatically. Pydantic goes further: validation, serialization, JSON schema, and FastAPI integration. This guide covers both with real production patterns.

TL;DR: Use dataclasses for internal data structures where you trust the data. Use Pydantic for external data (API requests, config files, user input) where validation is critical. Pydantic v2 is 5-50x faster than v1. Both integrate with FastAPI.

Python dataclasses — zero-boilerplate data containers

from dataclasses import dataclass, field
from datetime import datetime

@dataclass
class User:
    id: int
    name: str
    email: str
    created_at: datetime = field(default_factory=datetime.now)
    tags: list[str] = field(default_factory=list)

    def __post_init__(self):
        # Validation after auto-generated __init__
        if not self.email or '@' not in self.email:
            raise ValueError(f'Invalid email: {self.email}')

# Auto-generated: __init__, __repr__, __eq__
user = User(id=1, name='Alice', email='alice@example.com')
print(user)  # User(id=1, name='Alice', email='alice@example.com', ...)

# Immutable dataclass:
@dataclass(frozen=True)  # __hash__ generated, fields immutable
class Point:
    x: float
    y: float

Pydantic v2 — validation + serialization

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

class CreateUserRequest(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    email: str = Field(pattern=r'^[\w.-]+@[\w.-]+\.[a-zA-Z]{2,}$')
    age: int = Field(ge=18, le=120)
    role: str = Field(default='user')

    @field_validator('email')
    @classmethod
    def lowercase_email(cls, v: str) -> str:
        return v.lower().strip()

    @field_validator('role')
    @classmethod
    def valid_role(cls, v: str) -> str:
        if v not in ('user', 'admin', 'moderator'):
            raise ValueError(f'Invalid role: {v}')
        return v

# Parse and validate:
try:
    user = CreateUserRequest(name='Alice', email='ALICE@Example.COM', age=25)
    print(user.email)  # 'alice@example.com' (auto-lowercased)
except ValidationError as e:
    print(e.errors())  # Structured error list with field details

FastAPI integration

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class UserCreate(BaseModel):
    name: str
    email: str
    age: int

class UserResponse(BaseModel):
    id: int
    name: str
    email: str

@app.post('/users', response_model=UserResponse, status_code=201)
async def create_user(body: UserCreate):  # Auto-validated from request JSON
    # body is already validated UserCreate instance
    user = await db.create_user(body.model_dump())
    return UserResponse(**user)  # Only returns fields in response_model
# FastAPI generates OpenAPI docs automatically from Pydantic models

dataclasses vs Pydantic — when to use each

  • ✅ dataclasses: internal data structures, performance-critical, trusted data
  • ✅ Pydantic: API request bodies, config files, external data sources
  • ✅ Pydantic v2: 5-50x faster than v1 — upgrade if still on v1
  • ✅ Use model_dump() for dict conversion, model_json() for JSON string
  • ❌ Never use plain dicts for structured data — no validation, no type hints
  • ❌ Never use Pydantic for pure performance-critical inner loops — overhead

Pydantic models power FastAPI which is covered in the production API patterns. For validation in AWS Lambda, Lambda cold start shows why keeping Pydantic import lean matters. External reference: Pydantic v2 documentation.

Recommended Reading

Designing Data-Intensive Applications — The essential book every senior developer needs.

The Pragmatic Programmer — Timeless engineering wisdom for writing better code.

Affiliate links. We earn a small commission at no extra cost to you.

Free Weekly Newsletter

🚀 Don’t Miss the Next Cheat Code

Join 1,000+ senior developers getting expert JS, Python, AWS and system design secrets weekly.

✓ No spam✓ Unsubscribe anytime

Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply