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.
📋 Table of Contents
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.
🔗 Share this article
✍️ Leave a Comment