Files
tfm_ainventory/backend/config_manager.py
Daniel Bedeleanu 28cdc90056 feat(07-02): update config_manager.py for YAML support
- Replace dotenv with PyYAML
- Implement read_config, update_config, validate_config_file for backend.yaml
- Update get_masked_key to use new config structure
- Remove inventory.env references
2026-04-23 12:45:51 +03:00

119 lines
3.9 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()