Powrót do strony głównej

Ładowanie konfiguracji TOML z Pydantic w Pythonie

Moduł ładuje settings.toml za pomocą tomllib, buforuje z lru_cache i waliduje modele Pydantic. Wspiera typizację TypeVar dla dowolnych klas config. Idealny dla aplikacji production z separacją logiki i parametrów.

Parsowanie settings.toml i walidacja Pydantic w Pythonie
Advertisement 728x90

Czytanie i walidacja settings.toml z Pydantic w aplikacjach Python

Moduł do czytania settings.toml określa ścieżkę do pliku, parsuje go za pomocą tomllib, buforuje wynik przez lru_cache i waliduje sekcje z Pydantic. To zapewnia oddzielenie logiki od parametrów, minimalizuje wielokrotne odczyty z dysku i gwarantuje bezpieczeństwo typów.

from functools import lru_cache
from pathlib import Path
from tomllib import load
from typing import Any, Type, TypeVar

from pydantic import BaseModel, SecretStr

ConfigType = TypeVar("ConfigType", bound=BaseModel)

class BotConfig(BaseModel):
    token: SecretStr

@lru_cache
def parse_config_file() -> dict[str, Any]:
    config_path = Path(__file__).resolve().parent.parent.joinpath("settings.toml")

    if not config_path.exists():
        error = "Could not find settings file"
        raise ValueError(error)

    with open(config_path, "rb") as file:
        config_data = load(file)

    return config_data

def get_config(model: Type[ConfigType], root_key: str) -> ConfigType:
    config_dict = parse_config_file()

    if root_key not in config_dict:
        error = f"Key {root_key} not found"
        raise ValueError(error)

    return model.model_validate(config_dict[root_key])

Dlaczego TOML i Pydantic do konfiguracji

Pliki TOML są czytelne dla człowieka i parsowane do słowników Python w sposób ścisły. Sekcje takie jak [bot] zamieniają się w zagnieżdżone słowniki. Pydantic sprawdza strukturę, konwertuje typy i maskuje sekrety za pomocą SecretStr.

Przykład TOML:

Google AdInline article slot
[bot]
token = "123456:ABCDEF"

Parsowanie daje {"bot": {"token": "123456:ABCDEF"}}. Walidacja tworzy obiekt BotConfig.

Buforowanie z @lru_cache eliminuje wielokrotne odczyty: pierwszy wywołanie parsuje, kolejne zwracają bufor. Do przeładowania — parse_config_file.cache_clear().

Praca ze ścieżkami za pomocą pathlib

Path(__file__).resolve().parent.parent.joinpath("settings.toml") buduje absolutną ścieżkę do pliku w katalogu nadrzędnym projektu. Metody exists() i właściwości parent ułatwiają sprawdzanie.

Google AdInline article slot
  • Path.joinpath(): dołączanie segmentów ścieżki niezależnie od platformy.
  • resolve(): rozwiązuje symlinki do absolutnej ścieżki.
  • Otwieranie w "rb": wymaganie tomllib.load() do odczytu binarnego.

Typowanie z TypeVar i Pydantic

ConfigType = TypeVar("ConfigType", bound=BaseModel) pozwala funkcji get_config działać z dowolnymi modelami Pydantic, zwracając instancję podanego typu.

  • Type[ConfigType]: parametr to klasa modelu, nie instancja.
  • model_validate(): waliduje słownik do obiektu, rzucając ValidationError przy błędach.
  • Any dla słownika: TOML zawiera mieszane typy (str, int, bool, dict).

Rozszerz dla innych sekcji:

class DatabaseConfig(BaseModel):
    host: str
    port: int
    user: str

config = get_config(DatabaseConfig, "database")

Buforowanie i dekoratory

@lru_cache zapisuje wynik według argumentów (tu funkcja bezargumentowa). LRU usuwa stare wpisy przy limicie, ale dla konfiguracji wystarczy jeden slot.

Google AdInline article slot

Zalety:

  • Redukcja I/O: dysk odczytywany tylko raz.
  • Idempotencja: wielokrotne wywołania get_config używają jednego parsowania.
  • Bezpieczeństwo wątkowe: standardowy dekorator jest thread-safe.

Wady: zmiany w TOML na dysku ignorowane do cache_clear().

Struktura modułu

  • Modele (BotConfig): niezależne od loaderów.
  • parse_config_file(): parsuje cały TOML.
  • get_config(): wyciąga/waliduje sekcję.

Rozdzielenie odpowiedzialności poprawia testowalność: mockuj ścieżki lub ciągi TOML.

Co ważne:

  • Używaj tomllib (Python 3.11+), fallback na tomli dla starszych wersji.
  • Zawsze waliduj konfigurację z Pydantic przed użyciem.
  • Buforuj parsowanie, ale przewiduj reload.
  • Przechowuj sekrety w SecretStr do maskowania w logach/debugu.
  • Buduj ścieżki z pathlib dla przenośności między platformami.

— Editorial Team

Advertisement 728x90

Czytaj dalej