Python Pydantic v2: tipo patvirtinimas

Kas yra Pydantic ir kodėl verta domėtis antrąja versija

Jei dirbate su Python ir bent kartą susidūrėte su duomenų validavimu, greičiausiai girdėjote apie Pydantic. Ši biblioteka tapo neatsiejama FastAPI dalimi ir daugelio kitų projektų pamatu. Tačiau Pydantic v2 – tai ne tiesiog paprastas atnaujinimas. Tai beveik visiškai perrašyta biblioteka, kuri atneša ne tik greitesnį veikimą, bet ir daug naujų galimybių tipo validavimui.

Pirmoji Pydantic versija buvo parašyta grynuoju Python kodu. Antroji versija naudoja Rust pagrindą per pydantic-core biblioteką, o tai reiškia iki 50 kartų greitesnį veikimą tam tikrose operacijose. Bet greitis – ne vienintelis privalumas. Tipo validavimo sistema tapo daug lankstesnė ir galingesnė.

Kai kurie programuotojai vis dar naudoja pirmąją versiją, nes migracija gali atrodyti bauginanti. Tačiau jei kuriate naują projektą arba planuojate atnaujinti esamą kodą, Pydantic v2 tikrai verta dėmesio. Tipo validavimas čia veikia intuityviau ir suteikia daugiau kontrolės.

Pagrindiniai tipo validavimo principai v2 versijoje

Pydantic v2 tipo validavimas remiasi Python tipo anotacijomis, bet eina daug toliau nei standartinė typing biblioteka. Štai paprastas pavyzdys:

„`python
from pydantic import BaseModel, ValidationError

class User(BaseModel):
id: int
username: str
email: str
age: int | None = None

user = User(id=”123″, username=”jonas”, email=”[email protected]”)
print(user.id, type(user.id)) # 123 <class ‘int’>
„`

Atkreipkite dėmesį – perduodame id kaip string’ą, bet Pydantic automatiškai konvertuoja jį į integer’į. Tai vadinama „coercion” arba tipo priverstine konversija. V2 versijoje šis mechanizmas tapo daug protingesnis ir nuspėjamesnis.

Anksčiau Pydantic kartais darydavo netikėtas konversijas, kurios galėjo sukelti klaidų. Dabar validavimo režimai yra aiškiai atskirti. Galite pasirinkti tarp „lax” (atlaidaus) ir „strict” (griežto) režimų. Lax režimas leidžia konversijas (kaip pavyzdyje aukščiau), o strict režimas reikalauja tikslaus tipo atitikimo:

„`python
from pydantic import BaseModel, Field

class StrictUser(BaseModel):
id: int = Field(strict=True)
username: str

try:
user = StrictUser(id=”123″, username=”jonas”)
except ValidationError as e:
print(e) # Klaida: id turi būti integer, ne string
„`

Sudėtingesni tipai ir jų validavimas

Realūs projektai retai apsiriboja paprastais integer ir string tipais. Pydantic v2 puikiai dirba su sudėtingomis duomenų struktūromis. Pavyzdžiui, galite validuoti sąrašus, žodynus, įdėtus objektus:

„`python
from pydantic import BaseModel, EmailStr, HttpUrl
from typing import List, Dict

class SocialMedia(BaseModel):
platform: str
url: HttpUrl

class UserProfile(BaseModel):
username: str
email: EmailStr
tags: List[str]
metadata: Dict[str, int]
social_media: List[SocialMedia]

profile = UserProfile(
username=”jonas”,
email=”[email protected]”,
tags=[„python”, „developer”],
metadata={„followers”: 100, „posts”: 50},
social_media=[
{„platform”: „github”, „url”: „https://github.com/jonas”}
]
)
„`

Pastebėjote EmailStr ir HttpUrl? Tai specialūs Pydantic tipai, kurie atlieka papildomą validavimą. EmailStr patikrina, ar eilutė yra teisingas el. pašto adresas, o HttpUrl validuoja URL struktūrą. V2 versijoje šie validatoriai tapo greitesni ir tikslesni.

Dar vienas galingas įrankis – Annotated tipas, kuris leidžia pridėti papildomų apribojimų:

„`python
from typing import Annotated
from pydantic import BaseModel, Field, StringConstraints

class Product(BaseModel):
name: Annotated[str, StringConstraints(min_length=3, max_length=50)]
price: Annotated[float, Field(gt=0, le=1000000)]
description: Annotated[str, StringConstraints(strip_whitespace=True)]

product = Product(
name=”Laptop”,
price=999.99,
description=” Puikus nešiojamas kompiuteris ”
)
print(product.description) # „Puikus nešiojamas kompiuteris” (be tarpų)
„`

Custom validatoriai ir transformacijos

Kartais standartinio validavimo nepakanka. Pydantic v2 siūlo kelis būdus, kaip sukurti savo validavimo logiką. Populiariausias būdas – naudoti field_validator dekoratorių:

„`python
from pydantic import BaseModel, field_validator

class Password(BaseModel):
password: str

@field_validator(‘password’)
@classmethod
def validate_password(cls, v: str) -> str:
if len(v) < 8: raise ValueError(‘Slaptažodis turi būti bent 8 simbolių’) if not any(char.isdigit() for char in v): raise ValueError(‘Slaptažodis turi turėti bent vieną skaičių’) if not any(char.isupper() for char in v): raise ValueError(‘Slaptažodis turi turėti bent vieną didžiąją raidę’) return v „` Svarbu pažymėti, kad v2 versijoje validatoriai turi būti @classmethod. Tai skiriasi nuo pirmosios versijos ir yra viena iš pagrindinių migracijos problemų. Bet šis pakeitimas turi prasmę – jis daro kodą aiškesnį ir leidžia geriau valdyti validavimo kontekstą.

Jei norite transformuoti duomenis prieš validavimą, naudokite model_validator:

„`python
from pydantic import BaseModel, model_validator

class Coordinates(BaseModel):
latitude: float
longitude: float

@model_validator(mode=’before’)
@classmethod
def parse_coordinates(cls, data):
if isinstance(data, str):
# Jei gauta string’as formato „lat,lng”
parts = data.split(‘,’)
return {‘latitude’: float(parts[0]), ‘longitude’: float(parts[1])}
return data

coords = Coordinates(„54.6872,25.2797”)
print(coords.latitude, coords.longitude) # 54.6872 25.2797
„`

Serialization ir deserialization niuansai

Pydantic v2 aiškiai atskiria validavimą (deserialization) nuo serializacijos. Tai svarbu, kai dirbate su API ar duomenų bazėmis. Modelio metodas model_dump() (anksčiau dict()) leidžia kontroliuoti, kaip duomenys konvertuojami atgal į žodynus:

„`python
from pydantic import BaseModel, Field, ConfigDict
from datetime import datetime

class Article(BaseModel):
model_config = ConfigDict(ser_json_timedelta=’float’)

title: str
created_at: datetime
view_count: int = Field(serialization_alias=’views’)
internal_id: int = Field(exclude=True)

article = Article(
title=”Pydantic v2 gidas”,
created_at=datetime.now(),
view_count=150,
internal_id=12345
)

print(article.model_dump())
# {‘title’: ‘Pydantic v2 gidas’, ‘created_at’: datetime(…), ‘views’: 150}
# Pastebėkite: internal_id neįtrauktas, view_count pavadintas ‘views’
„`

Serialization alias’ai ypač naudingi, kai dirbate su išorinėmis API, kurios naudoja skirtingas pavadinimų konvencijas. Galite turėti Python stiliaus snake_case kintamuosius viduje, bet eksportuoti juos kaip camelCase JSON’e.

Dar viena nauja galimybė – serialization konteksto naudojimas. Tai leidžia keisti serializacijos elgesį dinamiškai:

„`python
from pydantic import BaseModel, field_serializer

class User(BaseModel):
username: str
email: str
password_hash: str

@field_serializer(‘password_hash’)
def hide_password(self, value, _info):
if _info.context and _info.context.get(‘include_sensitive’):
return value
return ‘***HIDDEN***’

user = User(username=”jonas”, email=”[email protected]”, password_hash=”abc123″)

print(user.model_dump()) # password_hash: ‘***HIDDEN***’
print(user.model_dump(context={‘include_sensitive’: True})) # password_hash: ‘abc123’
„`

Performance optimizacijos ir best practices

Nors Pydantic v2 yra greitesnis už pirmąją versiją, vis tiek galite optimizuoti savo kodą. Vienas didžiausių performance killer’ių – nereikalingas validavimas toje pačioje duomenų grandinėje.

Jei jau turite validuotą Pydantic objektą ir norite jį perduoti kitam modeliui, naudokite model_validate() su from_attributes=True:

„`python
from pydantic import BaseModel, ConfigDict

class UserInput(BaseModel):
username: str
email: str

class UserDB(BaseModel):
model_config = ConfigDict(from_attributes=True)

id: int
username: str
email: str

input_data = UserInput(username=”jonas”, email=”[email protected]”)
# Vėliau, kai gauname ID iš DB
db_user = UserDB(id=1, **input_data.model_dump())
„`

Kitas svarbus aspektas – model_config naudojimas. V2 versijoje konfigūracija perkelta iš vidinės Config klasės į ConfigDict:

„`python
from pydantic import BaseModel, ConfigDict

class OptimizedModel(BaseModel):
model_config = ConfigDict(
validate_assignment=True, # Validuoti ir priskyrus reikšmę
use_enum_values=True, # Naudoti enum reikšmes, ne objektus
str_strip_whitespace=True, # Automatiškai šalinti tarpus
frozen=False, # Leisti modifikuoti objektą
)

name: str
status: str
„`

Jei dirbate su dideliais duomenų kiekiais, apsvarstykite TypeAdapter naudojimą. Tai leidžia validuoti duomenis be pilno modelio kūrimo:

„`python
from pydantic import TypeAdapter
from typing import List

adapter = TypeAdapter(List[int])

# Greitai validuoja sąrašą
validated = adapter.validate_python([„1”, „2”, „3”, „4”])
print(validated) # [1, 2, 3, 4]
„`

Darbas su JSON Schema ir dokumentacija

Viena iš geriausių Pydantic savybių – automatinis JSON Schema generavimas. V2 versijoje tai tapo dar galingsiau. Schema gali būti naudojama dokumentacijai, frontend validavimui ar API kontraktams:

„`python
from pydantic import BaseModel, Field
from typing import List

class Task(BaseModel):
„””Užduoties modelis projekto valdymui”””
title: str = Field(…, description=”Užduoties pavadinimas”, min_length=1)
description: str | None = Field(None, description=”Išsamus aprašymas”)
priority: int = Field(1, ge=1, le=5, description=”Prioritetas nuo 1 iki 5″)
tags: List[str] = Field(default_factory=list)

print(Task.model_json_schema())
„`

Tai sugeneruos pilną JSON Schema su visais aprašymais, apribojimais ir pavyzdžiais. FastAPI automatiškai naudoja šią schemą OpenAPI dokumentacijai, bet galite ją naudoti ir kitur.

Jei norite dar labiau pritaikyti schemą, galite naudoti json_schema_extra:

„`python
from pydantic import BaseModel, ConfigDict

class APIResponse(BaseModel):
model_config = ConfigDict(
json_schema_extra={
„examples”: [
{
„status”: „success”,
„data”: {„id”: 1, „name”: „Example”}
}
]
}
)

status: str
data: dict
„`

Kaip tai viskas pritaikyti realiame projekte

Teorija – viena, o praktika – kita. Pažiūrėkime, kaip Pydantic v2 tipo validavimas gali atrodyti realiame FastAPI projekte:

„`python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr, Field, field_validator
from typing import List, Optional
from datetime import datetime

app = FastAPI()

class CreateUserRequest(BaseModel):
username: str = Field(…, min_length=3, max_length=20)
email: EmailStr
password: str = Field(…, min_length=8)
age: Optional[int] = Field(None, ge=13, le=120)

@field_validator(‘username’)
@classmethod
def username_alphanumeric(cls, v: str) -> str:
if not v.isalnum():
raise ValueError(‘Vartotojo vardas gali turėti tik raides ir skaičius’)
return v.lower()

class UserResponse(BaseModel):
id: int
username: str
email: EmailStr
created_at: datetime

model_config = ConfigDict(from_attributes=True)

@app.post(„/users”, response_model=UserResponse)
async def create_user(user: CreateUserRequest):
# Čia būtų DB logika
# Pydantic jau validavo visus duomenis!
new_user = {
„id”: 1,
„username”: user.username,
„email”: user.email,
„created_at”: datetime.now()
}
return UserResponse(**new_user)
„`

Šiame pavyzdyje Pydantic automatiškai:
– Validuoja visus įeinančius duomenis
– Konvertuoja tipus (jei reikia)
– Generuoja aiškias klaidas, jei validavimas nepavyksta
– Dokumentuoja API per OpenAPI schemą
– Užtikrina, kad response atitinka nurodytą modelį

Dar vienas praktinis patarimas – naudokite paveldimumo hierarchiją bendriems laukams:

„`python
from pydantic import BaseModel, Field
from datetime import datetime

class TimestampedModel(BaseModel):
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)

class Article(TimestampedModel):
title: str
content: str
author_id: int

class Comment(TimestampedModel):
article_id: int
text: str
user_id: int
„`

Taip išvengsite kodo dubliavimo ir užtikrinsite nuoseklumą visame projekte. Pydantic v2 puikiai palaiko paveldimumą ir leidžia perrašyti validatorius bei konfigūraciją.

Tipo validavimas su Pydantic v2 – tai ne tik saugumo priemonė, bet ir būdas padaryti kodą aiškesnį, geriau dokumentuotą ir lengviau prižiūrimą. Investicija į teisingą validavimo logiką atsipirks sutaupytu laiku debuginant ir mažiau klaidų production’e. Antroji versija suteikia visus įrankius, kad tai pasiektumėte be didesnių pastangų – tereikia išmokti juos teisingai naudoti.

Daugiau

Aiven: debesų duomenų bazės