13 KiB
Executable File
Pydantic ist ein zentrales Werkzeug im heutigen Python-Ökosystem, vor allem im Umfeld von APIs (z.B. FastAPI), Konfiguration und Datenvalidierung.
Im Folgenden bekommst du eine systematische Einführung mit praxisnahen Beispielen.
Hinweis: Die Beispiele orientieren sich an Pydantic v2 (aktuelle Hauptversion). In v1 ist die Syntax ähnlich, aber es gibt einige Unterschiede (z.B. @validator vs. @field_validator).
1. Grundsätzliche Definition: Was ist Pydantic?
Kurz:
Pydantic ist eine Bibliothek für Datenmodelle mit Validierung und Parsing auf Basis von Python-Typannotationen.
Du beschreibst deine Datenstruktur wie bei einer Klasse mit Typen:
- Pydantic prüft zur Laufzeit, ob eingehende Daten diese Struktur erfüllen.
- Es konvertiert (parst) Werte soweit wie möglich in die gewünschten Typen.
- Es gibt strukturierte Fehlermeldungen aus, wenn etwas nicht passt.
- Es kann aus deinen Modellen u.a. JSON-Schemas generieren.
Beispiel – ein einfaches Datenmodell:
from pydantic import BaseModel, ValidationError
from typing import List
class User(BaseModel):
id: int
name: str
tags: List[str] = []
# Daten aus einer externen Quelle (z.B. JSON)
payload = {
"id": "123", # wird zu int konvertiert
"name": "Alice",
"tags": ["admin", "beta"]
}
user = User(**payload)
print(user)
print(user.id, type(user.id))
# Fehlvalidierung
try:
User(id="abc", name=123)
except ValidationError as e:
print(e.errors())
Wichtige Punkte:
idist alsintdeklariert, ein String"123"wird automatisch konvertiert.- Wenn Konvertierung scheitert (z.B.
"abc"→int), erzeugt Pydantic eine ValidationError mit detailierten Fehlerinfos.
2. Grundkonzepte von Pydantic (v2)
2.1 BaseModel und Felder
Alle Modelle erben typically von BaseModel:
from pydantic import BaseModel, Field
from typing import Optional
class Product(BaseModel):
id: int
name: str = Field(..., min_length=3, description="Produktname")
price: float = Field(ge=0)
description: Optional[str] = None
Field(...)bedeutet „Pflichtfeld“ mit zusätzlichen Metadaten/Constraints.ge=0= „greater or equal 0“.Optional[str] = None= optionales Feld, defaultNone.
2.2 Validierung & Parsing
Pydantic führt Validierung und Parsing beim Erstellen des Modells durch.
Man kann explizit „parsen“:
from pydantic import TypeAdapter
from typing import List
# Einzelnes Modell: meistens direkt Model(**data)
product = Product(id="1", name="TV", price="999.90")
# Sammlung von Modellen validieren:
ta = TypeAdapter(List[Product])
data = [
{"id": 1, "name": "TV", "price": 999.90},
{"id": "2", "name": "Laptop", "price": "1299.50"},
]
products = ta.validate_python(data)
print(products)
TypeAdapter in v2 ersetzt viele frühere parse_obj_as-Usecases.
2.3 Serialisierung
Pydantic-Modelle lassen sich leicht in z.B. JSON-kompatible Strukturen umwandeln:
product = Product(id=1, name="TV", price=999.90)
print(product.model_dump()) # dict
print(product.model_dump_json()) # JSON-String
Man kann steuern:
- welche Felder inkludiert/exkludiert werden,
- wie verschachtelte Modelle serialisiert werden,
- ob Alias-Namen verwendet werden sollen etc.
3. Abgrenzung zu verwandten Konzepten / Bibliotheken
3.1 Pydantic vs. dataclasses
Python dataclasses:
from dataclasses import dataclass
@dataclass
class UserDC:
id: int
name: str
dataclassesstellen nur strukturelle Container bereit.- Keine automatische Validierung oder Typkonvertierung.
- Typannotationen sind rein informativ (für IDE, mypy), nicht enforced.
Pydantic:
class UserModel(BaseModel):
id: int
name: str
- Führt Validierung & Parsing beim Erstellen durch.
- Gibt strukturierte Fehler aus.
- Generiert optional JSON-Schemas.
- Basiert auch auf Typannotationen, aber wertet sie zur Laufzeit aus.
Kurz:
dataclasses: leichtgewichtige Container.- Pydantic: Container + Validierung + Parsing + Schema.
3.2 Pydantic vs. Marshmallow / Cerberus u.ä.
-
Marshmallow ist ebenfalls eine Validierungs-/Serialisierungsbibliothek.
- Du definierst Schemas explizit über Felder (z.B.
fields.Int()) statt über Typannotationen. - Skill: starke Serialisierung/Deserialisierung, aber andere API.
- Du definierst Schemas explizit über Felder (z.B.
-
Pydantic:
- Nutzt standardmäßige Python-Typannotationen (nativer für moderne Python-Code).
- Sehr eng mit Typing-Ökosystem (mypy, IDEs).
- Performance-fokussiert, in v2 mit
pydantic-corein Rust.
3.3 Pydantic vs. Typing-Features (TypedDict, Protocol, …)
TypedDictdefiniert nur statische Typinformationen für Dictionaries.- Pydantic-Modelle sind richtige Klassen mit Methoden, Validierung und Verhalten.
3.4 Pydantic vs. ORMs (z.B. Django Models, SQLAlchemy Models)
- ORM-Modelle repräsentieren Datenbanktabellen und kümmern sich um Persistenz (CRUD, Queries).
- Pydantic-Modelle sind reine Daten- und Validierungsmodelle, ohne DB-Anbindung.
In der Praxis:
- Du kannst Pydantic-Modelle nutzen, um Requests/Responses zu validieren und zu dokumentieren.
- ORMs nutzen, um die Daten in der Datenbank zu speichern.
FastAPI macht genau das:
- Pydantic-Modelle für Request/Response,
- SQLAlchemy/SQLModel/etc. für DB.
4. Welche Probleme löst Pydantic?
4.1 Validierung externer Daten (APIs, Formulare, Message Queues)
Externe Daten sind oft:
- unvollständig,
- im falschen Typ,
- fehlerhaft strukturiert.
Pydantic sorgt für:
- Zentral definierte Datenstruktur.
- Automatische Validierung bei jedem Eingang.
- Konvertierung (z.B.
"123"→int,"2024-01-01"→datetime).
Beispiel: Request-Daten einer (pseudo) API:
from pydantic import BaseModel, HttpUrl
from typing import List
class Article(BaseModel):
title: str
url: HttpUrl
tags: List[str] = []
payload = {
"title": "Pydantic Einführung",
"url": "https://example.com/pydantic",
"tags": ["python", "validation"]
}
article = Article(**payload)
print(article)
Wenn url kein gültiger URL-String ist, kommt eine strukturierte Fehlermeldung.
4.2 Konfiguration und Umgebungsvariablen
Pydantic kann Konfiguration aus:
- Umgebungsvariablen,
.env-Dateien,- kwargs,
- etc. laden und validieren.
In v2 nutzt man pydantic-settings:
from pydantic_settings import BaseSettings
class AppSettings(BaseSettings):
debug: bool = False
database_url: str
port: int = 8000
model_config = {
"env_file": ".env",
"env_prefix": "APP_",
}
settings = AppSettings()
print(settings.database_url, settings.debug)
APP_DATABASE_URLin der Umgebung oder.envwird gelesen.- Falsche Typen werden validiert/konvertiert (z.B.
"true"→bool). - Fehlende Pflichtwerte (z.B.
database_url) führen zu Fehlern.
4.3 Saubere Domain-Modelle und Business-Logik
Du kannst Pydantic-Modelle verwenden, um deine Domain-Objekte zu modellieren, inklusive:
- Validierung von Invarianten (z.B. Preis > 0, Datum in der Zukunft/ Vergangenheit),
- Standardwerte,
- abgeleitete Felder.
from pydantic import BaseModel, field_validator
from datetime import datetime
class Event(BaseModel):
name: str
start: datetime
end: datetime
@field_validator("end")
def end_must_be_after_start(cls, v, info):
start = info.data.get("start")
if start and v <= start:
raise ValueError("end must be after start")
return v
5. Herausforderungen & Stolpersteine
5.1 Performance und Overhead
- Pydantic führt bei jedem Instanziieren eines Modells Validierung/Parsing durch.
- Bei sehr großen Datenmengen oder sehr häufigen Instanziierungen kann das Performance kosten.
- Lösung: gezielt einsetzen, ggf.
model_validatemitfrom_attributes=Trueo.Ä., Caching, oder an bestimmten Stellen auf „raw“ Datenstrukturen ausweichen.
5.2 Lax vs. Strict Typen
Standardmäßig ist Pydantic recht „freundlich“:
"123"→int(123)"true"→bool(True)(bei Settings)"1.23"→float(1.23)
Das ist praktisch, kann aber auch unerwartete Effekte haben.
Du kannst strict-Typen verwenden oder striktere Konfiguration:
from pydantic import BaseModel, StrictInt
class Model(BaseModel):
value: StrictInt
# Model(value="1") -> ValidationError (keine Autokonvertierung)
Oder über model_config:
class Model(BaseModel):
value: int
model_config = {
"strict": True,
}
5.3 Umgang mit Optional, Defaults, Required
Typische Stolperfallen:
from typing import Optional
from pydantic import BaseModel, Field
class Example(BaseModel):
a: int # Pflichtfeld
b: Optional[int] # „darf None sein“, aber kein Default → ebenfalls Pflichtfeld
c: int = 0 # optional, default = 0
d: Optional[int] = None # optional, default = None
e: int = Field(..., description="explizit required") # Pflichtfeld
Optional[int]heißt nur „intoderNone“, nicht automatisch optional im Sinne von „nicht im Input vorhanden“.- „Required“ bedeutet: Feld muss im Input vorhanden sein, außer es gibt einen Default.
5.4 Migration v1 → v2
Wenn du Codebeispiele im Netz findest, sind viele noch Pydantic v1:
@validatorwurde größtenteils zu@field_validator.parse_obj_as→TypeAdapter.Config-Inner-Class →model_configoderConfigDict.
Beim Einstieg: gleich v2-Doku lesen und wählen.
5.5 Komplexe verschachtelte Strukturen
Pydantic kann sehr komplexe Strukturen validieren (verschachtelte Modelle, Union-Typen, generische Modelle).
Herausforderung ist eher das Verständnis der Typen und Validierungsreihenfolge.
6. Praxisnahe Beispiele
6.1 Verschachtelte Modelle
from pydantic import BaseModel
from typing import List
class Address(BaseModel):
street: str
city: str
zip_code: str
class Customer(BaseModel):
id: int
name: str
addresses: List[Address]
data = {
"id": "1",
"name": "Bob",
"addresses": [
{"street": "Main St 1", "city": "Berlin", "zip_code": "10115"},
{"street": "Side St 2", "city": "Hamburg", "zip_code": "20095"},
]
}
customer = Customer(**data)
print(customer)
Fehler in einer Adresse werden detailliert auf der jeweiligen „Pfad“-Ebene ausgegeben.
6.2 Feld-Constraints & Metadaten
from pydantic import BaseModel, Field
from typing import Literal
class Order(BaseModel):
id: int
status: Literal["open", "paid", "shipped"]
quantity: int = Field(gt=0, description="Muss > 0 sein")
customer_email: str = Field(pattern=r"[^@]+@[^@]+\.[^@]+")
order = Order(
id=1,
status="open",
quantity=5,
customer_email="test@example.com"
)
Literalbeschränkt mögliche Werte (Enum-artig).pattern(Regex) validiert z.B. einfache E-Mail-Formate.
6.3 Custom Validierung mit field_validator und model_validator
from pydantic import BaseModel, field_validator, model_validator
class User(BaseModel):
username: str
password: str
password_repeat: str
@field_validator("username")
def username_not_empty(cls, v):
if not v.strip():
raise ValueError("username must not be empty")
return v
@model_validator(mode="after")
def passwords_match(self):
if self.password != self.password_repeat:
raise ValueError("passwords do not match")
return self
field_validatorprüft einzelne Felder.model_validator(v2) hat Zugriff auf das ganze Modell (z.B. um zwei Felder zu vergleichen).
6.4 JSON-Schema / OpenAPI-Integration
Pydantic kann JSON-Schemas erzeugen, die u.a. von FastAPI genutzt werden, um automatisch Doku (OpenAPI/Swagger) zu generieren:
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
print(Item.model_json_schema())
Das ausgegebene Schema beschreibt die Struktur, Typen und Constraints – ideal für API-Dokumentation.
7. Zusammenfassung
- Definition: Pydantic ist eine Python-Bibliothek für Datenmodelle mit Validierung, Parsing und Serialisierung auf Basis von Typannotationen.
- Abgrenzung:
- Mehr als
dataclasses(mit Validierung & Parsing). - Nutzt Python-Typing natürlicher als Marshmallow & Co.
- Kein ORM, sondern ergänzt diese (oft für API-Schicht).
- Mehr als
- Probleme, die gelöst werden:
- Validierung externer Daten (APIs, Config, User-Input).
- Typ-sichere Domain-Modelle.
- Konfiguration aus Umgebungsvariablen/Dateien inkl. Typenprüfung.
- Automatische Generierung von JSON-Schemas (z.B. für APIs).
- Herausforderungen:
- Performance bei massiver Nutzung.
- Verständnis von strict vs. lax Typen.
- Stolperfallen bei Optional/Defaults.
- Versionsunterschiede (v1 vs. v2).