~/wiki

Pydantic v2

Mis à jour le 2026-08-07Confiance : high
pydanticpythonvalidationschemajsonfastapistructured-output

Validation de données par annotations de type. La v2 a un cœur en Rust et une API qui diffère de la v1 sur plusieurs noms.

Modèle de base

from pydantic import BaseModel, Field

class Item(BaseModel):
    id: int
    title: str
    price: float = Field(gt=0, description="Prix TTC en euros")
    tags: list[str] = []

item = Item(id="42", title="Chaise", price=19.9)   # "42" est converti en int
print(item)
print(item.model_dump())
id=42 title='Chaise' price=19.9 tags=[]
{'id': 42, 'title': 'Chaise', 'price': 19.9, 'tags': []}

La coercition est volontaire : "42" devient 42. Pour l'interdire, model_config = ConfigDict(strict=True).

Les méthodes v2, et leurs anciens noms

v2 v1
model_dump() .dict()
model_dump_json() .json()
model_validate(obj) parse_obj()
model_validate_json(s) parse_raw()
model_json_schema() schema()
model_copy(update=...) .copy()

Erreurs de validation

from pydantic import ValidationError

try:
    Item(id="abc", title="Chaise", price=-1)
except ValidationError as exc:
    print(exc)
2 validation errors for Item
id
  Input should be a valid integer, unable to parse string as an integer
    [type=int_parsing, input_value='abc', input_type=str]
price
  Input should be greater than 0
    [type=greater_than, input_value=-1, input_type=int]

exc.errors() renvoie la même chose en liste de dicts — c'est la forme à renvoyer à un modèle quand on valide ses tool calls.

Contraintes utiles

from typing import Literal, Annotated
from pydantic import BaseModel, Field, HttpUrl, EmailStr

class Agent(BaseModel):
    name: Annotated[str, Field(min_length=1, max_length=64)]
    url: HttpUrl
    email: EmailStr | None = None            # pip install pydantic[email]
    mode: Literal["fast", "default"] = "default"
    budget: int = Field(default=60, ge=1, le=500)

Field(gt, ge, lt, le) pour les nombres, min_length / max_length pour chaînes et listes, pattern= pour une regex.

Valeurs par défaut mutables

from pydantic import BaseModel

class Bad(BaseModel):
    tags: list[str] = []          # sûr ici : pydantic copie la valeur par défaut

Contrairement aux dataclasses et aux fonctions Python, un défaut mutable ne fuit pas entre instances. Pour un défaut calculé : Field(default_factory=list).

Validateurs

from pydantic import BaseModel, field_validator, model_validator

class Range(BaseModel):
    low: int
    high: int

    @field_validator("low", "high")
    @classmethod
    def positive(cls, v: int) -> int:
        if v < 0:
            raise ValueError("doit être positif")
        return v

    @model_validator(mode="after")
    def ordered(self):
        if self.low > self.high:
            raise ValueError("low doit être <= high")
        return self

mode="before" s'exécute sur la valeur brute avant conversion, mode="after" sur l'objet déjà validé.

Modèles imbriqués et parsing JSON

class Result(BaseModel):
    findable: bool
    clicks: int
    notes: str | None = None

class Report(BaseModel):
    site: str
    results: list[Result]

payload = '{"site":"versace.com","results":[{"findable":true,"clicks":3}]}'
report = Report.model_validate_json(payload)
print(report.results[0].clicks)
3

C'est le pattern des sorties structurées de LLM : on impose un schéma, on parse, et une réponse hors format lève au lieu de contaminer la suite.

Schéma JSON pour un tool call

print(Result.model_json_schema())
{'properties': {'findable': {'title': 'Findable', 'type': 'boolean'},
                'clicks': {'title': 'Clicks', 'type': 'integer'},
                'notes': {'anyOf': [{'type': 'string'}, {'type': 'null'}],
                          'default': None, 'title': 'Notes'}},
 'required': ['findable', 'clicks'],
 'title': 'Result',
 'type': 'object'}

C'est exactement ce qu'attend un tools=[...] d'API LLM.

Configuration et settings

from pydantic import ConfigDict
from pydantic_settings import BaseSettings   # pip install pydantic-settings

class Item(BaseModel):
    model_config = ConfigDict(extra="forbid", frozen=True, strict=False)

class Settings(BaseSettings):
    api_key: str                              # lit API_KEY dans l'environnement
    base_url: str = "https://api.example.com"

settings = Settings()                         # lève si API_KEY manque

extra="forbid" refuse les champs inconnus — précieux pour attraper une faute de frappe dans une config plutôt que de l'ignorer en silence.

See also