Pydantic in der Praxis: Datenvalidierung für Python
Inhalt
Pydantic ist das meistgeladene Python-Package auf PyPI und das Fundament von FastAPI. Aber auch außerhalb von FastAPI ist es ein unglaublich nützliches Tool für saubere Datenvalidierung.
Grundlagen: BaseModel
from pydantic import BaseModel, EmailStr, Field
from datetime import datetime
from typing import Optional, List
class UserCreate(BaseModel):
name: str = Field(min_length=2, max_length=100)
email: EmailStr
age: int = Field(ge=18, le=120) # Zwischen 18 und 120
tags: List[str] = [] # Default: leere Liste
bio: Optional[str] = None
# Validierung
try:
user = UserCreate(
name="Yevhen",
email="eugen@chubchyk.de",
age=43
)
print(user.model_dump())
except ValidationError as e:
print(e.errors())# Verschachtelte Models
class Address(BaseModel):
street: str
city: str
country: str = "DE"
class Company(BaseModel):
name: str
address: Address
employees: List[UserCreate] = []
# JSON direkt parsen
json_str = '{"name": "Acme", "address": {"street": "Hauptstr. 1", "city": "Berlin"}}'
company = Company.model_validate_json(json_str)Custom Validators
from pydantic import BaseModel, field_validator, model_validator
class OrderCreate(BaseModel):
product_id: int
quantity: int
discount: float = 0.0
price: float
@field_validator("quantity")
@classmethod
def quantity_must_be_positive(cls, v):
if v <= 0:
raise ValueError("Menge muss größer 0 sein")
return v
@field_validator("discount")
@classmethod
def discount_range(cls, v):
if not 0 <= v <= 1:
raise ValueError("Rabatt muss zwischen 0 und 1 liegen")
return v
@model_validator(mode="after")
def check_total_positive(self):
total = self.price * self.quantity * (1 - self.discount)
if total <= 0:
raise ValueError("Gesamtpreis muss positiv sein")
return selfSettings Management mit BaseSettings
from pydantic_settings import BaseSettings
from typing import Optional
class Settings(BaseSettings):
# Liest aus Umgebungsvariablen (case-insensitive)
database_url: str
redis_url: str = "redis://localhost:6379/0"
debug: bool = False
secret_key: str
allowed_hosts: list[str] = ["localhost"]
api_rate_limit: int = 100
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
# Singleton Pattern
from functools import lru_cache
@lru_cache(maxsize=1)
def get_settings() -> Settings:
return Settings()
settings = get_settings()Pydantic mit Django
Pydantic und Django sind kein Gegensatz. Ich nutze Pydantic in Django für:
# 1. Validierung von externen API-Antworten
class GitHubUser(BaseModel):
login: str
id: int
email: Optional[str] = None
public_repos: int
import httpx
async def get_github_user(username: str) -> GitHubUser:
async with httpx.AsyncClient() as client:
r = await client.get(f"https://api.github.com/users/{username}")
return GitHubUser.model_validate(r.json())
# 2. DTOs (Data Transfer Objects) zwischen Schichten
class ArticleDTO(BaseModel):
id: int
title: str
author_name: str
tags: List[str]
@classmethod
def from_model(cls, article) -> "ArticleDTO":
return cls(
id=article.id,
title=article.title,
author_name=article.author.get_full_name(),
tags=list(article.tags.values_list("name", flat=True))
)Pydantic v2: Was ist neu?
Pydantic v2 (2023) ist in Rust geschrieben und 5-50x schneller als v1. Wichtige Änderungen:
.dict()→.model_dump(),.json()→.model_dump_json()@validator→@field_validator(mit@classmethod).parse_obj()→.model_validate()BaseSettingsin separates Packagepydantic-settingsausgelagert
