whiteh4t's picture
Release final BGC retrieval checkpoints and model card
c87881a verified
Raw
History Blame Contribute Delete
1.96 kB
"""Configuration loading with explicit path resolution and validation."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any
import yaml
@dataclass(frozen=True)
class LoadedConfig:
path: Path
values: dict[str, Any]
@property
def base_dir(self) -> Path:
return self.path.parent.parent
def resolve_path(self, section: str, key: str) -> Path:
value = self.values[section][key]
path = Path(value)
if not path.is_absolute():
path = (self.base_dir / path).resolve()
return path
def _merge_values(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
merged = dict(base)
for key, value in override.items():
if isinstance(value, dict) and isinstance(merged.get(key), dict):
merged[key] = _merge_values(merged[key], value)
else:
merged[key] = value
return merged
def _read_config(config_path: Path, seen: set[Path]) -> dict[str, Any]:
if config_path in seen:
raise ValueError(f"Configuration inheritance cycle at {config_path}")
with config_path.open("r", encoding="utf-8") as handle:
values = yaml.safe_load(handle) or {}
parent = values.pop("extends", None)
if parent is None:
return values
parent_path = Path(parent)
if not parent_path.is_absolute():
parent_path = (config_path.parent / parent_path).resolve()
return _merge_values(_read_config(parent_path, seen | {config_path}), values)
def load_config(path: str | Path) -> LoadedConfig:
config_path = Path(path).resolve()
values = _read_config(config_path, set())
required = {"project", "data", "model", "training", "evaluation"}
missing = required.difference(values)
if missing:
raise ValueError(f"Configuration is missing sections: {sorted(missing)}")
return LoadedConfig(path=config_path, values=values)