Biblioteka pydantic

⏱ Czas czytania: ~18 minut | 📊 Poziom: Poczatkujacy/Sredni | 📅 Aktualizacja: Styczen 2026

Pydantic to najpopularniejsza biblioteka do walidacji danych w Pythonie. Wykorzystuje type hints do definiowania schematow danych, automatycznej walidacji i serializacji. Jest fundamentem wielu frameworkow AI — w tym LangChain, CrewAI, FastAPI i OpenAI SDK. Jesli pracujesz z agentami AI, musisz znac Pydantic.

🎯 Czego sie nauczysz:
  • Podstawy Pydantic: BaseModel i Field
  • Walidacja danych i typy
  • Walidatory: @field_validator i @model_validator
  • Serializacja i deserializacja (JSON, dict)
  • Nested models i dziedziczenie
  • Settings management
  • Integracja z frameworkami AI

Dlaczego Pydantic?

Python jest dynamicznie typowany — co oznacza, ze bledy typow pojawiaja sie dopiero w runtime. Pydantic rozwiazuje ten problem:

✅ Walidacja

Automatyczna walidacja typow i wartosci

🔧 Serializacja

Latwa konwersja do/z JSON, dict

📝 Dokumentacja

Automatyczne JSON Schema

🚀 Wydajnosc

Core w Rust (Pydantic V2)

Instalacja

# Podstawowa instalacja
pip install pydantic

# Z dodatkami (email-validator, etc.)
pip install pydantic[email]

# Pydantic settings (dla konfiguracji)
pip install pydantic-settings

# Sprawdz wersje (powinna byc 2.x)
python -c "import pydantic; print(pydantic.VERSION)"
⚠ Pydantic V1 vs V2:

Ten artykul dotyczy Pydantic V2 (wydany w 2023). V2 jest ~5-50x szybszy dzieki core w Rust. Jesli masz stary kod z V1, sprawdz migration guide.

Podstawy: BaseModel

Kazdy model Pydantic dziedziczy po BaseModel:

from pydantic import BaseModel
from datetime import datetime
from typing import Optional

# Definicja modelu
class User(BaseModel):
    id: int
    name: str
    email: str
    age: Optional[int] = None  # pole opcjonalne z domyslna wartoscia
    is_active: bool = True     # pole z domyslna wartoscia
    created_at: datetime = datetime.now()

# Tworzenie instancji - dane sa WALIDOWANE
user = User(
    id=1,
    name="Jan Kowalski",
    email="jan@example.com"
)

print(user)
# id=1 name='Jan Kowalski' email='jan@example.com' age=None is_active=True ...

print(user.name)       # Jan Kowalski
print(user.model_dump())  # {'id': 1, 'name': 'Jan Kowalski', ...}

Automatyczna konwersja typow

# Pydantic automatycznie konwertuje typy gdy to mozliwe
user = User(
    id="123",        # string -> int ✅
    name="Anna",
    email="anna@example.com",
    age="25",        # string -> int ✅
    is_active="true"  # string -> bool ✅
)

print(user.id)        # 123 (int, nie string!)
print(type(user.id))  # <class 'int'>

Bledy walidacji

from pydantic import ValidationError

try:
    user = User(
        id="not-a-number",  # nie da sie skonwertowac na int
        name="Test",
        email="test@example.com"
    )
except ValidationError as e:
    print(e)
    # 1 validation error for User
    # id
    #   Input should be a valid integer [type=int_parsing, ...]
    
    # Szczegoly bledow jako lista
    print(e.errors())
    # [{'type': 'int_parsing', 'loc': ('id',), 'msg': '...', 'input': 'not-a-number'}]

Field — szczegolowa konfiguracja pol

Field pozwala dodac walidacje, opisy i ograniczenia:

from pydantic import BaseModel, Field
from typing import Optional

class Product(BaseModel):
    # Pole wymagane z opisem
    name: str = Field(
        ...,  # ... oznacza "wymagane"
        min_length=1,
        max_length=100,
        description="Nazwa produktu"
    )
    
    # Pole z ograniczeniami numerycznymi
    price: float = Field(
        ...,
        gt=0,        # greater than 0
        le=100000,   # less or equal 100000
        description="Cena w PLN"
    )
    
    # Pole z domyslna wartoscia i aliasem
    quantity: int = Field(
        default=0,
        ge=0,        # greater or equal 0
        alias="qty"  # alternatywna nazwa w JSON
    )
    
    # Pole opcjonalne
    description: Optional[str] = Field(
        default=None,
        max_length=1000
    )
    
    # Pole z wzorcem regex
    sku: str = Field(
        ...,
        pattern=r'^[A-Z]{3}-\d{4}$',  # np. ABC-1234
        description="Kod produktu (format: ABC-1234)"
    )

# Poprawne uzycie
product = Product(
    name="Laptop Dell",
    price=4599.99,
    qty=10,  # uzywamy aliasu
    sku="LAP-0001"
)

# Blad - cena ujemna
try:
    Product(name="Test", price=-10, sku="TST-0001")
except ValidationError as e:
    print(e)  # Input should be greater than 0

Ograniczenia Field

Parametr Typ Opis
gt, ge, lt, le Numeryczne Greater/less than (or equal)
min_length, max_length String/List Minimalna/maksymalna dlugosc
pattern String Wzorzec regex
alias Wszystkie Alternatywna nazwa pola
default, default_factory Wszystkie Wartosc domyslna
description Wszystkie Opis dla dokumentacji/schema

Typy danych

Pydantic wspiera wszystkie standardowe typy Pythona oraz dodaje wlasne:

from pydantic import (
    BaseModel, 
    EmailStr,        # walidowany email
    HttpUrl,         # walidowany URL
    PositiveInt,     # int > 0
    PositiveFloat,   # float > 0
    NegativeInt,     # int < 0
    NonNegativeInt,  # int >= 0
    constr,          # constrained string
    conint,          # constrained int
    confloat,        # constrained float
    conlist,         # constrained list
)
from datetime import datetime, date
from typing import List, Dict, Optional, Literal, Union
from enum import Enum
from uuid import UUID

class Status(str, Enum):
    PENDING = "pending"
    ACTIVE = "active"
    COMPLETED = "completed"

class CompleteModel(BaseModel):
    # Podstawowe typy
    name: str
    count: int
    price: float
    is_valid: bool
    
    # Typy Pydantic
    email: EmailStr                  # walidowany email
    website: HttpUrl                 # walidowany URL
    quantity: PositiveInt            # musi byc > 0
    
    # Constrained types
    code: constr(min_length=3, max_length=10, to_upper=True)
    rating: confloat(ge=0, le=5)
    tags: conlist(str, min_length=1, max_length=10)
    
    # Datetime
    created_at: datetime
    birth_date: date
    
    # Kolekcje
    items: List[str]
    metadata: Dict[str, str]
    
    # Enum
    status: Status
    
    # Literal - tylko te wartosci
    priority: Literal["low", "medium", "high"]
    
    # Union - jeden z typow
    value: Union[int, str]
    
    # UUID
    id: UUID
    
    # Optional
    description: Optional[str] = None

# Przyklad uzycia
from uuid import uuid4

model = CompleteModel(
    name="Test",
    count=10,
    price=99.99,
    is_valid=True,
    email="test@example.com",
    website="https://example.com",
    quantity=5,
    code="abc",  # zostanie zamienione na "ABC"
    rating=4.5,
    tags=["python", "pydantic"],
    created_at=datetime.now(),
    birth_date="1990-01-15",  # string -> date
    items=["item1", "item2"],
    metadata={"key": "value"},
    status="active",  # string -> Status enum
    priority="high",
    value=42,
    id=uuid4()
)

Walidatory

Walidatory pozwalaja na niestandardowa logike walidacji:

@field_validator — walidacja pojedynczego pola

from pydantic import BaseModel, field_validator

class User(BaseModel):
    name: str
    email: str
    age: int
    password: str
    
    # Walidator dla jednego pola
    @field_validator('name')
    @classmethod
    def name_must_not_be_empty(cls, v: str) -> str:
        if not v.strip():
            raise ValueError('Imie nie moze byc puste')
        return v.strip().title()  # transformacja: "jan kowalski" -> "Jan Kowalski"
    
    # Walidator z mode='before' - przed konwersja typow
    @field_validator('email', mode='before')
    @classmethod
    def email_to_lowercase(cls, v):
        if isinstance(v, str):
            return v.lower().strip()
        return v
    
    # Walidator dla wielu pol naraz
    @field_validator('age')
    @classmethod
    def age_must_be_valid(cls, v: int) -> int:
        if v < 0 or v > 150:
            raise ValueError('Wiek musi byc miedzy 0 a 150')
        return v
    
    # Walidator hasla
    @field_validator('password')
    @classmethod
    def password_strength(cls, v: str) -> str:
        if len(v) < 8:
            raise ValueError('Haslo musi miec min. 8 znakow')
        if not any(c.isupper() for c in v):
            raise ValueError('Haslo musi zawierac wielka litere')
        if not any(c.isdigit() for c in v):
            raise ValueError('Haslo musi zawierac cyfre')
        return v

# Test
user = User(
    name="  jan kowalski  ",
    email="JAN@EXAMPLE.COM",
    age=30,
    password="Haslo123"
)
print(user.name)   # "Jan Kowalski"
print(user.email)  # "jan@example.com"

@model_validator — walidacja calego modelu

from pydantic import BaseModel, model_validator
from typing import Self

class DateRange(BaseModel):
    start_date: date
    end_date: date
    
    # Walidator PRZED utworzeniem instancji
    @model_validator(mode='before')
    @classmethod
    def check_dates_exist(cls, data: dict):
        if 'start_date' not in data or 'end_date' not in data:
            raise ValueError('Obie daty sa wymagane')
        return data
    
    # Walidator PO utworzeniu instancji
    @model_validator(mode='after')
    def check_date_order(self) -> Self:
        if self.end_date < self.start_date:
            raise ValueError('end_date musi byc po start_date')
        return self

class UserRegistration(BaseModel):
    password: str
    password_confirm: str
    
    @model_validator(mode='after')
    def passwords_match(self) -> Self:
        if self.password != self.password_confirm:
            raise ValueError('Hasla musza byc identyczne')
        return self

# Test
try:
    reg = UserRegistration(password="abc123", password_confirm="xyz789")
except ValidationError as e:
    print(e)  # Hasla musza byc identyczne

Serializacja i deserializacja

from pydantic import BaseModel
from datetime import datetime

class Event(BaseModel):
    name: str
    date: datetime
    attendees: int

event = Event(name="Konferencja", date=datetime.now(), attendees=100)

# === Do dict ===
data = event.model_dump()
print(data)
# {'name': 'Konferencja', 'date': datetime.datetime(...), 'attendees': 100}

# Z wybranymi polami
data = event.model_dump(include={'name', 'date'})
data = event.model_dump(exclude={'attendees'})

# === Do JSON ===
json_str = event.model_dump_json()
print(json_str)
# '{"name":"Konferencja","date":"2025-01-12T...","attendees":100}'

# Z formatowaniem
json_str = event.model_dump_json(indent=2)

# === Z dict ===
data = {"name": "Meetup", "date": "2025-06-15T18:00:00", "attendees": 50}
event = Event.model_validate(data)
# lub
event = Event(**data)

# === Z JSON ===
json_str = '{"name": "Workshop", "date": "2025-07-20T10:00:00", "attendees": 25}'
event = Event.model_validate_json(json_str)

# === JSON Schema ===
schema = Event.model_json_schema()
print(schema)
# {'properties': {'name': {'title': 'Name', 'type': 'string'}, ...}}

Nested Models

Modele moga zawierac inne modele:

from pydantic import BaseModel
from typing import List, Optional

class Address(BaseModel):
    street: str
    city: str
    zip_code: str
    country: str = "Polska"

class Company(BaseModel):
    name: str
    nip: str

class Person(BaseModel):
    name: str
    email: str
    address: Address                    # nested model
    company: Optional[Company] = None   # optional nested
    tags: List[str] = []

# Tworzenie z nested dict - Pydantic automatycznie tworzy nested models
person = Person(
    name="Jan",
    email="jan@example.com",
    address={
        "street": "ul. Marszalkowska 1",
        "city": "Warszawa",
        "zip_code": "00-001"
    },
    company={
        "name": "Firma XYZ",
        "nip": "1234567890"
    }
)

print(person.address.city)     # Warszawa
print(person.company.name)     # Firma XYZ

# Lub z instancjami
address = Address(street="ul. Dluga 5", city="Krakow", zip_code="30-001")
person2 = Person(name="Anna", email="anna@example.com", address=address)

Dziedziczenie

from pydantic import BaseModel
from datetime import datetime
from typing import Optional

# Model bazowy
class BaseEntity(BaseModel):
    id: int
    created_at: datetime = datetime.now()
    updated_at: Optional[datetime] = None

# Dziedziczenie
class User(BaseEntity):
    name: str
    email: str

class Product(BaseEntity):
    name: str
    price: float

# User ma: id, created_at, updated_at, name, email
user = User(id=1, name="Jan", email="jan@example.com")

# Product ma: id, created_at, updated_at, name, price
product = Product(id=1, name="Laptop", price=4999.99)

Settings Management

Pydantic Settings to idealny sposob na zarzadzanie konfiguracja aplikacji:

# pip install pydantic-settings
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field
from typing import Optional

class Settings(BaseSettings):
    """Konfiguracja aplikacji z env variables."""
    
    # Automatycznie czytane z env
    app_name: str = "My App"
    debug: bool = False
    
    # API keys
    openai_api_key: str = Field(..., description="OpenAI API Key")
    anthropic_api_key: Optional[str] = None
    
    # Database
    database_url: str = "sqlite:///./app.db"
    db_pool_size: int = 5
    
    # Server
    host: str = "0.0.0.0"
    port: int = 8000
    
    # Konfiguracja
    model_config = SettingsConfigDict(
        env_file=".env",              # czytaj z pliku .env
        env_file_encoding="utf-8",
        case_sensitive=False,         # OPENAI_API_KEY = openai_api_key
        extra="ignore"                 # ignoruj nieznane env vars
    )

# Uzycie
settings = Settings()
print(settings.openai_api_key)
print(settings.database_url)

# Plik .env:
# OPENAI_API_KEY=sk-...
# DEBUG=true
# DATABASE_URL=postgresql://user:pass@localhost/db

Integracja z frameworkami AI

Pydantic jest fundamentem wielu frameworkow AI:

OpenAI Structured Output

from pydantic import BaseModel
from openai import OpenAI

class MovieReview(BaseModel):
    title: str
    rating: float
    summary: str
    pros: list[str]
    cons: list[str]

client = OpenAI()
response = client.beta.chat.completions.parse(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Review the movie Inception"}],
    response_format=MovieReview  # Pydantic model!
)

review = response.choices[0].message.parsed  # MovieReview instance
print(review.title)
print(review.rating)

LangChain Output Parsers

from langchain_core.output_parsers import PydanticOutputParser
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field

class TaskExtraction(BaseModel):
    """Extracted task from text."""
    task: str = Field(description="The main task")
    priority: str = Field(description="Priority: low, medium, high")
    deadline: str = Field(description="Deadline if mentioned")

parser = PydanticOutputParser(pydantic_object=TaskExtraction)
llm = ChatOpenAI().with_structured_output(TaskExtraction)

result = llm.invoke("Muszę skończyć raport do piątku, to pilne!")
print(result.task)      # "skończyć raport"
print(result.priority)  # "high"
print(result.deadline)  # "piątek"

CrewAI State

from crewai.flow.flow import Flow, start, listen
from pydantic import BaseModel

class ResearchState(BaseModel):
    """State for research flow."""
    topic: str = ""
    findings: list[str] = []
    summary: str = ""

class ResearchFlow(Flow[ResearchState]):  # Pydantic model jako state!
    @start()
    def init(self):
        self.state.topic = "AI Agents"
        return self.state.topic

FastAPI

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    description: str | None = None

@app.post("/items/")
async def create_item(item: Item):  # automatyczna walidacja!
    return {"item": item, "status": "created"}

# FastAPI automatycznie:
# - waliduje request body
# - generuje OpenAPI schema
# - tworzy dokumentacje Swagger

Konfiguracja modelu

from pydantic import BaseModel, ConfigDict

class StrictModel(BaseModel):
    """Model z customowa konfiguracja."""
    
    model_config = ConfigDict(
        # Walidacja
        strict=True,              # brak automatycznej konwersji typow
        validate_assignment=True, # waliduj przy przypisaniu
        
        # Pola
        extra="forbid",           # blad przy nieznanych polach
        frozen=True,              # immutable (jak dataclass frozen)
        
        # Aliasy
        populate_by_name=True,    # akceptuj i alias i nazwa pola
        
        # JSON
        use_enum_values=True,     # serializuj enum jako wartosc
        
        # String
        str_strip_whitespace=True, # strip() na stringach
        str_min_length=1,          # minimalna dlugosc stringow
    )
    
    name: str
    value: int

# Strict mode - brak konwersji
try:
    StrictModel(name="Test", value="123")  # BLAD! string != int
except ValidationError:
    print("Strict mode wymaga dokladnych typow")

Podsumowanie API

Metoda/Klasa Opis
BaseModel Bazowa klasa dla modeli
Field(…) Konfiguracja pola (walidacja, opis)
@field_validator Walidator pojedynczego pola
@model_validator Walidator calego modelu
.model_dump() Konwersja do dict
.model_dump_json() Konwersja do JSON string
.model_validate(data) Tworzenie z dict
.model_validate_json() Tworzenie z JSON string
.model_json_schema() Generowanie JSON Schema
ConfigDict Konfiguracja modelu
💡 Pro tips:
  • Uzywaj strict mode w produkcji dla bezpieczenstwa
  • Dodawaj opisy do Field() — trafiaja do JSON Schema i dokumentacji
  • Preferuj model_validator(mode=’after’) — masz dostep do zwalidowanych pol
  • Uzywaj BaseSettings do konfiguracji — laczy .env, env vars i defaults
  • Testuj walidacje — pisz unit testy dla custom walidatorow

📚 Bibliografia

  1. Pydantic. (2025). Pydantic Documentation. docs.pydantic.dev
  2. Pydantic. (2025). Pydantic V2 Migration Guide. docs.pydantic.dev/latest/migration
  3. Pydantic. (2025). Pydantic Settings. docs.pydantic.dev/latest/concepts/pydantic_settings
  4. GitHub. (2025). Pydantic Repository. github.com/pydantic/pydantic
  5. FastAPI. (2025). Request Body with Pydantic. fastapi.tiangolo.com