"""Configuration loader for context_evolver.
Loads configuration from:
1. .env file (for sensitive credentials like API keys)
2. config.yaml file (for algorithm and other settings)
Provides a simple get/set_value interface similar to os.getenv.
"""
import os
import yaml
from dotenv import load_dotenv
_config = {}
_config_loaded = False
def _convert_value(value):
"""Convert string values to appropriate types.
Args:
value: String value from .env file.
Returns:
Converted value (bool, int, float, or original string).
"""
if not isinstance(value, str):
return value
if value.lower() in ('true', 'yes', '1'):
return True
if value.lower() in ('false', 'no', '0'):
return False
try:
if '.' in value:
return float(value)
return int(value)
except ValueError:
pass
return value
def load(config_path=None, env_path=None):
"""Load configuration from .env and YAML files.
Args:
config_path: Path to the YAML config file. If None, defaults to
config.yaml in the context_evolver root directory.
env_path: Path to the .env file. If None, defaults to
.env in the context_evolver root directory.
"""
global _config, _config_loaded
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if env_path is None:
env_path = os.path.join(root_dir, ".env")
if os.path.exists(env_path):
load_dotenv(env_path, override=True)
with open(env_path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
key, value = line.split("=", 1)
_config[key.strip()] = _convert_value(value.strip())
if config_path is None:
config_path = os.path.join(root_dir, "config.yaml")
if os.path.exists(config_path):
with open(config_path, "r", encoding="utf-8") as f:
yaml_config = yaml.safe_load(f) or {}
for key, value in yaml_config.items():
if key not in _config:
_config[key] = value
_config_loaded = True
def get(key, default=None):
"""Get a configuration value.
Checks in order:
1. Loaded config (from .env and config.yaml)
2. Environment variables
3. Default value
Args:
key: Configuration key.
default: Default value if key is not found.
Returns:
The configuration value, or default if not found.
"""
if not _config_loaded:
load()
if key in _config:
return _config[key]
env_value = os.environ.get(key)
if env_value is not None:
return _convert_value(env_value)
return default
def set_value(key, value):
"""Set a configuration value (useful for testing overrides).
Args:
key: Configuration key.
value: Value to set.
"""
if not _config_loaded:
load()
_config[key] = value
def delete(key):
"""Delete a configuration value (useful for testing).
Args:
key: Configuration key to remove.
"""
if not _config_loaded:
load()
_config.pop(key, None)
def snapshot():
"""Take a snapshot of the current configuration (for test save/restore).
Returns:
A copy of the current config dict.
"""
if not _config_loaded:
load()
return dict(_config)
def restore(snap):
"""Restore configuration from a snapshot (for test save/restore).
Args:
snap: A config dict previously returned by snapshot().
"""
global _config
_config = dict(snap)
def reload():
"""Force reload configuration from files.
Useful when config files have been modified.
"""
global _config, _config_loaded
_config = {}
_config_loaded = False
load()