Load Typed Settings from a TOML File
Parse TOML into frozen dataclasses, validate nested service and database tables, and report missing or mistyped keys with clear errors.
Code
import tempfile
import tomllib
from dataclasses import dataclass
from pathlib import Path
class ConfigError(ValueError):
pass
@dataclass(frozen=True)
class DatabaseSettings:
host: str
port: int
name: str
pool_size: int = 5
@dataclass(frozen=True)
class Settings:
service: str
debug: bool
database: DatabaseSettings
def load_settings(path: Path) -> Settings:
try:
raw = tomllib.loads(path.read_text(encoding="utf-8"))
except tomllib.TOMLDecodeError as error:
raise ConfigError(f"invalid TOML: {error}") from error
service = raw.get("service")
if not isinstance(service, dict):
raise ConfigError("missing [service] table")
name = service.get("name")
debug = service.get("debug", False)
if not isinstance(name, str) or not name.strip():
raise ConfigError("service.name must be a non-empty string")
if not isinstance(debug, bool):
raise ConfigError("service.debug must be a boolean")
database = raw.get("database")
if not isinstance(database, dict):
raise ConfigError("missing [database] table")
host = database.get("host")
port = database.get("port")
db_name = database.get("name")
pool_size = database.get("pool_size", 5)
if not isinstance(host, str) or not host:
raise ConfigError("database.host must be a non-empty string")
if not isinstance(port, int) or isinstance(port, bool) or not 1 <= port <= 65535:
raise ConfigError("database.port must be an integer between 1 and 65535")
if not isinstance(db_name, str) or not db_name:
raise ConfigError("database.name must be a non-empty string")
if not isinstance(pool_size, int) or isinstance(pool_size, bool) or pool_size < 1:
raise ConfigError("database.pool_size must be a positive integer")
return Settings(
service=name.strip(),
debug=debug,
database=DatabaseSettings(
host=host,
port=port,
name=db_name,
pool_size=pool_size,
),
)
def main() -> None:
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "config.toml"
path.write_text(
"""
[service]
name = "orders"
debug = true
[database]
host = "db.internal"
port = 5432
name = "orders"
""",
encoding="utf-8",
)
settings = load_settings(path)
print(settings)
print("pool size:", settings.database.pool_size)
broken = Path(tmp) / "broken.toml"
broken.write_text(
"""
[service
name = 'x'
""",
encoding="utf-8",
)
try:
load_settings(broken)
except ConfigError as error:
print("error:", error)
if __name__ == "__main__":
main()
Report a problem with this exampleOpens a pre-filled issue on GitHub.