m2-market/cli/src/m2_market/config.py

73 lines
1.9 KiB
Python

"""Config loading for the m2-market CLI (docs/config.md, contracts/cli.md)."""
from __future__ import annotations
import os
import tomllib
from dataclasses import dataclass
from pathlib import Path
DEFAULT_CONFIG_PATH = Path.home() / ".m2-market" / "config.toml"
REQUIRED_KEYS = (
"operator_id",
"tenant",
"ledger_url",
"ledger_api_key",
"memory_api_url",
"memory_api_key",
"forgejo_url",
"forgejo_token",
)
class ConfigError(Exception):
"""Raised when config.toml is missing, unreadable, or invalid."""
@dataclass
class Config:
operator_id: str
tenant: str
ledger_url: str
ledger_api_key: str
memory_api_url: str
memory_api_key: str
forgejo_url: str
forgejo_token: str
apply_adapter: str = "local"
def config_path() -> Path:
"""Resolve the config.toml path, honoring the M2_MARKET_CONFIG override."""
override = os.environ.get("M2_MARKET_CONFIG")
return Path(override).expanduser() if override else DEFAULT_CONFIG_PATH
def load_config(path: Path | None = None) -> Config:
"""Load and validate ~/.m2-market/config.toml, raising ConfigError on failure."""
path = path or config_path()
if not path.is_file():
raise ConfigError(
f"config not found: {path} (set M2_MARKET_CONFIG or see docs/config.md)"
)
try:
with path.open("rb") as f:
data = tomllib.load(f)
except tomllib.TOMLDecodeError as exc:
raise ConfigError(f"invalid TOML in {path}: {exc}") from exc
except OSError as exc:
raise ConfigError(f"cannot read {path}: {exc}") from exc
missing = [key for key in REQUIRED_KEYS if not data.get(key)]
if missing:
raise ConfigError(
f"{path} missing required key(s): {', '.join(missing)} (see docs/config.md)"
)
kwargs = {key: data[key] for key in REQUIRED_KEYS}
kwargs["apply_adapter"] = data.get("apply_adapter", "local")
return Config(**kwargs)