# meteo/config.py from __future__ import annotations from dataclasses import dataclass from typing import Self import os from dotenv import load_dotenv @dataclass(frozen=True) class InfluxSettings: """ Configuration nécessaire pour communiquer avec un serveur InfluxDB 2.x. Les valeurs sont généralement chargées depuis des variables d'environnement, éventuellement via un fichier `.env` à la racine du projet. """ url: str token: str org: str bucket: str @classmethod def from_env(cls) -> Self: """ Construit un objet `InfluxSettings` à partir des variables d'environnement. Variables attendues : - INFLUXDB_URL - INFLUXDB_TOKEN - INFLUXDB_ORG - INFLUXDB_BUCKET Lève une RuntimeError si une variable obligatoire est manquante. """ # Charge un éventuel fichier .env (idempotent) load_dotenv() url = os.getenv("INFLUXDB_URL") token = os.getenv("INFLUXDB_TOKEN") org = os.getenv("INFLUXDB_ORG") bucket = os.getenv("INFLUXDB_BUCKET") values = { "INFLUXDB_URL": url, "INFLUXDB_TOKEN": token, "INFLUXDB_ORG": org, "INFLUXDB_BUCKET": bucket, } missing = [name for name, value in values.items() if not value] if missing: missing_str = ", ".join(missing) raise RuntimeError( f"Les variables d'environnement suivantes sont manquantes : {missing_str}. " "Définissez-les dans votre environnement ou dans un fichier .env." ) return cls( url=url, # type: ignore[arg-type] token=token, # type: ignore[arg-type] org=org, # type: ignore[arg-type] bucket=bucket, # type: ignore[arg-type] ) @dataclass(frozen=True) class StationLocation: """ Décrit la position géographique de la station météo. Utilisée pour les calculs astronomiques (ex: élévation du soleil). """ latitude: float longitude: float elevation_m: float = 0.0 @classmethod def from_env(cls, *, optional: bool = False) -> Self | None: """ Charge les coordonnées GPS depuis les variables d'environnement : - STATION_LATITUDE (obligatoire) - STATION_LONGITUDE (obligatoire) - STATION_ELEVATION (optionnelle, en mètres) """ load_dotenv() lat = os.getenv("STATION_LATITUDE") lon = os.getenv("STATION_LONGITUDE") elev = os.getenv("STATION_ELEVATION") if not lat or not lon: if optional: return None raise RuntimeError( "Les variables STATION_LATITUDE et STATION_LONGITUDE doivent être définies " "pour calculer l'élévation solaire." ) latitude = float(lat) longitude = float(lon) elevation = float(elev) if elev else 0.0 return cls(latitude=latitude, longitude=longitude, elevation_m=elevation) def to_astral_observer_kwargs(self) -> dict[str, float]: """ Prépare les arguments attendus par astral.Observer. """ return { "latitude": self.latitude, "longitude": self.longitude, "elevation": self.elevation_m, }