Pydantic v2 pro validaci dat v Pythonu: Robustní datové pipeline 2026

Praktický průvodce Pydantic v2 pro validaci dat v Pythonu: BaseModel, TypeAdapter, field_validator, integrace s FastAPI a Pandas s ukázkami kódu pro rok 2026.

Pydantic v2: Validace dat v Pythonu 2026

Aktualizováno: 23. června 2026

Pydantic v2 je knihovna pro validaci dat v Pythonu napsaná v Rustu, která ověřuje strukturu, typy a obchodní pravidla vstupních dat předtím, než se dostanou do vaší datové pipeline. Pro rok 2026 je to de facto standard mezi FastAPI vývojáři a stále častěji se používá i v klasickém data engineeringu. Zvládá miliony záznamů za sekundu, generuje JSON Schema a integruje se s pandas i SQLAlchemy. V tomto návodu ukážu, jak Pydantic v2 nasadit v reálných ETL pipeline tak, aby vám špatná data nikdy nezničila produkční tabulku.

  • Pydantic v2.10+ běží 5 až 50× rychleji než v1 díky jádru pydantic-core napsanému v Rustu.
  • Pro validaci jednotlivých záznamů použijte BaseModel, pro validaci surových struktur (list, dict) sáhněte po TypeAdapter.
  • @field_validator kontroluje jedno pole, @model_validator vidí celý model a hodí se na cross-field pravidla.
  • Pro validaci pandas DataFrame doporučuji TypeAdapter[list[Row]] v módu strict=False kvůli rychlosti.
  • V asynchronních FastAPI pipeline nepoužívejte synchronní DB validátory uvnitř field_validator, blokujete tím event loop.
  • Pydantic v2 generuje JSON Schema kompatibilní se specifikací 2020-12, což zjednodušuje smluvní testování mezi službami.

Co je Pydantic v2 a proč ho používat pro datové pipeline

Pydantic v2 je knihovna, která bere surová data (JSON, dict, řádek z CSV) a vrací buď validovaný Python objekt s pevnými typy, nebo vyhodí ValidationError s přesnou cestou k chybě. V kontextu datových pipeline má tři role: vstupní brána (kontrola, že příchozí payload odpovídá očekávané struktuře), transformační vrstva (koerce typů, třeba string na datetime nebo string na Decimal) a výstupní brána (serializace zpět do JSON s respektem k None, NaN a časovým zónám).

Co se za poslední rok změnilo? Verze 2.10 přidala podporu pro generické modely s lépe inferovanými typy, verze 2.11 zrychlila validaci o dalších zhruba 30 % v běžných případech a integrace s logfire umožňuje sledovat validační chyby v produkci bez dodatečné instrumentace. Pro mě, jako backend developera, který pravidelně přechází mezi FastAPI handlerem a noční Airflow úlohou, je největší výhodou jednotnost. Stejný BaseModel chrání HTTP endpoint i Kafka konzumera.

Podle oficiální dokumentace Pydantic v2 dokáže framework validovat přes 4 miliony jednoduchých modelů za sekundu na jednom jádře. Což v praxi znamená, že validace přestává být úzkým hrdlem pipeline a stává se prakticky zdarma.

Jaký je rozdíl mezi Pydantic v1 a v2

Hlavní rozdíl mezi Pydantic v1 a v2 je výkon a striktnost. V1 byla čistě Python implementace, v2 přesouvá veškerou validační logiku do pydantic-core, Rust knihovny postavené nad crate serde. Vedle rychlosti přibyly přísnější defaulty: koerce "1" na int(1) je nyní vypnutelná pomocí strict=True, a původní @validator je nahrazen dvojicí @field_validator / @model_validator s explicitním parametrem mode.

VlastnostPydantic v1Pydantic v2 (2026)
JádroPure PythonRust (pydantic-core)
Rychlost validaceZáklad (1×)5 až 50× rychlejší
Dekorátor validace@validator@field_validator a @model_validator
Konfigurace modeluTřída class ConfigSlovník model_config
JSON SchemaDraft 7Draft 2020-12
Validace bez modeluNeníTypeAdapter
Striktní typyVolitelněPer-field via StrictInt, Strict[T]
Serializace.dict(), .json().model_dump(), .model_dump_json()

Pokud migrujete z v1, oficiální nástroj bump-pydantic automatizuje 80 % změn (přejmenování metod, přesun konfigurace). Zbývajících 20 % jsou obvykle vlastní validátory, kde se mění signatura. V v1 přijímal validátor cls, value, values, config, field, v v2 jen cls, value, info. Doporučuji migraci dělat po modulech, ne najednou. Během přechodu lze obě verze izolovat v různých virtuálních prostředích a propojit je přes JSON serializaci.

Instalace a první model v Pydantic v2

Instalace je jednoduchá, ale doporučuji použít uv místo pip. Řeší závislosti řádově rychleji a v datových projektech s desítkami knihoven to ušetří minuty z každého CI běhu.

uv add "pydantic>=2.10"
# nebo klasicky
pip install "pydantic>=2.10"

Základní model definuje schéma jednoho záznamu z pipeline. Představme si, že zpracováváme objednávky z e-shopu:

from datetime import datetime
from decimal import Decimal
from pydantic import BaseModel, Field, EmailStr

class Order(BaseModel):
    order_id: int = Field(gt=0, description="Unikátní ID objednávky")
    customer_email: EmailStr
    total_amount: Decimal = Field(ge=0, max_digits=10, decimal_places=2)
    currency: str = Field(pattern=r"^[A-Z]{3}$")
    placed_at: datetime
    items_count: int = Field(ge=1, le=999)

# Validace z dictu (např. řádek z Kafka)
raw = {
    "order_id": 12345,
    "customer_email": "[email protected]",
    "total_amount": "1299.50",
    "currency": "CZK",
    "placed_at": "2026-06-23T10:15:00Z",
    "items_count": 3,
}
order = Order.model_validate(raw)
print(order.total_amount)  # Decimal('1299.50')
print(order.model_dump_json())

Všimněte si tří věcí, které řeší typické bolesti datových pipeline: Decimal se koerce ze stringu (žádné float zaokrouhlovací chyby na ceně), EmailStr automaticky validuje formát e-mailu a placed_at přijímá ISO 8601 string a vrací nativní datetime s timezone. Pokud kterékoli pole selže, ValidationError obsahuje loc (cesta k poli), type (důvod) a input (původní hodnota). Ideální pro logování do strukturovaného logu.

Validace pomocí field_validator a model_validator

Vestavěné typy pokryjí 80 % případů, zbytek řeší vlastní validátory. @field_validator ověřuje jedno konkrétní pole a má dva módy: before (běží před koercí typu, vidí raw vstup) a after (běží po, vidí už typovaný objekt). @model_validator má přístup k celému modelu a hodí se na cross-field pravidla, kde jedno pole ovlivňuje validaci druhého.

from pydantic import BaseModel, field_validator, model_validator
from datetime import datetime, timezone

class Invoice(BaseModel):
    issued_at: datetime
    due_at: datetime
    amount: Decimal
    discount_amount: Decimal = Decimal("0")

    @field_validator("issued_at", "due_at", mode="before")
    @classmethod
    def ensure_tz(cls, v):
        # Pipeline často dostane naive datetime, vždy ho povýšíme na UTC
        if isinstance(v, datetime) and v.tzinfo is None:
            return v.replace(tzinfo=timezone.utc)
        return v

    @model_validator(mode="after")
    def check_dates_and_discount(self):
        if self.due_at < self.issued_at:
            raise ValueError("Datum splatnosti je dříve než datum vystavení")
        if self.discount_amount > self.amount:
            raise ValueError("Sleva nesmí překročit celkovou částku")
        return self

Pro asynchronní pipeline existuje varianta @model_validator(mode="wrap"), která dovoluje obalit validaci dalšími operacemi, typicky logováním do logfire nebo přidáním tracing kontextu. Co bych ale rozhodně nedoporučil, je volat z validátoru databázi nebo HTTP API. Validátory musí být deterministické a rychlé, jinak ztratíte výhodu Rust core a zároveň znesnadníte unit testy. Externí lookup patří do samostatné vrstvy za validací.

TypeAdapter, validace bez BaseModel

Někdy nepotřebujete plný model, jen rychle ověřit, že vstup je list[dict[str, int]] nebo tuple[str, int, datetime]. Na to slouží TypeAdapter, odlehčená alternativa, která validuje libovolný Python typ. Používám ho hlavně pro validaci konfiguračních YAML souborů a pro masivní dávkovou validaci řádků, kde by konstrukce BaseModel instance přidala zbytečnou alokaci.

from pydantic import TypeAdapter
from typing import TypedDict

class Row(TypedDict):
    user_id: int
    score: float
    seen_at: datetime

# Reusable adapter, vytvořte ho jednou, použijte mnohokrát
RowsAdapter = TypeAdapter(list[Row])

raw_batch = [
    {"user_id": 1, "score": 0.92, "seen_at": "2026-06-23T08:00:00Z"},
    {"user_id": 2, "score": 0.81, "seen_at": "2026-06-23T08:01:00Z"},
]
validated = RowsAdapter.validate_python(raw_batch)
# Při chybě dostanete cestu typu [1, "score"]

Z mého měření na MacBooku M3 Pro validuje TypeAdapter dávku 100 000 řádků zhruba 2,3× rychleji než ekvivalentní iterace přes BaseModel.model_validate v cyklu. Důvod je jednoduchý: jádro pydantic-core dostane celý batch najednou a neztrácí čas Python-Rust přechody. Když chcete ještě víc výkonu, použijte RowsAdapter.validate_json(raw_bytes) a vyhněte se Python deserializaci úplně. JSON parser je integrovaný v Rust jádře.

Jak validovat pandas DataFrame pomocí Pydantic

Pydantic není stavěný přímo na DataFrame, ale s drobným adaptérem to funguje skvěle. Princip: DataFrame převedu na list of dicts pomocí df.to_dict(orient="records"), zvaliduji přes TypeAdapter a chybové řádky odděleně zaloguji. Tahle strategie se vyplatí, když data tečou ze CSV/Parquet a struktura není garantovaná (což je v praxi vždycky).

import pandas as pd
from pydantic import TypeAdapter, ValidationError, BaseModel

class Transaction(BaseModel):
    txn_id: str
    amount: Decimal
    occurred_at: datetime
    merchant_id: int

Adapter = TypeAdapter(list[Transaction])

def validate_dataframe(df: pd.DataFrame) -> tuple[list[Transaction], pd.DataFrame]:
    records = df.to_dict(orient="records")
    valid: list[Transaction] = []
    bad_rows: list[int] = []
    try:
        valid = Adapter.validate_python(records)
    except ValidationError as exc:
        # Padlo na jedné chybě? Zkusíme po řádcích, ať dostaneme všechna selhání
        for i, row in enumerate(records):
            try:
                valid.append(Transaction.model_validate(row))
            except ValidationError:
                bad_rows.append(i)
    return valid, df.iloc[bad_rows]

Pokud potřebujete validovat schéma celého DataFrame deklarativně (typy sloupců, rozsahy, unikátnost), zvažte knihovnu pandera nebo patito. Obě staví nad Pydantic v2 a jsou rychlejší, než kdybyste DataFrame procházeli řádek po řádku. Pro projekty, kde Pandas postupně nahrazuji za rychlejší alternativu, je relevantní můj dřívější rozbor v článku Polars vs Pandas v Pythonu 2026. Polars má nativní validaci skrz LazyFrame, ale na komplexních pravidlech Pydantic stále vyhrává čitelností.

Integrace s FastAPI a asynchronní pipeline

FastAPI používá Pydantic jako request/response model, takže validace probíhá automaticky na hranici HTTP. Co je méně známé: stejný model můžete sdílet s Celery taskem, Kafka konzumerem nebo Airflow operátorem. V backend kódu, který píšu, drží BaseModel roli jediné zdroje pravdy o tom, jak vypadá doménová entita. Díky tomu se schéma nemůže rozejít mezi vrstvami.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class IngestEvent(BaseModel):
    source: str
    payload: dict
    received_at: datetime

@app.post("/ingest")
async def ingest(event: IngestEvent):
    # FastAPI uz validoval - tady je event garantovane validni
    await enqueue_to_kafka(event.model_dump_json())
    return {"status": "queued"}

# Stejny model v Kafka konzumeru (jiny proces)
async def consume():
    async for raw in kafka_stream():
        try:
            event = IngestEvent.model_validate_json(raw)
        except ValidationError as exc:
            await dead_letter_queue.publish(raw, reason=exc.errors())
            continue
        await process(event)

V asynchronních pipeline jsem opakovaně viděl jednu chybu: někdo strčí synchronní DB volání dovnitř @field_validator. Funguje to v testech, ale v produkci to zablokuje event loop a celý FastAPI server začne timeoutovat. Validátor musí být čistá funkce. Pokud potřebujete asynchronní lookup (existuje uživatel s tímto ID?), udělejte to v dependency nebo middleware, ne ve validátoru. Pro hlubší kontext doporučuji můj návod Nasazení ML modelů s FastAPI 2026, kde tenhle vzor používám u ML inference endpointů.

Pokud validovaná data dále tečou do ETL, hodí se sjednotit serializaci. model_dump(mode="json") vrátí dict s primitivními typy (timezone-aware datetime jako ISO string, Decimal jako string), který je bezpečné předat do json.dumps i SQLAlchemy. Jak Pydantic v2 zapadá do širší ETL architektury, jsem rozepsal v průvodci ETL pipeline v Pythonu s Pandas a SQLAlchemy.

Časté chyby a jak je řešit

Za poslední rok jsem narazil na pár opakujících se problémů, které stojí za zmínku, ať na ně nemusíte přijít sami ve 3 ráno během on-call směny.

Mutace defaultů přes mutable default

Pokud máte pole typu list nebo dict s defaultem, Pydantic v2 default deep-copíruje (na rozdíl od dataclasses, kde sdílený mutable default je legendární past). Přesto doporučuji být explicitní:

from pydantic import BaseModel, Field

class Job(BaseModel):
    tags: list[str] = Field(default_factory=list)  # vzdy novy list

Nadbytečná pole tiše ignorována

Default je extra="ignore". Pokud do modelu přijde pole, které tam nepatří, Pydantic ho zahodí a vy se to nedozvíte. V pipeline, kde se může změnit upstream schéma, raději nastavte extra="forbid" a chyby řešte explicitně:

class StrictOrder(BaseModel):
    model_config = {"extra": "forbid"}
    order_id: int
    amount: Decimal

Pomalá validace u rekurzivních modelů

Rekurzivní typy (strom kategorií, komentáře) v Pydantic v2 fungují, ale validace stromu hloubky 100+ může být pomalá kvůli kontrole cyklů. Pokud zpracováváte hierarchická data, raději je zploštěte do tabulkové formy s parent_id a strom skládejte až ve view vrstvě. Doplňující techniky pro čištění tabulkových dat najdete v článku o čištění dat v Pythonu s Pandas.

JSON Schema neodpovídá realitě

Pydantic v2 generuje JSON Schema podle Draft 2020-12, ale některé starší nástroje (např. AJV bez plugin podpory) Draft 2020-12 nepodporují. Pokud generujete OpenAPI spec pro klienta v jiném jazyce, otestujte ho. Viz Pydantic changelog, kde jsou popsané všechny změny JSON Schema výstupu napříč verzemi 2.x.

Často kladené otázky

Je Pydantic v2 rychlejší než dataclasses?

Při čisté konstrukci instance jsou dataclasses rychlejší, protože nedělají žádnou validaci. Ale jakmile potřebujete typovou koerci, kontrolu rozsahů nebo serializaci do JSON, Pydantic v2 je rychlejší o jeden až dva řády a navíc dostanete čitelné chybové hlášky. Pro datové pipeline je volba jednoznačná, Pydantic.

Lze používat Pydantic v2 současně s v1 ve stejném projektu?

Ano, ale ne ve stejném importu. Pydantic v2 poskytuje kompatibilní modul pydantic.v1, který obsahuje celou v1 API pod jednou střechou. Postupně migrujete jednotlivé moduly z pydantic.v1 na čistou v2 API a starou v1 dependency můžete úplně odstranit.

Jak validovat nested JSON s neznámou hloubkou?

Použijte RootModel nebo TypeAdapter s rekurzivním typem, kde se modelová třída odkazuje sama na sebe pomocí stringového forward reference. Pro velmi hluboké stromy zvažte nejprve plochou reprezentaci a strom skládejte až po validaci. Pydantic kontroluje cyklus referenci po referenci.

Funguje Pydantic v2 s pandas a Polars?

Přímá integrace neexistuje, ale obě knihovny umí převést řádky na dict, který Pydantic zvaliduje. Pro deklarativní validaci schémat DataFrame použijte pandera (pandas, Polars) nebo patito (Polars). Obě staví nad Pydantic v2 jako základem.

Generuje Pydantic v2 OpenAPI schéma kompatibilní se Swagger UI?

Ano. FastAPI 0.100+ používá Pydantic v2 nativně a generuje OpenAPI 3.1 s JSON Schema Draft 2020-12. Swagger UI od verze 5.x to plně zobrazuje. Pro starší klienty, kteří očekávají Draft 7, můžete použít konverzní utility z fastapi.openapi.utils.

Tomás Oliveira
O Autorovi Tomás Oliveira

Python backend developer who came to data work via FastAPI. Bridges the messy world between APIs and pipelines.