File size: 4,284 Bytes
0828c2c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
"""Configuration loader for YAML settings and environment variables."""

import logging
import os
from pathlib import Path
from typing import Any

import yaml
from dotenv import load_dotenv

logger = logging.getLogger(__name__)


class ConfigLoader:
    """Load and manage configuration from YAML and environment variables."""

    def __init__(self, config_path: str = "config.yaml", env_path: str = ".env"):
        """Initialize configuration loader.

        Args:
            config_path: Path to YAML configuration file
            env_path: Path to .env file
        """
        self.config_path = Path(config_path)
        self.env_path = Path(env_path)
        self._config: dict[str, Any] = {}

        # Load environment variables first
        self._load_env()

        # Load YAML configuration
        self._load_yaml()

        # Setup logging
        self._setup_logging()

    def _load_env(self) -> None:
        """Load environment variables from .env file."""
        if self.env_path.exists():
            load_dotenv(self.env_path)
            logger.debug(f"Loaded environment variables from {self.env_path}")
        else:
            logger.warning(f".env file not found at {self.env_path}, using env.template defaults")
            # Try to load from env.template as fallback
            env_template = Path("env.template")
            if env_template.exists():
                load_dotenv(env_template)

    def _load_yaml(self) -> None:
        """Load configuration from YAML file."""
        if not self.config_path.exists():
            msg = f"Configuration file not found: {self.config_path}"
            raise FileNotFoundError(msg)

        with open(self.config_path) as f:
            self._config = yaml.safe_load(f)

        logger.debug(f"Loaded configuration from {self.config_path}")

    def _setup_logging(self) -> None:
        """Setup logging configuration."""
        log_config = self._config.get("logging", {})
        log_level = os.getenv("LOG_LEVEL", log_config.get("level", "INFO"))

        logging.basicConfig(
            level=getattr(logging, log_level),
            format=log_config.get("format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"),
        )

    def get(self, key: str, default: Any = None) -> Any:
        """Get configuration value by key (supports nested keys with dot notation).

        Args:
            key: Configuration key (e.g., 'llm.model' for nested access)
            default: Default value if key not found

        Returns:
            Configuration value
        """
        keys = key.split(".")
        value = self._config

        for k in keys:
            if isinstance(value, dict):
                value = value.get(k)
                if value is None:
                    return default
            else:
                return default

        return value

    def get_env(self, key: str, default: str | None = None) -> str | None:
        """Get environment variable.

        Args:
            key: Environment variable name
            default: Default value if not found

        Returns:
            Environment variable value
        """
        return os.getenv(key, default)

    @property
    def config(self) -> dict[str, Any]:
        """Get the entire configuration dictionary."""
        return self._config

    def format_template(self, template: str) -> str:
        """Format template string with profile information.

        Args:
            template: Template string with {name}, {title} placeholders

        Returns:
            Formatted string
        """
        profile = self._config.get("profile", {})
        return template.format(
            name=profile.get("name", "the candidate"),
            title=profile.get("title", "professional"),
        )


# Singleton instance
_config_instance: ConfigLoader | None = None


def get_config() -> ConfigLoader:
    """Get singleton configuration instance."""
    global _config_instance
    if _config_instance is None:
        _config_instance = ConfigLoader()
    return _config_instance


def reload_config() -> ConfigLoader:
    """Reload configuration (useful for testing or config changes)."""
    global _config_instance
    _config_instance = ConfigLoader()
    return _config_instance