"""Configuration management for WeMo MCP server."""
import copy
import logging
import os
from pathlib import Path
from typing import Any
try:
import yaml
YAML_AVAILABLE = True
except ImportError:
YAML_AVAILABLE = False
logger = logging.getLogger(__name__)
# Default configuration values
DEFAULT_CONFIG = {
"network": {
"default_subnet": "192.168.1.0/24",
"scan_timeout": 0.6,
"max_workers": 60,
"retry_attempts": 3,
"retry_delay": 0.5,
},
"cache": {
"enabled": True,
"file_path": str(Path.home() / ".wemo_mcp_cache.json"),
"ttl_seconds": 3600, # 1 hour
},
"logging": {
"level": "INFO",
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
},
}
# Environment variable prefix
ENV_PREFIX = "WEMO_MCP_"
class Config:
"""Manages configuration for WeMo MCP server."""
def __init__(self, config_file: Path | None = None):
"""Initialize configuration manager.
Args:
----
config_file: Optional path to YAML configuration file
"""
self._config: dict[str, Any] = copy.deepcopy(DEFAULT_CONFIG)
self._config_file = config_file
# Load from file if specified
if config_file and config_file.exists():
self._load_from_file(config_file)
# Override with environment variables
self._load_from_env()
def _load_from_file(self, config_file: Path) -> None:
"""Load configuration from YAML file.
Args:
----
config_file: Path to YAML configuration file
"""
if not YAML_AVAILABLE:
logger.warning(
"PyYAML not installed. Cannot load config file. "
"Install with: pip install pyyaml",
)
return
try:
with config_file.open() as f:
file_config = yaml.safe_load(f)
if file_config:
self._merge_config(file_config)
logger.info(f"Loaded configuration from {config_file}")
except Exception as e:
logger.error(f"Failed to load config from {config_file}: {e}")
def _load_from_env(self) -> None:
"""Load configuration from environment variables."""
# Network settings
if subnet := os.getenv(f"{ENV_PREFIX}DEFAULT_SUBNET"):
self._config["network"]["default_subnet"] = subnet
if timeout := os.getenv(f"{ENV_PREFIX}SCAN_TIMEOUT"):
try:
self._config["network"]["scan_timeout"] = float(timeout)
except ValueError:
logger.warning(f"Invalid SCAN_TIMEOUT value: {timeout}")
if workers := os.getenv(f"{ENV_PREFIX}MAX_WORKERS"):
try:
self._config["network"]["max_workers"] = int(workers)
except ValueError:
logger.warning(f"Invalid MAX_WORKERS value: {workers}")
# Cache settings
if cache_enabled := os.getenv(f"{ENV_PREFIX}CACHE_ENABLED"):
self._config["cache"]["enabled"] = cache_enabled.lower() in ("true", "1", "yes")
if cache_file := os.getenv(f"{ENV_PREFIX}CACHE_FILE"):
self._config["cache"]["file_path"] = cache_file
if ttl := os.getenv(f"{ENV_PREFIX}CACHE_TTL"):
try:
self._config["cache"]["ttl_seconds"] = int(ttl)
except ValueError:
logger.warning(f"Invalid CACHE_TTL value: {ttl}")
# Logging settings
if log_level := os.getenv(f"{ENV_PREFIX}LOG_LEVEL"):
self._config["logging"]["level"] = log_level.upper()
def _merge_config(self, new_config: dict[str, Any]) -> None:
"""Merge new configuration into existing config.
Args:
----
new_config: Dictionary with new configuration values
"""
for section, values in new_config.items():
if section in self._config and isinstance(values, dict):
self._config[section].update(values)
else:
self._config[section] = values
def get(self, section: str, key: str, default: Any = None) -> Any:
"""Get a configuration value.
Args:
----
section: Configuration section (e.g., 'network', 'cache')
key: Configuration key within section
default: Default value if not found
Returns:
-------
Configuration value or default
"""
return self._config.get(section, {}).get(key, default)
def get_section(self, section: str) -> dict[str, Any]:
"""Get entire configuration section.
Args:
----
section: Configuration section name
Returns:
-------
Dictionary with section configuration
"""
return dict(self._config.get(section, {}))
def set(self, section: str, key: str, value: Any) -> None:
"""Set a configuration value.
Args:
----
section: Configuration section
key: Configuration key
value: Value to set
"""
if section not in self._config:
self._config[section] = {}
self._config[section][key] = value
def get_all(self) -> dict[str, Any]:
"""Get all configuration.
Returns
-------
Complete configuration dictionary
"""
return self._config.copy()
def save_to_file(self, config_file: Path) -> bool:
"""Save current configuration to YAML file.
Args:
----
config_file: Path to save configuration
Returns:
-------
True if saved successfully, False otherwise
"""
if not YAML_AVAILABLE:
logger.error(
"PyYAML not installed. Cannot save config file. "
"Install with: pip install pyyaml",
)
return False
try:
config_file.parent.mkdir(parents=True, exist_ok=True)
with config_file.open("w") as f:
yaml.safe_dump(self._config, f, default_flow_style=False, sort_keys=False)
logger.info(f"Saved configuration to {config_file}")
return True
except Exception as e:
logger.error(f"Failed to save config to {config_file}: {e}")
return False
# Global configuration instance
_config = Config()
def get_config() -> Config:
"""Get the global configuration instance.
Returns
-------
Config instance
"""
return _config
def init_config(config_file: Path | None = None) -> Config:
"""Initialize configuration with optional config file.
Args:
----
config_file: Optional path to YAML configuration file
Returns:
-------
Config instance
"""
global _config # noqa: PLW0603
_config = Config(config_file)
return _config