Pydantic v2: Gelişmiş Doğrulama, BaseModel, TypeAdapter ve ConfigDict
Pydantic v2'deki yenilikleri keşfedin (Rust core, 5-50 kat daha hızlı): Yeni API model_validate/model_dump, model dışı doğrulama için TypeAdapter, doğrulayıcılar mode='before'/'after' ve v1'den geçiş yapılıyor.
Pydantic v2: Rust'ta Yeniden Yazma
Pydantic v2 (Haziran 2023) tamamen yeniden yazılmıştır: doğrulama çekirdeği
artık Rust'ta uygulanıyor (pydantic-core), 5-50x yapıyor
çoğu kullanım durumunda v1'den daha hızlıdır. FastAPI 0.100+ kullanım
Pydantic v2 yerel olarak.
Ne Öğreneceksiniz
- Pydantic v1 ve v2 arasındaki farklar: API'ler değişti, nelerin taşınması gerekiyor
- model_validate ve model_dump: yeni serileştirme API'si
- Field(): kısıtlama, takma ad, varsayılan fabrikalar
- field_validator ve model_validator: modlu doğrulayıcılar
- TypeAdapter: BaseModel olmayan türler için doğrulama
- ConfigDict: Dahili Config sınıfı olmayan model konfigürasyonu
- model_rebuild: ileri referanslar ve dairesel modeller
Kurulum ve Önkoşullar
# Pydantic v2 (installato automaticamente con FastAPI recente)
pip install pydantic[email] # Include EmailStr e validatori email
pip install pydantic-settings # Per configurazione app
# Verifica versione
python -c "import pydantic; print(pydantic.VERSION)"
# 2.x.x
BaseModel: Temel Yapı
Pydantic modelleri, Python sınıflarından miras alan Python sınıflarıdır. BaseModel.
Her alan, tür açıklamasına sahip bir sınıf niteliğidir. Pdantik
yöntemi otomatik olarak oluşturur __init__, doğrulama ve
serileştirme.
# Modello base con Pydantic v2
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime
from enum import Enum
class UserStatus(str, Enum):
active = "active"
inactive = "inactive"
banned = "banned"
class Address(BaseModel):
street: str
city: str
country: str = "IT" # Default value
postal_code: Optional[str] = None
class User(BaseModel):
# Field() fornisce metadata e vincoli
id: int = Field(gt=0, description="User ID, must be positive")
name: str = Field(
min_length=2,
max_length=100,
description="Full name",
examples=["Mario Rossi"],
)
email: EmailStr
status: UserStatus = UserStatus.active
address: Optional[Address] = None # Modello annidato (nested model)
# Field con default_factory: valore di default calcolato al momento della creazione
tags: list[str] = Field(default_factory=list)
created_at: datetime = Field(default_factory=datetime.now)
# Alias: nome diverso nella serializzazione JSON
internal_id: str = Field(alias="internalId", default="")
# Creazione con keyword arguments
user = User(
id=1,
name="Mario Rossi",
email="mario@example.com",
address=Address(street="Via Roma 1", city="Milano"),
tags=["developer", "python"],
)
# v2: model_dump() sostituisce .dict()
user_dict = user.model_dump()
print(user_dict)
# {"id": 1, "name": "Mario Rossi", "email": "mario@example.com", ...}
# Con exclude e include
minimal = user.model_dump(include={"id", "name", "email"})
without_dates = user.model_dump(exclude={"created_at"})
# JSON string
user_json = user.model_dump_json()
model_validate: Dış Verilerden Model Oluşturma
model_validate() bir model oluşturmanın v2 yoludur
bir sözlük veya rastgele bir nesne. Doğrudan yapıcı e'nin yerini alır
.parse_obj() v1'in.
# model_validate: parsing da diverse sorgenti
import json
# Da dizionario Python
data = {
"id": 1,
"name": "Mario Rossi",
"email": "mario@example.com",
}
user = User.model_validate(data)
# Da JSON string (convenienza)
json_data = '{"id": 2, "name": "Luigi Bianchi", "email": "luigi@example.com"}'
user2 = User.model_validate_json(json_data)
# Con alias: se il JSON usa camelCase ma il modello usa snake_case
raw_data = {"internalId": "abc-123", "id": 3, "name": "Test User", "email": "test@example.com"}
user3 = User.model_validate(raw_data)
print(user3.internal_id) # "abc-123" - letto dall'alias
# Validazione di dati da ORM (SQLAlchemy objects)
# Pydantic v2 puo leggere attributi da oggetti non-dict
class SQLAlchemyUser:
def __init__(self):
self.id = 1
self.name = "ORM User"
self.email = "orm@example.com"
orm_obj = SQLAlchemyUser()
user4 = User.model_validate(orm_obj, from_attributes=True)
# from_attributes=True: legge attributi invece che chiavi dict
Doğrulayıcılar: field_validator ve model_validator
Pydantic v2, doğrulayıcıları tamamen yeniden tasarladı. İki ana dekoratör
ben @field_validator (bireysel alanlar için) e @model_validator
(alanlar arası doğrulamalar için).
# Validators in Pydantic v2
from pydantic import BaseModel, field_validator, model_validator, ValidationError
from typing import Any
class PaymentOrder(BaseModel):
amount: float
currency: str
discount_percent: float = 0.0
final_amount: float = 0.0
# field_validator: valida un singolo campo
# mode="before": eseguito PRIMA della conversione di tipo
# mode="after": eseguito DOPO (default in v2)
@field_validator("currency", mode="before")
@classmethod
def normalize_currency(cls, v: Any) -> str:
if isinstance(v, str):
return v.upper().strip() # "eur" -> "EUR"
return v
@field_validator("currency")
@classmethod
def validate_currency(cls, v: str) -> str:
supported = {"EUR", "USD", "GBP", "JPY"}
if v not in supported:
raise ValueError(f"Currency {v} not supported. Use: {supported}")
return v
@field_validator("amount", "discount_percent")
@classmethod
def must_be_positive(cls, v: float) -> float:
if v < 0:
raise ValueError("Must be non-negative")
return v
# model_validator: accesso a tutti i campi dopo la validazione
# mode="after": riceve il modello gia validato
@model_validator(mode="after")
def compute_final_amount(self) -> "PaymentOrder":
discount = self.amount * (self.discount_percent / 100)
self.final_amount = round(self.amount - discount, 2)
return self
# model_validator mode="before": riceve il dict grezzo
@model_validator(mode="before")
@classmethod
def check_required_fields(cls, data: Any) -> Any:
if isinstance(data, dict):
if "amount" not in data:
raise ValueError("amount is required")
return data
# Test
try:
order = PaymentOrder(amount=100.0, currency="eur", discount_percent=10.0)
print(order.currency) # "EUR" (normalizzato)
print(order.final_amount) # 90.0 (calcolato)
except ValidationError as e:
print(e.errors()) # Lista strutturata degli errori
TypeAdapter: BaseModel Olmayan Türler için Doğrulama
TypeAdapter v2'nin en kullanışlı yeni özelliklerinden biridir:
oluşturmadan herhangi bir Python türünde Pydantic doğrulamasını kullanmanıza olanak tanır
bir BaseModel özel.
# TypeAdapter: validazione di tipi primitivi e complessi
from pydantic import TypeAdapter
from typing import List, Dict, Union
# Validazione di una lista di int
int_list_adapter = TypeAdapter(List[int])
validated = int_list_adapter.validate_python([1, "2", 3.0])
print(validated) # [1, 2, 3] - coercizione automatica
# Validazione di un tipo Union
NumberOrString = Union[int, str]
ns_adapter = TypeAdapter(NumberOrString)
print(ns_adapter.validate_python(42)) # 42
print(ns_adapter.validate_python("hello")) # "hello"
# Validazione di dict complessi
UserDict = Dict[str, Union[int, str, List[str]]]
dict_adapter = TypeAdapter(UserDict)
result = dict_adapter.validate_python({
"name": "Mario",
"age": "30", # Stringa che viene coerta a int? No: rimane str perche Union[int, str]
"tags": ["dev", "python"],
})
# Uso pratico: validare config da variabili d'ambiente
from typing import Annotated
from pydantic import Field as PydanticField
PositiveInt = Annotated[int, PydanticField(gt=0)]
port_adapter = TypeAdapter(PositiveInt)
try:
port = port_adapter.validate_python(int("8080")) # 8080
port = port_adapter.validate_python(-1) # ValidationError!
except Exception as e:
print(e)
# Serializzazione con TypeAdapter
data = [1, 2, 3]
json_str = int_list_adapter.dump_json(data) # b'[1,2,3]'
ConfigDict: Model Yapılandırması
V2'de şablon yapılandırması şunları kullanır: model_config = ConfigDict(...)
sınıf yerine Config v1'in içi.
# ConfigDict: tutte le opzioni principali
from pydantic import BaseModel, ConfigDict
class APIResponse(BaseModel):
model_config = ConfigDict(
# Permette la lettura da attributi ORM (SQLAlchemy, Django ORM)
from_attributes=True,
# Usa alias invece del nome Python nella serializzazione JSON
populate_by_name=True, # Permette anche il nome Python (non solo l'alias)
# Serializzazione: converti enum al loro valore
use_enum_values=True,
# Validation: accetta campi extra senza errore (li ignora)
extra="ignore", # "allow", "ignore", "forbid"
# JSON schema: titolo personalizzato
title="API Response Model",
# Validazione al momento dell'assegnazione (non solo alla creazione)
validate_assignment=True,
# Frozen: rende il modello immutabile dopo la creazione
frozen=False, # True = immutabile, genera __hash__
# Stripping whitespace dagli str automaticamente
str_strip_whitespace=True,
# Serializzazione: esclude None per default
# (utile per API che non vogliono campi null nel JSON)
# Non disponibile come ConfigDict, usa model_dump(exclude_none=True)
)
user_id: int
user_name: str # str_strip_whitespace rimuove spazi iniziali/finali
# Esempio: modello con from_attributes per ORM
class OrmUser(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
email: str
# Compatibile con oggetti SQLAlchemy
class FakeOrmObject:
id = 1
name = " Mario Rossi " # Con spazi
email = "mario@example.com"
orm_obj = FakeOrmObject()
user = OrmUser.model_validate(orm_obj)
print(repr(user.name)) # "Mario Rossi" (spazi rimossi da str_strip_whitespace)
Pydantic v1'den v2'ye geçiş
Pydantic v1'e sahip mevcut kodunuz varsa en yaygın değişiklikler şunlardır:
# PYDANTIC v1 -> v2: Cheat Sheet
# 1. .dict() -> .model_dump()
user.dict() # v1
user.model_dump() # v2
# 2. .json() -> .model_dump_json()
user.json() # v1
user.model_dump_json() # v2
# 3. .parse_obj() -> .model_validate()
User.parse_obj(data) # v1
User.model_validate(data) # v2
# 4. .parse_raw() -> .model_validate_json()
User.parse_raw(json_str) # v1
User.model_validate_json(json_str) # v2
# 5. class Config -> model_config = ConfigDict()
# v1:
class User(BaseModel):
class Config:
orm_mode = True
# v2:
class User(BaseModel):
model_config = ConfigDict(from_attributes=True) # orm_mode -> from_attributes
# 6. Validators: @validator -> @field_validator
# v1:
from pydantic import validator
class User(BaseModel):
@validator("name")
def name_must_not_be_empty(cls, v):
return v.strip()
# v2:
from pydantic import field_validator
class User(BaseModel):
@field_validator("name", mode="after")
@classmethod
def name_must_not_be_empty(cls, v: str) -> str:
return v.strip()
# 7. @root_validator -> @model_validator
# v1:
from pydantic import root_validator
class Model(BaseModel):
@root_validator
def check_fields(cls, values):
return values
# v2:
from pydantic import model_validator
class Model(BaseModel):
@model_validator(mode="after")
def check_fields(self) -> "Model":
return self
Sonuçlar
Pydantic v2, Python veri doğrulamasını önemli ölçüde daha hızlı hale getirdi
ve daha etkileyici. Rust çekirdeği aynı zamanda mükemmel performansı da garanti eder.
yoğun doğrulama, TypeAdapter kullanım durumunu çözer
Özel modeller oluşturmadan türleri doğrulayın. FastAPI'de her uç nokta fayda sağlar
bu optimizasyonların otomatik olarak yapılması.
FastAPI Serisinde Gelecek Makaleler
- Madde 4: FastAPI'de Bağımlılık Enjeksiyonu: Temiz ve Test Edilebilir Kod için Depends()
- Madde 5: SQLAlchemy 2.0, AsyncSession ve Alembic ile Async Veritabanı
- Madde 6: Arka Plan Görevleri: Arka Plan Görevlerinden Kereviz ve ARQ'ya







