"""Configuration management for WLED Screen Controller.""" import os from pathlib import Path from typing import List, Literal import yaml from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict class ServerConfig(BaseSettings): """Server configuration.""" host: str = "0.0.0.0" port: int = 8080 log_level: str = "INFO" cors_origins: List[str] = ["*"] class AuthConfig(BaseSettings): """Authentication configuration.""" api_keys: dict[str, str] = {} # label: key mapping (required for security) class StorageConfig(BaseSettings): """Storage configuration.""" devices_file: str = "data/devices.json" templates_file: str = "data/capture_templates.json" postprocessing_templates_file: str = "data/postprocessing_templates.json" picture_sources_file: str = "data/picture_sources.json" picture_targets_file: str = "data/picture_targets.json" pattern_templates_file: str = "data/pattern_templates.json" color_strip_sources_file: str = "data/color_strip_sources.json" profiles_file: str = "data/profiles.json" class LoggingConfig(BaseSettings): """Logging configuration.""" format: Literal["json", "text"] = "json" file: str = "logs/wled_controller.log" max_size_mb: int = 100 backup_count: int = 5 class Config(BaseSettings): """Main application configuration.""" model_config = SettingsConfigDict( env_prefix="WLED_", env_nested_delimiter="__", case_sensitive=False, ) server: ServerConfig = Field(default_factory=ServerConfig) auth: AuthConfig = Field(default_factory=AuthConfig) storage: StorageConfig = Field(default_factory=StorageConfig) logging: LoggingConfig = Field(default_factory=LoggingConfig) @classmethod def from_yaml(cls, config_path: str | Path) -> "Config": """Load configuration from YAML file. Args: config_path: Path to YAML configuration file Returns: Config instance """ config_path = Path(config_path) if not config_path.exists(): raise FileNotFoundError(f"Configuration file not found: {config_path}") with open(config_path, "r") as f: config_data = yaml.safe_load(f) return cls(**config_data) @classmethod def load(cls) -> "Config": """Load configuration from default locations. Tries to load from: 1. Environment variable WLED_CONFIG_PATH 2. ./config/default_config.yaml 3. Default values Returns: Config instance """ config_path = os.getenv("WLED_CONFIG_PATH") if config_path: return cls.from_yaml(config_path) # Try default location default_path = Path("config/default_config.yaml") if default_path.exists(): return cls.from_yaml(default_path) # Use defaults return cls() # Global configuration instance config: Config | None = None def get_config() -> Config: """Get global configuration instance. Returns: Config instance """ global config if config is None: config = Config.load() return config def reload_config() -> Config: """Reload configuration from file. Returns: New Config instance """ global config config = Config.load() return config