Source code for astra.config
# astra/config.py
"""ASTRA Core Global Configuration.
This module houses **all** process-global feature flags for the engine.
Centralising flags here prevents scattered ``os.environ.get(...)`` calls
from spreading across modules and becoming impossible to test or override.
Flags
-----
``ASTRA_STRICT_MODE``
Controls the dual-profile mode (Relaxed vs Flight-Grade). In strict
mode every missing or low-fidelity data source raises a typed error
instead of silently substituting a heuristic fallback.
``SPACEBOOK_ENABLED``
Controls whether any Spacebook (COMSPOC) network calls are allowed.
Set the ``ASTRA_SPACEBOOK_ENABLED=false`` environment variable before
importing ``astra`` to permanently disable all Spacebook I/O, or use
:func:`set_spacebook_enabled` at runtime (useful in tests / CI).
Thread safety
-------------
Both flags are process-global. Direct mutation is safe for single-threaded
scripts. For multi-threaded applications, use the provided ``set_*``
functions which acquire the module-level RLock before updating the flag,
preventing races during concurrent reads/writes.
"""
from __future__ import annotations
import os
import threading
# ---------------------------------------------------------------------------
# Locks — one per flag to avoid cross-flag contention.
# ---------------------------------------------------------------------------
_STRICT_MODE_LOCK: threading.RLock = threading.RLock()
_SPACEBOOK_LOCK: threading.RLock = threading.RLock()
_MAX_WORKERS_LOCK: threading.RLock = threading.RLock()
# ---------------------------------------------------------------------------
# ASTRA_STRICT_MODE
# Default: Relaxed mode — synthesises fallback data and emits warnings.
# Set True to enforce strict orbital equations and raise on missing data.
# ---------------------------------------------------------------------------
ASTRA_STRICT_MODE: bool = True
[docs]
def set_strict_mode(enabled: bool) -> None:
"""Thread-safe setter for :data:`ASTRA_STRICT_MODE`.
Acquires the module lock before updating the flag, which is required
when toggling strict mode from a different thread than physics workers.
Args:
enabled: ``True`` for flight-grade strict mode, ``False`` for
beginner-friendly relaxed mode.
"""
global ASTRA_STRICT_MODE
with _STRICT_MODE_LOCK:
ASTRA_STRICT_MODE = enabled
# ---------------------------------------------------------------------------
# SPACEBOOK_ENABLED
# Single authoritative read of ASTRA_SPACEBOOK_ENABLED happens exactly once
# at import time. All modules must import from here, not call os.environ.get
# directly, so that tests can override the flag via set_spacebook_enabled().
# ---------------------------------------------------------------------------
SPACEBOOK_ENABLED: bool = (
os.environ.get("ASTRA_SPACEBOOK_ENABLED", "true").strip().lower() != "false"
)
[docs]
def set_spacebook_enabled(enabled: bool) -> None:
"""Thread-safe setter for :data:`SPACEBOOK_ENABLED`.
Allows tests and CLI tools to enable or disable Spacebook calls without
restarting the process. The change takes effect immediately for all
subsequent calls to any Spacebook-guarded function.
Args:
enabled: ``True`` to allow Spacebook network I/O (default),
``False`` to disable all Spacebook calls and force fallback
to CelesTrak or raise ``SpacebookError`` where applicable.
Example::
import astra.config as cfg
cfg.set_spacebook_enabled(False) # disable for offline tests
# ... run tests ...
cfg.set_spacebook_enabled(True) # restore
"""
global SPACEBOOK_ENABLED
with _SPACEBOOK_LOCK:
SPACEBOOK_ENABLED = enabled
# ---------------------------------------------------------------------------
# ASTRA_MAX_WORKERS
# Centralized worker-pool sizing for concurrency-heavy algorithms.
# ---------------------------------------------------------------------------
def _read_max_workers(default: int | None = None) -> int | None:
raw = os.environ.get("ASTRA_MAX_WORKERS", "").strip()
if not raw:
return default
try:
value = int(raw)
except ValueError as exc:
raise ValueError(f"ASTRA_MAX_WORKERS must be an integer, got {raw!r}.") from exc
if value < 1:
raise ValueError(f"ASTRA_MAX_WORKERS must be >= 1, got {value}.")
return value
try:
ASTRA_MAX_WORKERS: int | None = _read_max_workers()
except ValueError:
ASTRA_MAX_WORKERS = None
[docs]
def get_max_workers(default: int) -> int:
"""Return configured worker count or a caller-provided default.
Args:
default: Fallback worker count when ``ASTRA_MAX_WORKERS`` is unset.
Returns:
Positive worker count suitable for thread-pool construction.
"""
with _MAX_WORKERS_LOCK:
return ASTRA_MAX_WORKERS if ASTRA_MAX_WORKERS is not None else default
[docs]
def set_max_workers(value: int | None) -> None:
"""Thread-safe setter for :data:`ASTRA_MAX_WORKERS`.
Args:
value: Positive worker count, or ``None`` to restore caller defaults.
"""
global ASTRA_MAX_WORKERS
if value is not None and value < 1:
raise ValueError(f"ASTRA_MAX_WORKERS must be >= 1, got {value}.")
with _MAX_WORKERS_LOCK:
ASTRA_MAX_WORKERS = value