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ů
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ů
19. Repositář s demonstračními příklady
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
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
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)
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
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
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
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:
20. Odkazy na Internetu
- Pydantic: domácí stránka
https://docs.pydantic.dev/latest/ - Pydantic na GitHubu
https://github.com/pydantic/pydantic - Pydantic na PyPi
https://pypi.org/project/pydantic/ - Introduction to Python Pydantic Library
https://www.geeksforgeeks.org/python/introduction-to-python-pydantic-library/ - An introduction to Pydantic (with basic example)
https://www.slingacademy.com/article/an-introduction-to-pydantic-with-basic-example/ - Pydantic: Simplifying Data Validation in Python
https://realpython.com/python-pydantic/ - Pydantic: A Guide With Practical Examples
https://www.datacamp.com/tutorial/pydantic - Pydantic validators
https://docs.pydantic.dev/latest/concepts/validators/ - 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/ - 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/ - 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/ - 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/ - Mastering Pydantic – A Guide for Python Developers
https://dev.to/devasservice/mastering-pydantic-a-guide-for-python-developers-3kan - 7 Best Python Libraries for Validating Data
https://www.yeahhub.com/7-best-python-libraries-validating-data/ - Universally unique identifier (Wikipedia)
https://en.wikipedia.org/wiki/Universally_unique_identifier - UUID objects according to RFC 4122 (knihovna pro Python)
https://docs.python.org/3.5/library/uuid.html#uuid.uuid4 - Object identifier (Wikipedia)
https://en.wikipedia.org/wiki/Object_identifier - Digital object identifier (Wikipedia)
https://en.wikipedia.org/wiki/Digital_object_identifier - voluptuous na (na PyPi)
https://pypi.python.org/pypi/voluptuous - voluptuous (na GitHubu)
https://github.com/alecthomas/voluptuous - pytest-voluptuous 1.0.2 (na PyPi)
https://pypi.org/project/pytest-voluptuous/ - pytest-voluptuous (na GitHubu)
https://github.com/F-Secure/pytest-voluptuous - schemagic 0.9.1 (na PyPi)
https://pypi.python.org/pypi/schemagic/0.9.1 - Schemagic / Schemagic.web (na GitHubu)
https://github.com/Mechrophile/schemagic - schema 0.6.7 (na PyPi)
https://pypi.python.org/pypi/schema - schema (na GitHubu)
https://github.com/keleshev/schema - XML Schema validator and data conversion library for Python
https://github.com/brunato/xmlschema - xmlschema 0.9.7
https://pypi.python.org/pypi/xmlschema/0.9.7 - jsonschema 2.6.0
https://pypi.python.org/pypi/jsonschema - Tired of Pydantic? Try These 5 Game-Changing Python Libraries
https://developer-service.blog/tired-of-pydantic-try-these-5-game-changing-python-libraries/