Source code for ztforce.config

"""Credential resolution and photometry hyperparameters."""

from __future__ import annotations

import os
import sys
from dataclasses import dataclass
from pathlib import Path

from ._constants import DEFAULT_CUTOUT_SIZE_ARCMIN
from .exceptions import ConfigError

# Environment variable names for credentials
[docs] _ENV_IRSA_USER = "ZTFORCE_IRSA_USER"
[docs] _ENV_IRSA_PASS = "ZTFORCE_IRSA_PASS"
# Default config file location
[docs] _DEFAULT_CONFIG_PATH = Path.home() / ".ztforce" / "config.toml"
@dataclass
[docs] class ZTForceConfig: """All runtime configuration for a ztforce session.""" # Credentials
[docs] irsa_user: str = ""
[docs] irsa_pass: str = ""
# IRSA IBE cutout size for science image downloads
[docs] cutout_size_arcmin: float = DEFAULT_CUTOUT_SIZE_ARCMIN
# Retry / download
[docs] max_retries: int = 3
[docs] retry_base_delay: float = 1.0
[docs] retry_jitter: float = 0.5
# Gain fallback when GAIN header is absent
[docs] default_gain: float = 6.2
[docs] def _load_toml(path: Path) -> dict: """Load a TOML file; return {} if absent or unreadable.""" if not path.exists(): return {} if sys.version_info >= (3, 11): import tomllib else: try: import tomli as tomllib # type: ignore[no-redef] except ImportError: return {} with open(path, "rb") as f: return tomllib.load(f)
[docs] def build_config( irsa_user: str | None = None, irsa_pass: str | None = None, config_path: str | Path | None = None, **overrides, ) -> ZTForceConfig: """Build a ZTForceConfig by resolving credentials in priority order. Resolution order (highest first): 1. Direct parameters passed here 2. Environment variables ZTFORCE_IRSA_USER / ZTFORCE_IRSA_PASS 3. ~/.ztforce/config.toml [credentials] section Raises ConfigError listing all sources tried if credentials are not found. """ cfg_path = Path(config_path) if config_path else _DEFAULT_CONFIG_PATH toml_data = _load_toml(cfg_path) def resolve(key_direct, env_var, toml_key): if key_direct: return key_direct if env_val := os.environ.get(env_var): return env_val if toml_val := toml_data.get("credentials", {}).get(toml_key): return toml_val return None resolved_user = resolve(irsa_user, _ENV_IRSA_USER, "irsa_user") resolved_pass = resolve(irsa_pass, _ENV_IRSA_PASS, "irsa_pass") if not resolved_user or not resolved_pass: tried = [ " direct parameters (irsa_user/irsa_pass)", f" env vars: {_ENV_IRSA_USER}, {_ENV_IRSA_PASS}", f" config file: {cfg_path} ([credentials] irsa_user/irsa_pass)", ] raise ConfigError("IRSA credentials not found. Tried:\n" + "\n".join(tried)) # Photometry overrides from config.toml [photometry] section phot_section = toml_data.get("photometry", {}) cfg = ZTForceConfig(irsa_user=resolved_user, irsa_pass=resolved_pass) for k, v in {**phot_section, **overrides}.items(): if hasattr(cfg, k): setattr(cfg, k, v) return cfg