🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

Guia completo do Python Pydantic v2 2026: validação, serialização e FastAPI

⏱️5 min read  ·  915 words

Pydantic v2 é a atualização mais importante da biblioteca Python em 2026. Reescrito em Rust (via pydantic-core), é 5-50x mais rápido que v1 com uma API mais limpa, modo estrito e validadores de modelo. Este guia cobre todos os recursos do Pydantic v2 com exemplos práticos.

Instalação e migração

pip install pydantic>=2.0

# Auto-migrate from v1
pip install bump-pydantic
bump-pydantic .

# Key changes from v1:
# - class Config -> model_config = ConfigDict(...)
# - @validator -> @field_validator + @model_validator
# - .dict() -> .model_dump()
# - .json() -> .model_dump_json()
# - __fields__ -> model_fields

Modelos Básicos

from pydantic import BaseModel, Field, ConfigDict, EmailStr
from datetime import datetime
from typing import Optional

class User(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,     # strip whitespace from strings
        str_min_length=1,              # no empty strings
        validate_assignment=True,      # validate on attribute assignment
        populate_by_name=True,         # allow field name OR alias
        use_enum_values=True,          # store enum.value not enum
    )

    id: int
    name: str = Field(min_length=2, max_length=50)
    email: EmailStr
    age: Optional[int] = Field(default=None, ge=13, le=120)
    role: str = Field(default="user", pattern=r"^(user|admin|moderator)$")
    created_at: datetime = Field(default_factory=datetime.utcnow)
    tags: list[str] = Field(default_factory=list, max_length=10)

# Create
user = User(id=1, name="Alice Chen", email="alice@example.com", age=30)
print(user.model_dump())
print(user.model_dump_json(indent=2))

# Partial update
updated = user.model_copy(update={"name": "Alice Smith"})

# From dict
user2 = User.model_validate({"id": 2, "name": "Bob", "email": "bob@example.com"})

# From JSON string
user3 = User.model_validate_json('{"id": 3, "name": "Carol", "email": "carol@example.com"}')

Validadores

from pydantic import BaseModel, field_validator, model_validator, Field
import re

class PasswordChange(BaseModel):
    current_password: str
    new_password: str = Field(min_length=8)
    confirm_password: str

    @field_validator("new_password")
    @classmethod
    def password_strength(cls, v: str) -> str:
        if not re.search(r"[A-Z]", v):
            raise ValueError("Must contain uppercase letter")
        if not re.search(r"[0-9]", v):
            raise ValueError("Must contain digit")
        if not re.search(r"[!@#$%^&*]", v):
            raise ValueError("Must contain special character")
        return v

    @model_validator(mode="after")  # runs after all field validators
    def passwords_match(self) -> "PasswordChange":
        if self.new_password != self.confirm_password:
            raise ValueError("Passwords do not match")
        if self.new_password == self.current_password:
            raise ValueError("New password must differ from current")
        return self

# Validate and get all errors at once
from pydantic import ValidationError

try:
    PasswordChange(
        current_password="old123",
        new_password="short",
        confirm_password="different"
    )
except ValidationError as e:
    print(e.error_count(), "errors")
    for error in e.errors():
        print(f"  {error['loc']}: {error['msg']}")

Modo Estrito

from pydantic import BaseModel, ConfigDict

class StrictUser(BaseModel):
    model_config = ConfigDict(strict=True)  # no coercion!

    id: int
    name: str
    active: bool

# Without strict: "1" coerced to int, "true" to bool
# With strict: exact types required
try:
    StrictUser(id="1", name="Alice", active="true")  # raises!
except Exception as e:
    print(e)

# Strict per-field
from pydantic import Strict
from typing import Annotated

class PartiallyStrict(BaseModel):
    id: Annotated[int, Strict()]  # strict for this field only
    name: str  # coercion allowed

Aliases e serialização

from pydantic import BaseModel, Field, AliasPath
from pydantic.functional_serializers import model_serializer

class APIResponse(BaseModel):
    # Accept camelCase from API, use snake_case internally
    model_config = ConfigDict(populate_by_name=True)

    user_id: int = Field(alias="userId")
    first_name: str = Field(alias="firstName")
    last_name: str = Field(alias="lastName")
    created_at: str = Field(alias="createdAt")

    # Nested alias path
    city: str = Field(validation_alias=AliasPath("address", "city"))

# Parse camelCase JSON
data = {
    "userId": 1,
    "firstName": "Alice",
    "lastName": "Chen",
    "createdAt": "2026-01-15",
    "address": {"city": "Sydney", "country": "AU"}
}
response = APIResponse.model_validate(data)
print(response.user_id, response.first_name, response.city)

# Serialize back to camelCase
print(response.model_dump(by_alias=True))  # {"userId": 1, ...}

Pydantic + FastAPI (padrão 2026)

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, ConfigDict
from typing import Annotated
from datetime import datetime

app = FastAPI()

class UserCreate(BaseModel):
    name: Annotated[str, Field(min_length=2, max_length=50)]
    email: Annotated[str, Field(pattern=r"^[^@]+@[^@]+\.[^@]+$")]
    password: Annotated[str, Field(min_length=8, exclude=True)]  # exclude from response

class UserResponse(BaseModel):
    model_config = ConfigDict(from_attributes=True)  # allows ORM model input

    id: int
    name: str
    email: str
    created_at: datetime

    @classmethod
    def from_orm_with_extras(cls, obj, **extras) -> "UserResponse":
        return cls.model_validate({**obj.__dict__, **extras})

@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(data: UserCreate) -> UserResponse:
    # data.password available but won't appear in response
    user = await db.users.create({
        "name": data.name,
        "email": data.email,
        "password_hash": hash_password(data.password)
    })
    return UserResponse.model_validate(user)

# Automatic OpenAPI docs show the schema
# Pydantic v2 JSON Schema generation is much better than v1

Desempenho: v1 vs v2

import timeit
from pydantic import BaseModel

class BenchModel(BaseModel):
    id: int
    name: str
    email: str
    score: float

data = {"id": 1, "name": "Alice", "email": "alice@example.com", "score": 9.5}

n = 100_000
t = timeit.timeit(lambda: BenchModel(**data), number=n)
print(f"Pydantic v2: {t:.2f}s for {n:,} validations ({n/t:,.0f}/sec)")
# Typical: ~2-5x faster than v1, up to 50x for complex nested models

Pydantic v2 em 2026 é o padrão de validação para Python. O núcleo Rust oferece ganhos significativos de desempenho. Os validadores de campo detectam problemas de dados antecipadamente, os validadores de modelo impõem restrições entre campos e o modo estrito elimina bugs de coerção silenciosos. Migre da v1 usando bump-pydantic — ele lida com 90% das alterações automaticamente.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা