settings.toml mit Pydantic in Python-Apps lesen und validieren
Dieses Modul übernimmt das Lesen von settings.toml, das Parsen mit tomllib, das Cachen des Ergebnisses via lru_cache und die Validierung von Abschnitten mit Pydantic. Es trennt die Konfigurationslogik von den App-Parametern, vermeidet wiederholte Festplatten-Zugriffe und gewährleistet Typsicherheit.
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])
Warum TOML und Pydantic für Konfigurationen?
TOML-Dateien sind menschenlesbar und lassen sich sauber in Python-Dictionaries parsen. Abschnitte wie [bot] werden zu verschachtelten Dicts. Pydantic validiert die Struktur, kümmert sich um Typkonvertierungen und maskiert Geheimnisse mit SecretStr.
Beispiel-TOML:
[bot]
token = "123456:ABCDEF"
Das Parsen ergibt {"bot": {"token": "123456:ABCDEF"}}. Die Validierung erzeugt ein BotConfig-Objekt.
Durch Caching mit @lru_cache entfallen wiederholte Lesevorgänge: Der erste Aufruf parst, spätere greifen auf den Cache zu. Zum Neuladen: parse_config_file.cache_clear().
Pfade mit pathlib handhaben
Path(__file__).resolve().parent.parent.joinpath("settings.toml") baut einen absoluten Pfad zur Datei im übergeordneten Projektverzeichnis auf. exists() prüft die Existenz, parent-Eigenschaften erleichtern die Validierung.
Path.joinpath(): hängt Pfadsegmente plattformübergreifend an.resolve(): löst Symlinks zu absoluten Pfaden auf.- Öffnen im Modus
"rb": Vorgabe vontomllib.load()für binäres Lesen.
Typ-Hinweise mit TypeVar und Pydantic
ConfigType = TypeVar("ConfigType", bound=BaseModel) ermöglicht es get_config, mit beliebigen Pydantic-Modellen zu arbeiten und eine Instanz des übergebenen Typs zurückzugeben.
Type[ConfigType]: erwartet eine Modellklasse, kein Objekt.model_validate(): wandelt ein Dict in ein validiertes Objekt um, wirft bei FehlernValidationError.Anyfür Dicts: TOML mischt Typen (str, int, bool, dict).
Erweiterung für weitere Abschnitte:
class DatabaseConfig(BaseModel):
host: str
port: int
user: str
config = get_config(DatabaseConfig, "database")
Caching und Dekoratoren
@lru_cache speichert Ergebnisse basierend auf Argumenten (hier keine, also einfach). LRU räumt alte Einträge bei Limits auf, für Konfigs reicht ein Slot.
Vorteile:
- Spart I/O: Festplattenlesung nur einmal.
- Idempotent: Mehrere
get_config-Aufrufe teilen ein Parsing. - Thread-sicher: Der Standardbibliotheks-Dekorator regelt das.
Nachteile: Änderungen an der TOML-Datei werden ignoriert, bis cache_clear() aufgerufen wird.
Modulstruktur
- Modelle (
BotConfig): unabhängig von Loaders. parse_config_file(): parst die gesamte TOML.get_config(): extrahiert und validiert einen Abschnitt.
Diese Trennung der Verantwortlichkeiten verbessert die Testbarkeit: Pfade oder TOML-Strings mocken.
Wichtige Tipps:
tomllib(Python 3.11+) nutzen, für ältere Versionen auftomliausweichen.- Konfiguration immer mit Pydantic validieren, bevor sie genutzt wird.
- Parsing cachen, aber Neuladen unterstützen.
- Geheimnisse in
SecretStrspeichern, um sie in Logs/Debug auszublenden. - Pfade mit
pathlibbauen für plattformübergreifende Kompatibilität.
— Editorial Team
Noch keine Kommentare.