"""Configuration classes and utilities.""" from dataclasses import dataclass, field from pathlib import Path from typing import Dict, List, Optional import toml import dacite from flask import current_app @dataclass class CoreConfig: """Core application configuration.""" debug: bool = False port: int = 5000 name: str = "Front page" @dataclass class OidcConfig: """OIDC client configuration.""" issuer: str client_id: str client_secret: Optional[str] = None client_secret_file: Optional[str] = None scopes: List[str] = field(default_factory=list) @dataclass class AppConfig: """App link configuration.""" url: str name: Optional[str] = None image: Optional[str] = None description: Optional[str] = None groups: List[str] = field(default_factory=list) @dataclass class Config: """Top-level configuration.""" core: CoreConfig = field(default_factory=CoreConfig) oidc: OidcConfig = field(default_factory=OidcConfig) apps: Dict[str, AppConfig] = field(default_factory=dict) class ConfigError(Exception): pass def _validate(config: Config) -> None: oidc = config.oidc if oidc.client_secret is None and oidc.client_secret_file is None: raise ConfigError( "exactly one of oidc.client_secret or oidc.client_secret_file is required" ) if oidc.client_secret is not None and oidc.client_secret_file is not None: raise ConfigError( "exactly one of oidc.client_secret or oidc.client_secret_file is required" ) def load(file: str) -> Config: """ Load the configuration from a `file` path containing a TOML configuration. :param file: a file path to a TOML config :return: the configuration object """ path = Path(file) cfg = dacite.from_dict(data_class=Config, data=toml.load(path)) _validate(cfg) return cfg def current_config() -> Config: """ Load the configuration from the current Flask app. :return: the configuration object """ return current_app.config["user_config"]