"""Konfigurations-Loader für das Moon/Apollo-Projekt. Lädt Werte aus .env in Umgebungsvariablen und stellt sie typisiert bereit. Verwendet python-dotenv. Enthält Validierung & sinnvolle Defaults. """ from __future__ import annotations import os import re from dataclasses import dataclass from typing import Tuple from dotenv import load_dotenv # .env → os.environ laden load_dotenv(override=False) def _get_env(name: str, default: str) -> str: val = os.getenv(name) return val if val is not None and val != "" else default def _parse_int(name: str, default: int, min_val: int | None = None) -> int: raw = _get_env(name, str(default)) try: v = int(raw) if min_val is not None and v < min_val: raise ValueError return v except Exception as _: return default def _parse_float(name: str, default: float, min_val: float | None = None) -> float: raw = _get_env(name, str(default)) try: v = float(raw) if min_val is not None and v < min_val: raise ValueError return v except Exception as _: return default _HEX = re.compile(r"^#?([0-9a-fA-F]{6})$") def _parse_rgb(name: str, default_hex: str) -> Tuple[int, int, int]: """Erlaubt '#rrggbb' oder 'rrggbb'. Fällt auf default zurück, wenn ungültig.""" raw = _get_env(name, default_hex) m = _HEX.match(raw) hx = m.group(1) if m else default_hex.lstrip("#") r = int(hx[0:2], 16) g = int(hx[2:4], 16) b = int(hx[4:6], 16) return (r, g, b) @dataclass(frozen=True) class Settings: # Anzeige width: int height: int fps: int bg_color: Tuple[int, int, int] # Erde earth_color: Tuple[int, int, int] earth_radius: int # Mond moon_color: Tuple[int, int, int] moon_radius: int moon_orbit_radius: int moon_angular_speed_deg: float # Grad/Sekunde # Apollo (optional) apollo_color: Tuple[int, int, int] apollo_radius: int apollo_orbit_radius: int def get_settings() -> Settings: """Erzeugt Settings aus Umgebungsvariablen oder Defaults.""" return Settings( # Anzeige width=_parse_int("WIDTH", 800, min_val=100), height=_parse_int("HEIGHT", 600, min_val=100), fps=_parse_int("FPS", 60, min_val=1), bg_color=_parse_rgb("BG_COLOR", "#0a0a14"), # Erde earth_color=_parse_rgb("EARTH_COLOR", "#2878ff"), earth_radius=_parse_int("EARTH_RADIUS", 30, min_val=1), # Mond moon_color=_parse_rgb("MOON_COLOR", "#f0f0f0"), moon_radius=_parse_int("MOON_RADIUS", 10, min_val=1), moon_orbit_radius=_parse_int("MOON_ORBIT_RADIUS", 160, min_val=10), moon_angular_speed_deg=_parse_float("MOON_ANGULAR_SPEED_DEG", 45.0, min_val=0.1), # Apollo apollo_color=_parse_rgb("APOLLO_COLOR", "#dc3232"), apollo_radius=_parse_int("APOLLO_RADIUS", 7, min_val=1), apollo_orbit_radius=_parse_int("APOLLO_ORBIT_RADIUS", 40, min_val=5), )