Cheatsheets — vue longue
retour à la listeToutes les pages concaténées sur un seul document, pour un Ctrl-F direct.
Pydantic v2
page dédiée →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
Python requests
page dédiée →Client HTTP synchrone. Pas de timeout par défaut — toujours le passer explicitement.
Le squelette à écrire de mémoire
import os
import requests
BASE = "https://api.example.com/v1"
r = requests.post(
f"{BASE}/environments",
headers={
"Authorization": f"Bearer {os.environ['API_KEY']}",
"Content-Type": "application/json",
},
json={"name": "demo", "url": "https://example.com"},
timeout=30,
)
r.raise_for_status()
print(r.status_code, r.json())
json= sérialise le dict et pose Content-Type: application/json tout seul. data= envoie
du form-urlencoded ou des bytes bruts — c'est la confusion la plus fréquente.
Réponse
| Attribut | Contenu |
|---|---|
r.status_code |
entier, 200, 404… |
r.ok |
True si status_code < 400 |
r.json() |
corps parsé, lève JSONDecodeError si ce n'est pas du JSON |
r.text |
corps brut décodé |
r.headers |
dict insensible à la casse |
r.raise_for_status() |
lève HTTPError sur 4xx/5xx, ne fait rien sinon |
Session : réutiliser la connexion et les headers
with requests.Session() as s:
s.headers.update({"Authorization": f"Bearer {os.environ['API_KEY']}"})
for page in range(1, 6):
r = s.get(f"{BASE}/items", params={"page": page}, timeout=30)
r.raise_for_status()
print(r.json()["items"])
params= construit la query string, pas besoin de la concaténer à la main.
Polling d'un job asynchrone
import time
def wait_for(session_id: str, timeout_s: int = 300, every_s: int = 3) -> dict:
deadline = time.time() + timeout_s
while time.time() < deadline:
r = requests.get(f"{BASE}/sessions/{session_id}", timeout=30)
r.raise_for_status()
payload = r.json()
if payload["status"] in {"completed", "failed"}:
return payload
time.sleep(every_s)
raise TimeoutError(f"session {session_id} toujours en cours après {timeout_s}s")
Erreurs à distinguer
try:
r = requests.get(url, timeout=10)
r.raise_for_status()
except requests.Timeout: # dépassement du timeout
...
except requests.ConnectionError: # DNS, refus de connexion, réseau
...
except requests.HTTPError as exc: # 4xx / 5xx après raise_for_status
print(exc.response.status_code, exc.response.text[:200])
Toutes héritent de requests.RequestException.
Charger la clé depuis un .env
from dotenv import load_dotenv # pip install python-dotenv
load_dotenv()
key = os.environ["API_KEY"] # KeyError explicite si absente
os.environ["X"] lève si la variable manque, os.getenv("X") renvoie None en silence.
Préférer le premier pour une clé obligatoire.
Debug rapide
print(r.request.method, r.request.url)
print(r.request.headers)
print(r.request.body)
Équivalences avec curl
| curl | requests |
|---|---|
-X POST |
requests.post(...) |
-H "K: V" |
headers={"K": "V"} |
-d '{"a":1}' |
json={"a": 1} |
-G --data-urlencode "q=x" |
params={"q": "x"} |
-u user:pass |
auth=("user", "pass") |
--max-time 30 |
timeout=30 |
See also
- httpx — même API, plus l'async et un timeout par défaut