Source code for nqs_sdk.core.config

import json
from pathlib import Path
from typing import Any, Dict, Tuple, Union


[docs] class ConfigLoader: """ Utility class for loading and processing simulation configuration files This class provides static methods for handling different configuration formats (JSON, YAML) and sources (files, strings, dictionaries). It includes validation and format detection capabilities to ensure robust configuration loading The loader supports both file-based and string-based configurations, with automatic format detection based on content or file extensions """
[docs] @staticmethod def load(config: Union[str, dict, Path]) -> Tuple[str, str]: """ Load configuration from various sources and return standardized format This method handles multiple input types and automatically detects the configuration format, returning a tuple of (content, format_type) Args: config: Configuration source, can be: - dict: Configuration dictionary (converted to JSON) - str: JSON string, YAML string, or file path - Path: Path object pointing to configuration file Returns: Tuple of (content_string, format_type) where: - content_string: String representation of the configuration - format_type: Either "json" or "yaml" Raises: FileNotFoundError: If file path doesn't exist ValueError: If string appears to be malformed JSON or unsupported format Example: >>> content, fmt = ConfigLoader.load({"agents": [], "protocols": {}}) >>> content, fmt = ConfigLoader.load("config.yaml") >>> content, fmt = ConfigLoader.load('{"simulation": {"blocks": 1000}}') """ if isinstance(config, dict): return json.dumps(config), "json" if isinstance(config, (str, Path)): if isinstance(config, str): try: json.loads(config) return config, "json" except json.JSONDecodeError: if config.strip().startswith("{") or config.strip().startswith("["): raise ValueError("The provided string appears to be JSON but is malformed") if config.strip().startswith("---") or ":" in config.splitlines()[0]: return config, "yaml" path = Path(config) else: path = config else: raise ValueError(f"Unsupported configuration type: {type(config)}") if not path.exists(): raise FileNotFoundError(f"Configuration file '{path}' does not exist") content = path.read_text() if path.suffix.lower() in (".yml", ".yaml"): format_type = "yaml" elif path.suffix.lower() == ".json": format_type = "json" else: raise ValueError(f"Unsupported file format: {path.suffix}") return content, format_type
[docs] @staticmethod def merge_configs(base_config: Dict[str, Any], update_config: Dict[str, Any]) -> Dict[str, Any]: """ Recursively merge two configuration dictionaries Performs a deep merge where nested dictionaries are merged recursively, and non-dictionary values in update_config override those in base_config Args: base_config: Base configuration dictionary update_config: Configuration updates to apply Returns: New dictionary containing the merged configuration Example: >>> base = {"simulation": {"blocks": 1000}, "agents": []} >>> update = {"simulation": {"timestep": 12}, "protocols": {}} >>> merged = ConfigLoader.merge_configs(base, update) >>> # Result: {"simulation": {"blocks": 1000, "timestep": 12}, >>> # "agents": [], "protocols": {}} """ result = base_config.copy() for key, value in update_config.items(): if key in result and isinstance(result[key], dict) and isinstance(value, dict): result[key] = ConfigLoader.merge_configs(result[key], value) else: result[key] = value return result