Source code for mrv.utils.config

"""
mrv.utils.config -- Load YAML configuration.

Single source of truth is config.yaml. No built-in defaults to maintain.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any, Dict, List, Optional

import yaml  # type: ignore[import-untyped]

__all__ = [
    "load",
    "get_data_dir",
    "get_assets",
]


def _find_default_config() -> Path:
    """Locate config.yaml by walking upward from this file."""
    anchor = Path(__file__).resolve().parent
    for _ in range(6):
        candidate = anchor / "config.yaml"
        if candidate.exists():
            return candidate
        anchor = anchor.parent
    return Path.cwd() / "config.yaml"


_DEFAULT_CONFIG_PATH = _find_default_config()


[docs] def load(path: Optional[str | Path] = None) -> Dict[str, Any]: """ Load configuration from YAML. Parameters ---------- path : str or Path, optional If None, searches for config.yaml automatically. Raises ------ FileNotFoundError If no config file found. """ cfg_path = Path(path) if path else _DEFAULT_CONFIG_PATH if not cfg_path.exists(): raise FileNotFoundError(f"Config not found: {cfg_path}") with open(cfg_path, encoding="utf-8") as f: cfg = yaml.safe_load(f) if not isinstance(cfg, dict): raise ValueError(f"Invalid config (expected dict): {cfg_path}") return cfg
[docs] def get_data_dir(cfg: Dict[str, Any], base: Optional[Path] = None) -> Path: """Resolve data directory from config.""" data_dir = Path(cfg.get("download", {}).get("data_dir", "data")) if not data_dir.is_absolute(): data_dir = (base or Path.cwd()) / data_dir data_dir.mkdir(parents=True, exist_ok=True) return data_dir
def _normalize_freq(raw) -> List[str]: """Ensure freq is always a list of strings.""" if isinstance(raw, list): return [str(f) for f in raw] if raw is None: return ["1d"] return [str(raw)]
[docs] def get_assets(cfg: Dict[str, Any], freq: Optional[str] = None) -> List[Dict[str, Any]]: """Expand download.symbols into asset dicts.""" dl = cfg.get("download", {}) symbols = dl.get("symbols", []) freqs = _normalize_freq(dl.get("freq")) start = dl.get("start") end = dl.get("end") assets = [ {"symbol": str(s), "freq": freqs, "start": start, "end": end} for s in symbols ] if freq is not None: assets = [a for a in assets if freq in a["freq"]] return assets