from __future__ import annotations import json import os from dataclasses import dataclass from pathlib import Path from typing import Any, Mapping, Optional, Union from .exceptions import ClientConfigurationError ConfigPath = Union[str, Path] DEFAULT_BASE_URL_FALLBACK = "https://brecaldevel.bsmd-emswe.eu" CONFIG_FILENAME = "client.json" def _default_config_path() -> Path: xdg = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) return (xdg / "brecal" / CONFIG_FILENAME).expanduser() @dataclass(frozen=True) class ClientConfig: base_url: str environment: Optional[str] = None @classmethod def from_mapping(cls, data: Mapping[str, Any]) -> "ClientConfig": environment = data.get("environment") base_url = data.get("base_url") endpoints = data.get("endpoints") if isinstance(endpoints, Mapping): if environment and environment in endpoints: base_url = endpoints[environment] elif not base_url and endpoints: # Pick the first entry as a last resort _, base_url = next(iter(endpoints.items())) if not base_url: raise ClientConfigurationError( "Client configuration requires either 'base_url' or an " "'endpoints' mapping." ) return cls( base_url=str(base_url).rstrip("/"), environment=str(environment) if environment else None, ) @classmethod def load(cls, path: Optional[ConfigPath] = None) -> "ClientConfig": file_path = Path(path) if path else _default_config_path() data = json.loads(file_path.read_text(encoding="utf-8")) return cls.from_mapping(data) def get_default_base_url(path: Optional[ConfigPath] = None) -> str: """Resolve the default base URL using env vars or ~/.config/brecal/client.json.""" env_override = os.getenv("BRECAL_BASE_URL") if env_override: return env_override.rstrip("/") try: config = ClientConfig.load(path=path) return config.base_url except FileNotFoundError: return DEFAULT_BASE_URL_FALLBACK except ClientConfigurationError: raise except Exception as exc: raise ClientConfigurationError( f"Failed to load BreCal client configuration: {exc}" ) from exc