Ga naar hoofdinhoud

Pydantic: een gids met praktische voorbeelden

Leer wat Pydantic is, waar het voor wordt gebruikt en hoe het zich verhoudt tot alternatieven zoals Marshmallow of Python’s dataclasses.
· 12 min lezen

Verken met AI

Pydantic is Python’s populairste bibliotheek voor datavalidatie die type hints kan omzetten in validatieregels tijdens runtime.

In plaats van tientallen if isinstance()-checks en eigen validatiefuncties te schrijven, definieer je je datastructuur één keer met vertrouwde Python-syntax.

Pydantic doet de rest: binnenkomende data valideren, types converteren waar passend en duidelijke foutmeldingen geven als validatie faalt.

Bekijk deze eenvoudige Python-functie met type hints en nette documentatie:

def calculate_user_discount(age: int, is_premium_member: bool, purchase_amount: float) -> float:
   """Calculate discount percentage based on user profile and purchase amount."""
   if age >= 65:
       base_discount = 0.15
   elif is_premium_member:
       base_discount = 0.10
   else:
       base_discount = 0.05
  
   return purchase_amount * base_discount

Aan de hand van de signatuur weet je precies welk soort data erin gaat en wat er aan de andere kant uitkomt. Maar geldt dat ook voor de eindgebruiker? Die type hints zijn immers vooral documentatie — als iemand een onjuist datatype doorgeeft, trekt Python’s interpreter zich daar niets van aan:

# This runs without error, even though types are completely wrong!
discount = calculate_user_discount(True, 1, 5)
print(discount)  # Output: 0.5 (True becomes 1, 1 is truthy, so 5 * 0.10)

Dit is een klassiek voorbeeld van dynamische typing in Python. Het geeft programmeurs snelheid en vrijheid, maar dat gaat ten koste van typevalidatieproblemen die vaak pas in productie opduiken.

In deze tutorial leg ik uit hoe je Pydantic inzet voor real-world toepassingen. We beginnen met je eerste datamodel en werken toe naar geavanceerde patronen die in productie worden gebruikt. Je ontdekt hoe je geneste datastructuren valideert, eigen validators schrijft voor complexe bedrijfsregels en Pydantic integreert met populaire frameworks zoals FastAPI en SQLAlchemy.

Hieronder vind je ook onze videotutorial over de basis van Pydantic.

Wat is Pydantic?

Je speelt in feite een potje data-roulette wanneer je applicaties bouwt die met de buitenwereld communiceren. Gebruikers kunnen een telefoonnummer als string in plaats van integer indienen, een API kan null-waarden teruggeven waar jij nummers verwacht, of een configuratiebestand kan typefouten bevatten die je applicatie om 3 uur ’s nachts laten crashen.

Traditionele Python-aanpakken worden hiervoor al snel onhandelbaar. Je eindigt met validatiecode zoals dit:

def create_user(data):
   # Manual validation nightmare
   if not isinstance(data.get('age'), int):
       raise ValueError("Age must be an integer")
   if data['age'] < 0 or data['age'] > 150:
       raise ValueError("Age must be between 0 and 150")
   if not isinstance(data.get('email'), str) or '@' not in data['email']:
       raise ValueError("Invalid email format")
   if not isinstance(data.get('is_active'), bool):
       raise ValueError("is_active must be a boolean")
  
   # Finally create the user...
   return User(data['age'], data['email'], data['is_active'])

Vermenigvuldig dit met elke datastructuur in je applicatie, en je besteedt meer tijd aan validatiecode dan aan je businesslogica.

Pydantic lost dit op door drie krachtige concepten te combineren: type hints, validatie tijdens runtime en automatische serialisatie. In plaats van handmatige checks definieer je je datastructuur één keer met Python’s typeannotaties, en Pydantic doet automatisch alle validatie:

from pydantic import BaseModel, EmailStr
from typing import Optional

class User(BaseModel):
   age: int
   email: EmailStr
   is_active: bool = True
   nickname: Optional[str] = None

# Pydantic automatically validates and converts data
user_data = {
   "age": "25",  # String gets converted to int
   "email": "[email protected]",
   "is_active": "true"  # String gets converted to bool
}

user = User(**user_data)
print(user.age)  # 25 (as integer)
print(user.model_dump())  # Clean dictionary output

De features van Pydantic

De aanpak van Pydantic levert je meerdere voordelen op. Prestaties zijn het eerste voordeel dat je merkt — de kern van de validatielogica is in Rust geschreven, waardoor het in de meeste gevallen sneller is dan handgeschreven Python-validatie. Je applicatie kan duizenden requests verwerken zonder dat validatie een bottleneck wordt.

Pydantic laat zich ook integreren met moderne frameworks. FastAPI, een van de snelstgroeiende webframeworks voor Python, gebruikt Pydantic-modellen om automatisch API-documentatie te genereren, request bodies te valideren en responses te serialiseren. Als je een Pydantic-model definieert, krijg je OpenAPI-schemageneratie er gratis bij:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class UserCreate(BaseModel):
   name: str
   email: EmailStr
   age: int

@app.post("/users/")
async def create_user(user: UserCreate):
   # FastAPI automatically validates the request body
   # and generates API docs from your Pydantic model
   return {"message": f"Created user {user.name}"}

JSON-schema’s worden automatisch gegenereerd voor elk Pydantic-model. Dit betekent dat je datastructuren zelfdocumenterend worden, en je vanuit dezelfde bron clientbibliotheken, validatieregels voor frontend-applicaties of databasemodellen kunt genereren.

Pydantic vs. Dataclasses vs. Marshmallow

Maar wanneer kies je Pydantic boven alternatieven? Als je Pydantic vergelijkt met dataclasses, draait de keuze om je validatiebehoeften. Python’s @dataclass is perfect voor eenvoudige datacontainers waarbij je de input vertrouwt, terwijl Pydantic uitblinkt als je validatie, serialisatie en integratie met webframeworks nodig hebt:

from dataclasses import dataclass
from pydantic import BaseModel

# Dataclass: fast, simple, no validation
@dataclass
class UserDataclass:
   name: str
   age: int

# Pydantic: validation, serialization, framework integration
class UserPydantic(BaseModel):
   name: str
   age: int

Bij een vergelijking met Marshmallow voelt Pydantic’s type-hint-aanpak natuurlijker voor Python-ontwikkelaars, terwijl Marshmallow schema-classes gebruikt. Pydantic biedt ook betere prestaties en eenvoudigere integratie met moderne async-frameworks.

Pydantic werkt het best wanneer je API’s bouwt, externe data verwerkt, configuratie beheert, of in elke situatie waarin validatiefouten vroeg moeten worden opgevangen in plaats van later vage bugs te veroorzaken. Het zet runtime-fouten om in duidelijke, actiegerichte validatieberichten die zowel ontwikkelaars als gebruikers helpen begrijpen wat er misging.

Aan de slag met Pydantic

Laten we induiken in hoe je Pydantic gebruikt in je projecten, van installatie tot het maken van je eerste datamodellen.

Installatie en setup

Pydantic instellen is eenvoudig, maar een paar valkuilen kunnen beginners parten spelen. Ik neem je mee door het juiste installatieproces en help je fouten voorkomen die tot onnodig debuggen leiden.

Maak eerst een virtuele omgeving om de afhankelijkheden van je project te isoleren. Dit voorkomt versieconflicten die later mysterieuze importfouten kunnen veroorzaken:

# Create and activate virtual environment (works in zsh and bash)
python -m venv pydantic_env
source pydantic_env/bin/activate  # On macOS/Linux
# pydantic_env\Scripts\activate  # On Windows

Installeer nu Pydantic met het volgende commando:

pip install pydantic

Voor e-mailvalidatie, die je in echte applicaties waarschijnlijk nodig hebt (en in deze tutorial), installeer je de e-mailextras:

pip install "pydantic[email]"

De aanhalingstekens rond pydantic[email] zijn in vrijwel alle terminals belangrijk. Zonder krijg je mogelijk een "no matches found"-fout.

Let op: Krijg je ModuleNotFoundError: No module named 'pydantic'? Controleer dan het volgende:

  1. Verkeerde Python-omgeving: zorg dat je virtuele omgeving is geactiveerd
  2. Meerdere Python-versies: verifieer dat je dezelfde Python-versie gebruikt waarmee Pydantic is geïnstalleerd
  3. IDE-configuratie: je code-editor kan een andere Python-interpreter gebruiken

Belangrijke naamgevingswaarschuwing: noem je Python-bestand nooit pydantic.py. Dit creëert een circulaire import die je code breekt met verwarrende foutmeldingen. Python probeert dan jouw bestand te importeren in plaats van de echte Pydantic-bibliotheek.

Je eerste Pydantic-model

Laten we een eenvoudig usermodel bouwen om Pydantic in actie te zien. In plaats van met abstracte voorbeelden te beginnen, lossen we een echt probleem op: registratiedata van een webformulier valideren.

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

class User(BaseModel):
   name: str
   email: EmailStr
   age: int
   is_active: bool = True
   created_at: datetime = None

# Test with clean data
clean_data = {
   "name": "Alice Johnson",
   "email": "[email protected]",
   "age": 28
}

user = User(**clean_data)
print(f"User created: {user.name}, Age: {user.age}")
print(f"Model output: {user.model_dump()}")

Output:

User created: Alice Johnson, Age: 28
Model output: {'name': 'Alice Johnson', 'email': '[email protected]', 'age': 28, 'is_active': True, 'created_at': None}

Laten we uiteenzetten wat er in deze modeldefinitie gebeurt:

  • Een Pydantic-model maken: je User-klasse erft van BaseModel, wat alle validatie- en serialisatiecapaciteiten van Pydantic oplevert. Deze overerving maakt van een gewone Python-klasse een tool voor datavalidatie.
  • Velddefinities: elke regel in de klasse definieert een veld met het verwachte type. De syntax name: str vertelt Pydantic dat name een string moet zijn, age: int betekent dat age een integer moet zijn, enzovoort.
  • EmailStr uitgelegd: EmailStr is een speciaal Pydantic-type dat e-mailadressen automatisch valideert. Het komt uit het eerder geïnstalleerde pakket pydantic[email] en gebruikt reguliere expressies om te controleren of het e-mailadres geldig is. Als iemand "not-an-email" doorgeeft, geeft Pydantic een validatiefout.
  • Standaardwaarden: velden zoals is_active: bool = True hebben standaardwaarden. Als je deze velden niet meegeeft bij het maken van een user, gebruikt Pydantic de defaults. De = None voor created_at maakt dit veld optioneel.
  • Modelinstantiatie: wanneer je User(**clean_data) aanroept, pakt ** je dictionary uit en geeft elk sleutel-waardepaar als keywordargument door aan de modelconstructor.

Nu zien we Pydantic’s automatische typeconversie in actie:

# Messy data that still works
messy_data = {
   "name": "Bob Smith",
   "email": "[email protected]",
   "age": "35",  # String instead of int
   "is_active": "true"  # String instead of bool
}

user = User(**messy_data)
print(f"Age type: {type(user.age)}")  # <class 'int'>
print(f"Is active type: {type(user.is_active)}")  # <class 'bool'>

Nu zien we Pydantic’s automatische typeconversie in actie:

# Messy data that still works
messy_data = {
   "name": "Bob Smith",
   "email": "[email protected]",
   "age": "35",  # String instead of int
   "is_active": "true"  # String instead of bool
}

user = User(**messy_data)
print(f"Age type: {type(user.age)}")  # <class 'int'>
print(f"Is active type: {type(user.is_active)}")  # <class 'bool'>

Output:

Age type: <class 'int'>

Is active type: <class 'bool'>

Zoals je ziet worden de velden age en is_inactive automatisch naar de juiste formaten geconverteerd tijdens de validatie.

Als validatie faalt, geeft Pydantic duidelijke foutmeldingen:

from pydantic import ValidationError

try:
   invalid_user = User(
       name="",  # Empty string
       email="not-an-email",  # Invalid email
       age=-5  # Negative age
   )
except ValidationError as e:
   print(e)

Output:

   # Shows exactly which fields failed and why
1 validation error for User
email
 value is not a valid email address: An email address must have an @-sign. [type=value_error, input_value='not-an-email', input_type=str]

BaseModel vs. data classes

Begrijpen wanneer je Pydantic’s BaseModel gebruikt versus Python’s @dataclass helpt je het juiste gereedschap per situatie te kiezen.

Python-dataclasses zijn perfect voor eenvoudige datacontainers waarin jij de input beheerst:

from dataclasses import dataclass

@dataclass
class ProductDataclass:
   name: str
   price: float
   in_stock: bool

# Fast, simple, but no validation
product = ProductDataclass("Laptop", 999.99, True)

# This also works, even though types are wrong:
broken_product = ProductDataclass(123, "expensive", "maybe")

Pydantic-modellen voegen validatie, serialisatie en frameworkintegratie toe:

from pydantic import BaseModel, Field

class ProductPydantic(BaseModel):
   name: str = Field(min_length=1)
   price: float = Field(gt=0)  # Must be greater than 0
   in_stock: bool

# Automatic validation prevents bad data
try:
   product = ProductPydantic(name="", price=-10, in_stock="maybe")
except ValidationError as e:
   print("Validation caught the errors!")

# Valid data works perfectly
good_product = ProductPydantic(
   name="Laptop",
   price="999.99",  # String converted to float
   in_stock=True
)

Wanneer kies je welke aanpak:

  • Gebruik dataclasses voor interne datastructuren, configuratieobjecten of wanneer performance cruciaal is en je je databronnen vertrouwt
  • Gebruik Pydantic voor API-endpoints, gebruikersinput, externe dataparsing of wanneer je JSON-serialisatie nodig hebt

Pydantic voegt wat overhead toe vergeleken met dataclasses, maar die kosten zijn meestal verwaarloosbaar vergeleken met de bugs die het voorkomt en de ontwikkelingstijd die het bespaart. Voor webapplicaties maakt de automatische integratie met frameworks zoals FastAPI Pydantic de logische keuze.

De validatie- en serialisatiefuncties worden waardevoller naarmate je applicatie groeit. Starten met Pydantic-modellen geeft je een solide basis die meegroeit met je behoeften.

Datamodellen bouwen met Pydantic

Nu je de basis begrijpt, pakken we de uitdagingen aan die je tegenkomt bij het bouwen van productieklare applicaties.

Veldvalidatie en constraints

Neem een productcatalogus-API waarbij pricedata van meerdere leveranciers komt met verschillende formaten. Sommigen sturen prijzen als strings, anderen als floats, en af en toe stuurt iemand een negatieve prijs die je factureringssysteem laat crashen.

De functie Field() van Pydantic verandert eenvoudige type hints in verfijnde validatieregels die je applicatie beschermen:

from pydantic import BaseModel, Field
from decimal import Decimal
from typing import Optional

class Product(BaseModel):
   name: str = Field(min_length=1, max_length=100)
   price: Decimal = Field(gt=0, le=10000)  # Greater than 0, less than or equal to 10,000
   description: Optional[str] = Field(None, max_length=500)
   category: str = Field(..., pattern=r'^[A-Za-z\s]+$')  # Only letters and spaces
   stock_quantity: int = Field(ge=0)  # Greater than or equal to 0
   is_available: bool = True

# This works - all constraints satisfied
valid_product = Product(
   name="Wireless Headphones",
   price="199.99",  # String converted to Decimal
   description="High-quality wireless headphones",
   category="Electronics",
   stock_quantity=50
)

# This fails with clear error messages
try:
   invalid_product = Product(
       name="",  # Too short
       price=-50,  # Negative price
       category="Electronics123",  # Contains numbers
       stock_quantity=-5  # Negative stock
   )
except ValidationError as e:
   print(f"Validation errors: {len(e.errors())} issues found")

Elke parameter van Field() heeft een specifiek doel: min_length en max_length voorkomen schendingen van je databaseschema, gt en le creëren grenzen in je businesslogica, en pattern valideert geformatteerde data met reguliere expressies. De syntax Field(...) met ellips markeert verplichte velden, terwijl Field(None, ...) optionele velden met validatieregels maakt.

Typecoercion vs strikte validatie

Standaard converteert Pydantic compatibele types in plaats van ze direct af te wijzen. Die flexibiliteit werkt goed voor gebruikersinput, maar sommige scenario’s vereisen exacte typematches:

from pydantic import BaseModel, Field, ValidationError

# Default: lenient type coercion
class FlexibleOrder(BaseModel):
   order_id: int
   total_amount: float
   is_paid: bool

# These all work due to automatic conversion
flexible_order = FlexibleOrder(
   order_id="12345",  # String to int
   total_amount="99.99",  # String to float
   is_paid="true"  # String to bool
)

# Strict validation when precision matters
class StrictOrder(BaseModel):
   model_config = {"str_strip_whitespace": True, "validate_assignment": True}
  
   order_id: int = Field(strict=True)
   total_amount: float = Field(strict=True)
   is_paid: bool = Field(strict=True)

De dictionary model_config stuurt het validatiegedrag voor je hele model. De optie str_strip_whitespace ruimt strings automatisch op, terwijl validate_assignment ervoor zorgt dat wijzigingen na het maken van het model alsnog validatie triggeren. Individuele velden kunnen deze instellingen overschrijven met Field(strict=True) voor situaties die exacte types vereisen, zoals financiële berekeningen of wetenschappelijke data.

Geneste modellen en complexe data

Echte applicaties verwerken complexe, onderling verbonden datastructuren. Een e-commercebestelling bevat klantinformatie, verzendadressen en meerdere productitems ,  die elk hun eigen validatie vereisen:

from typing import List
from datetime import datetime

class Address(BaseModel):
   street: str = Field(min_length=5)
   city: str = Field(min_length=2)
   postal_code: str = Field(pattern=r'^\d{5}(-\d{4})?$')
   country: str = "USA"

class Customer(BaseModel):
   name: str = Field(min_length=1)
   email: EmailStr
   shipping_address: Address
   billing_address: Optional[Address] = None

class OrderItem(BaseModel):
   product_id: int = Field(gt=0)
   quantity: int = Field(gt=0, le=100)
   unit_price: Decimal = Field(gt=0)

class Order(BaseModel):
   order_id: str = Field(pattern=r'^ORD-\d{6}$')
   customer: Customer
   items: List[OrderItem] = Field(min_items=1)
   order_date: datetime = Field(default_factory=datetime.now)

# Complex nested data validation
order_data = {
   "order_id": "ORD-123456",
   "customer": {
       "name": "John Doe",
       "email": "[email protected]",
       "shipping_address": {
           "street": "123 Main Street",
           "city": "Anytown",
           "postal_code": "12345"
       }
   },
   "items": [
       {"product_id": 1, "quantity": 2, "unit_price": "29.99"},
       {"product_id": 2, "quantity": 1, "unit_price": "149.99"}
   ]
}

order = Order(**order_data)
print(f"Order validated with {len(order.items)} items")

Pydantic valideert geneste structuren recursief — het veld Customer wordt een volwaardig Customer-object, dat zijn eigen Address-veld valideert. De syntax List[OrderItem] valideert elk lijstitem als een OrderItem, terwijl Field(min_items=1) voorkomt dat lege orders je voorraadbeheer bereiken. Met default_factory=datetime.now creëer je unieke tijdstempels per orderinstantie.

Optionele velden en None-afhandeling

Verschillende operaties vragen om verschillende data-eisen. Bij het aanmaken van een gebruiker is volledige informatie vereist, terwijl updates gedeeltelijke wijzigingen moeten accepteren:

from typing import Optional

class UserCreate(BaseModel):
   name: str = Field(min_length=1)
   email: EmailStr
   age: int = Field(ge=13, le=120)
   phone: Optional[str] = Field(None, pattern=r'^\+?1?\d{9,15}$')

class UserUpdate(BaseModel):
   name: Optional[str] = Field(None, min_length=1)
   email: Optional[EmailStr] = None
   age: Optional[int] = Field(None, ge=13, le=120)
   phone: Optional[str] = Field(None, pattern=r'^\+?1?\d{9,15}$')

# PATCH request with partial data
update_data = {"name": "Jane Smith", "age": 30}
user_update = UserUpdate(**update_data)

# Serialize only provided fields
patch_data = user_update.model_dump(exclude_none=True)
print(f"Fields to update: {list(patch_data.keys())}")

Serialisatie zet Pydantic-objecten terug om in dictionaries of JSON-strings voor opslag of verzending. De methode model_dump() handelt deze conversie af, waarbij exclude_none=True niet-aangeleverde velden verwijdert. Dit patroon werkt perfect voor PATCH-requests waarbij clients alleen de velden sturen die ze willen wijzigen, zodat je onbedoelde overschrijvingen in je database voorkomt.

Met deze basis ben je klaar voor de volgende uitdaging: eigen validatielogica implementeren die de unieke bedrijfsregels van je applicatie vastlegt.

Aangepaste validatie en integratie in de praktijk

Productieapplicaties moeten data verwerken die niet in standaard typechecks past.

Denk aan een registratieformulier waarbij wachtwoordeisen variëren per abonnementstype, of een API die adresdata uit meerdere landen ontvangt met verschillende postcodeformaten. Zulke scenario’s vereisen aangepaste validatielogica die jouw specifieke bedrijfsregels vastlegt en soepel integreert met webframeworks en configuratiesystemen.

In deze sectie zie je hoe je praktische patronen voor aangepaste validatie implementeert, Pydantic-modellen integreert met FastAPI voor automatische API-documentatie en applicatie-instellingen beheert via omgevingsvariabelen met de .env-aanpak waar de meeste productieapplicaties op vertrouwen.

Field validators en modelvalidatie

Wanneer businesslogica bepaalt of data geldig is, verandert de decorator @field_validator je validatiefuncties in onderdeel van het model zelf. Stel een registratiesysteem voor waarin verschillende abonnementstiers verschillende wachtwoordeisen hebben:

from pydantic import BaseModel, field_validator, Field
import re

class UserRegistration(BaseModel):
   username: str = Field(min_length=3)
   email: EmailStr
   password: str
   subscription_tier: str = Field(pattern=r'^(free|pro|enterprise)$')
  
   @field_validator('password')
   @classmethod
   def validate_password_complexity(cls, password, info):
       tier = info.data.get('subscription_tier', 'free')
      
       if len(password) < 8:
           raise ValueError('Password must be at least 8 characters')
          
       if tier == 'enterprise' and not re.search(r'[A-Z]', password):
           raise ValueError('Enterprise accounts require uppercase letters')
          
       return password

De decorator @field_validator geeft je via de parameter info.data toegang tot andere veldwaarden, wat validatieregels mogelijk maakt die van meerdere velden afhangen. De validator draait nadat basis-typecontroles zijn geslaagd, dus je mag aannemen dat subscription_tier een van de toegestane waarden is.

Voor validatie die meerdere velden omvat, draait de decorator @model_validator nadat alle individuele velden zijn gevalideerd:

from datetime import datetime
from pydantic import model_validator

class EventRegistration(BaseModel):
   start_date: datetime
   end_date: datetime
   max_attendees: int = Field(gt=0)
   current_attendees: int = Field(ge=0)
  
   @model_validator(mode='after')
   def validate_event_constraints(self):
       if self.end_date <= self.start_date:
           raise ValueError('Event end date must be after start date')
          
       if self.current_attendees > self.max_attendees:
           raise ValueError('Current attendees cannot exceed maximum')
          
       return self

De parameter mode='after' zorgt dat de validator een volledig geconstrueerde modelinstantie ontvangt, ideaal voor businesslogica die alle gevalideerde velden nodig heeft. De validator moet self retourneren om succesvolle validatie aan te geven.

Integratie met FastAPI

De integratie van FastAPI met Pydantic zorgt voor automatische requestvalidatie en API-documentatie. Het kernpatroon is om aparte modellen te maken voor verschillende operaties, zodat je controle hebt over welke data elke richting op gaat:

from fastapi import FastAPI
from typing import Optional
from datetime import datetime

app = FastAPI()

class UserCreate(BaseModel):
   username: str = Field(min_length=3)
   email: EmailStr
   password: str = Field(min_length=8)

class UserResponse(BaseModel):
   id: int
   username: str
   email: EmailStr
   created_at: datetime
  
@app.post("/users/", response_model=UserResponse)
async def create_user(user: UserCreate):
   # FastAPI automatically validates the request body
   new_user = {
       "id": 1,
       "username": user.username,
       "email": user.email,
       "created_at": datetime.now()
   }
   return UserResponse(**new_user)

De scheiding tussen input- en outputmodellen heeft meerdere voordelen. Inputmodellen kunnen validatieregels en verplichte velden bevatten, terwijl outputmodellen precies bepalen welke data naar clients gaat. FastAPI genereert automatisch OpenAPI-documentatie uit je Pydantic-modellen, wat interactieve API-docs oplevert waarmee ontwikkelaars endpoints kunnen testen.

Voor updateoperaties kun je modellen maken waarin alle velden optioneel zijn:

class UserUpdate(BaseModel):
   username: Optional[str] = Field(None, min_length=3)
   email: Optional[EmailStr] = None

@app.patch("/users/{user_id}")
async def update_user(user_id: int, user_update: UserUpdate):
   # Only update provided fields
   update_data = user_update.model_dump(exclude_unset=True)
   # Your database update logic here
   return {"message": f"Updated user {user_id}"}

De parameter exclude_unset=True in PATCH-operaties zorgt dat je alleen velden bijwerkt die expliciet zijn meegegeven, zodat je onbedoelde overschrijvingen voorkomt. Dit patroon werkt perfect voor REST-API’s waarin clients gedeeltelijke updates sturen.

Configuratiebeheer met omgevingsvariabelen

Productieapplicaties hebben een veilige, deployment-vriendelijke manier van configuratiebeheer nodig. Pydantic’s BaseSettings in combinatie met .env-bestanden biedt typeveilige configuratie die werkt in development-, staging- en productieomgevingen.

Maak eerst een .env-bestand in de hoofdmap van je project:

# .env file
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
SECRET_KEY=your-secret-key-here
DEBUG=false
ALLOWED_HOSTS=localhost,127.0.0.1,yourdomain.com

Definieer daarna je settings-model:

from pydantic import BaseSettings, Field
from typing import List

class AppSettings(BaseSettings):
   database_url: str = Field(description="Database connection URL")
   secret_key: str = Field(description="Secret key for JWT tokens")
   debug: bool = Field(default=False)
   allowed_hosts: List[str] = Field(default=["localhost"])
  
   class Config:
       env_file = ".env"
       case_sensitive = False

# Load settings automatically from environment and .env file
settings = AppSettings()

De klasse BaseSettings leest automatisch uit omgevingsvariabelen, .env-bestanden en commandline-argumenten. Omgevingsvariabelen hebben voorrang op waarden uit het .env-bestand, wat het makkelijk maakt om instellingen per omgeving te overschrijven. De instelling case_sensitive = False biedt flexibiliteit in naamgeving van variabelen.

Voor complexe applicaties kun je settings logisch groeperen:

class DatabaseSettings(BaseSettings):
   url: str = Field(env="DATABASE_URL")
   max_connections: int = Field(default=5, env="DB_MAX_CONNECTIONS")

class AppSettings(BaseSettings):
   secret_key: str
   debug: bool = False
   database: DatabaseSettings = DatabaseSettings()
  
   class Config:
       env_file = ".env"

settings = AppSettings()
# Access nested configuration
db_url = settings.database.url

Deze geneste aanpak houdt gerelateerde instellingen bij elkaar, terwijl je een duidelijke scheiding behoudt tussen de verschillende componenten van je applicatie. Elke groep kan zijn eigen env-prefix en validatieregels hebben.

De .env-aanpak werkt met platforms zoals Heroku, AWS en Docker, waar omgevingsvariabelen de standaard zijn voor applicatieconfiguratie. Je applicatie krijgt typeveiligheid en validatie, terwijl je cloud-native configuratiepatronen volgt die operations-teams verwachten.

Deze patronen vormen de basis voor onderhoudbare applicaties die echte complexiteit aankunnen. Het validatiesysteem van Pydantic past zich aan jouw specifieke eisen aan en levert duidelijke foutmeldingen en automatische documentatie die je hele team helpt de verwachte datastructuren te begrijpen.

Conclusie

Je hebt nu gezien hoe Pydantic je kan besparen van het saaie werk om validatiecode met de hand te schrijven. In plaats van je functies te verrommelen met isinstance()-checks en eigen foutafhandeling, definieer je je datastructuur één keer en laat je Pydantic de rest doen.

Lees zeker de FAQ-sectie hieronder voor specifiekere Pydantic-vragen. Voor uitgebreide documentatie en geavanceerde patronen kun je terecht bij deze bronnen:

FAQs

Vertraagt Pydantic-validatie mijn applicatie?

Pydantic V2 is onder de motorkap in Rust gebouwd en daardoor erg snel. Voor de meeste applicaties is de validatie-overhead minimaal vergeleken met de bugs die het voorkomt. Je kunt model_construct() gebruiken om validatie over te slaan voor vertrouwde databronnen als performance cruciaal is.

Wanneer moet ik Pydantic gebruiken in plaats van Python-dataclasses?

Gebruik Pydantic wanneer je validatie nodig hebt, met externe databronnen werkt of API’s bouwt. Gebruik dataclasses voor eenvoudige interne datastructuren waarbij je de input vertrouwt. Pydantic blinkt uit wanneer data afkomstig is van gebruikers, API’s of configuratiebestanden.

Hoe ga ik in productie om met Pydantic-validatiefouten?

Vang ValidationError-exceptions af en haal de gestructureerde foutinformatie op met de lijst .errors(). Elke fout bevat de velden loc, msg en type, waarmee je gebruiksvriendelijke foutmeldingen kunt tonen of gedetailleerde debuginformatie kunt loggen.

Kan ik Pydantic toevoegen aan bestaande code zonder alles te breken?

Zeker! Begin met het omringen van externe datapunt-ingangen (API-requests, bestanden parsen) met Pydantic-modellen. Je kunt interne datastructuren geleidelijk omzetten. Pydantic werkt naast bestaande code en vereist niet dat je in één keer je hele codebase verandert.

Wat is de meest voorkomende fout bij het beginnen met Pydantic?

Het niet begrijpen van typecoercion. Pydantic converteert standaard compatibele types (zoals string "123" naar integer 123). Als je strikte typecontrole nodig hebt, gebruik dan Field(strict=True) of zet strict mode aan in je modelconfiguratie om automatische conversies te voorkomen.

Bexruz (Bex) Tuychiev

Auteur

Bexruz (Bex) Tuychiev

Ik ben een contentmaker op het gebied van data science met meer dan 2 jaar ervaring en een van de grootste achterbannen op Medium. Ik schrijf graag uitgebreide artikelen over AI en ML met een vleugje sarcastische stijl, want je moet toch íets doen om ze wat minder saai te maken. Ik heb meer dan 130 artikelen en een DataCamp-cursus gemaakt, en er is nog een nieuwe in de maak. Mijn content is door meer dan 5 miljoen paar ogen gezien, van wie 20k volgers werden op zowel Medium als LinkedIn. 

Onderwerpen

Gerelateerd