🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

Python Pydantic v2 Complete Guide 2026: Validierung, Serialisierung und FastAPI

⏱️5 min read  ·  888 words

Pydantic v2 ist das wichtigste Upgrade der Python-Bibliothek im Jahr 2026. In Rust neu geschrieben (über pydantic-core), ist es 5-50x schneller als v1 mit einer saubereren API, einem strengen Modus und Modellvalidatoren. Dieser Leitfaden behandelt alle Funktionen von Pydantic v2 mit praktischen Beispielen.

Installation und Migration

pip install pydantic>=2.0

# Auto-migrate from v1
pip install bump-pydantic
bump-pydantic .

# Key changes from v1:
# - class Config -> model_config = ConfigDict(...)
# - @validator -> @field_validator + @model_validator
# - .dict() -> .model_dump()
# - .json() -> .model_dump_json()
# - __fields__ -> model_fields

Grundmodelle

from pydantic import BaseModel, Field, ConfigDict, EmailStr
from datetime import datetime
from typing import Optional

class User(BaseModel):
    model_config = ConfigDict(
        str_strip_whitespace=True,     # strip whitespace from strings
        str_min_length=1,              # no empty strings
        validate_assignment=True,      # validate on attribute assignment
        populate_by_name=True,         # allow field name OR alias
        use_enum_values=True,          # store enum.value not enum
    )

    id: int
    name: str = Field(min_length=2, max_length=50)
    email: EmailStr
    age: Optional[int] = Field(default=None, ge=13, le=120)
    role: str = Field(default="user", pattern=r"^(user|admin|moderator)$")
    created_at: datetime = Field(default_factory=datetime.utcnow)
    tags: list[str] = Field(default_factory=list, max_length=10)

# Create
user = User(id=1, name="Alice Chen", email="alice@example.com", age=30)
print(user.model_dump())
print(user.model_dump_json(indent=2))

# Partial update
updated = user.model_copy(update={"name": "Alice Smith"})

# From dict
user2 = User.model_validate({"id": 2, "name": "Bob", "email": "bob@example.com"})

# From JSON string
user3 = User.model_validate_json('{"id": 3, "name": "Carol", "email": "carol@example.com"}')

Validatoren

from pydantic import BaseModel, field_validator, model_validator, Field
import re

class PasswordChange(BaseModel):
    current_password: str
    new_password: str = Field(min_length=8)
    confirm_password: str

    @field_validator("new_password")
    @classmethod
    def password_strength(cls, v: str) -> str:
        if not re.search(r"[A-Z]", v):
            raise ValueError("Must contain uppercase letter")
        if not re.search(r"[0-9]", v):
            raise ValueError("Must contain digit")
        if not re.search(r"[!@#$%^&*]", v):
            raise ValueError("Must contain special character")
        return v

    @model_validator(mode="after")  # runs after all field validators
    def passwords_match(self) -> "PasswordChange":
        if self.new_password != self.confirm_password:
            raise ValueError("Passwords do not match")
        if self.new_password == self.current_password:
            raise ValueError("New password must differ from current")
        return self

# Validate and get all errors at once
from pydantic import ValidationError

try:
    PasswordChange(
        current_password="old123",
        new_password="short",
        confirm_password="different"
    )
except ValidationError as e:
    print(e.error_count(), "errors")
    for error in e.errors():
        print(f"  {error['loc']}: {error['msg']}")

Strenger Modus

from pydantic import BaseModel, ConfigDict

class StrictUser(BaseModel):
    model_config = ConfigDict(strict=True)  # no coercion!

    id: int
    name: str
    active: bool

# Without strict: "1" coerced to int, "true" to bool
# With strict: exact types required
try:
    StrictUser(id="1", name="Alice", active="true")  # raises!
except Exception as e:
    print(e)

# Strict per-field
from pydantic import Strict
from typing import Annotated

class PartiallyStrict(BaseModel):
    id: Annotated[int, Strict()]  # strict for this field only
    name: str  # coercion allowed

Aliase und Serialisierung

from pydantic import BaseModel, Field, AliasPath
from pydantic.functional_serializers import model_serializer

class APIResponse(BaseModel):
    # Accept camelCase from API, use snake_case internally
    model_config = ConfigDict(populate_by_name=True)

    user_id: int = Field(alias="userId")
    first_name: str = Field(alias="firstName")
    last_name: str = Field(alias="lastName")
    created_at: str = Field(alias="createdAt")

    # Nested alias path
    city: str = Field(validation_alias=AliasPath("address", "city"))

# Parse camelCase JSON
data = {
    "userId": 1,
    "firstName": "Alice",
    "lastName": "Chen",
    "createdAt": "2026-01-15",
    "address": {"city": "Sydney", "country": "AU"}
}
response = APIResponse.model_validate(data)
print(response.user_id, response.first_name, response.city)

# Serialize back to camelCase
print(response.model_dump(by_alias=True))  # {"userId": 1, ...}

Pydantic + FastAPI (Muster 2026)

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, ConfigDict
from typing import Annotated
from datetime import datetime

app = FastAPI()

class UserCreate(BaseModel):
    name: Annotated[str, Field(min_length=2, max_length=50)]
    email: Annotated[str, Field(pattern=r"^[^@]+@[^@]+\.[^@]+$")]
    password: Annotated[str, Field(min_length=8, exclude=True)]  # exclude from response

class UserResponse(BaseModel):
    model_config = ConfigDict(from_attributes=True)  # allows ORM model input

    id: int
    name: str
    email: str
    created_at: datetime

    @classmethod
    def from_orm_with_extras(cls, obj, **extras) -> "UserResponse":
        return cls.model_validate({**obj.__dict__, **extras})

@app.post("/users", response_model=UserResponse, status_code=201)
async def create_user(data: UserCreate) -> UserResponse:
    # data.password available but won't appear in response
    user = await db.users.create({
        "name": data.name,
        "email": data.email,
        "password_hash": hash_password(data.password)
    })
    return UserResponse.model_validate(user)

# Automatic OpenAPI docs show the schema
# Pydantic v2 JSON Schema generation is much better than v1

Leistung: v1 vs. v2

import timeit
from pydantic import BaseModel

class BenchModel(BaseModel):
    id: int
    name: str
    email: str
    score: float

data = {"id": 1, "name": "Alice", "email": "alice@example.com", "score": 9.5}

n = 100_000
t = timeit.timeit(lambda: BenchModel(**data), number=n)
print(f"Pydantic v2: {t:.2f}s for {n:,} validations ({n/t:,.0f}/sec)")
# Typical: ~2-5x faster than v1, up to 50x for complex nested models

Pydantic v2 im Jahr 2026 ist der Validierungsstandard für Python. Der Rust-Kern liefert erhebliche Leistungssteigerungen. Feldvalidatoren erkennen Datenprobleme frühzeitig, Modellvalidatoren erzwingen feldübergreifende Einschränkungen und der strikte Modus eliminiert stille Zwangsfehler. Migrieren Sie von Version 1 mit Bump-Pydantic – es verarbeitet 90 % der Änderungen automatisch.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা