Validace dat v Pythonu s využitím knihovny Pydantic (2. část)

2. 9. 2025
Doba čtení: 30 minut

Sdílet

Autor: Root.cz s využitím DALL-E
Dnes si ukážeme načtení konfigurace ze souborů typu YAML, pochopitelně s plnou validací načítaných dat, možností doplnění hodnot z proměnných prostředí nebo ze zadaných souborů do načítané konfigurace.

Obsah

1. Validace dat v Pythonu s využitím knihovny Pydantic (2. část)

2. Načtení konfigurace z konfiguračního souboru s konstrukcí modelu

3. Přidání nové závislosti do projektového souboru pyproject.toml

4. Realizace načtení konfiguračního souboru s inicializací a validací modelu

5. Export modelu do formátu JSON

6. Chování modelu v případě, že konfigurační soubor obsahuje neznámé prvky

7. Detekce nadbytečných atributů při validaci modelu

8. Demonstrační příklad: detekce neznámých prvků v konfiguračním souboru

9. Model s komplikovanější konfigurační strukturou

10. Úplný zdrojový kód příkladu s rozšířeným modelem

11. Konfigurace připojení k databázi jako součást konfigurace služby

12. Načtení hodnot vybraných konfiguračních parametrů z proměnných prostředí

13. Instalace balíčku pyaml-env

14. Realizace načtení konfiguračního souboru s náhradou hodnot za obsah proměnných prostředí

15. Načtení hodnoty parametrů ze souboru

16. Demonstrační příklad: načtení konfigurace se získáním hesla ze samostatného souboru

17. Kontrola, zda parametr typu řetězec obsahuje očekávanou hodnotu

18. Demonstrační příklad: kontrola obsahu vybraných parametrů typu řetězec

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

20. Odkazy na Internetu

1. Validace dat v Pythonu s využitím knihovny Pydantic (2. část)

Na úvodní článek o knihovně Pydantic dnes navážeme. Zatímco minule jsme si ukazovali především pouze relativně jednoduché (a také do jisté míry umělé) demonstrační příklady, dnes se zaměříme více prakticky. Řekneme si totiž, jakým způsobem je možné realizovat načtení konfigurace z konfiguračních souborů (v dnes populárním formátu YAML) s plnou validací načítaných dat (což je velmi důležité), možností doplnění hodnot z proměnných prostředí (environment variables) nebo ze zadaných souborů do načítané konfigurace atd. Všechny popisované vlastnosti knihovny Pydantic budou vyzkoušeny na konfiguračním souboru, jehož základní struktura do značné míry odpovídá konfiguraci reálného projektu; model však byl pro účely tohoto článku do značné míry zjednodušen.

2. Načtení konfigurace z konfiguračního souboru s konstrukcí modelu

Ve vývojářské praxi se velmi často setkáme s požadavkem na načtení konfigurace nějakého programu (resp. služby) z konfiguračního souboru. To většinou zahrnuje i nutnost validace dat (včetně validace povolených hodnot atd.), ale i nutnost načtení hodnot ze souborů zmíněných v konfiguračním souboru (hesla, certifikáty) nebo načtení některých hodnot z proměnných prostředí (environment variables). Právě těmito požadavky a způsobem jejich řešení se budeme zabývat v dnešním článku. Ukážeme si, jak načíst a zvalidovat konfigurační soubory s touto strukturou:

name: Service configuration #1
service:
  host: 127.0.0.1
  port: 8080                      # povoleny jen hodnoty od 0 do 65535
  auth_enabled: false
  workers: 5                      # povoleny jen kladné hodnoty
  database:
    host: db1.pg.com
    db: test
    user: !ENV ${DB_USER}         # hodnota je uložena v proměnné prostředí
    password_file: password.txt   # hodnota je uložena v souboru
    ssl_mode: verify-ca           # povoleny jen některé hodnoty
    gss_encmode: disable

3. Přidání nové závislosti do projektového souboru pyproject.toml

Podobně, jako tomu bylo v úvodním článku, budeme i dnes zkoumat všechny popisované vlastnosti knihovny Pydantic na krátkých demonstračních příkladech. Vzhledem k tomu, že budeme načítat obsah souborů ve formátu YAML, budeme muset do projektového souboru přidat další závislost, a to konkrétně balíček pyaml.

Původně vypadal projektový soubor pyproject.toml následovně:

[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",
]

Příkazem uv add pyaml nebo pdm add pyaml do něj přidáme další závislost:

$ uv add pyaml
 
⠙ pydantic-demo==0.1.0
Resolved 8 packages in 130ms
░░░░░░░░░░░░░░░░░░░░ [0/2] Installing wheels...                                                                                         warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
         If the cache and target directories are on different filesystems, hardlinking may not be supported.
         If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
Installed 2 packages in 14ms
 + pyaml==25.7.0
 + pyyaml==6.0.2

Nyní by měl mít projektový soubor tento obsah:

[project]
name = "pydantic-demo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "pyaml>=25.7.0",
    "pydantic>=2.11.7",
]
Poznámka: současně se změní i obsah uv.lock nebo pdm.lock.

4. Realizace načtení konfiguračního souboru s inicializací a validací modelu

V dnešním prvním demonstračním příkladu je implementována funkce pro načtení konfiguračního souboru i pro inicializaci modelu s jeho validací. Využívá se zde toho, že funkce yaml.safe_load() vrací slovník (dictionary) s obsahem souboru YAML a slovník můžeme s využitím ** při volání funkce „rozložit“ na pojmenované parametry funkce. Celé načtení s validací a inicializací modelu je tedy záležitostí několika programových řádků:

with open(filename, encoding="utf-8") as fin:
    config_dict = yaml.safe_load(fin)
    return Configuration(**config_dict)

Úplný zdrojový kód dnešního prvního demonstračního příkladu vypadá následovně:

"""Global configuration."""
 
import yaml
 
from pydantic import (
    BaseModel,
)
 
 
class Configuration(BaseModel):
    """Global configuration."""
 
    name: str
 
 
def load_configuration(filename: str) -> Configuration:
    """Load configuration from YAML file."""
    with open(filename, encoding="utf-8") as fin:
        config_dict = yaml.safe_load(fin)
        return Configuration(**config_dict)
 
 
configuration = load_configuration("config_01.yaml")
print(configuration)

Samotný konfigurační soubor má jen jediný řádek:

name: Service configuration #1

Otestujme si, že příklad lze spustit a že skutečně konfigurační soubor načte a zpracuje:

$ uv run config_01.py
 
name='Service configuration'

5. Export modelu do formátu JSON

V praxi se mi osvědčila realizace exportu celého modelu (po všech jeho validacích, načtení obsahu proměnných prostředí atd.) do formátu JSON. Může se například jednat o přepínač na příkazové řádce, po jehož zadání se provede export (dump je možná lepší výraz) celého modelu do JSONu, který je následně možné prozkoumat atd., a to bez nutnosti spouštět debugger nebo analyzovat logy. Samotný export je vlastně triviální, protože ho již známe z úvodního článku o knihovně Pydantic; realizace je provedena formou metody modelu:

    def dump(self, filename: str = "configuration.json") -> None:
        """Dump actual configuration into JSON file."""
        with open(filename, "w", encoding="utf-8") as fout:
            fout.write(self.model_dump_json(indent=4))

Zdrojový kód demonstračního příkladu se rozšířil do této podoby:

"""Global configuration."""
 
import yaml
 
from pydantic import (
    BaseModel,
)
 
 
class Configuration(BaseModel):
    """Global configuration."""
 
    name: str
 
    def dump(self, filename: str = "configuration.json") -> None:
        """Dump actual configuration into JSON file."""
        with open(filename, "w", encoding="utf-8") as fout:
            fout.write(self.model_dump_json(indent=4))
 
 
def load_configuration(filename: str) -> Configuration:
    """Load configuration from YAML file."""
    with open(filename, encoding="utf-8") as fin:
        config_dict = yaml.safe_load(fin)
        return Configuration(**config_dict)
 
 
configuration = load_configuration("config_02.yaml")
 
configuration.dump("config_02.json")

Pro tento vstupní konfigurační soubor:

name: Service configuration #1

…by měl po spuštění příkladu vzniknout soubor config02.json s tímto obsahem:

{
    "name": "Service configuration"
}
Poznámka: jak je z výpisu patrné, je výsledný JSON naformátován, takže se velmi dobře čte.

6. Chování modelu v případě, že konfigurační soubor obsahuje neznámé prvky

Náš výchozí model Configuration je (alespoň prozatím) odvozen od třídy BaseModel:

class Configuration(BaseModel):
     ...
     ...
     ...

Při inicializaci takového modelu se ignorují neznámé prvky, což je poněkud problematické chování, které později opravíme (problematické je proto, že se mohou ignorovat například přepisy v názvu atributu v případě, že je definována jeho výchozí hodnota). Nicméně se nejdříve podívejme, jak to vypadá v praxi. Pokusme se například načíst následující konfigurační soubor config03.yaml:

name: Service configuration #1
foo: 1
bar: "*"
baz: true

Náš demonstrační příklad tento soubor bez problému načte, provede inicializaci a validaci modelu a následně model vyexportuje do formátu JSON. Zde pochopitelně budou uvedeny jen známé prvky (atributy):

{
    "name": "Service configuration"
}

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

"""Global configuration."""
 
import yaml
 
from pydantic import (
    BaseModel,
)
 
 
class Configuration(BaseModel):
    """Global configuration."""
 
    name: str
 
    def dump(self, filename: str = "configuration.json") -> None:
        """Dump actual configuration into JSON file."""
        with open(filename, "w", encoding="utf-8") as fout:
            fout.write(self.model_dump_json(indent=4))
 
 
def load_configuration(filename: str) -> Configuration:
    """Load configuration from YAML file."""
    with open(filename, encoding="utf-8") as fin:
        config_dict = yaml.safe_load(fin)
        return Configuration(**config_dict)
 
 
configuration = load_configuration("config_03.yaml")
 
configuration.dump("config_03.json")

7. Detekce nadbytečných atributů při validaci modelu

Ve skutečnosti je relativně snadné „donutit“ knihovnu Pydantic k tomu, aby validace modelu vyhodila výjimku v případě, že se na vstupu naleznou neznámé prvky. Musíme změnit konfiguraci modelu specifikací atributu model_config. Ovšem abychom nemuseli tento řádek přidávat do každé třídy odvozené od BaseModel, vytvoříme ve třídní hierarchii novou třídu nazvanou například ConfigurationBase, od které budeme odvozovat všechny další modely:

class ConfigurationBase(BaseModel):
    """Base class for all configuration models that rejects unknown fields."""
 
    model_config = ConfigDict(extra="forbid")

Náš model reprezentovaný třídou Configuration nyní nebude odvozen přímo od třídy BaseModel, ale od výše uvedené třídy nazvané ConfigurationBase:

class Configuration(ConfigurationBase):
    ...
    ...
    ...

8. Demonstrační příklad: detekce neznámých prvků v konfiguračním souboru

Výše uvedenou úpravu definice modelu provedeme v dalším demonstračním příkladu. Ten se od předchozího příkladu odlišuje pouze v tom, že třída Configuration je odvozena od třídy ConfigurationBase a nikoli přímo od třídy BaseModel:

"""Global configuration."""
 
 
import yaml
 
from pydantic import (
    BaseModel,
    ConfigDict,
)
 
 
class ConfigurationBase(BaseModel):
    """Base class for all configuration models that rejects unknown fields."""
 
    model_config = ConfigDict(extra="forbid")
 
 
class Configuration(ConfigurationBase):
    """Global configuration."""
 
    name: str
 
    def dump(self, filename: str = "configuration.json") -> None:
        """Dump actual configuration into JSON file."""
        with open(filename, "w", encoding="utf-8") as fout:
            fout.write(self.model_dump_json(indent=4))
 
 
def load_configuration(filename: str) -> Configuration:
    """Load configuration from YAML file."""
    with open(filename, encoding="utf-8") as fin:
        config_dict = yaml.safe_load(fin)
        return Configuration(**config_dict)
 
 
configuration = load_configuration("config_04.yaml")
 
configuration.dump("config_04.json")

Příklad se pokusíme spustit s tím, že vstupní konfigurační soubor config04.yaml bude vypadat takto:

foo: 1
bar: "*"
baz: true

Po spuštění dojde při validaci modelu k detekci nadbytečných prvků a skript (podle očekávání) zhavaruje. Podtržené řádky obsahují jména nadbytečných atributů:

Traceback (most recent call last):
  File "/tmp/ramdisk/pydantic/pydantic-demo/config_04.py", line 36, in <module>
    configuration = load_configuration("config_04.yaml")
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/ramdisk/pydantic/pydantic-demo/config_04.py", line 33, in load_configuration
    return Configuration(**config_dict)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  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: 3 validation errors for Configuration
foo
  Extra inputs are not permitted [type=extra_forbidden, input_value=1, input_type=int]
    For further information visit https://errors.pydantic.dev/2.11/v/extra_forbidden
bar
  Extra inputs are not permitted [type=extra_forbidden, input_value='*', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/extra_forbidden
baz
  Extra inputs are not permitted [type=extra_forbidden, input_value=True, input_type=bool]
    For further information visit https://errors.pydantic.dev/2.11/v/extra_forbidden

9. Model s komplikovanější konfigurační strukturou

Naše konfigurace, kterou jsme načítali a zpracovávali, byla prozatím značně triviální, protože obsahovala pouze jméno služby, ale už žádné další atributy. Takže si v rámci dalšího kroku konfiguraci rozšíříme, například tak, že do ní přidáme podsekci s informacemi o webové službě, která se má spustit na zadaném portu. Specifikovat bude možné i to, zda se má povolit autentizace (a popř. i autorizace), v kolika procesech se má služba spustit atd. Konfigurační soubor může vypadat například následovně:

name: Service configuration #1
service:
  host: 127.0.0.1
  port: 8080
  auth_enabled: false
  workers: 5

Do zdrojového kódu přidáme další třídu představující model. Povšimněte si, že kontrolujeme i rozsah hodnot portu. Stejně tak by bylo možné omezit počet procesů atd.:

class ServiceConfiguration(ConfigurationBase):
    """Service configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 8080
    auth_enabled: bool = False
    workers: PositiveInt = 1
 
    @model_validator(mode="after")
    def check_service_configuration(self) -> Self:
        """Check service configuration."""
        if self.port > 65535:
            raise ValueError("Port value should be less than 65536")
        return self

Základní model rozšíříme o další atribut nazvaný service:

class Configuration(ConfigurationBase):
    """Global configuration."""
 
    name: str
    service: ServiceConfiguration
Poznámka: nyní se při načítání modelu bude rekurzivně zpracovávat i model ServiceConfiguration.

10. Úplný zdrojový kód příkladu s rozšířeným modelem

To je vše – výsledný demonstrační příklad bude vypadat následovně:

"""Global configuration."""
 
import yaml
 
from typing_extensions import Self
 
from pydantic import (
    BaseModel,
    ConfigDict,
    PositiveInt,
    model_validator,
)
 
 
class ConfigurationBase(BaseModel):
    """Base class for all configuration models that rejects unknown fields."""
 
    model_config = ConfigDict(extra="forbid")
 
 
class ServiceConfiguration(ConfigurationBase):
    """Service configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 8080
    auth_enabled: bool = False
    workers: PositiveInt = 1
 
    @model_validator(mode="after")
    def check_service_configuration(self) -> Self:
        """Check service configuration."""
        if self.port > 65535:
            raise ValueError("Port value should be less than 65536")
        return self
 
 
class Configuration(ConfigurationBase):
    """Global configuration."""
 
    name: str
    service: ServiceConfiguration
 
    def dump(self, filename: str = "configuration.json") -> None:
        """Dump actual configuration into JSON file."""
        with open(filename, "w", encoding="utf-8") as fout:
            fout.write(self.model_dump_json(indent=4))
 
 
def load_configuration(filename: str) -> Configuration:
    """Load configuration from YAML file."""
    with open(filename, encoding="utf-8") as fin:
        config_dict = yaml.safe_load(fin)
        return Configuration(**config_dict)
 
 
configuration = load_configuration("config_05.yaml")
 
configuration.dump("config_05.json")

Po spuštění tohoto skriptu by se měl vygenerovat soubor config05.json s tímto obsahem:

{
    "name": "Service configuration",
    "service": {
        "host": "127.0.0.1",
        "port": 8080,
        "auth_enabled": false,
        "workers": 5
    }
}

11. Konfigurace připojení k databázi jako součást konfigurace služby

Konfiguraci služby dále rozšíříme o další konfigurační volby. Ty se budou týkat připojení k databázi; v tomto konkrétním případě k PostgreSQL. Konfigurovat bude možné jméno počítače a port, na kterém databáze přijímá požadavky na připojení. Poté pochopitelně jméno databáze, jméno uživatele i jeho heslo. A konečně umožníme zadat i režimy SSL a GSS. Konfigurační soubor může vypadat následovně:

name: Service configuration #1
service:
  host: 127.0.0.1
  port: 8080
  auth_enabled: false
  workers: 5
  database:
    host: db1.pg.com
    db: test
    user: test
    password: 123qwe
    ssl_mode: prefer
    gss_encmode: prefer

Model s konfigurací databáze lze realizovat relativně snadno (konstanty POSTGRES_DEFAULT_SSL_MODE a POSTGRES_DEFAULT_GSS_ENCMODE obsahují výchozí nastavení ve formě řetězců):

class DatabaseConfiguration(ConfigurationBase):
    """Database configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 5432
    db: str
    user: str
    password: str
    ssl_mode: str = POSTGRES_DEFAULT_SSL_MODE
    gss_encmode: str = POSTGRES_DEFAULT_GSS_ENCMODE
    ca_cert_path: Optional[FilePath] = None
 
    @model_validator(mode="after")
    def check_postgres_configuration(self) -> Self:
        """Check PostgreSQL configuration."""
        if self.port > 65535:
            raise ValueError("Port value should be less than 65536")
        return self

Úplný zdrojový kód skriptu, který akceptuje i konfiguraci připojení k databázi, by mohl vypadat následovně:

"""Global configuration."""
 
import yaml
 
from typing_extensions import Self, Optional
 
from pydantic import (
    BaseModel,
    ConfigDict,
    PositiveInt,
    model_validator,
    FilePath,
)
 
 
# PostgreSQL connection constants
# See: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLMODE
POSTGRES_DEFAULT_SSL_MODE = "prefer"
# See: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-GSSENCMODE
POSTGRES_DEFAULT_GSS_ENCMODE = "prefer"
 
 
class ConfigurationBase(BaseModel):
    """Base class for all configuration models that rejects unknown fields."""
 
    model_config = ConfigDict(extra="forbid")
 
 
class DatabaseConfiguration(ConfigurationBase):
    """Database configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 5432
    db: str
    user: str
    password: str
    ssl_mode: str = POSTGRES_DEFAULT_SSL_MODE
    gss_encmode: str = POSTGRES_DEFAULT_GSS_ENCMODE
    ca_cert_path: Optional[FilePath] = None
 
    @model_validator(mode="after")
    def check_postgres_configuration(self) -> Self:
        """Check PostgreSQL configuration."""
        if self.port > 65535:
            raise ValueError("Port value should be less than 65536")
        return self
 
 
class ServiceConfiguration(ConfigurationBase):
    """Service configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 8080
    auth_enabled: bool = False
    workers: PositiveInt = 1
    database: DatabaseConfiguration
 
    @model_validator(mode="after")
    def check_service_configuration(self) -> Self:
        """Check service configuration."""
        if self.port > 65535:
            raise ValueError("Port value should be less than 65536")
        return self
 
 
class Configuration(ConfigurationBase):
    """Global configuration."""
 
    name: str
    service: ServiceConfiguration
 
    def dump(self, filename: str = "configuration.json") -> None:
        """Dump actual configuration into JSON file."""
        with open(filename, "w", encoding="utf-8") as fout:
            fout.write(self.model_dump_json(indent=4))
 
 
def load_configuration(filename: str) -> Configuration:
    """Load configuration from YAML file."""
    with open(filename, encoding="utf-8") as fin:
        config_dict = yaml.safe_load(fin)
        return Configuration(**config_dict)
 
 
configuration = load_configuration("config_06.yaml")
 
configuration.dump("config_06.json")

Po spuštění tohoto skriptu se načte konfigurační soubor config06.yaml a aktuální konfigurace se vyexportuje do souboru se jménem config06.json:

{
    "name": "Service configuration",
    "service": {
        "host": "127.0.0.1",
        "port": 8080,
        "auth_enabled": false,
        "workers": 5,
        "database": {
            "host": "db1.pg.com",
            "port": 5432,
            "db": "test",
            "user": "test",
            "password": "123qwe",
            "ssl_mode": "prefer",
            "gss_encmode": "prefer",
            "ca_cert_path": null
        }
    }
}

12. Načtení hodnot vybraných konfiguračních parametrů z proměnných prostředí

V praxi se poměrně často setkáme s požadavkem na to, aby se hodnoty některých konfiguračních parametrů načítaly z proměnných prostředí – tj. aby nebyly přímo součástí konfiguračního souboru. Typicky se jedná o údaje, které musí být z nějakých důvodů utajené (hesla, certifikáty, klíče) nebo o údaje, které se liší podle toho, na jakém počítači je služba nasazena (typ a verze operačního systému apod.). Příklad jsme si již ukazovali ve druhé kapitole, takže jen krátce:

  database:
    host: db1.pg.com
    db: test
    user: !ENV ${DB_USER}         # hodnota je uložena v proměnné prostředí
Poznámka: existuje hned několik ustálených způsobů, jak v konfiguračním souboru zapsat informaci, že se konkrétní hodnota má přečíst z proměnné prostředí. Někdy se setkáme se zápisem:
  database:
    host: db1.pg.com
    db: test
    user: ${env.DB_USER}         # hodnota je uložena v proměnné prostředí

13. Instalace balíčku pyaml-env

Načtení hodnot některých konfiguračních parametrů z proměnných prostředí je možné realizovat různými způsoby, například rekurzivním průchodem celým modelem a náhradou vhodně označených parametrů za obsah proměnných prostředí. Popř. lze využít již existujících knihoven, které tuto funkcionalitu nabízí. Jedna z těchto knihoven se jmenuje pyaml-env. Dokáže načíst konfiguraci uloženou ve formátu YAML a nahradit označené parametry za obsah proměnných prostředí (popř. pokud proměnná prostředí neexistuje se doplní N/A apod.).

Nejdříve je pochopitelně nutné tuto knihovnu nainstalovat, což je v praxi při použití pdm nebo uv snadné:

$ uv add pyaml-env
 
Resolved 9 packages in 413ms
Prepared 1 package in 94ms
Installed 1 package in 4ms
 + pyaml-env==1.2.2

Projektový soubor pyproject.toml by nyní měl obsahovat tři primární závislosti na knihovnách pyaml, pyaml-env a pydantic:

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

14. Realizace načtení konfiguračního souboru s náhradou hodnot za obsah proměnných prostředí

Původně vypadala funkce pro načtení konfigurace ze souboru ve formátu YAML následovně:

def load_configuration(filename: str) -> Configuration:
    """Load configuration from YAML file."""
    with open(filename, encoding="utf-8") as fin:
        config_dict = yaml.safe_load(fin)
        return Configuration(**config_dict)

Nyní provedeme nepatrnou změnu jediného řádku (viz podtrženou část kódu):

from pyaml_env import parse_config
 
def load_configuration(filename: str) -> Configuration:
    """Load configuration from YAML file."""
    config_dict = parse_config(filename)
    return Configuration(**config_dict)

Po této nepatrné úpravě vyexportujeme dvě proměnné prostředí:

$ export DB_USER=tester
$ export DB_PASSWORD=top_secret

A pokusíme se načíst tuto konfiguraci:

name: Service configuration #1
service:
  host: 127.0.0.1
  port: 8080
  auth_enabled: false
  workers: 5
  database:
    host: db1.pg.com
    db: test
    user: !ENV ${DB_USER}
    password: !ENV ${DB_PASSWORD}

Výsledek by měl vypadat takto:

{
    "name": "Service configuration",
    "service": {
        "host": "127.0.0.1",
        "port": 8080,
        "auth_enabled": false,
        "workers": 5,
        "database": {
            "host": "db1.pg.com",
            "port": 5432,
            "db": "test",
            "user": "tester",
            "password": "top_secret",
            "ssl_mode": "prefer",
            "gss_encmode": "prefer",
            "ca_cert_path": null
        }
    }
}

Pro úplnost si ještě ukažme celý zdrojový kód takto upraveného demonstračního příkladu:

"""Global configuration."""
 
from pyaml_env import parse_config
 
from typing_extensions import Self, Optional
 
from pydantic import (
    BaseModel,
    ConfigDict,
    PositiveInt,
    model_validator,
    FilePath,
)
 
 
# PostgreSQL connection constants
# See: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLMODE
POSTGRES_DEFAULT_SSL_MODE = "prefer"
# See: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-GSSENCMODE
POSTGRES_DEFAULT_GSS_ENCMODE = "prefer"
 
 
class ConfigurationBase(BaseModel):
    """Base class for all configuration models that rejects unknown fields."""
 
    model_config = ConfigDict(extra="forbid")
 
 
class DatabaseConfiguration(ConfigurationBase):
    """Database configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 5432
    db: str
    user: str
    password: str
    ssl_mode: str = POSTGRES_DEFAULT_SSL_MODE
    gss_encmode: str = POSTGRES_DEFAULT_GSS_ENCMODE
    ca_cert_path: Optional[FilePath] = None
 
    @model_validator(mode="after")
    def check_postgres_configuration(self) -> Self:
        """Check PostgreSQL configuration."""
        if self.port > 65535:
            raise ValueError("Port value should be less than 65536")
        return self
 
 
class ServiceConfiguration(ConfigurationBase):
    """Service configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 8080
    auth_enabled: bool = False
    workers: PositiveInt = 1
    database: DatabaseConfiguration
 
    @model_validator(mode="after")
    def check_service_configuration(self) -> Self:
        """Check service configuration."""
        if self.port > 65535:
            raise ValueError("Port value should be less than 65536")
        return self
 
 
class Configuration(ConfigurationBase):
    """Global configuration."""
 
    name: str
    service: ServiceConfiguration
 
    def dump(self, filename: str = "configuration.json") -> None:
        """Dump actual configuration into JSON file."""
        with open(filename, "w", encoding="utf-8") as fout:
            fout.write(self.model_dump_json(indent=4))
 
 
def load_configuration(filename: str) -> Configuration:
    """Load configuration from YAML file."""
    config_dict = parse_config(filename)
    return Configuration(**config_dict)
 
 
configuration = load_configuration("config_07.yaml")
 
configuration.dump("config_07.json")

15. Načtení hodnoty parametrů ze souboru

Kromě načtení některých konfiguračních parametrů z proměnných prostředí se ještě v některých případech setkáme s dalším požadavkem – hodnoty (hesla, certifikáty atd.) jsou uloženy v samostatných souborech a konfigurační soubor pouze obsahuje jména těchto souborů. Příkladem může být možnost uložení hesla pro připojení do databáze v samostatném souboru, takže konfigurační soubor obsahuje jen jméno (a cestu) k tomuto souboru:

name: Service configuration #1
service:
  host: 127.0.0.1
  port: 8080
  auth_enabled: false
  workers: 5
  database:
    host: db1.pg.com
    db: test
    user: !ENV ${DB_USER}
    password_file: password.txt

Implementace pomocné třídy a dvou pomocných funkcí pro načítání parametrů ze souborů může vypadat takto:

class InvalidConfigurationError(Exception):
    """Lightspeed configuration is invalid."""
 
 
def get_attribute_from_file(file_path: FilePath) -> Optional[str]:
    """Retrieve value of an attribute from a file."""
    with open(file_path, encoding="utf-8") as f:
        return f.read().rstrip()
 
 
def file_check(file_path: FilePath) -> None:
    """Check that path is a readable regular file."""
    if not os.path.isfile(file_path):
        raise InvalidConfigurationError(f"{desc} '{path}' is not a file")
    if not os.access(file_path, os.R_OK):
        raise InvalidConfigurationError(f"{desc} '{path}' is not readable")

Samotné načtení hesla může být součástí modelu, ze konkrétně modelu DatabaseConfiguration:

    @model_validator(mode="after")
    def check_postgres_configuration(self) -> Self:
        """Check PostgreSQL configuration."""
        file_check(self.password_file)
        self.password = get_attribute_from_file(self.password_file)
        return self

16. Demonstrační příklad: načtení konfigurace se získáním hesla ze samostatného souboru

V dalším, dnes již předposledním demonstračním příkladu, je realizováno načtení hesla použitého pro připojení k databázi ze samostatného souboru, jehož jméno je uloženo v konfiguračním souboru:

"""Global configuration."""
 
import os
 
from pyaml_env import parse_config
 
from typing_extensions import Self, Optional
 
from pydantic import (
    BaseModel,
    ConfigDict,
    PositiveInt,
    model_validator,
    FilePath,
)
 
 
# PostgreSQL connection constants
# See: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLMODE
POSTGRES_DEFAULT_SSL_MODE = "prefer"
# See: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-GSSENCMODE
POSTGRES_DEFAULT_GSS_ENCMODE = "prefer"
 
 
class InvalidConfigurationError(Exception):
    """Lightspeed configuration is invalid."""
 
 
def get_attribute_from_file(file_path: FilePath) -> Optional[str]:
    """Retrieve value of an attribute from a file."""
    with open(file_path, encoding="utf-8") as f:
        return f.read().rstrip()
 
 
def file_check(file_path: FilePath) -> None:
    """Check that path is a readable regular file."""
    if not os.path.isfile(file_path):
        raise InvalidConfigurationError(f"{desc} '{path}' is not a file")
    if not os.access(file_path, os.R_OK):
        raise InvalidConfigurationError(f"{desc} '{path}' is not readable")
 
 
class ConfigurationBase(BaseModel):
    """Base class for all configuration models that rejects unknown fields."""
 
    model_config = ConfigDict(extra="forbid")
 
 
class DatabaseConfiguration(ConfigurationBase):
    """Database configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 5432
    db: str
    user: str
    password: str = None
    password_file: FilePath
    ssl_mode: str = POSTGRES_DEFAULT_SSL_MODE
    gss_encmode: str = POSTGRES_DEFAULT_GSS_ENCMODE
    ca_cert_path: Optional[FilePath] = None
 
    @model_validator(mode="after")
    def check_postgres_configuration(self) -> Self:
        """Check PostgreSQL configuration."""
        if self.port > 65535:
            raise ValueError("Port value should be less than 65536")
        file_check(self.password_file)
        self.password = get_attribute_from_file(self.password_file)
        return self
 
 
class ServiceConfiguration(ConfigurationBase):
    """Service configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 8080
    auth_enabled: bool = False
    workers: PositiveInt = 1
    database: DatabaseConfiguration
 
    @model_validator(mode="after")
    def check_service_configuration(self) -> Self:
        """Check service configuration."""
        if self.port > 65535:
            raise ValueError("Port value should be less than 65536")
        return self
 
 
class Configuration(ConfigurationBase):
    """Global configuration."""
 
    name: str
    service: ServiceConfiguration
 
    def dump(self, filename: str = "configuration.json") -> None:
        """Dump actual configuration into JSON file."""
        with open(filename, "w", encoding="utf-8") as fout:
            fout.write(self.model_dump_json(indent=4))
 
 
def load_configuration(filename: str) -> Configuration:
    """Load configuration from YAML file."""
    config_dict = parse_config(filename)
    return Configuration(**config_dict)
 
 
configuration = load_configuration("config_08.yaml")
 
configuration.dump("config_08.json")
{
    "name": "Service configuration",
    "service": {
        "host": "127.0.0.1",
        "port": 8080,
        "auth_enabled": false,
        "workers": 5,
        "database": {
            "host": "db1.pg.com",
            "port": 5432,
            "db": "test",
            "user": "N/A",
            "password": "123qwe",
            "password_file": "password.txt",
            "ssl_mode": "prefer",
            "gss_encmode": "prefer",
            "ca_cert_path": null
        }
    }
}

Vstupní konfigurační soubor:

name: Service configuration #1
service:
  host: 127.0.0.1
  port: 8080
  auth_enabled: false
  workers: 5
  database:
    host: db1.pg.com
    db: test
    user: !ENV ${DB_USER}
    password_file: password.txt
    ssl_mode: verify-ca
    gss_encmode: disable

Po spuštění skriptu by se měl vygenerovat tento výsledek:

{
    "name": "Service configuration",
    "service": {
        "host": "127.0.0.1",
        "port": 8080,
        "auth_enabled": false,
        "workers": 5,
        "database": {
            "host": "db1.pg.com",
            "port": 5432,
            "db": "test",
            "user": "N/A",
            "password": "123qwe",
            "password_file": "password.txt",
            "ssl_mode": "prefer",
            "gss_encmode": "prefer",
            "ca_cert_path": null
        }
    }
}

17. Kontrola, zda parametr typu řetězec obsahuje očekávanou hodnotu

Podívejme se na následující konfigurační soubor. Ten je po stránce syntaxe i s přihlédnutím k použitým datovým typům zcela korektní. Problém ovšem spočívá v tom, že zvýrazněné konfigurační volby neobsahují platné hodnoty:

name: Service configuration #1
service:
  host: 127.0.0.1
  port: 8080
  auth_enabled: false
  workers: 5
  database:
    host: db1.pg.com
    db: test
    user: !ENV ${DB_USER}
    password_file: password.txt
    ssl_mode: false
    gss_encmode: yes

Jakým způsobem se však provádí validace modelu, pokud jsou (například) řetězcové hodnoty omezeny jen na několik možných variant? Řešení spočívá ve využití typového systému jazyka Python, konkrétně ve specifikaci typu Literal:

class DatabaseConfiguration(ConfigurationBase):
    """Database configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 5432
    db: str
    user: str
    password: str = None
    password_file: FilePath
    ssl_mode: Literal[
        "disable", "allow", "prefer", "require", "verify-ca", "verify-full"
    ] = POSTGRES_DEFAULT_SSL_MODE
    gss_encmode: Literal["disable", "prefer", "require"] = POSTGRES_DEFAULT_GSS_ENCMODE
    ca_cert_path: Optional[FilePath] = None

18. Demonstrační příklad: kontrola obsahu vybraných parametrů typu řetězec

Vyzkoušejme si nyní, co se stane v případě, že do konfiguračních parametrů ssl_mode a gss_encmode zadáme nekorektní hodnoty:

Traceback (most recent call last):
  File "/tmp/ramdisk/pydantic/pydantic-demo/config_09.py", line 113, in <module>
    configuration = load_configuration("config_09_wrong.yaml")
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/ramdisk/pydantic/pydantic-demo/config_09.py", line 106, in load_configuration
    return Configuration(**config_dict)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  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 Configuration
service.database.ssl_mode
  Input should be 'disable', 'allow', 'prefer', 'require', 'verify-ca' or 'verify-full' [type=literal_error, input_value=False, input_type=bool]
    For further information visit https://errors.pydantic.dev/2.11/v/literal_error
service.database.gss_encmode
  Input should be 'disable', 'prefer' or 'require' [type=literal_error, input_value=True, input_type=bool]
    For further information visit https://errors.pydantic.dev/2.11/v/literal_error
Poznámka: nyní tedy model korektně ohlásil chybné hodnoty u vyznačených parametrů a navíc i napsal, jaké hodnoty jsou očekávané.

Pro úplnost si ještě uvedeme celý zdrojový kód dnešního posledního demonstračního příkladu:

"""Global configuration."""
 
import os
 
from pyaml_env import parse_config
 
from typing_extensions import Self, Optional, Literal
 
from pydantic import (
    BaseModel,
    ConfigDict,
    PositiveInt,
    model_validator,
    FilePath,
)
 
 
# PostgreSQL connection constants
# See: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-SSLMODE
POSTGRES_DEFAULT_SSL_MODE = "prefer"
# See: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-GSSENCMODE
POSTGRES_DEFAULT_GSS_ENCMODE = "prefer"
 
 
class InvalidConfigurationError(Exception):
    """Lightspeed configuration is invalid."""
 
 
def get_attribute_from_file(file_path: FilePath) -> Optional[str]:
    """Retrieve value of an attribute from a file."""
    with open(file_path, encoding="utf-8") as f:
        return f.read().rstrip()
 
 
def file_check(file_path: FilePath) -> None:
    """Check that path is a readable regular file."""
    if not os.path.isfile(file_path):
        raise InvalidConfigurationError(f"{desc} '{path}' is not a file")
    if not os.access(file_path, os.R_OK):
        raise InvalidConfigurationError(f"{desc} '{path}' is not readable")
 
 
class ConfigurationBase(BaseModel):
    """Base class for all configuration models that rejects unknown fields."""
 
    model_config = ConfigDict(extra="forbid")
 
 
class DatabaseConfiguration(ConfigurationBase):
    """Database configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 5432
    db: str
    user: str
    password: str = None
    password_file: FilePath
    ssl_mode: Literal[
        "disable", "allow", "prefer", "require", "verify-ca", "verify-full"
    ] = POSTGRES_DEFAULT_SSL_MODE
    gss_encmode: Literal["disable", "prefer", "require"] = POSTGRES_DEFAULT_GSS_ENCMODE
    ca_cert_path: Optional[FilePath] = None
 
    @model_validator(mode="after")
    def check_postgres_configuration(self) -> Self:
        """Check PostgreSQL configuration."""
        if self.port > 65535:
            raise ValueError("Port value should be less than 65536")
        file_check(self.password_file)
        self.password = get_attribute_from_file(self.password_file)
        return self
 
 
class ServiceConfiguration(ConfigurationBase):
    """Service configuration."""
 
    host: str = "localhost"
    port: PositiveInt = 8080
    auth_enabled: bool = False
    workers: PositiveInt = 1
    database: DatabaseConfiguration
 
    @model_validator(mode="after")
    def check_service_configuration(self) -> Self:
        """Check service configuration."""
        if self.port > 65535:
            raise ValueError("Port value should be less than 65536")
        return self
 
 
class Configuration(ConfigurationBase):
    """Global configuration."""
 
    name: str
    service: ServiceConfiguration
 
    def dump(self, filename: str = "configuration.json") -> None:
        """Dump actual configuration into JSON file."""
        with open(filename, "w", encoding="utf-8") as fout:
            fout.write(self.model_dump_json(indent=4))
 
 
def load_configuration(filename: str) -> Configuration:
    """Load configuration from YAML file."""
    config_dict = parse_config(filename)
    return Configuration(**config_dict)
 
 
configuration = load_configuration("config_09.yaml")
 
configuration.dump("config_09.json")
 
configuration = load_configuration("config_09_wrong.yaml")

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

Demonstrační příklady vytvořené v Pythonu a popsané v minulém i 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 config01.py model představující konfiguraci aplikace: pouze její jméno https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig01.py
16 config01.yaml vstupní konfigurační soubor ve formátu YAML https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig01.yaml
       
17 config02.py přidaná metoda pro uložení konfigurace do formátu JSON https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig02.py
18 config02.yaml vstupní konfigurační soubor ve formátu YAML https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig02.yaml
19 config02.json vygenerovaný konfigurační soubor ve formátu JSON https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig02.json
       
20 config03.py načtení konfiguračního souboru s položkami, které nebudou načteny ani zpracovány https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig03.py
21 config03.yaml konfigurační soubor obsahující volby, které nebudou načteny https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig03.yaml
22 config03.json vygenerovaný konfigurační soubor ve formátu JSON https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig03.json
       
23 config04.py úprava modelu takovým způsobem, že nepovolí načtení nepodporovaných položek https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig04.py
24 config04.yaml konfigurační soubor obsahující volby, které nebudou načteny https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig04.yaml
       
25 config05.py konfigurace obsahující další model: konfiguraci služby https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig05.py
26 config05.yaml konfigurační soubor ve formátu YAML https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig05.yaml
27 config05.json vygenerovaný konfigurační soubor ve formátu JSON https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig05.json
       
27 config06.py konfigurace obsahující ještě jeden model: konfiguraci databáze https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig06.py
28 config06.yaml konfigurační soubor ve formátu YAML https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig06.yaml
29 config06.json vygenerovaný konfigurační soubor ve formátu JSON https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig06.json
       
30 config07.py nová vlastnost: vyplnění hodnot z proměnných prostředí https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig07.py
31 config07.yaml konfigurační soubor ve formátu YAML https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig07.yaml
32 config07.json vygenerovaný konfigurační soubor ve formátu JSON https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig07.json
       
33 config08.py nová vlastnost: načtení hesla ze souboru https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig08.py
34 config08.yaml konfigurační soubor ve formátu YAML https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig08.yaml
35 config08.json vygenerovaný konfigurační soubor ve formátu JSON https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig08.json
       
36 config09.py nová vlastnost: test, zda je řetězcová hodnota korektní https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig09.py
37 config09.yaml konfigurační soubor ve formátu YAML https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig09.yaml
38 config09.json vygenerovaný konfigurační soubor ve formátu JSON https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig09.json
39 config09_wrong.yaml konfigurační soubor ve formátu YAML s nekorektním obsahem https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig09_wrong.yaml
       
40 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/
  31. PostgreSQL: 32.1. Database Connection Control Functions
    https://www.postgresql.or­g/docs/current/libpq-connect.html#LIBPQ-CONNECT-GSSENCMODE
  32. Python type hints: Literals
    https://typing.python.org/en/la­test/spec/literal.html
  33. YAML
    https://yaml.org/
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.