add synced notes on IT know how

This commit is contained in:
Mathias Schneider
2026-03-17 19:01:50 +01:00
parent b8797d5ca8
commit fc3aef4da8
31 changed files with 13162 additions and 1 deletions
+470
View File
@@ -0,0 +1,470 @@
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:
```python
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:
- `id` ist als `int` deklariert, 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`:
```python
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, default `None`.
### 2.2 Validierung & Parsing
Pydantic führt **Validierung und Parsing beim Erstellen** des Modells durch.
Man kann explizit „parsen“:
```python
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:
```python
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`:
```python
from dataclasses import dataclass
@dataclass
class UserDC:
id: int
name: str
```
- `dataclasses` stellen nur **strukturelle Container** bereit.
- Keine automatische Validierung oder Typkonvertierung.
- Typannotationen sind rein informativ (für IDE, mypy), nicht enforced.
Pydantic:
```python
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.
- **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-core` in Rust.
### 3.3 Pydantic vs. Typing-Features (`TypedDict`, `Protocol`, …)
- `TypedDict` definiert 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:
```python
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`:
```python
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_URL` in der Umgebung oder `.env` wird 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.
```python
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_validate` mit `from_attributes=True` o.Ä., 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:
```python
from pydantic import BaseModel, StrictInt
class Model(BaseModel):
value: StrictInt
# Model(value="1") -> ValidationError (keine Autokonvertierung)
```
Oder über `model_config`:
```python
class Model(BaseModel):
value: int
model_config = {
"strict": True,
}
```
### 5.3 Umgang mit Optional, Defaults, Required
Typische Stolperfallen:
```python
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 „`int` oder `None`“, 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:
- `@validator` wurde größtenteils zu `@field_validator`.
- `parse_obj_as``TypeAdapter`.
- `Config`-Inner-Class → `model_config` oder `ConfigDict`.
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
```python
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
```python
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"
)
```
- `Literal` beschrä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`
```python
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_validator` prü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:
```python
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).
- **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).