gateway/modules/configuration.py
2025-04-20 22:22:22 +02:00

183 lines
No EOL
6.5 KiB
Python

"""
Utility module for configuration management.
This module provides a global APP_CONFIG object for accessing configuration from both
config.ini files and environment variables stored in .env files, using a flat structure.
"""
import os
import logging
from typing import Any, Dict, Optional
from pathlib import Path
# Set up logging
logger = logging.getLogger(__name__)
class Configuration:
"""
Configuration class with attribute-style access to flattened configuration.
"""
def __init__(self):
"""Initialize the configuration object"""
self._data = {}
self._config_file_path = None
self._env_file_path = None
self._config_mtime = 0
self._env_mtime = 0
self.refresh()
def refresh(self):
"""Reload configuration from files"""
self._load_config()
self._load_env()
logger.info("Configuration refreshed")
def _load_config(self):
"""Load configuration from config.ini file in flattened format"""
# Find config.ini file (look in current directory and parent directory)
config_path = Path('config.ini')
if not config_path.exists():
# Try in parent directory
config_path = Path('../config.ini')
if not config_path.exists():
logger.warning(f"Configuration file not found at {config_path.absolute()}")
return
self._config_file_path = config_path
current_mtime = os.path.getmtime(config_path)
# Skip if file hasn't changed
if current_mtime <= self._config_mtime:
return
self._config_mtime = current_mtime
try:
with open(config_path, 'r') as f:
for line in f:
line = line.strip()
# Skip empty lines and comments
if not line or line.startswith('#'):
continue
# Parse key-value pairs
if '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip()
# Add directly to data dictionary
self._data[key] = value
except Exception as e:
logger.error(f"Error loading configuration: {e}")
def _load_env(self):
"""Load environment variables from .env file"""
# Find .env file (look in current directory and parent directory)
env_path = Path('.env')
if not env_path.exists():
# Try in parent directory
env_path = Path('../.env')
if not env_path.exists():
logger.warning(f"Environment file not found at {env_path.absolute()}")
return
self._env_file_path = env_path
current_mtime = os.path.getmtime(env_path)
# Skip if file hasn't changed
if current_mtime <= self._env_mtime:
return
self._env_mtime = current_mtime
try:
with open(env_path, 'r') as f:
for line in f:
line = line.strip()
# Skip empty lines and comments
if not line or line.startswith('#'):
continue
# Parse key-value pairs
if '=' in line:
key, value = line.split('=', 1)
key = key.strip()
value = value.strip()
# Add directly to data dictionary
self._data[key] = value
logger.info(f"Loaded environment variables from {env_path.absolute()}")
# Also load system environment variables (don't override existing)
for key, value in os.environ.items():
if key not in self._data:
self._data[key] = value
except Exception as e:
logger.error(f"Error loading environment variables: {e}")
def check_for_updates(self):
"""Check if configuration files have changed and reload if necessary"""
if self._config_file_path and os.path.exists(self._config_file_path):
current_mtime = os.path.getmtime(self._config_file_path)
if current_mtime > self._config_mtime:
logger.info("Config file has changed, reloading...")
self._load_config()
if self._env_file_path and os.path.exists(self._env_file_path):
current_mtime = os.path.getmtime(self._env_file_path)
if current_mtime > self._env_mtime:
logger.info("Environment file has changed, reloading...")
self._load_env()
def get(self, key: str, default: Any = None) -> Any:
"""Get configuration value with optional default"""
self.check_for_updates() # Check for file changes
if key in self._data:
value = self._data[key]
# Handle secrets (keys ending with _SECRET)
if key.endswith("_SECRET"):
return handle_secret(value)
return value
return default
def __getattr__(self, name: str) -> Any:
"""Enable attribute-style access to configuration"""
self.check_for_updates() # Check for file changes
value = self.get(name)
if value is None:
raise AttributeError(f"Configuration key '{name}' not found")
return value
def __dir__(self) -> list:
"""Support auto-completion of attributes"""
self.check_for_updates() # Check for file changes
return list(self._data.keys()) + super().__dir__()
def set(self, key: str, value: Any) -> None:
"""Set a configuration value (for testing/overrides)"""
self._data[key] = value
def handle_secret(value: str) -> str:
"""
Handle secret values. Currently just returns the plain text value,
but can be enhanced to provide actual decryption in the future.
Args:
value: The secret value to handle
Returns:
str: Processed secret value
"""
# For now, just return the value as-is
# In the future, this could be enhanced to decrypt values
return value
# Create the global APP_CONFIG instance
APP_CONFIG = Configuration()