Validace dat v Pythonu s využitím knihovny Pydantic

21. 8. 2025
Doba čtení: 24 minut

Sdílet

Autor: Root.cz s využitím DALL-E
Seznámíme se s vlastnostmi knihovny Pydantic. Jedná se o knihovnu určenou pro jazyk Python, která umožňuje definici struktury dat, validaci vstupních dat (ověření zda odpovídají definici), jejich serializaci i deserializaci atd.

Obsah

1. Validace dat v Pythonu s využitím knihovny Pydantic

2. Příprava projektu pro vyzkoušení možností nabízených Pydanticem

3. Základ všech modelů – třída BaseModel

4. Definice vlastního jednoduchého modelu

5. Inicializace objektu s explicitním nastavením všech jeho atributů

6. Typová kontrola atributů

7. Jak se pracuje s nepovinnými atributy?

8. Atributy s typem umožňující i reprezentaci neexistující hodnoty

9. Specializované datové typy poskytované knihovnou Pydantic

10. Explicitní kontrola zadané hodnoty atributu

11. Omezení délky řetězců zapisovaných do atributů

12. Přečtení a validace celé struktury z JSONu

13. Serializace celé struktury do formátu JSON

14. Komplikovanější datová struktura obsahující další struktury

15. Kontrola všech povinných atributů

16. Nepovinný atribut popsaný modelem

17. Výchozí hodnota nepovinných atributů

18. Obsah navazujícího článku

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Validace dat v Pythonu s využitím knihovny Pydantic

Jednou z nejpopulárnějších a současně i velmi dobře navržených knihoven pro ekosystém programovacího jazyka Python je knihovna nazvaná Pydantic. Tato knihovna je určena pro definici takzvaných modelů, které popisují strukturu dat (například výsledků dotazů přes REST API atd.). Následně umožňuje validaci takových dat, jejich serializaci, deserializaci atd. Pro definici modelů se využívá typový systém programovacího jazyka Python, tj. nativní Pythonní typové informace (type hints). To znamená, že programátoři mohou využít svých znalostí (bez typových informací se v Pythonu jen těžko tvoří rozsáhlejší aplikace) a nemusí se učit nový doménově specifický jazyk. Pydantic je v současnosti velmi často využíván společně s knihovnou FastAPI; nově se taktéž začíná využívat Pydantic AI, což je velmi zajímavý framework, který bude podrobněji popsán v samostatném článku.

2. Příprava projektu pro vyzkoušení možností nabízených Pydanticem

Tento článek je zaměřen na praktické ukázky použití knihovny Pydantic. Musíme si tedy připravit projekt v Pythonu a následně do něj knihovnu Pydantic přidat formou přímé závislosti (dependency). Pro vytvoření projektu použijeme buď nástroj PDM – viz též PDM: moderní správce balíčků a virtuálních prostředí Pythonu nebo (což je v současnosti výhodnější) nástroj uv:

$ uv init pydantic-demo
 
Initialized project `pydantic-demo` at `/tmp/ramdisk/yyy/pydantic-demo`

Projekt bude vytvořen v novém (původně prázdném) adresáři a jeho projektový soubor pyproject.toml může vypadat následovně:

[project]
name = "pydantic-demo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

Příkazem pdm add nebo uv add do projektu přidáme knihovnu pydantic:

$ uv add pydantic

Ve skutečnosti se automaticky přidají i tranzitivní závislosti, ovšem ve skutečnosti jich není mnoho:

Using CPython 3.12.10 interpreter at: /usr/bin/python3.12
Creating virtual environment at: .venv
Resolved 6 packages in 199ms
Installed 5 packages in 38ms
 + annotated-types==0.7.0
 + pydantic==2.11.7
 + pydantic-core==2.33.2
 + typing-extensions==4.14.1
 + typing-inspection==0.4.1

Výsledný projektový soubor by měl vypadat takto:

[project]
name = "pydantic-demo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "pydantic>=2.11.7",
]

Nyní si již můžeme otestovat, zda je knihovna Pydantic dostupná. Spustíme interpret Pythonu, ovšem v rámci virtuálního prostředí projektu:

$ uv run python
 
Python 3.12.10 (main, Apr 22 2025, 00:00:00) [GCC 14.2.1 20240912 (Red Hat 14.2.1-3)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Provedeme import knihovny Pydantic a necháme si zobrazit její nápovědu:

>>> import pydantic
 
>>> help(pydantic)

Výsledek by měl vypadat následovně:

Help on package pydantic:
 
NAME
    pydantic
 
PACKAGE CONTENTS
    _internal (package)
    _migration
    alias_generators
    aliases
    annotated_handlers
    class_validators
    color
    config
    dataclasses
    datetime_parse
    decorator
    deprecated (package)
    env_settings
    error_wrappers
    errors
    experimental (package)
    fields
    functional_serializers
    ...
    ...
    ...

3. Základ všech modelů – třída BaseModel

Základem všech modelů, resp. přesněji řečeno všech vývojářem vytvářených modelů, je třída nazvaná BaseModel. Od této třídy jsou totiž modely odvozovány (je tedy z pohledu objektově orientovaného programování jejich rodičovskou třídou). Třídu BaseModel využijeme ve všech demonstračních příkladech a pochopitelně si pro ni můžeme nechat zobrazit nápovědu:

Help on class BaseModel in module pydantic.main:
 
class BaseModel(builtins.object)
 |  BaseModel(**data: 'Any') -> 'None'
 |
 |  Usage docs: https://docs.pydantic.dev/2.10/concepts/models/
 |
 |  A base class for creating Pydantic models.
 |
 |  Attributes:
 |      __class_vars__: The names of the class variables defined on the model.
 |      __private_attributes__: Metadata about the private attributes of the model.
 |      __signature__: The synthesized `__init__` [`Signature`][inspect.Signature] of the model.
 |
 |      __pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
 |      __pydantic_core_schema__: The core schema of the model.
 |      __pydantic_custom_init__: Whether the model has a custom `__init__` function.
 |      __pydantic_decorators__: Metadata containing the decorators defined on the model.
 |          This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.
 |      __pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to
 |          __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
 |      __pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
 |      __pydantic_post_init__: The name of the post-init method for the model, if defined.
 |      __pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel].
 |      __pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model.
 |      __pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model.
 |
 |      __pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects.
 |      __pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.
 |
 |      __pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra]
 |          is set to `'allow'`.
 |      __pydantic_fields_set__: The names of fields explicitly set during instantiation.
 |      __pydantic_private__: Values of private attributes set on the model instance.
 ...
 ...
 ...

4. Definice vlastního jednoduchého modelu

Vyzkoušejme si nyní, jakým způsobem se vlastně postupuje při definici modelu. Je to vlastně velmi snadné – postačuje odvodit vlastní třídu od třídy BaseModel a nadefinovat její atributy, u nichž je kromě jejich jména uveden i jejich typ. Pokud bychom například chtěli nadefinovat model reprezentující uživatele, přičemž atributy by bylo jméno, příjmení, věk a příznak, že je uživatel zaregistrován, bude celý skript vypadat následovně:

from pydantic import BaseModel
 
 
class User(BaseModel):
    name: str
    surname: str
    age: int
    registered: bool
 
 
user1 = User()
print(user1)

Ve skutečnosti dojde při spuštění tohoto skriptu k běhové výjimce, protože jsme při konstrukci instance třídy User nespecifikovali hodnoty atributů a současně nebyly nastaveny jejich výchozí hodnoty. Tyto kontroly plně provádí knihovna Pydantic:

Traceback (most recent call last):
  File "/tmp/ramdisk/pydantic/pydantic-demo/user_1.py", line 11, in <module>
    user1 = User()
            ^^^^^^
  File "/tmp/ramdisk/pydantic/pydantic-demo/.venv/lib64/python3.12/site-packages/pydantic/main.py", line 253, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 4 validation errors for User
name
  Field required [type=missing, input_value={}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.11/v/missing
surname
  Field required [type=missing, input_value={}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.11/v/missing
age
  Field required [type=missing, input_value={}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.11/v/missing
registered
  Field required [type=missing, input_value={}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.11/v/missing
Poznámka: povšimněte si, že z vypsaných chybových hlášení lze poznat, k jaké chybě došlo. Jména atributů jsou ve výpise podtrženy pro větší přehlednost.

5. Inicializace objektu s explicitním nastavením všech jeho atributů

Náš první demonstrační příklad, jenž byl uvedený v páté kapitole, zhavaroval z toho důvodu, že knihovna Pydantic obecně vyžaduje, aby byly všechny atributy objektu inicializovány už při jeho konstrukci – jakmile objekt existuje, je zajištěno, že je plně validní a inicializovaný. V našem konkrétním modelu jsme uvedli, že jméno, příjmení, věk i příznak o registraci jsou povinné atributy a současně není uvedena jejich výchozí hodnota, takže je skutečně musíme explicitně nastavit:

user1 = User(name="John", surname="Doe", age=42, registered=False)

Demonstrační příklad si tedy nepatrně upravíme do této podoby:

from pydantic import BaseModel
 
 
class User(BaseModel):
    name: str
    surname: str
    age: int
    registered: bool
 
 
user1 = User(name="John", surname="Doe", age=42, registered=False)
print(user1)

O tom, že je nyní objekt vytvořen korektně, se snadno přesvědčíme spuštěním skriptu:

$ uv run user_2.py 
 
name='John' surname='Doe' age=42 registered=False

Mohlo by se zdát, že kvůli tomu, že se v modelu definují atributy třídy (a nikoli objektu), přepíšou se tyto atributy při konstrukci dalšího objektu. Ve skutečnosti tomu tak není, což si můžeme velmi snadno ověřit konstrukcí dvou objektů představujících dva různé uživatele:

from pydantic import BaseModel
 
 
class User(BaseModel):
    name: str
    surname: str
    age: int
    registered: bool
 
 
user1 = User(name="John", surname="Doe", age=42, registered=False)
user2 = User(name="Adam", surname="Bernau", age=52, registered=True)
 
print(user1)
print(user2)

Z výpisu, který získáme po spuštění takto upraveného skriptu, je zřejmé, že jsme vytvořili dva objekty, které mají rozdílné atributy:

name='John' surname='Doe' age=42 registered=False
name='Adam' surname='Bernau' age=52 registered=True

6. Typová kontrola atributů

Díky tomu, že každý atribut má v modelu stanovený svůj datový typ, dokáže knihovna Pydantic provádět striktní typovou kontrolu při každé konstrukci objektu. Ukažme si to na jednoduchém příkladu, ve kterém se budeme snažit nastavit atributy age a registered na hodnoty nekorektních typů (řetězce namísto celého čísla resp. pravdivostní hodnoty):

from pydantic import BaseModel
 
 
class User(BaseModel):
    name: str
    surname: str
    age: int
    registered: bool
 
 
user1 = User(name="John", surname="Doe", age="unknown", registered="yes")
print(user1)

Po spuštění tohoto příkladu se (korektně) vypíše první chyba, na kterou knihovna Pydantic narazila. Konkrétně se jedná o hodnotu předávanou do atributu age:

$ uv run user_3.py
 
Traceback (most recent call last):
  File "/tmp/ramdisk/pydantic/pydantic-demo/user_3.py", line 11, in <module>
    user1 = User(name="John", surname="Doe", age="unknown", registered="yes")
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/ramdisk/pydantic/pydantic-demo/.venv/lib64/python3.12/site-packages/pydantic/main.py", line 253, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for User
age
  Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='unknown', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/int_parsing
Poznámka: z toho mj. plyne, že modely by ideálně neměly obsahovat atributy typu Any. Tím se totiž připravíte o jednu z dobrých (možná dokonce nejlepších) vlastností knihovny Pydantic.

7. Jak se pracuje s nepovinnými atributy?

U mnoha modelů je nutné nějakým způsobem pracovat s nepovinnými resp. s neznámými nebo nevyčíslitelnými atributy. Tyto atributy se typicky nastavují na hodnotu None. Ovšem musíme si dát pozor na to, že v Pythonu (na rozdíl od některých jiných programovacích jazyků) má hodnota None svůj vlastní typ nazvaný NoneType, který obecně není kompatibilní s jinými typy (což je ostatně jen dobře!).

V našem testovacím modelu máme u všech atributů nastaveny konkrétní datové typy a současně u nich neuvádíme žádné výchozí hodnoty. To ovšem v důsledku znamená, že všechny atributy je nutné explicitně nastavit na nějakou hodnotu, jejíž typ musí atributu odpovídat. Neboli například do atributu age není možné předat hodnotu None, která je odlišného typu. Můžeme si to ukázat:

from pydantic import BaseModel
 
 
class User(BaseModel):
    name: str
    surname: str
    age: int
    registered: bool
 
 
user1 = User(name="John", surname="Doe", age=None, registered=None)
print(user1)

Skript se pokusíme spustit běžným způsobem:

$ uv run user_4.py

Knihovna Pydantic správně odhalí, že se pokoušíme do atributů age a registered předávat hodnoty None a přitom jsou vyžadovány hodnoty typu int resp. bool:

Traceback (most recent call last):
  File "/tmp/ramdisk/pydantic/pydantic-demo/user_4.py", line 11, in
    user1 = User(name="John", surname="Doe", age=None, registered=None)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/ramdisk/pydantic/pydantic-demo/.venv/lib64/python3.12/site-packages/pydantic/main.py", line 253, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 2 validation errors for User
age
  Input should be a valid integer [type=int_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.11/v/int_type
registered
  Input should be a valid boolean [type=bool_type, input_value=None, input_type=NoneType]
    For further information visit https://errors.pydantic.dev/2.11/v/bool_type

8. Atributy s typem umožňující i reprezentaci neexistující hodnoty

Ukažme si nyní trojici způsobů, kterými je možné specifikovat, že některé atributy mohou reprezentovat i neexistující hodnoty (neboli None). První z naznačených způsobů je nejlepší, ovšem vyžaduje novější verzi jazyka Python:

from pydantic import BaseModel
 
 
class User(BaseModel):
    name: str
    surname: str
    age: int | None
    registered: bool = False
 
 
user1 = User(name="John", surname="Doe", age=None)
print(user1)
Poznámka: povšimněte si ještě jedné změny – atribut registered má definovanou výchozí hodnotu, takže ho není nutné explicitně uvádět. Pokud není jeho hodnota uživatelem/programátorem zadána, přiřadí se mu právě nastavená výchozí hodnota.

Druhý způsob využívá typ Union, tedy „spojení“ více datových typů. Specifikujeme, že atribut age může být typu celé číslo nebo typu NoneType:

from typing import Union
 
from pydantic import BaseModel
 
 
class User(BaseModel):
    name: str
    surname: str
    age: Union[int, None]
    registered: bool = False
 
 
user1 = User(name="John", surname="Doe", age=None)
print(user1)

Ovšem vzhledem k tomu, že se Union[typ, None] používá velmi často, je k dispozici i typ Optional s prakticky stejnou sémantikou:

from typing import Optional
 
from pydantic import BaseModel
 
 
class User(BaseModel):
    name: str
    surname: str
    age: Optional[int]
    registered: bool = False
 
 
user1 = User(name="John", surname="Doe", age=None)
print(user1)

Ve všech třech případech se objekt inicializuje naprosto stejným způsobem:

name='John' surname='Doe' age=None registered=False
Poznámka: nejlepší je první způsob zápisu, takže pokud nemusíte podporovat starší interpretry Pythonu, použijte právě ten.

9. Specializované datové typy poskytované knihovnou Pydantic

V knihovně Pydantic nalezneme i některé specializované datové typy, tj. typy, které konkretizují vlastnosti nějakého více univerzálního datového typu. Velmi dobrým příkladem je typ nazvaný PositiveInt. Jedná se o zúžení původního typu int (tedy množina všech celých čísel) na čísla kladná. A příkladem atributu, který by měl mít kladnou hodnotu (nebo nemusí být vůbec známý) je atribut age, jehož definici změníme do podoby:

from pydantic import BaseModel, PositiveInt
 
 
class User(BaseModel):
    name: str
    surname: str
    age: PositiveInt | None
    registered: bool = False
 
 
user1 = User(name="John", surname="Doe", age=-1)
print(user1)

Model User nyní automaticky kontroluje nejenom to, zda je atribut age zadán, ale i fakt, že se musí striktně jednat o kladná čísla:

$ uv run user_6.py
 
Traceback (most recent call last):
  File "/tmp/ramdisk/pydantic/pydantic-demo/user_6.py", line 11, in
    user1 = User(name="John", surname="Doe", age=-1)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/ramdisk/pydantic/pydantic-demo/.venv/lib64/python3.12/site-packages/pydantic/main.py", line 253, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for User
age
  Input should be greater than 0 [type=greater_than, input_value=-1, input_type=int]
    For further information visit https://errors.pydantic.dev/2.11/v/greater_than

10. Explicitní kontrola zadané hodnoty atributu

U mnoha modelů existují další konkrétnější požadavky na hodnoty atributů. Pokud nejsou tyto požadavky popsány nějakým datovým typem nebo jeho specializací (ještě si ukážeme další příklady), je možné požadavek, resp. přesněji řečeno podmínku, která musí být splněna, zapsat programově. Příkladem může být opět kontrola zadaného věku. Předpokládejme, že se do systému mohou registrovat jen dospělé osoby, tj. jejich věk by měl dosáhnout alespoň osmnácti let. Takovou podmínku můžeme zapsat formou funkce, která buď vrací upravenou hodnotu atributu nebo vyhodí výjimku typu ValueError popř. ValidationError. Tuto funkci s podmínkou je nutné zapsat s dekorátorem field_validator tak, jak je to ukázáno v následujícím skriptu:

from pydantic import BaseModel, PositiveInt, field_validator
 
 
class User(BaseModel):
    name: str
    surname: str
    age: PositiveInt | None
    registered: bool = False
 
    @field_validator("age")
    def check_age(cls, value):
        if value < 18:
            raise ValueError("You are too young to register")
        return value
 
 
user1 = User(name="John", surname="Doe", age=17)
print(user1)

Následuje ukázka provedené kontroly:

$ uv run user_7.py
 
Traceback (most recent call last):
  File "/tmp/ramdisk/pydantic/pydantic-demo/user_7.py", line 17, in
    user1 = User(name="John", surname="Doe", age=17)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/ramdisk/pydantic/pydantic-demo/.venv/lib64/python3.12/site-packages/pydantic/main.py", line 253, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for User
age
  Value error, You are too young to register [type=value_error, input_value=17, input_type=int]
    For further information visit https://errors.pydantic.dev/2.11/v/value_error
Poznámka: povšimněte si, že se opět zobrazí všechny důležité informace jak o provedené kontrole, tak i o chybě která nastala.

11. Omezení délky řetězců zapisovaných do atributů

Ve skutečnosti není nutné (a většinou ani vhodné) kontrolovat hodnoty atributů programovým kódem. Výhodnější je využít dalších možností poskytovaných knihovnou Pydantic, která umožňuje různé kontroly (resp. požadavky) zapsat deklarativním způsobem. Dobrým příkladem může být požadavek na omezení délky jména a příjmení na (řekněme) maximálně deset znaků. Pochopitelně by bylo možné zapsat příslušnou kontrolu funkcí označenou dekorátorem, ale existuje i jednodušší způsob spočívající ve využití objektu typu Field.

K popisu tohoto objektu se ještě později vrátíme; dnes nám ovšem bude postačovat znát, že mj. je možné parametrem max_length omezit délku řetězců. Taktéž si povšimněte tří teček na místě prvního parametru. V dokumentaci se tato trojtečka nazývá ellipsis a v kontextu, ve kterém ji používáme, značí výchozí hodnotu:

from pydantic import BaseModel, Field, PositiveInt, field_validator
 
 
class User(BaseModel):
    name: str = Field(..., max_length=10)
    surname: str = Field(..., max_length=10)
    age: PositiveInt | None
    registered: bool = False
 
    @field_validator("age")
    def check_age(cls, value):
        if value < 18:
            raise ValueError("You are too young to register")
        return value
 
 
user1 = User(name="Nabuchodonozor", surname="II", age=18)
print(user1)

Otestování chování takto zapsaného modelu:

$ uv run user_8.py
 
Traceback (most recent call last):
  File "/tmp/ramdisk/pydantic/pydantic-demo/user_8.py", line 17, in
    user1 = User(name="Nabuchodonozor", surname="II", age=18)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/ramdisk/pydantic/pydantic-demo/.venv/lib64/python3.12/site-packages/pydantic/main.py", line 253, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for User
name
  String should have at most 10 characters [type=string_too_long, input_value='Nabuchodonozor', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/string_too_long
Poznámka: opět si povšimněte velmi přesné informace o chybě.

12. Přečtení a validace celé struktury z JSONu

Velmi často se v praxi setkáme s tím, že datová struktura, která se má v Pythonu zpracovat, je předána ve formátu JSON. Příkladem mohou být různé webové služby atd. Deserializace datové struktury z JSONu současně s její validací je možné provést přímo knihovnou Pydantic, a to konkrétně zavoláním třídní metody nazvané model_validate_json (tato metoda je dostupná pro každý model).

Opět si ukážeme jednoduchý příklad z deserializací a validací struktury User:

from pydantic import BaseModel, Field, PositiveInt, field_validator
 
 
class User(BaseModel):
    name: str = Field(..., max_length=10)
    surname: str = Field(..., max_length=10)
    age: PositiveInt | None
    registered: bool = False
 
    @field_validator("age")
    def check_age(cls, value):
        if value < 18:
            raise ValueError("You are too young to register")
        return value
 
 
data = """
    {"name": "John",
     "surname": "Doe",
     "age": 18
    }
"""
 
user1 = User.model_validate_json(data)
print(user1)

Výsledkem činnosti tohoto skriptu bude načtená a korektně zvalidovaná hodnota typu User:

$ uv run user_9.py 
 
name='John' surname='Doe' age=18 registered=False

13. Serializace celé struktury do formátu JSON

Opakem deserializace datové struktury z formátu JSON je naopak její serializace. I ta je knihovnou Pydantic podporována. Je přitom možné si zvolit, zda má být výsledkem nenaformátovaný JSON vhodný pro strojové zpracování, nebo JSON naformátovaný, s vhodným odsazením atd., který je mnohem čitelnější. Obě možnosti jsou ukázány v dalším demonstračním příkladu:

from pydantic import BaseModel, Field, PositiveInt, field_validator
 
 
class User(BaseModel):
    name: str = Field(..., max_length=10)
    surname: str = Field(..., max_length=10)
    age: PositiveInt | None
    registered: bool = False
 
    @field_validator("age")
    def check_age(cls, value):
        if value < 18:
            raise ValueError("You are too young to register")
        return value
 
 
user1 = User(name="John", surname="Doe", age=18)
 
as_json = user1.model_dump_json()
print(as_json)
 
print()
 
as_json = user1.model_dump_json(indent=4)
print(as_json)

Po spuštění tohoto skriptu se zobrazí dva JSONy se stejnými daty. První z nich není naformátovaný, druhý již naformátovaný je, přičemž odsazení vnitřních podstruktur má šířku čtyř mezer:

{"name":"John","surname":"Doe","age":18,"registered":false}
 
{
    "name": "John",
    "surname": "Doe",
    "age": 18,
    "registered": false
}

14. Komplikovanější datová struktura obsahující další struktury

Prozatím měla námi definovaná datová struktura User atributy s jednoduchými (primitivními) typy (sem pro jednoduchost řadím i řetězce). Ovšem v praxi je situace mnohdy odlišná a pochopitelně se setkáme i s takovými strukturami, které jako své atributy obsahují další podstruktury. I tuto variantu knihovna Pydantic pochopitelně podporuje. Příkladem může být definice modelu nazvaného Character se třemi atributy, přičemž jeden z atributů je typu Address (taktéž model) a další typu User (opět model):

from pydantic import BaseModel, Field, PositiveInt, field_validator
 
 
class Address(BaseModel):
    street: str
    house_number: PositiveInt | str
    city: str
 
 
class User(BaseModel):
    name: str = Field(..., max_length=10)
    surname: str = Field(..., max_length=10)
    age: PositiveInt | None
    registered: bool = False
 
    @field_validator("age")
    def check_age(cls, value):
        if value < 18:
            raise ValueError("You are too young to register")
        return value
 
 
class Character(BaseModel):
    role: str
    user: User
    address: Address
 
 
character = Character(
    role="Detective",
    user=User(name="Sherlock", surname="Holmes", age=42),
    address=Address(street="Baker", house_number="221B", city="London"),
)
 
as_json = character.model_dump_json(indent=4)
print(as_json)

Knihovna Pydantic pochopitelně provádí (rekurzivně) i kontrolu všech atributů. V tomto příkladu bude vše v naprostém pořádku:

$ uv run character_1.py 

Výsledek by měl vypadat následovně:

{
    "role": "Detective",
    "user": {
        "name": "Sherlock",
        "surname": "Holmes",
        "age": 42,
        "registered": false
    },
    "address": {
        "street": "Baker",
        "house_number": "221B",
        "city": "London"
    }
}

15. Kontrola všech povinných atributů

Již v předchozích kapitolách jsme si řekli, že knihovna Pydantic kontroluje, jestli jsou při konstrukci objektu zadány všechny povinné atributy. To platí i pro atributy, které jsou samy o sobě definovány modelem. Vykoušejme si tedy, co se stane ve chvíli, kdy využijeme tento model:

class Character(BaseModel):
    role: str
    user: User
    address: Address

pro konstrukci objektu, u něhož nejsou uvedeny všechny jeho povinné atributy:

character = Character(
    role="Detective",
    user=User(name="Sherlock", surname="Holmes", age=42)
)

To pochopitelně není dovoleno, takže opět dojde k běhové chybě, protože je vyžadováno zadání celé adresy, tj. atributu popsaného modelem:

$ uv run character_2.py 
 
Traceback (most recent call last):
  File "/tmp/ramdisk/pydantic/pydantic-demo/character_2.py", line 29, in
    character = Character(
                ^^^^^^^^^^
  File "/tmp/ramdisk/pydantic/pydantic-demo/.venv/lib64/python3.12/site-packages/pydantic/main.py", line 253, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for Character
address
  Field required [type=missing, input_value={'role': 'Detective', 'us...e=42, registered=False)}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.11/v/missing

Pro úplnost si uveďme celý zdrojový kód tohoto demonstračního příkladu:

from pydantic import BaseModel, Field, PositiveInt, field_validator
 
 
class Address(BaseModel):
    street: str
    house_number: PositiveInt | str
    city: str
 
 
class User(BaseModel):
    name: str = Field(..., max_length=10)
    surname: str = Field(..., max_length=10)
    age: PositiveInt | None
    registered: bool = False
 
    @field_validator("age")
    def check_age(cls, value):
        if value < 18:
            raise ValueError("You are too young to register")
        return value
 
 
class Character(BaseModel):
    role: str
    user: User
    address: Address
 
 
character = Character(
    role="Detective",
    user=User(name="Sherlock", surname="Holmes", age=42)
)
 
as_json = character.model_dump_json(indent=4)
print(as_json)

16. Nepovinný atribut popsaný modelem

Některé atributy, a to včetně atributů popsaných jiným modelem, pochopitelně mohou být nepovinné. Příkladem může být atribut address, jehož hodnotou je buď instance modelu Address nebo hodnota None (víme již, že tento požadavek lze deklarovat třemi různými způsoby; další dva vyžadují typy Union nebo Optional):

class Character(BaseModel):
    role: str
    user: User
    address: Address | None

V tomto případě můžeme adresu při konstrukci objektu typu Character legálně vynechat:

character = Character(
    role="Detective",
    user=User(name="Sherlock", surname="Holmes", age=42),
    address=None
)

Výsledek bude vypadat následovně:

$ uv run character_3.py
 
{
    "role": "Detective",
    "user": {
        "name": "Sherlock",
        "surname": "Holmes",
        "age": 42,
        "registered": false
    },
    "address": null
}

A opět si pro úplnost uvedeme celý zdrojový kód tohoto demonstračního příkladu:

from pydantic import BaseModel, Field, PositiveInt, field_validator
 
 
class Address(BaseModel):
    street: str
    house_number: PositiveInt | str
    city: str
 
 
class User(BaseModel):
    name: str = Field(..., max_length=10)
    surname: str = Field(..., max_length=10)
    age: PositiveInt | None
    registered: bool = False
 
    @field_validator("age")
    def check_age(cls, value):
        if value < 18:
            raise ValueError("You are too young to register")
        return value
 
 
class Character(BaseModel):
    role: str
    user: User
    address: Address | None
 
 
character = Character(
    role="Detective",
    user=User(name="Sherlock", surname="Holmes", age=42),
    address=None
)
 
as_json = character.model_dump_json(indent=4)
print(as_json)

17. Výchozí hodnota nepovinných atributů

Poslední úprava, kterou v rámci dnešního článku provedeme, spočívá v explicitním nastavení výchozí hodnoty nepovinného atributu – viz podtržená část upravené definice modelu:

class Character(BaseModel):
    role: str
    user: User
    address: Address | None = None

Výsledek bude naprosto stejný, jako tomu bylo v předchozím příkladu:

$ uv run character_4.py
 
{
    "role": "Detective",
    "user": {
        "name": "Sherlock",
        "surname": "Holmes",
        "age": 42,
        "registered": false
    },
    "address": null
}

A opět si, dnes již naposledy, uvedeme celý zdrojový kód upraveného skriptu:

from pydantic import BaseModel, Field, PositiveInt, field_validator
 
 
class Address(BaseModel):
    street: str
    house_number: PositiveInt | str
    city: str
 
 
class User(BaseModel):
    name: str = Field(..., max_length=10)
    surname: str = Field(..., max_length=10)
    age: PositiveInt | None
    registered: bool = False
 
    @field_validator("age")
    def check_age(cls, value):
        if value < 18:
            raise ValueError("You are too young to register")
        return value
 
 
class Character(BaseModel):
    role: str
    user: User
    address: Address | None = None
 
 
character = Character(
    role="Detective",
    user=User(name="Sherlock", surname="Holmes", age=42)
)
 
as_json = character.model_dump_json(indent=4)
print(as_json)

18. Obsah navazujícího článku

V dnešním článku jsme se seznámili pouze s naprostými základy knihovny Pydantic. Jedná se o poměrně rozsáhlou knihovnu s mnoha dalšími možnostmi. S některými z těchto pokročilejších vlastností se setkáme v navazujícím článku, ve kterém si představíme reálné (nutno podotknout, že poměrně rozsáhlé) modely, jejichž deklarace jsou popsány několika sty programovými řádky, takže se skutečně nejedná pouze o jednoduché ukázkové příklady. Taktéž si ukážeme, jakým způsobem je možné z deklarace modelů vygenerovat dokumentaci, a to jak v textové, tak i v grafické podobě.

19. Repositář s demonstračními příklady

Demonstrační příklady vytvořené v Pythonu a popsané v dnešním článku najdete v repositáři https://github.com/tisnik/most-popular-python-libs/. Následují odkazy na jednotlivé příklady:

# Příklad Stručný popis Adresa
1 user01.py definice vlastního jednoduchého modelu https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/user01.py
2 user02.py inicializace objektu s explicitním nastavením všech jeho atributů https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/user02.py
3 user03.py typová kontrola atributů https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/user03.py
4 user04.py pokus o nastavení některých atributů na hodnotu None https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/user04.py
5 user05.py atributy s typem umožňující i reprezentaci neexistující hodnoty https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/user05.py
6 user06.py ukázka použití specializovaného typu poskytovaného knihovnou Pydantic https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/user06.py
7 user07.py explicitní kontrola zadané hodnoty atributu https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/user07.py
8 user08.py omezení délky řetězců zapisovaných do atributů https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/user08.py
9 user09.py přečtení celé datové struktury z JSONu https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/user09.py
10 user10.py zápis modelu do formátu JSON https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/user10.py
       
11 character1.py komplikovanější datová struktura obsahující další struktury https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/cha­racter1.py
12 character2.py kontrola všech povinných atributů https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/cha­racter2.py
13 character3.py výchozí hodnota strukturovaných atributů https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/cha­racter3.py
14 character4.py nepovinné strukturované atributy https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/cha­racter4.py
       
15 pyproject.toml soubor s definicí projektu i s jeho závislostmi https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/py­project.toml

20. Odkazy na Internetu

  1. Pydantic: domácí stránka
    https://docs.pydantic.dev/latest/
  2. Pydantic na GitHubu
    https://github.com/pydantic/pydantic
  3. Pydantic na PyPi
    https://pypi.org/project/pydantic/
  4. Introduction to Python Pydantic Library
    https://www.geeksforgeeks­.org/python/introduction-to-python-pydantic-library/
  5. An introduction to Pydantic (with basic example)
    https://www.slingacademy.com/ar­ticle/an-introduction-to-pydantic-with-basic-example/
  6. Pydantic: Simplifying Data Validation in Python
    https://realpython.com/python-pydantic/
  7. Pydantic: A Guide With Practical Examples
    https://www.datacamp.com/tu­torial/pydantic
  8. Pydantic validators
    https://docs.pydantic.dev/la­test/concepts/validators/
  9. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy/
  10. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy (2.část)
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy-2-cast/
  11. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy (3)
    https://www.root.cz/clanky/staticke-typove-kontroly-zdrojovych-kodu-pythonu-provadene-nastrojem-mypy-3/
  12. Novinky v typovém systému přidané do Pythonu 3.12
    https://www.root.cz/clanky/novinky-v-typovem-systemu-pridane-do-pythonu-3–12/
  13. Mastering Pydantic – A Guide for Python Developers
    https://dev.to/devasservice/mastering-pydantic-a-guide-for-python-developers-3kan
  14. 7 Best Python Libraries for Validating Data
    https://www.yeahhub.com/7-best-python-libraries-validating-data/
  15. Universally unique identifier (Wikipedia)
    https://en.wikipedia.org/wi­ki/Universally_unique_iden­tifier
  16. UUID objects according to RFC 4122 (knihovna pro Python)
    https://docs.python.org/3­.5/library/uuid.html#uuid­.uuid4
  17. Object identifier (Wikipedia)
    https://en.wikipedia.org/wi­ki/Object_identifier
  18. Digital object identifier (Wikipedia)
    https://en.wikipedia.org/wi­ki/Digital_object_identifi­er
  19. voluptuous na (na PyPi)
    https://pypi.python.org/py­pi/voluptuous
  20. voluptuous (na GitHubu)
    https://github.com/alectho­mas/voluptuous
  21. pytest-voluptuous 1.0.2 (na PyPi)
    https://pypi.org/project/pytest-voluptuous/
  22. pytest-voluptuous (na GitHubu)
    https://github.com/F-Secure/pytest-voluptuous
  23. schemagic 0.9.1 (na PyPi)
    https://pypi.python.org/py­pi/schemagic/0.9.1
  24. Schemagic / Schemagic.web (na GitHubu)
    https://github.com/Mechrop­hile/schemagic
  25. schema 0.6.7 (na PyPi)
    https://pypi.python.org/pypi/schema
  26. schema (na GitHubu)
    https://github.com/keleshev/schema
  27. XML Schema validator and data conversion library for Python
    https://github.com/brunato/xmlschema
  28. xmlschema 0.9.7
    https://pypi.python.org/py­pi/xmlschema/0.9.7
  29. jsonschema 2.6.0
    https://pypi.python.org/py­pi/jsonschema
  30. Tired of Pydantic? Try These 5 Game-Changing Python Libraries
    https://developer-service.blog/tired-of-pydantic-try-these-5-game-changing-python-libraries/
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.