Autogenerated type safety parameterisation of all methods

This commit is contained in:
Ian Renton
2026-09-20 20:02:19 +01:00
parent 6037e742cc
commit 324dd1414b
132 changed files with 1228 additions and 706 deletions
+22 -17
View File
@@ -1,7 +1,10 @@
from __future__ import annotations
import csv
import logging
from datetime import datetime, timedelta, timezone
from threading import Event, Thread
from typing import Any
import pytz
import requests
@@ -31,17 +34,17 @@ class GIROIonosonde(SolarConditionsProvider):
Designed to run alongside KC2GProp even though they produce similar data. GIRO has more stations and includes LUF
data, but is less reliable and often offline."""
def __init__(self, provider_config):
def __init__(self, provider_config: dict[str, Any]) -> None:
super().__init__("GIRO Ionosonde Data", provider_config)
self._stations = self._load_stations()
self._thread = None
self._stop_event = Event()
self._stations: list[dict[str, str]] = self._load_stations()
self._thread: Thread | None = None
self._stop_event: Event = Event()
# Pre-populate ionosonde_data with known station names for stations not already present,
# so the station dropdown is available before the first poll. Does not overwrite existing
# entries so KC2G cache data is preserved.
existing = self._solar_conditions.ionosonde_data or {}
new_entries = {
existing: dict[str, Any] = self._solar_conditions.ionosonde_data or {}
new_entries: dict[str, Any] = {
s["ursi"]: {
"ursi": s["ursi"],
"name": s["name"],
@@ -57,27 +60,27 @@ class GIROIonosonde(SolarConditionsProvider):
self.update_data({"ionosonde_data": {**existing, **new_entries}})
@staticmethod
def _load_stations():
stations = []
def _load_stations() -> list[dict[str, str]]:
stations: list[dict[str, str]] = []
with open(STATIONS_INDEX, newline="") as f:
for row in csv.reader(f):
if len(row) >= 2:
stations.append({"ursi": row[0].strip(), "name": row[1].strip()})
return stations
def start(self):
def start(self) -> None:
logger.info(f"Set up query of GIRO ionosonde data API every {POLL_INTERVAL} seconds.")
self._thread = Thread(target=self._run, name="GIROIonosondeDataProvider", daemon=True)
self._thread.start()
def stop(self):
def stop(self) -> None:
self._stop_event.set()
if self._thread:
self._thread.join(timeout=12)
if self._thread.is_alive():
logger.warning("GIRO ionosonde worker thread did not exit on time and will be killed.")
def _run(self):
def _run(self) -> None:
# Real interval at which we poll is the "once per hour" divided by the number of stations, so each one gets
# polled once per hour, just not all at once
interval = POLL_INTERVAL / len(self._stations)
@@ -88,7 +91,7 @@ class GIROIonosonde(SolarConditionsProvider):
if self._stop_event.wait(timeout=interval):
break
def _poll_station(self, station):
def _poll_station(self, station: dict[str, str]) -> None:
ursi = station["ursi"]
name = station["name"]
try:
@@ -139,7 +142,9 @@ class GIROIonosonde(SolarConditionsProvider):
self.status = "Error"
logger.exception(f"Exception fetching GIRO ionosonde data for {ursi} ({name})")
def _fetch_station_data(self, ursi, from_time, to_time):
def _fetch_station_data(
self, ursi: str, from_time: datetime, to_time: datetime
) -> tuple[dict[float, float] | None, dict[float, float] | None, dict[float, float] | None]:
"""Fetch foF2, MUF and LUF readings for a station. Returns (fof2_dict, muf_dict, luf_dict) keyed by UNIX timestamp."""
from_str = from_time.strftime("%Y.%m.%d+%H:%M:%S")
@@ -159,12 +164,12 @@ class GIROIonosonde(SolarConditionsProvider):
return None, None, None
@staticmethod
def _parse_all(text):
def _parse_all(text: str) -> tuple[dict[float, float], dict[float, float], dict[float, float]]:
"""Parse web server response and return (fof2_dict, muf_dict, luf_dict) keyed by UNIX timestamp."""
fof2_data = {}
muf_data = {}
luf_data = {}
fof2_data: dict[float, float] = {}
muf_data: dict[float, float] = {}
luf_data: dict[float, float] = {}
for line in text.splitlines():
line = line.strip()
if not line or line.startswith("#"):