Pydantic V2 Complete Guide (Migration + Examples 2026)

Pydantic V2 is Python’s most-used data validation library, rewritten in Rust for 17x speed improvement over V1. If you build APIs, work with JSON data, or need reliable input validation, Pydantic V2 is essential.

This complete 2026 guide covers models, validators, migration from V1, and integration with FastAPI.

Pydantic V2 Complete Guide (Migration + Examples 2026)

Why Pydantic V2 matters

  • 17x faster than Pydantic V1 (Rust core).
  • Type-safe validation with Python type hints.
  • JSON schema generation automatic.
  • FastAPI foundation: FastAPI relies on Pydantic.
  • Standard for API validation: used by OpenAI SDK, Anthropic SDK, etc.
  • Compatible with mypy: full IDE support.

Install Pydantic V2

# uv (recommended)
uv add pydantic

# pip
pip install pydantic

# For email validation
uv add "pydantic[email]"

Your first model

from pydantic import BaseModel

class User(BaseModel):
    id: int
    name: str
    email: str
    age: int = 0                # optional with default

# Create instance
user = User(
    id=1,
    name="Jude Suarez",
    email="[email protected]",
    age=30
)

# Validation happens automatically
print(user.model_dump())
# {'id': 1, 'name': 'Jude Suarez', 'email': '[email protected]', 'age': 30}

Type validation

from pydantic import BaseModel, ValidationError

class Product(BaseModel):
    name: str
    price: float
    in_stock: bool

# Valid
p1 = Product(name="Book", price=29.99, in_stock=True)

# String coerced to float
p2 = Product(name="Book", price="29.99", in_stock="true")
# Automatically: price=29.99 (float), in_stock=True (bool)

# Invalid, raises ValidationError
try:
    Product(name="Book", price="not-a-number", in_stock=True)
except ValidationError as e:
    print(e)
    # Clear error message showing which field, what type expected

Optional and nullable fields

from typing import Optional
from pydantic import BaseModel

class Article(BaseModel):
    title: str
    subtitle: Optional[str] = None       # can be None
    tags: list[str] = []                 # default empty list
    author: str = "Anonymous"            # default value

article = Article(title="Hello World")
# tags=[], author='Anonymous', subtitle=None

Field constraints

from pydantic import BaseModel, Field, EmailStr

class UserSignup(BaseModel):
    name: str = Field(..., min_length=2, max_length=50)
    email: EmailStr
    age: int = Field(..., ge=13, le=150)             # ge = greater or equal
    password: str = Field(..., min_length=8)
    role: str = Field("user", pattern="^(admin|user|guest)$")

# Invalid inputs raise clear errors
UserSignup(
    name="A",                                          # too short, ERROR
    email="not-email",                                 # invalid format, ERROR
    age=200,                                           # too old, ERROR
    password="short",                                  # too short, ERROR
    role="hacker",                                     # not in allowed, ERROR
)

Custom validators

from pydantic import BaseModel, field_validator, model_validator

class UserProfile(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("Username must be alphanumeric")
        return v.lower()

    @model_validator(mode="after")
    def passwords_match(self) -> "UserProfile":
        if self.password != self.password_confirm:
            raise ValueError("Passwords don't match")
        return self

Nested models

from pydantic import BaseModel

class Address(BaseModel):
    street: str
    city: str
    country: str = "Philippines"

class User(BaseModel):
    name: str
    email: str
    address: Address              # nested model
    other_addresses: list[Address] = []

user = User(
    name="Jude",
    email="[email protected]",
    address={
        "street": "123 Main St",
        "city": "Manila",
    },
)
# address is auto-parsed to Address instance

Serialize / deserialize

from pydantic import BaseModel

class Product(BaseModel):
    name: str
    price: float

p = Product(name="Book", price=29.99)

# To dict
d = p.model_dump()
# {'name': 'Book', 'price': 29.99}

# To JSON string
j = p.model_dump_json()
# '{"name":"Book","price":29.99}'

# From dict
p2 = Product.model_validate({"name": "Pen", "price": 5.0})

# From JSON string
p3 = Product.model_validate_json('{"name":"Pen","price":5.0}')

Migration from Pydantic V1

V1 → V2 key changes:

.dict() → .model_dump()
.json() → .model_dump_json()
.parse_obj() → .model_validate()
.parse_raw() → .model_validate_json()
.schema() → .model_json_schema()

# Config class change
V1:
class Config:
    allow_population_by_field_name = True

V2:
model_config = ConfigDict(populate_by_name=True)

# Validators (major change)
V1: @validator("field") def check(cls, v): ...
V2: @field_validator("field") @classmethod def check(cls, v): ...

# Root validators
V1: @root_validator
V2: @model_validator(mode="after")

# Use bump-pydantic tool to automate migration
uv run bump-pydantic ./src/

FastAPI + Pydantic integration

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class CreateUser(BaseModel):
    name: str
    email: str

@app.post("/users")
def create_user(user: CreateUser):
    # Automatic validation
    # If invalid, FastAPI returns 422 with error details
    # user is fully typed - IDE autocomplete works
    return {"id": 1, "name": user.name}

Environment variable settings

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    debug: bool = False
    openai_api_key: str

    class Config:
        env_file = ".env"

settings = Settings()
# Reads from env vars: DATABASE_URL, DEBUG, OPENAI_API_KEY

# Install: uv add pydantic-settings

Common pydantic mistakes to avoid

  • Skipping the “why” before adopting a new tool. Modern Python tools (uv, Ruff, Polars) are fast and clean. But adopting them without understanding what problem they solve wastes time. Read the tool’s motivation section first.
  • Migrating everything at once. Legacy code bases have too many surprises. Migrate one module at a time and test after each step.
  • Ignoring backwards compatibility. New Python tools sometimes break with older Python versions. Verify your target Python version supports the tool before committing.
  • Trusting benchmarks blindly. “10x faster than X” often means “10x faster for one specific workload”. Test with YOUR data before assuming the benchmark generalizes.
  • Not reading the CHANGELOG. Every dependency upgrade may break something. Skimming the changelog for breaking changes takes 2 minutes and saves hours.

Adoption path for pydantic

  1. Read official docs cover-to-cover for the core concepts. Yes, cover-to-cover.
  2. Complete the official tutorial project. Follow along exactly, no shortcuts.
  3. Adapt one small piece of your existing project to the new tool. Ship it.
  4. If it survives 2 weeks in production, expand adoption.
  5. If it caused issues, rollback and reassess whether the tool fits your use case.

When to use vs when to stick with what you know

Modern Python tools offer real improvements over their predecessors, but “better” is not the same as “necessary for you.” Consider adopting when:

  • Your current tool is slow enough to block productivity (uv/Ruff speedups matter here).
  • You are starting a new project with no legacy constraints.
  • Your team has bandwidth to learn and support the new tool.
  • The tool has been stable and community-adopted for 12+ months.

Stick with your existing tools when: production stability is critical and you have working infrastructure; your team is small and cannot afford learning curves; the tool is early-stage and API may change.

Ecosystem integration considerations

Every new Python tool interacts with the broader ecosystem. Before committing, check:

  • Does your CI/CD pipeline support it? Most tools have GitHub Actions and GitLab CI templates.
  • Does your IDE support it? VS Code and PyCharm have first-class support for most modern tools. Older editors may lag.
  • Do dependency scanners (Snyk, Dependabot) recognize it? Ensures security updates get flagged.
  • Is there production monitoring integration? Datadog, Sentry, New Relic support matters for production.

For teams shipping production Python code, the ecosystem answer is often more important than the tool answer. A slightly-worse tool that plays well with your stack beats a slightly-better tool that fights it.

Best practices summary

  • Read the docs before Stack Overflow. Official docs are cleaner and more accurate than forum answers.
  • Pin versions in production. Locked dependencies prevent surprise breakage. Update deliberately, not accidentally.
  • Test after every dependency upgrade. Run the full test suite. Catch regressions before deployment.
  • Contribute back. Report bugs, submit fixes, write tutorials. The tools improve when users engage.
  • Reassess yearly. The Python ecosystem moves fast. What was best last year may be second-best today.

Recommended Python API resources

The links below are affiliate links. We may earn a commission at no extra cost to you when you buy or sign up. See our affiliate disclosure.

Frequently Asked Questions

Should I migrate from Pydantic V1 to V2?

Yes. V1 is deprecated. Migration is straightforward with bump-pydantic tool. Speed improvement alone is worth it (17x faster).

Is Pydantic just for FastAPI?

No. Pydantic is general-purpose data validation. Used by OpenAI SDK, Anthropic SDK, and countless other Python projects. Great for any data validation need.

Does Pydantic V2 work with Pydantic V1 code?

V2 has a compatibility layer pydantic.v1 that provides V1 API. Not 100% compatible but eases gradual migration.

Alternatives to Pydantic?

msgspec (faster), attrs (older, less features), dataclasses (built-in but no validation). For production API work, Pydantic V2 is the standard.

Does Pydantic work with mypy?

Yes. Pydantic V2 fully supports mypy static type checking. Enable the pydantic mypy plugin in mypy config for best experience.

Angel Jude Suarez

Full-Stack Developer at PIES IT Solution

Focuses on Python development, machine learning, and AI integration. Has built production AI systems including OpenAI Whisper integration for medical transcription and GPT-4o-powered diagnosis assistance. Strong background in pandas, scikit-learn, and TensorFlow.

Expertise: Python  ·  PHP  ·  Java  ·  VB.NET  ·  ASP.NET  ·  Machine Learning  · View all posts by Angel Jude Suarez →

Leave a Comment