Source code for tseda.config.config_loader
"""Configuration loader for TSEDA.
This module handles loading and accessing configuration values from the
tseda_config.yaml file. The configuration is loaded once at startup and
provides a singleton interface for accessing configuration throughout the
application.
"""
import os
from pathlib import Path
from typing import Any, Optional
import yaml
[docs]
class ConfigurationManager:
"""Singleton configuration manager for TSEDA."""
_instance: Optional["ConfigurationManager"] = None
_config: dict[str, Any] = {}
_loaded: bool = False
def __new__(cls) -> "ConfigurationManager":
"""Ensure only one instance exists."""
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
[docs]
@classmethod
def load_config(cls) -> dict[str, Any]:
"""Load configuration from YAML file.
Returns:
Configuration dictionary.
Raises:
FileNotFoundError: If config file is not found.
yaml.YAMLError: If YAML parsing fails.
"""
if cls._loaded and cls._config:
return cls._config
# Locate config file relative to this module
config_dir = Path(__file__).parent
config_file = config_dir / "tseda_config.yaml"
if not config_file.exists():
raise FileNotFoundError(
f"Configuration file not found at {config_file}. "
"Please ensure tseda_config.yaml exists in {config_dir}."
)
try:
with open(config_file, "r", encoding="utf-8") as f:
cls._config = yaml.safe_load(f) or {}
cls._loaded = True
return cls._config
except yaml.YAMLError as e:
raise yaml.YAMLError(
f"Failed to parse configuration file {config_file}: {e}"
)
[docs]
@classmethod
def get(cls, key_path: str, default: Any = None) -> Any:
"""Get configuration value by dot-separated path.
Args:
key_path: Dot-separated path to config value (e.g., "file_upload.max_file_lines").
default: Default value if key is not found.
Returns:
Configuration value or default if not found.
Example:
>>> max_lines = ConfigurationManager.get("file_upload.max_file_lines")
>>> dw_low = ConfigurationManager.get("noise_validation.dw_low", 1.5)
"""
if not cls._config:
cls.load_config()
keys = key_path.split(".")
value = cls._config
for key in keys:
if isinstance(value, dict) and key in value:
value = value[key]
else:
return default
return value
[docs]
@classmethod
def get_section(cls, section: str) -> dict[str, Any]:
"""Get entire configuration section.
Args:
section: Top-level section name (e.g., "file_upload", "grouping_heuristic").
Returns:
Dictionary containing the section, or empty dict if not found.
Example:
>>> window_config = ConfigurationManager.get_section("window_selection")
"""
if not cls._config:
cls.load_config()
return cls._config.get(section, {})
[docs]
@classmethod
def reload(cls) -> dict[str, Any]:
"""Force reload of configuration from file.
Returns:
Reloaded configuration dictionary.
"""
cls._config = {}
cls._loaded = False
return cls.load_config()
[docs]
@classmethod
def reset(cls) -> None:
"""Reset configuration (for testing)."""
cls._config = {}
cls._loaded = False
[docs]
def get_config(key_path: str, default: Any = None) -> Any:
"""Convenience function to get configuration value.
Args:
key_path: Dot-separated path to config value.
default: Default value if key is not found.
Returns:
Configuration value or default.
Example:
>>> max_lines = get_config("file_upload.max_file_lines")
"""
return ConfigurationManager.get(key_path, default)
[docs]
def get_config_section(section: str) -> dict[str, Any]:
"""Convenience function to get entire configuration section.
Args:
section: Top-level section name.
Returns:
Dictionary containing the section.
Example:
>>> window_cfg = get_config_section("window_selection")
"""
return ConfigurationManager.get_section(section)