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

25. 11. 2025
Doba čtení: 41 minut

Sdílet

Autor: Root.cz s využitím DALL-E
Ve třetím článku o knihovně Pydantic si vysvětlíme, jakým způsobem je možné vygenerovat dokumentaci k definovanému modelu nebo i více modelům. Tento proces sice není složitý, ale vyžaduje několik triků.

Obsah

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

2. První varianta demonstračního datového modelu

3. Otestování demonstračního příkladu: načtení dat s jejich následným exportem

4. Export schématu modelu

5. Ukázka vyexportovaného schématu před a po jeho naformátování

6. Export schématu ve formátu OpenAPI

7. Ukázka vyexportovaného schématu před a po jeho naformátování

8. První pokus o načtení schématu do SwaggerEditoru

9. Změna atributu exclusiveMinimum na minimum

10. Druhý pokus o načtení schématu do SwaggerEditoru

11. Špinavý trik s opravou definice volitelných atributů

12. Třetí (úspěšný) pokus o načtení schématu do SwaggerEditoru

13. Vygenerování dokumentace ve formátu Markdown

14. Přidání popisků k jednotlivým atributům modelu

15. Úplný zdrojový kód příkladu s popsaným modelem

16. Dokument se schématem, které má popsány všechny atributy

17. Převod na HTML stránku či na jiný typ dokumentu

18. Závěr

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

20. Odkazy na Internetu

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

Na úvodní dva články o knihovně Pydantic [1] [2] dnes navážeme. Prozatím jsme si popsali způsob definice (datového) modelu, jeho načítání a ukládání a samozřejmě i způsob validace, zda jsou všechny atributy (a vůbec celá struktura modelu) korektní. Ovšem ještě si musíme ukázat další velmi důležitou techniku – způsob automatického vygenerování dokumentace k modelu nebo modelům (to znamená i volbu formátu dokumentace atd.).

Jedná se o oblast, která vyžaduje kombinaci možností nabízených knihovnou Pydantic s dalšími nástroji (a navíc i několika triky). Výsledkem může být dokumentace ve formátu specifikace OpenAPI (tu dokážou zpracovávat různé nástroje) nebo dokument ve formátu Markdown (který lze pochopitelně naimportovat do textových procesorů, systémů pro správu obsahu včetně Confluence atd.), popř. dokument převedený z Markdownu do dalších formátů (HTML atd.).

2. První varianta demonstračního datového modelu

V dalších kapitolách budeme pracovat s relativně jednoduchým datovým modelem, který později vylepšíme přidáním popisků k jeho jednotlivým atributům. V knihovně Pydantic jsou všechny modely přímo či nepřímo odvozeny od třídy BaseModel:

from pydantic import BaseModel

My ovšem budeme požadovat, aby vstupní data, která datový model reprezentuje, neobsahovala žádné atributy navíc. Z tohoto důvodu od třídy BaseModel odvodíme novou „základní“ třídu nazvanou ConfigurationBase, která sice obsahuje všechny vlastností původního základního modelu, ovšem navíc vyžaduje, aby model neobsahoval neznámé (předem nedefinované) atributy:

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

Náš datový model bude popsán třídou pojmenovanou Configuration. Jedná se o zdánlivě jednoduchý model s pouhými dvěma atributy:

class Configuration(ConfigurationBase):
    """Global configuration."""
 
    name: str
    service: ServiceConfiguration

Ovšem při prvním pohledu na typy těchto atributů je zřejmé, že musíme dodefinovat ServiceConfiguration. Základní varianta této třídy může vypadat následovně:

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

Ovšem stále ještě nejsme u konce, neboť zbývá dodefinovat typ atributu nazvaného database. Tento atribut je typu DatabaseConfiguration:

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

V tento okamžik již máme k dispozici všechny části dnešního prvního demonstračního příkladu, jehož úplný zdrojový kód vypadá následovně:

# Knihovna Pydantic
#
# - model s konfigurací obsahující čtyři atributy
# - jeden z atributů je tvořen dalším modelem
# - kontrola obsahu atributů po inicializaci modelu
# - zákaz inicializace modelu s neznámými atributy
 
import json
from typing_extensions import Optional, Self
 
from pydantic import (
    BaseModel,
    ConfigDict,
    FilePath,
    PositiveInt,
    model_validator,
)
 
# 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))

Programátoři, co nesnášíte BS, ale máte rádi business! Y Soft je česká firma s globálním dopadem (100+ zemí, 1M+ uživatelů a >100% meziroční růst). R&D úplně bez manažerů (130 developerů). Otevíráme 30 pozic pro Cloud a AI: Praha/Brno/Ostrava/remote. Zodpovědnost ano, mikro-management ne. Pojď někam, kde můžeš věci změnit.

Y Soft logo

3. Otestování demonstračního příkladu: načtení dat s jejich následným exportem

Výše uvedený příklad (resp. přesněji řečeno model, jenž je v něm definován) otestujeme relativně snadno. Načteme do modelu obsah tohoto souboru (ve formátu YAML) a následně budeme obsah modelu exportovat:

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

Načtení tohoto souboru obstará následující kód využívající knihovnu yaml:

import yaml
 
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)

Samotné načtení následované exportem dat je již otázkou dvou programových řádků:

configuration = load_configuration("config_10.yaml")
configuration.dump("config_10.json")

Výsledkem by měl být soubor config10.json s exportovaným modelem:

{
    "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
        }
    }
}
Poznámka: pro jednoduchost jsme vynechali část s načítáním hesel (nebo dalších atributů) z externích souborů popř. z proměnných prostředí (environment variables).

4. Export schématu modelu

Knihovna Pydantic umožňuje export schématu modelu (tj. popisu struktury dat) do formátu JSON. Postačuje zavolat metodu model_json_schema, která vrací slovník (dictionary) s popisem schématu modelu:

Help on method model_json_schema in module pydantic.main:
 
model_json_schema(by_alias: 'bool' = True, ref_template: 'str' = '#/$defs/{model}', schema_generator: 'type[GenerateJsonSchema]' = , mode: 'JsonSchemaMode' = 'validation') -> 'dict[str, Any]' class method of pydantic.main.BaseModel
    Generates a JSON schema for a model class.
 
    Args:
        by_alias: Whether to use attribute aliases or not.
        ref_template: The reference template.
        schema_generator: To override the logic used to generate the JSON schema, as a subclass of
            `GenerateJsonSchema` with your desired modifications
        mode: The mode in which to generate the schema.
 
    Returns:
        The JSON schema for the given model class.

Následný export do formátu JSON je již snadný, protože pro tento účel můžeme použít standardní balíček json:

with open("schema_11.json", "w") as fout:
    json.dump(Configuration.model_json_schema(), fout)

Upravme si tedy náš demonstrační příklad do takové podoby, aby po svém spuštění vytvořil soubor s popisem schématu modelu:

# Knihovna Pydantic
#
# - model s konfigurací obsahující čtyři atributy
# - jeden z atributů je tvořen dalším modelem
# - kontrola obsahu atributů po inicializaci modelu
# - zákaz inicializace modelu s neznámými atributy
# - export schématu modelu
 
import json
from typing_extensions import Optional, Self
 
from pydantic import (
    BaseModel,
    ConfigDict,
    FilePath,
    PositiveInt,
    model_validator,
)
 
# 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))
 
 
with open("schema_11.json", "w") as fout:
    json.dump(Configuration.model_json_schema(), fout)

5. Ukázka vyexportovaného schématu před a po jeho naformátování

Soubor schema11.json se schématem, který jsme získali, má následující obsah:

{"$defs": {"DatabaseConfiguration": {"additionalProperties": false, "description": "Database configuration.",
"properties": {"host": {"default": "localhost", "title": "Host", "type": "string"}, "port": {"default": 5432,
"exclusiveMinimum": 0, "title": "Port", "type": "integer"}, "db": {"title": "Db", "type": "string"}, "user":
{"title": "User", "type": "string"}, "password": {"title": "Password", "type": "string"}, "ssl_mode": {"default":
"prefer", "title": "Ssl Mode", "type": "string"}, "gss_encmode": {"default": "prefer", "title": "Gss Encmode",
"type": "string"}, "ca_cert_path": {"anyOf": [{"format": "file-path", "type": "string"}, {"type": "null"}],
"default": null, "title": "Ca Cert Path"}}, "required": ["db", "user", "password"], "title": "DatabaseConfiguration",
"type": "object"}, "ServiceConfiguration": {"additionalProperties": false, "description": "Service configuration.",
"properties": {"host": {"default": "localhost", "title": "Host", "type": "string"}, "port": {"default": 8080,
"exclusiveMinimum": 0, "title": "Port", "type": "integer"}, "auth_enabled": {"default": false, "title": "Auth Enabled",
"type": "boolean"}, "workers": {"default": 1, "exclusiveMinimum": 0, "title": "Workers", "type": "integer"},
"database": {"$ref": "#/$defs/DatabaseConfiguration"}}, "required": ["database"], "title": "ServiceConfiguration",
"type": "object"}}, "additionalProperties": false, "description": "Global configuration.", "properties": {"name":
{"title": "Name", "type": "string"}, "service": {"$ref": "#/$defs/ServiceConfiguration"}}, "required": ["name", "service"],
"title": "Configuration", "type": "object"}

To sice není moc čitelné, ovšem nic nám nebrání v naformátování JSONu, například oblíbeným nástrojem jq:

{
  "$defs": {
    "DatabaseConfiguration": {
      "additionalProperties": false,
      "description": "Database configuration.",
      "properties": {
        "host": {
          "default": "localhost",
          "title": "Host",
          "type": "string"
        },
        "port": {
          "default": 5432,
          "exclusiveMinimum": 0,
          "title": "Port",
          "type": "integer"
        },
        "db": {
          "title": "Db",
          "type": "string"
        },
        "user": {
          "title": "User",
          "type": "string"
        },
        "password": {
          "title": "Password",
          "type": "string"
        },
        "ssl_mode": {
          "default": "prefer",
          "title": "Ssl Mode",
          "type": "string"
        },
        "gss_encmode": {
          "default": "prefer",
          "title": "Gss Encmode",
          "type": "string"
        },
        "ca_cert_path": {
          "anyOf": [
            {
              "format": "file-path",
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Ca Cert Path"
        }
      },
      "required": [
        "db",
        "user",
        "password"
      ],
      "title": "DatabaseConfiguration",
      "type": "object"
    },
    "ServiceConfiguration": {
      "additionalProperties": false,
      "description": "Service configuration.",
      "properties": {
        "host": {
          "default": "localhost",
          "title": "Host",
          "type": "string"
        },
        "port": {
          "default": 8080,
          "exclusiveMinimum": 0,
          "title": "Port",
          "type": "integer"
        },
        "auth_enabled": {
          "default": false,
          "title": "Auth Enabled",
          "type": "boolean"
        },
        "workers": {
          "default": 1,
          "exclusiveMinimum": 0,
          "title": "Workers",
          "type": "integer"
        },
        "database": {
          "$ref": "#/$defs/DatabaseConfiguration"
        }
      },
      "required": [
        "database"
      ],
      "title": "ServiceConfiguration",
      "type": "object"
    }
  },
  "additionalProperties": false,
  "description": "Global configuration.",
  "properties": {
    "name": {
      "title": "Name",
      "type": "string"
    },
    "service": {
      "$ref": "#/$defs/ServiceConfiguration"
    }
  },
  "required": [
    "name",
    "service"
  ],
  "title": "Configuration",
  "type": "object"
}

6. Export schématu ve formátu OpenAPI

Výše uvedené schéma do určité míry odpovídá specifikaci OpenAPI, se kterým se pravděpodobně čtenáři tohoto článku již setkali. Ovšem do souboru s popisem schématu musíme doplnit další atributy, zejména specifikaci verze OpenAPI, metainformace, referenci na komponenty (což je vlastní popis modelu a vlastně i jediná zajímavá část celého souboru) a taktéž přidáme atribut paths, který bude prázdný – potřebujeme totiž získat jen popis modelu a nikoli i specifikaci koncových bodů REST API.

Zdrojový kód demonstračního příkladu tedy upravíme. Využijeme funkci models_json_schema z balíčku pydantic.json_schema:

from pydantic.json_schema import models_json_schema

Touto funkcí získáme slovník se schématem či schématy. Obsah tohoto slovníku „zabalíme“ do dalšího slovníku, jehož struktura (jména klíčů a typy hodnot) budou odpovídat OpenAPI. A poté se již provede export obsahu slovníku do souboru ve formátu JSON:

 
with open("schema_12.json", "w") as fout:
    _, schemas = models_json_schema(
        [(model, "validation") for model in [Configuration]],
        ref_template="#/components/schemas/{model}",
    )
    openapi_schema = {
        "openapi": "3.0.0",
        "info": {
            "title": "Weyland-Yutani Information System",
            "version": "0.3.0",
        },
        "components": {
            "schemas": schemas.get('$defs'),
        },
        "paths": {},
    }
    json.dump(openapi_schema, fout)
Poznámka: titulek a verzi si samozřejmě můžete modifikovat.

Zdrojový kód takto upraveného demonstračního příkladu bude vypadat následovně:

# Knihovna Pydantic
#
# - model s konfigurací obsahující čtyři atributy
# - jeden z atributů je tvořen dalším modelem
# - kontrola obsahu atributů po inicializaci modelu
# - zákaz inicializace modelu s neznámými atributy
# - úprava schématu do podoby OpenAPI
# - export schématu modelu
 
import json
from typing_extensions import Optional, Self
 
from pydantic import (
    BaseModel,
    ConfigDict,
    FilePath,
    PositiveInt,
    model_validator,
)
from pydantic.json_schema import models_json_schema
 
 
# 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))
 
 
with open("schema_12.json", "w") as fout:
    _, schemas = models_json_schema(
        [(model, "validation") for model in [Configuration]],
        ref_template="#/components/schemas/{model}",
    )
    openapi_schema = {
        "openapi": "3.0.0",
        "info": {
            "title": "Weyland-Yutani Information System",
            "version": "0.3.0",
        },
        "components": {
            "schemas": schemas.get('$defs'),
        },
        "paths": {},
    }
    json.dump(openapi_schema, fout)

7. Ukázka vyexportovaného schématu před a po jeho naformátování

Opět se podívejme na obsah JSONu, který jsme získali:

{"openapi": "3.0.0", "info": {"title": "Weyland-Yutani Information System",
"version": "0.3.0"}, "components": {"schemas": {"Configuration":
{"additionalProperties": false, "description": "Global configuration.",
"properties": {"name": {"title": "Name", "type": "string"}, "service": {"$ref":
"#/components/schemas/ServiceConfiguration"}}, "required": ["name", "service"],
"title": "Configuration", "type": "object"}, "DatabaseConfiguration":
{"additionalProperties": false, "description": "Database configuration.",
"properties": {"host": {"default": "localhost", "title": "Host", "type":
"string"}, "port": {"default": 5432, "exclusiveMinimum": 0, "title": "Port",
"type": "integer"}, "db": {"title": "Db", "type": "string"}, "user": {"title":
"User", "type": "string"}, "password": {"title": "Password", "type": "string"},
"ssl_mode": {"default": "prefer", "title": "Ssl Mode", "type": "string"},
"gss_encmode": {"default": "prefer", "title": "Gss Encmode", "type": "string"},
"ca_cert_path": {"anyOf": [{"format": "file-path", "type": "string"}, {"type":
"null"}], "default": null, "title": "Ca Cert Path"}}, "required": ["db",
"user", "password"], "title": "DatabaseConfiguration", "type": "object"},
"ServiceConfiguration": {"additionalProperties": false, "description": "Service
configuration.", "properties": {"host": {"default": "localhost", "title":
"Host", "type": "string"}, "port": {"default": 8080, "exclusiveMinimum": 0,
"title": "Port", "type": "integer"}, "auth_enabled": {"default": false,
"title": "Auth Enabled", "type": "boolean"}, "workers": {"default": 1,
"exclusiveMinimum": 0, "title": "Workers", "type": "integer"}, "database":
{"$ref": "#/components/schemas/DatabaseConfiguration"}}, "required":
["database"], "title": "ServiceConfiguration", "type": "object"}}}, "paths":
{}}

Po naformátování je již zřejmé, že se skutečně přibližujeme ke standardnímu OpenAPI:

{
  "openapi": "3.0.0",
  "info": {
    "title": "Weyland-Yutani Information System",
    "version": "0.3.0"
  },
  "components": {
    "schemas": {
      "Configuration": {
        "additionalProperties": false,
        "description": "Global configuration.",
        "properties": {
          "name": {
            "title": "Name",
            "type": "string"
          },
          "service": {
            "$ref": "#/components/schemas/ServiceConfiguration"
          }
        },
        "required": [
          "name",
          "service"
        ],
        "title": "Configuration",
        "type": "object"
      },
      "DatabaseConfiguration": {
        "additionalProperties": false,
        "description": "Database configuration.",
        "properties": {
          "host": {
            "default": "localhost",
            "title": "Host",
            "type": "string"
          },
          "port": {
            "default": 5432,
            "exclusiveMinimum": 0,
            "title": "Port",
            "type": "integer"
          },
          "db": {
            "title": "Db",
            "type": "string"
          },
          "user": {
            "title": "User",
            "type": "string"
          },
          "password": {
            "title": "Password",
            "type": "string"
          },
          "ssl_mode": {
            "default": "prefer",
            "title": "Ssl Mode",
            "type": "string"
          },
          "gss_encmode": {
            "default": "prefer",
            "title": "Gss Encmode",
            "type": "string"
          },
          "ca_cert_path": {
            "anyOf": [
              {
                "format": "file-path",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Ca Cert Path"
          }
        },
        "required": [
          "db",
          "user",
          "password"
        ],
        "title": "DatabaseConfiguration",
        "type": "object"
      },
      "ServiceConfiguration": {
        "additionalProperties": false,
        "description": "Service configuration.",
        "properties": {
          "host": {
            "default": "localhost",
            "title": "Host",
            "type": "string"
          },
          "port": {
            "default": 8080,
            "exclusiveMinimum": 0,
            "title": "Port",
            "type": "integer"
          },
          "auth_enabled": {
            "default": false,
            "title": "Auth Enabled",
            "type": "boolean"
          },
          "workers": {
            "default": 1,
            "exclusiveMinimum": 0,
            "title": "Workers",
            "type": "integer"
          },
          "database": {
            "$ref": "#/components/schemas/DatabaseConfiguration"
          }
        },
        "required": [
          "database"
        ],
        "title": "ServiceConfiguration",
        "type": "object"
      }
    }
  },
  "paths": {}
}

8. První pokus o načtení schématu do SwaggerEditoru

V dalším kroku se pokusíme schéma uvedené v předchozí kapitole přenést například do interaktivního Swagger editoru, jehož volně použitelná varianta je dostupná na adrese https://editor.swagger.io/. Výsledný Swagger s popisem schématu bude vypadat následovně (provede se převod do YAMLu, to je ovšem jen drobný detail):

Pydantic

Obrázek 1: Vygenerovaný popis schématu ve Swagger editoru

Autor: tisnik, podle licence: Rights Managed

To je sice poměrně velký krok dopředu k čitelné dokumentaci, ovšem současně Swagger editor ohlásí několik chyb v našem schématu:

Pydantic

Obrázek 2: Chyby, které Swagger editor vypíše po importu schématu

Autor: tisnik, podle licence: Rights Managed

9. Změna atributu exclusiveMinimum na minimum

SwaggerEditor korektně ohlásil chybu v použití atributu exclusiveMinimum. Ten má totiž odlišný význam, než v jakém je použit – je to totiž pravdivostní hodnota, která určuje, zda specifikované minimum určuje otevřený nebo uzavřený interval (tedy „kromě limitní hodnoty“ nebo „včetně limitní hodnoty“). V OpenAPI 3.0.0 tedy potřebujeme zaměnit jméno atributu exclusiveMinimum na minimum, a to (obecně) rekurzivně pro všechny uzly v popisu schématu. Tuto operaci lze provést ještě před exportem schématu do JSONu, a to následující funkcí:

def recursive_update(original: dict) -> dict:
    new = {}
    for key, value in original.items():
        if isinstance(value, dict):
            new[key] = recursive_update(original[key])
        elif key == "exclusiveMinimum":
            new["minimum"] = value
        else:
            new[key] = value
    return new

Zdrojový kód s našim modelem i instrukcemi pro export jeho schématu upravíme do následující podoby:

# Knihovna Pydantic
#
# - model s konfigurací obsahující čtyři atributy
# - jeden z atributů je tvořen dalším modelem
# - kontrola obsahu atributů po inicializaci modelu
# - zákaz inicializace modelu s neznámými atributy
# - úprava schématu do podoby OpenAPI
# - řešení atributu exclusiveMinimum
# - export schématu modelu
 
import json
from typing_extensions import Optional, Self
 
from pydantic import (
    BaseModel,
    ConfigDict,
    FilePath,
    PositiveInt,
    model_validator,
)
from pydantic.json_schema import models_json_schema
 
 
# 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 recursive_update(original: dict) -> dict:
    new = {}
    for key, value in original.items():
        if isinstance(value, dict):
            new[key] = recursive_update(original[key])
        elif key == "exclusiveMinimum":
            new["minimum"] = value
        else:
            new[key] = value
    return new
 
 
with open("schema_13.json", "w") as fout:
    _, schemas = models_json_schema(
        [(model, "validation") for model in [Configuration]],
        ref_template="#/components/schemas/{model}",
    )
    schemas = recursive_update(schemas)
    openapi_schema = {
        "openapi": "3.0.0",
        "info": {
            "title": "Weyland-Yutani Information System",
            "version": "0.3.0",
        },
        "components": {
            "schemas": schemas.get('$defs'),
        },
        "paths": {},
    }
    json.dump(openapi_schema, fout)

Výsledkem bude JSON se schématem, které bude po exportu vypadat následovně:

{"openapi": "3.0.0", "info": {"title": "Weyland-Yutani Information System",
"version": "0.3.0"}, "components": {"schemas": {"Configuration":
{"additionalProperties": false, "description": "Global configuration.",
"properties": {"name": {"title": "Name", "type": "string"}, "service": {"$ref":
"#/components/schemas/ServiceConfiguration"}}, "required": ["name", "service"],
"title": "Configuration", "type": "object"}, "DatabaseConfiguration":
{"additionalProperties": false, "description": "Database configuration.",
"properties": {"host": {"default": "localhost", "title": "Host", "type":
"string"}, "port": {"default": 5432, "minimum": 0, "title": "Port", "type":
"integer"}, "db": {"title": "Db", "type": "string"}, "user": {"title": "User",
"type": "string"}, "password": {"title": "Password", "type": "string"},
"ssl_mode": {"default": "prefer", "title": "Ssl Mode", "type": "string"},
"gss_encmode": {"default": "prefer", "title": "Gss Encmode", "type": "string"},
"ca_cert_path": {"anyOf": [{"format": "file-path", "type": "string"}, {"type":
"null"}], "default": null, "title": "Ca Cert Path"}}, "required": ["db",
"user", "password"], "title": "DatabaseConfiguration", "type": "object"},
"ServiceConfiguration": {"additionalProperties": false, "description": "Service
configuration.", "properties": {"host": {"default": "localhost", "title":
"Host", "type": "string"}, "port": {"default": 8080, "minimum": 0, "title":
"Port", "type": "integer"}, "auth_enabled": {"default": false, "title": "Auth
Enabled", "type": "boolean"}, "workers": {"default": 1, "minimum": 0, "title":
"Workers", "type": "integer"}, "database": {"$ref":
"#/components/schemas/DatabaseConfiguration"}}, "required": ["database"],
"title": "ServiceConfiguration", "type": "object"}}}, "paths": {}}

Po naformátování schématu nástrojem jq dostaneme tuto čitelnou podobu:

{
  "openapi": "3.0.0",
  "info": {
    "title": "Weyland-Yutani Information System",
    "version": "0.3.0"
  },
  "components": {
    "schemas": {
      "Configuration": {
        "additionalProperties": false,
        "description": "Global configuration.",
        "properties": {
          "name": {
            "title": "Name",
            "type": "string"
          },
          "service": {
            "$ref": "#/components/schemas/ServiceConfiguration"
          }
        },
        "required": [
          "name",
          "service"
        ],
        "title": "Configuration",
        "type": "object"
      },
      "DatabaseConfiguration": {
        "additionalProperties": false,
        "description": "Database configuration.",
        "properties": {
          "host": {
            "default": "localhost",
            "title": "Host",
            "type": "string"
          },
          "port": {
            "default": 5432,
            "minimum": 0,
            "title": "Port",
            "type": "integer"
          },
          "db": {
            "title": "Db",
            "type": "string"
          },
          "user": {
            "title": "User",
            "type": "string"
          },
          "password": {
            "title": "Password",
            "type": "string"
          },
          "ssl_mode": {
            "default": "prefer",
            "title": "Ssl Mode",
            "type": "string"
          },
          "gss_encmode": {
            "default": "prefer",
            "title": "Gss Encmode",
            "type": "string"
          },
          "ca_cert_path": {
            "anyOf": [
              {
                "format": "file-path",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "default": null,
            "title": "Ca Cert Path"
          }
        },
        "required": [
          "db",
          "user",
          "password"
        ],
        "title": "DatabaseConfiguration",
        "type": "object"
      },
      "ServiceConfiguration": {
        "additionalProperties": false,
        "description": "Service configuration.",
        "properties": {
          "host": {
            "default": "localhost",
            "title": "Host",
            "type": "string"
          },
          "port": {
            "default": 8080,
            "minimum": 0,
            "title": "Port",
            "type": "integer"
          },
          "auth_enabled": {
            "default": false,
            "title": "Auth Enabled",
            "type": "boolean"
          },
          "workers": {
            "default": 1,
            "minimum": 0,
            "title": "Workers",
            "type": "integer"
          },
          "database": {
            "$ref": "#/components/schemas/DatabaseConfiguration"
          }
        },
        "required": [
          "database"
        ],
        "title": "ServiceConfiguration",
        "type": "object"
      }
    }
  },
  "paths": {}
}

10. Druhý pokus o načtení schématu do SwaggerEditoru

Upravené schéma opět načteme do SwaggerEditoru. Výsledkem bude prakticky totožný Swagger, nyní ovšem s korektně nastavenými minimálními hodnotami (pochopitelně jen tam, kde jsme minimum definovali například typem PositiveInt):

Pydantic

Obrázek 3: Vygenerovaný popis schématu ve Swagger editoru

Autor: tisnik, podle licence: Rights Managed

Ovšem schéma ještě stále není zcela korektní, na což nás SwaggerEditor upozorní:

Pydantic

Obrázek 4: Chyba, kterou Swagger editor vypíše po importu schématu

Autor: tisnik, podle licence: Rights Managed

11. Špinavý trik s opravou definice volitelných atributů

Poslední chybou, kterou SwaggerEditor detekoval, byla špatná specifikace volitelných atributů modelu, tj. takových atributů, které mají v Pythonu typ Optional (to se převede na „spojení typů“ v uzlu anyOf). Úpravou transformační funkce recursive_update (transformuje jeden popis modelu na jinak strukturovaný popis) můžeme tento problém alespoň částečně obejít, když volitelné atributy nahradíme za přímou definici jejich typu s nastavením příznaku nullable. Výsledkem je korektní popis schématu modelu, i když samotná transformační funkce by si zasloužila mnohem lepší (ale i mnohem delší) implementaci:

def recursive_update(original: dict) -> dict:
    new = {}
    for key, value in original.items():
        # rekurzivní průchod podslovníky
        if isinstance(value, dict):
            new[key] = recursive_update(original[key])
        # řešení volitelných typů
        elif key == "anyOf" and isinstance(value, list) and value[1]["type"] == "null":
            # jen první typ je konktrétní, ingorovat druhý
            val = value[0]["type"]
            new["type"] = val
            # nový atribut
            new["nullable"] = True
        # řešení atributu exclusiveMinimum
        elif key == "exclusiveMinimum":
            new["minimum"] = value
        else:
            new[key] = value
    return new

Opravený zdrojový kód našeho příkladu:

# Knihovna Pydantic
#
# - model s konfigurací obsahující čtyři atributy
# - jeden z atributů je tvořen dalším modelem
# - kontrola obsahu atributů po inicializaci modelu
# - zákaz inicializace modelu s neznámými atributy
# - úprava schématu do podoby OpenAPI
# - řešení atributu exclusiveMinimum
# - řešení volitelné hodnoty
# - export schématu modelu
 
import json
from typing_extensions import Optional, Self
 
from pydantic import (
    BaseModel,
    ConfigDict,
    FilePath,
    PositiveInt,
    model_validator,
)
from pydantic.json_schema import models_json_schema
 
 
# 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 recursive_update(original: dict) -> dict:
    new = {}
    for key, value in original.items():
        # rekurzivní průchod podslovníky
        if isinstance(value, dict):
            new[key] = recursive_update(original[key])
        # řešení volitelných typů
        elif key == "anyOf" and isinstance(value, list) and value[1]["type"] == "null":
            # jen první typ je konktrétní, ingorovat druhý
            val = value[0]["type"]
            new["type"] = val
            # nový atribut
            new["nullable"] = True
        # řešení atributu exclusiveMinimum
        elif key == "exclusiveMinimum":
            new["minimum"] = value
        else:
            new[key] = value
    return new
 
 
with open("schema_14.json", "w") as fout:
    _, schemas = models_json_schema(
        [(model, "validation") for model in [Configuration]],
        ref_template="#/components/schemas/{model}",
    )
    schemas = recursive_update(schemas)
    openapi_schema = {
        "openapi": "3.0.0",
        "info": {
            "title": "Weyland-Yutani Information System",
            "version": "0.3.0",
        },
        "components": {
            "schemas": schemas.get('$defs'),
        },
        "paths": {},
    }
    json.dump(openapi_schema, fout)

Výsledkem bude kratší a současně i korektní schéma:

{"openapi": "3.0.0", "info": {"title": "Weyland-Yutani Information System",
"version": "0.3.0"}, "components": {"schemas": {"Configuration":
{"additionalProperties": false, "description": "Global configuration.",
"properties": {"name": {"title": "Name", "type": "string"}, "service": {"$ref":
"#/components/schemas/ServiceConfiguration"}}, "required": ["name", "service"],
"title": "Configuration", "type": "object"}, "DatabaseConfiguration":
{"additionalProperties": false, "description": "Database configuration.",
"properties": {"host": {"default": "localhost", "title": "Host", "type":
"string"}, "port": {"default": 5432, "minimum": 0, "title": "Port", "type":
"integer"}, "db": {"title": "Db", "type": "string"}, "user": {"title": "User",
"type": "string"}, "password": {"title": "Password", "type": "string"},
"ssl_mode": {"default": "prefer", "title": "Ssl Mode", "type": "string"},
"gss_encmode": {"default": "prefer", "title": "Gss Encmode", "type": "string"},
"ca_cert_path": {"type": "string", "nullable": true, "default": null, "title":
"Ca Cert Path"}}, "required": ["db", "user", "password"], "title":
"DatabaseConfiguration", "type": "object"}, "ServiceConfiguration":
{"additionalProperties": false, "description": "Service configuration.",
"properties": {"host": {"default": "localhost", "title": "Host", "type":
"string"}, "port": {"default": 8080, "minimum": 0, "title": "Port", "type":
"integer"}, "auth_enabled": {"default": false, "title": "Auth Enabled", "type":
"boolean"}, "workers": {"default": 1, "minimum": 0, "title": "Workers", "type":
"integer"}, "database": {"$ref":
"#/components/schemas/DatabaseConfiguration"}}, "required": ["database"],
"title": "ServiceConfiguration", "type": "object"}}}, "paths": {}}

Po naformátování do čitelné podoby:

{
  "openapi": "3.0.0",
  "info": {
    "title": "Weyland-Yutani Information System",
    "version": "0.3.0"
  },
  "components": {
    "schemas": {
      "Configuration": {
        "additionalProperties": false,
        "description": "Global configuration.",
        "properties": {
          "name": {
            "title": "Name",
            "type": "string"
          },
          "service": {
            "$ref": "#/components/schemas/ServiceConfiguration"
          }
        },
        "required": [
          "name",
          "service"
        ],
        "title": "Configuration",
        "type": "object"
      },
      "DatabaseConfiguration": {
        "additionalProperties": false,
        "description": "Database configuration.",
        "properties": {
          "host": {
            "default": "localhost",
            "title": "Host",
            "type": "string"
          },
          "port": {
            "default": 5432,
            "minimum": 0,
            "title": "Port",
            "type": "integer"
          },
          "db": {
            "title": "Db",
            "type": "string"
          },
          "user": {
            "title": "User",
            "type": "string"
          },
          "password": {
            "title": "Password",
            "type": "string"
          },
          "ssl_mode": {
            "default": "prefer",
            "title": "Ssl Mode",
            "type": "string"
          },
          "gss_encmode": {
            "default": "prefer",
            "title": "Gss Encmode",
            "type": "string"
          },
          "ca_cert_path": {
            "type": "string",
            "nullable": true,
            "default": null,
            "title": "Ca Cert Path"
          }
        },
        "required": [
          "db",
          "user",
          "password"
        ],
        "title": "DatabaseConfiguration",
        "type": "object"
      },
      "ServiceConfiguration": {
        "additionalProperties": false,
        "description": "Service configuration.",
        "properties": {
          "host": {
            "default": "localhost",
            "title": "Host",
            "type": "string"
          },
          "port": {
            "default": 8080,
            "minimum": 0,
            "title": "Port",
            "type": "integer"
          },
          "auth_enabled": {
            "default": false,
            "title": "Auth Enabled",
            "type": "boolean"
          },
          "workers": {
            "default": 1,
            "minimum": 0,
            "title": "Workers",
            "type": "integer"
          },
          "database": {
            "$ref": "#/components/schemas/DatabaseConfiguration"
          }
        },
        "required": [
          "database"
        ],
        "title": "ServiceConfiguration",
        "type": "object"
      }
    }
  },
  "paths": {}
}

12. Třetí (úspěšný) pokus o načtení schématu do SwaggerEditoru

Po načtení nového popisu schématu ve formátu JSON do SwaggerEditoru se již nehlásí žádné chyby a vytvořený interaktivní Swagger (pravá strana) je taktéž vygenerován zcela korektně. Pochopitelně obsahuje pouze popis komponent, ovšem nic dalšího stejně není zapotřebí:

Pydantic

Obrázek 5: Vygenerovaný popis schématu ve Swagger editoru

Autor: tisnik, podle licence: Rights Managed

13. Vygenerování dokumentace ve formátu Markdown

V dalším kroku z popisu schématu ve formátu JSON vygenerujeme čitelnou dokumentaci v Markdownu. Pro tento účel existuje několik více či méně kompletních nástrojů. Jedním z nich je i openapi-to-md, který lze nainstalovat přes PIP, do virtuálního prostředí Pythonu atd.:

$ uv add openapi-to-md
 
Using CPython 3.12.10 interpreter at: /usr/bin/python3.12
Creating virtual environment at: .venv
Resolved 7 packages in 259ms
Prepared 4 packages in 299ms
Installed 5 packages in 16ms
 + click==8.3.1
 + jinja2==3.1.6
 + markupsafe==3.0.3
 + openapi-to-md==0.1.0b2
 + pyyaml==6.0.3

Vlastní převod na Markdown proběhne takto:

$ openapi-to-markdown --input_file schema_14_formatted.json
 
filter_paths ()
Converting /home/ptisnovs/xy/xxx/schema_14_formatted.json to output.md...
Conversion complete.

Výsledkem bude soubor ve formátu Markdown, který obsahuje sadu tabulek s popisem komponent (což jsou části našeho modelu):

# Weyland-Yutani Information System
 
 
 
## 🌍 Base URL
 
 
| URL | Description |
|-----|-------------|
 
 
# 🛠️ APIs
 
---
 
# 📋 Components
 
 
 
## Configuration
 
 
Global configuration.
 
 
| Field | Type | Description |
|-------|------|-------------|
| name | string |  |
| service |  |  |
 
 
## DatabaseConfiguration
 
 
Database configuration.
 
 
| Field | Type | Description |
|-------|------|-------------|
| host | string |  |
| port | integer |  |
| db | string |  |
| user | string |  |
| password | string |  |
| ssl_mode | string |  |
| gss_encmode | string |  |
| ca_cert_path | string |  |
 
 
## ServiceConfiguration
 
 
Service configuration.
 
 
| Field | Type | Description |
|-------|------|-------------|
| host | string |  |
| port | integer |  |
| auth_enabled | boolean |  |
| workers | integer |  |
| database |  |  |

14. Přidání popisků k jednotlivým atributům modelu

Problémem vygenerované dokumentace jsou především chybějící popisy jednotlivých atributů. Ty ovšem můžeme doplnit, a to tak, že každému atributu přímo v definici modelu přiřadíme hodnotu typu Field. To nám umožní, aby měl každý atribut svoje označení (může se lišit od jména ve zdrojovém kódu) a taktéž popisek.

Pro atributy, které mají nastavenou výchozí hodnotu, je to snadné a přímočaré. Řádek:

host: str = "localhost"

se nahradí za:

host: str = Field(
    "localhost",
    title="Hostname",
    description="Database server host or socket directory",
)

U atributů, které nemají nastavenou výchozí hodnotu, je nutné namísto prvního parametru konstruktoru Field zapsat trojtečku (což je zcela legální konstrukce v Pythonu). Opět si uvedeme příklad jednoho atributu z našeho modelu:

password: str

Atribut password nemá žádnou výchozí hodnotu, takže jeho popisek doplníme s využitím trojtečky:

password: str = Field(
    ...,
    title="Password",
    description="Password used to authenticate",
)
Poznámka: podobným způsobem se doplní i popisky všech ostatních atributů.

15. Úplný zdrojový kód příkladu s popsaným modelem

Zdrojový kód obsahující mj. i definici datového modelu, vypadá po všech provedených úpravách následovně:

# Knihovna Pydantic
#
# - model s konfigurací obsahující čtyři atributy
# - jeden z atributů je tvořen dalším modelem
# - kontrola obsahu atributů po inicializaci modelu
# - zákaz inicializace modelu s neznámými atributy
# - úprava schématu do podoby OpenAPI
# - řešení atributu exclusiveMinimum
# - řešení volitelné hodnoty
# - export schématu modelu
 
import json
from typing_extensions import Optional, Self
 
from pydantic import (
    BaseModel,
    Field,
    ConfigDict,
    FilePath,
    PositiveInt,
    model_validator,
)
from pydantic.json_schema import models_json_schema
 
 
# 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 = Field(
        "localhost",
        title="Hostname",
        description="Database server host or socket directory",
    )
 
    port: PositiveInt = Field(
        5432,
        title="Port",
        description="Database server port",
    )
 
    db: str = Field(
        ...,
        title="Database name",
        description="Database name to connect to",
    )
 
    user: str = Field(
        ...,
        title="User name",
        description="Database user name used to authenticate",
    )
 
    password: str = Field(
        ...,
        title="Password",
        description="Password used to authenticate",
    )
 
    ssl_mode: str = Field(
        POSTGRES_DEFAULT_SSL_MODE,
        title="SSL mode",
        description="SSL mode",
    )
 
    gss_encmode: str = Field(
        POSTGRES_DEFAULT_GSS_ENCMODE,
        title="GSS encmode",
        description="This option determines whether or with what priority a secure GSS "
        "TCP/IP connection will be negotiated with the server.",
    )
 
    ca_cert_path: Optional[FilePath] = Field(
        None,
        title="CA certificate path",
        description="Path to CA certificate",
    )
 
    @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 = Field(
        "localhost",
        title="Host",
        description="Service hostname",
    )
 
    port: PositiveInt = 8080
    port: PositiveInt = Field(
        8080,
        title="Port",
        description="Service port",
    )
 
    auth_enabled: bool = Field(
        False,
        title="Authentication enabled",
        description="Enables authentication subsystem",
    )
 
    workers: PositiveInt = Field(
        1,
        title="Number of workers",
        description="Number of workers to be started",
    )
 
    database: DatabaseConfiguration = Field(
        ...,
        title="Database configuration",
        description="Database configuration",
    )
 
    @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 = Field(
        ...,
        title="Service name",
        description="Service name",
    )
 
    service: ServiceConfiguration = Field(
        ...,
        title="Service configuration",
        description="Service configuration",
    )
 
    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 recursive_update(original: dict) -> dict:
    new = {}
    for key, value in original.items():
        # rekurzivní průchod podslovníky
        if isinstance(value, dict):
            new[key] = recursive_update(original[key])
        # řešení volitelných typů
        elif key == "anyOf" and isinstance(value, list) and value[1]["type"] == "null":
            # jen první typ je konktrétní, ingorovat druhý
            val = value[0]["type"]
            new["type"] = val
            # nový atribut
            new["nullable"] = True
        # řešení atributu exclusiveMinimum
        elif key == "exclusiveMinimum":
            new["minimum"] = value
        else:
            new[key] = value
    return new
 
 
with open("schema_15.json", "w") as fout:
    _, schemas = models_json_schema(
        [(model, "validation") for model in [Configuration]],
        ref_template="#/components/schemas/{model}",
    )
    schemas = recursive_update(schemas)
    openapi_schema = {
        "openapi": "3.0.0",
        "info": {
            "title": "Weyland-Yutani Information System",
            "version": "0.3.0",
        },
        "components": {
            "schemas": schemas.get("$defs"),
        },
        "paths": {},
    }
    json.dump(openapi_schema, fout)

Po spuštění tohoto modifikovaného příkladu získáme popis schématu, který obsahuje i popisky jednotlivých atributů:

{"openapi": "3.0.0", "info": {"title": "Weyland-Yutani Information System",
"version": "0.3.0"}, "components": {"schemas": {"Configuration":
{"additionalProperties": false, "description": "Global configuration.",
"properties": {"name": {"description": "Service name", "title": "Service name",
"type": "string"}, "service": {"$ref":
"#/components/schemas/ServiceConfiguration", "description": "Service
configuration", "title": "Service configuration"}}, "required": ["name",
"service"], "title": "Configuration", "type": "object"},
"DatabaseConfiguration": {"additionalProperties": false, "description":
"Database configuration.", "properties": {"host": {"default": "localhost",
"description": "Database server host or socket directory", "title": "Hostname",
"type": "string"}, "port": {"default": 5432, "description": "Database server
port", "minimum": 0, "title": "Port", "type": "integer"}, "db": {"description":
"Database name to connect to", "title": "Database name", "type": "string"},
"user": {"description": "Database user name used to authenticate", "title":
"User name", "type": "string"}, "password": {"description": "Password used to
authenticate", "title": "Password", "type": "string"}, "ssl_mode": {"default":
"prefer", "description": "SSL mode", "title": "SSL mode", "type": "string"},
"gss_encmode": {"default": "prefer", "description": "This option determines
whether or with what priority a secure GSS TCP/IP connection will be negotiated
with the server.", "title": "GSS encmode", "type": "string"}, "ca_cert_path":
{"type": "string", "nullable": true, "default": null, "description": "Path to
CA certificate", "title": "CA certificate path"}}, "required": ["db", "user",
"password"], "title": "DatabaseConfiguration", "type": "object"},
"ServiceConfiguration": {"additionalProperties": false, "description": "Service
configuration.", "properties": {"host": {"default": "localhost", "description":
"Service hostname", "title": "Host", "type": "string"}, "port": {"default":
8080, "description": "Service port", "minimum": 0, "title": "Port", "type":
"integer"}, "auth_enabled": {"default": false, "description": "Enables
authentication subsystem", "title": "Authentication enabled", "type":
"boolean"}, "workers": {"default": 1, "description": "Number of workers to be
started", "minimum": 0, "title": "Number of workers", "type": "integer"},
"database": {"$ref": "#/components/schemas/DatabaseConfiguration",
"description": "Database configuration", "title": "Database configuration"}},
"required": ["database"], "title": "ServiceConfiguration", "type": "object"}}},
"paths": {}}

Po naformátování získáme poměrně dobře čitelné schéma, které je navíc zcela korektní:

{
  "openapi": "3.0.0",
  "info": {
    "title": "Weyland-Yutani Information System",
    "version": "0.3.0"
  },
  "components": {
    "schemas": {
      "Configuration": {
        "additionalProperties": false,
        "description": "Global configuration.",
        "properties": {
          "name": {
            "description": "Service name",
            "title": "Service name",
            "type": "string"
          },
          "service": {
            "$ref": "#/components/schemas/ServiceConfiguration",
            "description": "Service configuration",
            "title": "Service configuration"
          }
        },
        "required": [
          "name",
          "service"
        ],
        "title": "Configuration",
        "type": "object"
      },
      "DatabaseConfiguration": {
        "additionalProperties": false,
        "description": "Database configuration.",
        "properties": {
          "host": {
            "default": "localhost",
            "description": "Database server host or socket directory",
            "title": "Hostname",
            "type": "string"
          },
          "port": {
            "default": 5432,
            "description": "Database server port",
            "minimum": 0,
            "title": "Port",
            "type": "integer"
          },
          "db": {
            "description": "Database name to connect to",
            "title": "Database name",
            "type": "string"
          },
          "user": {
            "description": "Database user name used to authenticate",
            "title": "User name",
            "type": "string"
          },
          "password": {
            "description": "Password used to authenticate",
            "title": "Password",
            "type": "string"
          },
          "ssl_mode": {
            "default": "prefer",
            "description": "SSL mode",
            "title": "SSL mode",
            "type": "string"
          },
          "gss_encmode": {
            "default": "prefer",
            "description": "This option determines whether or with what priority a secure GSS TCP/IP connection will be negotiated with the server.",
            "title": "GSS encmode",
            "type": "string"
          },
          "ca_cert_path": {
            "type": "string",
            "nullable": true,
            "default": null,
            "description": "Path to CA certificate",
            "title": "CA certificate path"
          }
        },
        "required": [
          "db",
          "user",
          "password"
        ],
        "title": "DatabaseConfiguration",
        "type": "object"
      },
      "ServiceConfiguration": {
        "additionalProperties": false,
        "description": "Service configuration.",
        "properties": {
          "host": {
            "default": "localhost",
            "description": "Service hostname",
            "title": "Host",
            "type": "string"
          },
          "port": {
            "default": 8080,
            "description": "Service port",
            "minimum": 0,
            "title": "Port",
            "type": "integer"
          },
          "auth_enabled": {
            "default": false,
            "description": "Enables authentication subsystem",
            "title": "Authentication enabled",
            "type": "boolean"
          },
          "workers": {
            "default": 1,
            "description": "Number of workers to be started",
            "minimum": 0,
            "title": "Number of workers",
            "type": "integer"
          },
          "database": {
            "$ref": "#/components/schemas/DatabaseConfiguration",
            "description": "Database configuration",
            "title": "Database configuration"
          }
        },
        "required": [
          "database"
        ],
        "title": "ServiceConfiguration",
        "type": "object"
      }
    }
  },
  "paths": {}
}

16. Dokument se schématem, které má popsány všechny atributy

Nyní po otevření výše ukázaného schématu ve SwaggerEditoru můžeme vidět, že všechny atributy u sebe mají zobrazeny korektní popisky:

Pydantic

Obrázek 6: Vygenerovaný popis schématu ve Swagger editoru

Autor: tisnik, podle licence: Rights Managed

Podobně se „doplnily“ sloupce Description po převodu schématu ve formátu OpenAPI do formátu Markdown (viz obsahy všech tabulek):

# Weyland-Yutani Information System
 
 
 
## 🌍 Base URL
 
 
| URL | Description |
|-----|-------------|
 
 
# 🛠️ APIs
 
---
 
# 📋 Components
 
 
 
## Configuration
 
 
Global configuration.
 
 
| Field | Type | Description |
|-------|------|-------------|
| name | string | Service name |
| service |  | Service configuration |
 
 
## DatabaseConfiguration
 
 
Database configuration.
 
 
| Field | Type | Description |
|-------|------|-------------|
| host | string | Database server host or socket directory |
| port | integer | Database server port |
| db | string | Database name to connect to |
| user | string | Database user name used to authenticate |
| password | string | Password used to authenticate |
| ssl_mode | string | SSL mode |
| gss_encmode | string | This option determines whether or with what priority a secure GSS TCP/IP connection will be negotiated with the server. |
| ca_cert_path | string | Path to CA certificate |
 
 
## ServiceConfiguration
 
 
Service configuration.
 
 
| Field | Type | Description |
|-------|------|-------------|
| host | string | Service hostname |
| port | integer | Service port |
| auth_enabled | boolean | Enables authentication subsystem |
| workers | integer | Number of workers to be started |
| database |  | Database configuration |

17. Převod na HTML stránku či na jiný typ dokumentu

Posledním krokem, který provedeme, je převod dokumentu s popisem modelu z formátu Markdown do nějakého odlišného formátu vhodného pro přímé zobrazení (na druhou stranu Markdown lze umístit na GitHub pages atd., což může být plně dostačující). Pochopitelně je možné soubor ve formátu Markdown načíst například do textového procesoru, ovšem to není nejvhodnější technologie pro automatizaci generování dokumentace. Namísto toho použijeme populární Pandoc, který je spustitelný z příkazové řádky a nevyžaduje manuální operace:

$ pandoc -s --metadata title="nějaký titulek nebo jen mezera" --from markdown --to html config.md -o config.html

Výsledkem takto provedené konverze je HTML stránka obsahující jak popis modelu, tak i styly atd.:

Školení Hacking

Pydantic

Obrázek 6: Finální verze dokumentace

Autor: tisnik, podle licence: Rights Managed
Poznámka: vygenerovat je možné i „holou“ dokumentaci, tedy HTML stránku pouze s popisem atributů. V případě, že vyžadujete tento výsledek, postačuje vynechat přepínače -s a –metadata.

18. Závěr

Tvorba dokumentace je většinou dosti nezáživná činnost. Navíc má dokumentace tendenci zastarávat a proto je vhodné se zaměřit na automatizaci pro její generování (samozřejmě tam, kde je to vůbec možné). V případě modelů máme práci usnadněnou, protože vstupem budou přímo zdrojové kódy, což znamená, že by dokumentace neměla zastarávat, pokud bude automaticky z těchto kódů generovaná. Samotná tvorba dokumentace o modelech vyžaduje několik kroků, ovšem ty je možné plně automatizovat a provádět je například v rámci CI/CD pipeline, bez nutnosti manuálních zásahů.

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

Demonstrační příklady vytvořené v Pythonu a popsané v dnešním článku i v obou článcích předchozích 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 (vstup pro inicializaci modelu) 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 (vstup pro inicializaci modelu) 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 (export modelu) 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 (export modelu) 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 (vstup pro inicializaci modelu) 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 (export modelu) 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 (vstup pro inicializaci modelu) 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 (export modelu) 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 (vstup pro inicializaci modelu) 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 (export modelu) 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 (vstup pro inicializaci modelu) 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 (export modelu) 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 (vstup pro inicializaci modelu) 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 (export modelu) 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 config10.py definice modelu, který bude použitý i v dalších příkladech https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig10.py
41 config10.yaml konfigurační soubor ve formátu YAML (vstup pro inicializaci modelu) https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig10.yaml
42 config10.json vygenerovaný konfigurační soubor ve formátu JSON (export modelu) https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig10.json
       
43 config11.py export schématu modelu do formátu JSON https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig11.py
44 schema11.json vyexportovaný popis schématu modelu před naformátováním https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/sche­ma11.json
45 schema11_formatted.json vyexportovaný popis schématu modelu po naformátování https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/sche­ma11_formatted.json
       
46 config12.py přidání metainformací, aby schéma odpovídalo specifikaci OpenAPI https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig12.py
47 schema12.json vyexportovaný popis schématu modelu před naformátováním https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/sche­ma12.json
48 schema12_formatted.json vyexportovaný popis schématu modelu po naformátování https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/sche­ma12_formatted.json
       
49 config13.py oprava jména atributu export na minimum https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig13.py
50 schema13.json vyexportovaný popis schématu modelu před naformátováním https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/sche­ma13.json
51 schema13_formatted.json vyexportovaný popis schématu modelu po naformátování https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/sche­ma13_formatted.json
       
52 config14.py trik s opravou definice volitelných atributů https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig14.py
53 schema14.json vyexportovaný popis schématu modelu před naformátováním https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/sche­ma14.json
54 schema14_formatted.json vyexportovaný popis schématu modelu po naformátování https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/sche­ma14_formatted.json
       
55 config15.py přidání popisků k jednotlivým atributům modelu https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/con­fig15.py
55 schema15.json vyexportovaný popis schématu modelu před naformátováním https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/sche­ma15.json
56 schema15_formatted.json vyexportovaný popis schématu modelu po naformátování https://github.com/tisnik/most-popular-python-libs/blob/master/pydantic/sche­ma15_formatted.json
       
57 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. Validace dat v Pythonu s využitím knihovny Pydantic
    https://www.root.cz/clanky/validace-dat-v-pythonu-s-vyuzitim-knihovny-pydantic/
  2. Validace dat v Pythonu s využitím knihovny Pydantic (2. část)
    https://www.root.cz/clanky/validace-dat-v-pythonu-s-vyuzitim-knihovny-pydantic-2-cast/
  3. Pydantic: domácí stránka
    https://docs.pydantic.dev/latest/
  4. Pydantic na GitHubu
    https://github.com/pydantic/pydantic
  5. Pydantic na PyPi
    https://pypi.org/project/pydantic/
  6. Introduction to Python Pydantic Library
    https://www.geeksforgeeks­.org/python/introduction-to-python-pydantic-library/
  7. An introduction to Pydantic (with basic example)
    https://www.slingacademy.com/ar­ticle/an-introduction-to-pydantic-with-basic-example/
  8. Pydantic: Simplifying Data Validation in Python
    https://realpython.com/python-pydantic/
  9. Pydantic: A Guide With Practical Examples
    https://www.datacamp.com/tu­torial/pydantic
  10. Pydantic validators
    https://docs.pydantic.dev/la­test/concepts/validators/
  11. 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/
  12. 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/
  13. 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/
  14. 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/
  15. Mastering Pydantic – A Guide for Python Developers
    https://dev.to/devasservice/mastering-pydantic-a-guide-for-python-developers-3kan
  16. 7 Best Python Libraries for Validating Data
    https://www.yeahhub.com/7-best-python-libraries-validating-data/
  17. Universally unique identifier (Wikipedia)
    https://en.wikipedia.org/wi­ki/Universally_unique_iden­tifier
  18. UUID objects according to RFC 4122 (knihovna pro Python)
    https://docs.python.org/3­.5/library/uuid.html#uuid­.uuid4
  19. Object identifier (Wikipedia)
    https://en.wikipedia.org/wi­ki/Object_identifier
  20. Digital object identifier (Wikipedia)
    https://en.wikipedia.org/wi­ki/Digital_object_identifi­er
  21. voluptuous na (na PyPi)
    https://pypi.python.org/py­pi/voluptuous
  22. voluptuous (na GitHubu)
    https://github.com/alectho­mas/voluptuous
  23. pytest-voluptuous 1.0.2 (na PyPi)
    https://pypi.org/project/pytest-voluptuous/
  24. pytest-voluptuous (na GitHubu)
    https://github.com/F-Secure/pytest-voluptuous
  25. schemagic 0.9.1 (na PyPi)
    https://pypi.python.org/py­pi/schemagic/0.9.1
  26. Schemagic / Schemagic.web (na GitHubu)
    https://github.com/Mechrop­hile/schemagic
  27. schema 0.6.7 (na PyPi)
    https://pypi.python.org/pypi/schema
  28. schema (na GitHubu)
    https://github.com/keleshev/schema
  29. XML Schema validator and data conversion library for Python
    https://github.com/brunato/xmlschema
  30. xmlschema 0.9.7
    https://pypi.python.org/py­pi/xmlschema/0.9.7
  31. jsonschema 2.6.0
    https://pypi.python.org/py­pi/jsonschema
  32. Tired of Pydantic? Try These 5 Game-Changing Python Libraries
    https://developer-service.blog/tired-of-pydantic-try-these-5-game-changing-python-libraries/
  33. PostgreSQL: 32.1. Database Connection Control Functions
    https://www.postgresql.or­g/docs/current/libpq-connect.html#LIBPQ-CONNECT-GSSENCMODE
  34. Python type hints: Literals
    https://typing.python.org/en/la­test/spec/literal.html
  35. YAML
    https://yaml.org/
  36. How to Update Nested Dictionaries in Python Without Overwriting Existing Keys
    https://www.pythontutorial­s.net/blog/updating-nested-dictionaries-when-data-has-existing-key/
  37. Zpracování dat reprezentovaných ve formátu JSON nástrojem jq
    https://www.root.cz/clanky/zpracovani-dat-reprezentovanych-ve-formatu-json-nastrojem-jq/
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.