import os import yaml import logging from .config_loader import load_config, get_config log = logging.getLogger("ainventory") class ConfigManager: """Manages backend.yaml configuration file updates.""" @staticmethod def get_config_path(): """Returns the absolute path to backend.yaml.""" base_dir = os.path.dirname(os.path.abspath(__file__)) project_root = os.path.dirname(base_dir) return os.path.join(project_root, "config", "backend.yaml") @staticmethod def read_config() -> dict: """Read backend.yaml and return current config.""" path = ConfigManager.get_config_path() if not os.path.exists(path): log.warning(f"ℹ️ {path} not found. Returning empty dict.") return {} try: with open(path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) or {} except Exception as e: log.error(f"❌ Failed to read {path}: {e}") return {} @staticmethod def update_config(updates: dict) -> dict: """ Update backend.yaml with new values and return updated config. Only updates sections in backend.yaml. """ path = ConfigManager.get_config_path() current_yaml = ConfigManager.read_config() # Merge updates (deep merge for sections) for section, values in updates.items(): if isinstance(values, dict) and section in current_yaml and isinstance(current_yaml[section], dict): current_yaml[section].update(values) else: current_yaml[section] = values try: with open(path, 'w', encoding='utf-8') as f: yaml.safe_dump(current_yaml, f, default_flow_style=False, sort_keys=False) log.info(f"✅ Updated {path} with new values.") # Reload the global config load_config() return get_config() except Exception as e: log.error(f"❌ Failed to write {path}: {e}") raise @staticmethod def validate_config_file() -> bool: """Validate backend.yaml syntax and required fields.""" path = ConfigManager.get_config_path() if not os.path.exists(path): return False try: with open(path, 'r', encoding='utf-8') as f: yaml.safe_load(f) return True except Exception: return False @staticmethod def get_masked_key(key_name: str): """Returns a masked version of a configuration value or env var.""" config = get_config() # Try to find in config first val = None if key_name == "JWT_SECRET_KEY": val = config.get("auth", {}).get("jwt_secret_key") elif key_name == "GEMINI_API_KEY": val = config.get("ai", {}).get("gemini_api_key") elif key_name == "CLAUDE_API_KEY": val = config.get("ai", {}).get("claude_api_key") # Fallback to env if not val: val = os.environ.get(key_name) if not val: return None prefix = "" if "GEMINI" in key_name: prefix = "G-" elif "CLAUDE" in key_name: prefix = "sk-" if len(val) <= 8: return f"{prefix}****" return f"{prefix}****{val[-4:]}" # Module-level functions for backward compatibility and direct import def read_config() -> dict: """Read backend.yaml and return current config.""" return ConfigManager.read_config() def update_config(updates: dict) -> dict: """Update backend.yaml with new values and return updated config.""" return ConfigManager.update_config(updates) def validate_config_file() -> bool: """Validate backend.yaml syntax and required fields.""" return ConfigManager.validate_config_file()