Pydantic valida i serialitza de manera automàtica les dades JSON que consumeixes o produeixes.
Introducció
Pydantic és una biblioteca de validació de dades que utilitza Python - Typing
Instal·la la biblioteca pydantic
:
uv add pydantic
Models
Un model és una classe que hereta de BaseModel
i anota amb tipus els atributs de la classe.
Són molt semblants a un @dataclass
, excepte que estan pensants per:
- La validació i serialització de dades JSON
- La generació d’esquemes JSON.
Per serialitzar dades, Pydantic utilitzar una llibreria escrita en Rust: jiter
A continuació tens un exemple d’una classe User
que hereta de BaseModel
i defineix camps com a atributs anotats:
from pydantic import BaseModel
class User(BaseModel): id: int name: str | None = None
Llavors el model es pot instanciar:
user: User = User(id=1, name="David")
La inicialització de l’objecte fa tota l’anàlisi i validació.
Si no s’aixeca cap excepció ValidationError
, saps que la instància del model resultant és vàlida:
assert user.id == 1assert user.name == "David"
Però si escrius aquest codi, Python - PyCharm et dirà que és erroni:
from pydantic import BaseModel
class User(BaseModel): id: int name: str | None = None
david: User = User(name="David")
I pydantic genera un error en temps d’execució
> python test.py...pydantic_core._pydantic_core.ValidationError: 1 validation error for Userid Field required [type=missing, input_value={'name': 'David'}, input_type=dict]
S’aixecarà una única excepció independentment del nombre d’errors trobats, i aquell error de validació contindrà informació sobre tots els errors i com es van produir.
Per defecte, els models són mutables i els valors dels camps es poden canviar mitjançant l’assignació d’atributs:
user.id = 321assert user.id == 321
Validant dades
Pydantic utilitza un dict
per guardar les dades: podem passar directament un “punter” a un dict
per crear un User
.
Si crees objectes a partir de dades de sistemes externs, no hi ha cap garantia que siguin correctes:
from pydantic import BaseModelfrom typing import Any
class User(BaseModel): id: int name: str | None = None
data: Any = {"id": 1, "name": "David"}User(**data)
data = {"name": "apple", "price": 3}User(**data) # Error de validació
Pydantic proporciona tres mètodes a les classes de model per analitzar dades:
model_validate()
Això és molt similar al mètode __init__
del model, excepte que pren un diccionari o un objecte en lloc d’arguments amb paraula clau.
Si l’objecte passat no es pot validar, o si no és un diccionari o una instància del model en qüestió, s’aixecarà un ValidationError
.
from datetime import datetimefrom pydantic import BaseModel, ValidationError
class User(BaseModel): id: int name: str = 'John Doe' signup_ts: datetime | None = None
user = User.model_validate({'id': 123, 'name': 'James'})print(user)
try: User.model_validate(['not', 'a', 'dict'])except ValidationError as e: print(e)
1 validation error for User Input should be a valid dictionary or instance of User [type=model_type, input_value=['not', 'a', 'dict'], input_type=list] For further information visit https://errors.pydantic.dev/2.11/v/model_type
model_validate_json()
Això valida les dades proporcionades com un string JSON o objecte bytes
. Si les teves dades entrants són una càrrega útil JSON, generalment es considera més ràpid (en lloc d’analitzar manualment les dades com un diccionari).
user = User.model_validate_json('{"id": 123, "name": "James"}')print(user)
id=123 name='James' signup_ts=None
try: user = User.model_validate_json('{"id": 123, "name": 123}')except ValidationError as e: print(e)
1 validation error for Username Input should be a valid string [type=string_type, input_value=123, input_type=int] For further information visit https://errors.pydantic.dev/2.11/v/string_type
try: user = User.model_validate_json('invalid JSON')except ValidationError as e: print(e)
1 validation error for User Invalid JSON: expected value at line 1 column 1 [type=json_invalid, input_value='invalid JSON', input_type=str] For further information visit https://errors.pydantic.dev/2.11/v/json_invalid
model_validate_strings()
Ppren un diccionari (pot estar niuat) amb claus i valors de tipus string i valida les dades en mode JSON perquè aquestes strings puguin ser convertides als tipus correctes.
user = User.model_validate_strings({'id': '123', 'name': 'James'})print(user)#> id=123 name='James' signup_ts=None
user = User.model_validate_strings( {'id': '123', 'name': 'James', 'signup_ts': '2024-04-01T12:00:00'})print(user)#> id=123 name='James' signup_ts=datetime.datetime(2024, 4, 1, 12, 0)
try: user = User.model_validate_strings( {'id': '123', 'name': 'James', 'signup_ts': '2024-04-01'}, strict=True )except ValidationError as e: print(e) """ 1 validation error for User signup_ts Input should be a valid datetime, invalid datetime separator, expected `T`, `t`, `_` or space [type=datetime_parsing, input_value='2024-04-01', input_type=str] """
Serialització
La instància del model es pot serialitzar utilitzant el mètode model_dump
:
assert user.model_dump() == {'id': 1, 'name': 'David'}
El mètode .model_dump_json()
serialitza un model directament a un string codificat en JSON que és equivalent al resultat produït per .model_dump()
.
from datetime import datetime
from pydantic import BaseModel
class BarModel(BaseModel): whatever: int
class FooBarModel(BaseModel): foo: datetime bar: BarModel
m = FooBarModel(foo=datetime(2032, 6, 1, 12, 13, 14), bar={'whatever': 123})print(m.model_dump_json())#> {"foo":"2032-06-01T12:13:14","bar":{"whatever":123}}print(m.model_dump_json(indent=2))"""{ "foo": "2032-06-01T12:13:14", "bar": { "whatever": 123 }}"""
Models niuats
Un model pot utilitzar altres models.
Si tens aquest diagrama:
Pots escriure aquest codi:
from pydantic import BaseModel
class Client(BaseModel): id: int name: str
class Order(BaseModel): id: int client: Client
data = {"id": 1, "client": {"id": 45, "name": "David"}}order: Order = Order.model_validate(data)
assert order.client.id == 45
Activitat
Genera les classes corresponents a aquest diagrama:
TODO
Crea un objecte Order
a partir d’un dict
:
TODO
Field
La funció Field
s’utilitza per personalitzar i afegir metadades als camps dels models.
Restriccions numèriques
Hi ha alguns arguments amb paraula clau que es poden utilitzar per restringir valors numèrics:
gt
- major quelt
- menor quege
- major o igual quele
- menor o igual quemultiple_of
- un múltiple del nombre donatallow_inf_nan
- permet valors'inf'
,'-inf'
,'nan'
Aquí tens un exemple:
from pydantic import BaseModel, Field
class Foo(BaseModel): positive: int = Field(gt=0) non_negative: int = Field(ge=0) negative: int = Field(lt=0) non_positive: int = Field(le=0) even: int = Field(multiple_of=2) love_for_pydantic: float = Field(allow_inf_nan=True)
foo = Foo( positive=1, non_negative=0, negative=-1, non_positive=0, even=2, love_for_pydantic=float('inf'),)print(foo)
positive=1 non_negative=0 negative=-1 non_positive=0 even=2 love_for_pydantic=inf
Restriccions de strings
Hi ha camps que es poden utilitzar per restringir strings:
min_length
: Longitud mínima de la cadena.max_length
: Longitud màxima de la cadena.pattern
: Una expressió regular que la cadena ha de complir.
Aquí tens un exemple:
from pydantic import BaseModel, Field
class Foo(BaseModel): short: str = Field(min_length=3) long: str = Field(max_length=10) regex: str = Field(pattern=r'^\d*$')
foo = Foo(short='foo', long='foobarbaz', regex='123')print(foo)
short='foo' long='foobarbaz' regex='123'
Immutabilitat
El paràmetre frozen
s’utilitza per emular el comportament de dataclass congelat. S’utilitza per evitar que el camp rebi un valor nou després que el model es crea (immutabilitat).
from pydantic import BaseModel, Field, ValidationError
class User(BaseModel): name: str = Field(frozen=True) age: int
user = User(name='John', age=42)
try: user.name = 'Jane'
except ValidationError as e: print(e) """ 1 validation error for User name Field is frozen [type=frozen_field, input_value='Jane', input_type=str] """
Més informació a Concepts - Fields
JSON
Parsing
Amb pydantic pots consumir dades JSON.
En aquest exemple, demanes que la validació sigui estricta:
Pydantic proporciona anàlisi JSON integrat, que ajuda a aconseguir:
from datetime import datefrom typing import Tuplefrom pydantic import BaseModel, ConfigDict, ValidationError
class Event(BaseModel): model_config = ConfigDict(strict=True)
when: date where: Tuple[int, int]
data: str = '{"when": "1987-01-28", "where": [51, -1]}'event: Event = Event.model_validate_json(data)assert event.where[0] == 51