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:
[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.
Path.joinpath(): dołączanie segmentów ścieżki niezależnie od platformy.resolve(): rozwiązuje symlinki do absolutnej ścieżki.- Otwieranie w
"rb": wymaganietomllib.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ącValidationErrorprzy błędach.Anydla 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.
Zalety:
- Redukcja I/O: dysk odczytywany tylko raz.
- Idempotencja: wielokrotne wywołania
get_configuż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 natomlidla starszych wersji. - Zawsze waliduj konfigurację z Pydantic przed użyciem.
- Buforuj parsowanie, ale przewiduj reload.
- Przechowuj sekrety w
SecretStrdo maskowania w logach/debugu. - Buduj ścieżki z
pathlibdla przenośności między platformami.
— Editorial Team
Brak komentarzy.