atmos / src /utils /config.py
Edge001's picture
Upload folder using huggingface_hub
c2a61b6 verified
Raw
History Blame Contribute Delete
5.52 kB
"""
Configuration Management Module
Provides YAML-based configuration loading with validation and path resolution.
All system parameters are centralized in config files, avoiding hard-coded values.
Design Decisions:
- YAML format for human readability and easy editing
- Relative paths resolved from project root
- Deep merge support for config overrides
- Validation of required fields
Time Complexity: O(n) where n = number of config entries
Space Complexity: O(n) for config dictionary storage
"""
import os
from pathlib import Path
from typing import Any, Dict, Optional
import yaml
def get_project_root() -> Path:
"""
Find the project root directory.
Strategy: Walk up from this file until we find a directory containing
'config' folder or 'requirements.txt' (project markers).
Returns:
Path to project root directory
Raises:
RuntimeError: If project root cannot be determined
"""
current = Path(__file__).resolve().parent
# Walk up directory tree
for _ in range(10): # Limit search depth
if (current / "config").is_dir() or (current / "requirements.txt").is_file():
return current
parent = current.parent
if parent == current: # Reached filesystem root
break
current = parent
# Fallback: assume we're in src/utils, go up two levels
fallback = Path(__file__).resolve().parent.parent.parent
if fallback.is_dir():
return fallback
raise RuntimeError(
"Could not determine project root. "
"Ensure you're running from within the project directory."
)
def load_config(config_path: Optional[str] = None) -> Dict[str, Any]:
"""
Load configuration from YAML file.
Args:
config_path: Path to config file. If None, loads default.yaml
Returns:
Configuration dictionary with all parameters
Raises:
FileNotFoundError: If config file doesn't exist
yaml.YAMLError: If config file is malformed
"""
project_root = get_project_root()
if config_path is None:
config_path = project_root / "config" / "default.yaml"
else:
config_path = Path(config_path)
if not config_path.is_absolute():
config_path = project_root / config_path
if not config_path.exists():
raise FileNotFoundError(f"Configuration file not found: {config_path}")
with open(config_path, "r", encoding="utf-8") as f:
config = yaml.safe_load(f)
# Resolve relative paths to absolute paths
config = _resolve_paths(config, project_root)
# Add project root to config for convenience
config["_project_root"] = str(project_root)
return config
def _resolve_paths(config: Dict[str, Any], project_root: Path) -> Dict[str, Any]:
"""
Resolve relative paths in config to absolute paths.
Identifies path-like config values and resolves them relative to project root.
Path-like values are those ending in '_dir' or '_path'.
Args:
config: Configuration dictionary
project_root: Project root path for resolution
Returns:
Config with resolved paths
"""
path_suffixes = ("_dir", "_path", "_file")
def resolve_recursive(obj: Any, parent_key: str = "") -> Any:
if isinstance(obj, dict):
return {
k: resolve_recursive(v, k)
for k, v in obj.items()
}
elif isinstance(obj, list):
return [resolve_recursive(item, parent_key) for item in obj]
elif isinstance(obj, str):
# Check if this looks like a path
if any(parent_key.endswith(suffix) for suffix in path_suffixes):
path = Path(obj)
if not path.is_absolute():
return str(project_root / path)
return obj
else:
return obj
return resolve_recursive(config)
def save_config(config: Dict[str, Any], save_path: str) -> None:
"""
Save configuration to YAML file.
Useful for saving experiment configurations for reproducibility.
Args:
config: Configuration dictionary
save_path: Path to save config file
"""
save_path = Path(save_path)
save_path.parent.mkdir(parents=True, exist_ok=True)
# Remove internal keys before saving
config_to_save = {
k: v for k, v in config.items()
if not k.startswith("_")
}
with open(save_path, "w", encoding="utf-8") as f:
yaml.dump(config_to_save, f, default_flow_style=False, sort_keys=False)
def merge_configs(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
"""
Deep merge two configuration dictionaries.
Override values take precedence. Useful for command-line overrides.
Args:
base: Base configuration
override: Override values
Returns:
Merged configuration
"""
result = base.copy()
for key, value in override.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = merge_configs(result[key], value)
else:
result[key] = value
return result