Use ruff linter to fix issues and provide consistent formatting

This commit is contained in:
Ian Renton
2026-08-15 08:25:54 +01:00
parent 7391c28cd0
commit af3f82c14d
121 changed files with 1989 additions and 996 deletions
+27 -13
View File
@@ -1,11 +1,11 @@
import csv
import logging
from datetime import datetime, timezone, timedelta
from threading import Thread, Event
from datetime import datetime, timedelta, timezone
from threading import Event, Thread
import pytz
import requests
from requests.exceptions import ConnectionError, ReadTimeout, ConnectTimeout
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
from core.constants import HTTP_HEADERS
from providers.solarconditions.ionosonde_utils import compute_band_states
@@ -40,9 +40,16 @@ class GIROIonosonde(SolarConditionsProvider):
# entries so KC2G cache data is preserved.
existing = self._solar_conditions.ionosonde_data or {}
new_entries = {
s["ursi"]: {"ursi": s["ursi"], "name": s["name"], "fof2": None, "muf": None,
"luf": None, "band_states": None}
for s in self._stations if s["ursi"] not in existing
s["ursi"]: {
"ursi": s["ursi"],
"name": s["name"],
"fof2": None,
"muf": None,
"luf": None,
"band_states": None,
}
for s in self._stations
if s["ursi"] not in existing
}
if new_entries:
self.update_data({"ionosonde_data": {**existing, **new_entries}})
@@ -50,7 +57,7 @@ class GIROIonosonde(SolarConditionsProvider):
@staticmethod
def _load_stations():
stations = []
with open(STATIONS_INDEX, newline='') as f:
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()})
@@ -94,8 +101,14 @@ class GIROIonosonde(SolarConditionsProvider):
# Merge GIRO's readings into any existing data for this station.
existing = ionosonde_data.get(ursi, {})
merged_fof2 = {**{float(t): v for t, v in (existing.get("fof2") or {}).items()}, **fof2}
merged_muf = {**{float(t): v for t, v in (existing.get("muf") or {}).items()}, **muf}
merged_fof2 = {
**{float(t): v for t, v in (existing.get("fof2") or {}).items()},
**fof2,
}
merged_muf = {
**{float(t): v for t, v in (existing.get("muf") or {}).items()},
**muf,
}
merged_luf = dict(luf) if luf else {}
merged_fof2 = {t: v for t, v in merged_fof2.items() if t >= cutoff_ts}
@@ -104,7 +117,8 @@ class GIROIonosonde(SolarConditionsProvider):
band_states = compute_band_states(merged_fof2, merged_muf, merged_luf)
ionosonde_data[ursi] = {
"ursi": ursi, "name": name,
"ursi": ursi,
"name": name,
"fof2": merged_fof2 or None,
"muf": merged_muf or None,
"luf": merged_luf or None,
@@ -132,7 +146,7 @@ class GIROIonosonde(SolarConditionsProvider):
return None, None, None
return self._parse_all(http_response.text)
except (ConnectTimeout, ReadTimeout):
logging.warning(f"Timeout when accessing Giro ionosonde API.")
logging.warning("Timeout when accessing Giro ionosonde API.")
return None, None, None
except ConnectionError:
logging.warning("Connection error when accessing Giro ionosonde API.")
@@ -147,14 +161,14 @@ class GIROIonosonde(SolarConditionsProvider):
luf_data = {}
for line in text.splitlines():
line = line.strip()
if not line or line.startswith('#'):
if not line or line.startswith("#"):
continue
# Data rows have the following format: timestamp CS foF2 QD MUFD QD fmin QD
parts = line.split()
if len(parts) >= 5:
try:
# Python 3.8 TZ parsing fudge
ts = datetime.fromisoformat(parts[0].replace('Z', '+00:00')).timestamp()
ts = datetime.fromisoformat(parts[0].replace("Z", "+00:00")).timestamp()
except ValueError:
continue
try:
+11 -5
View File
@@ -2,9 +2,12 @@ import logging
from xml.etree import ElementTree
import pytz
from dateutil import parser as dateutil_parser, tz as dateutil_tz
from dateutil import parser as dateutil_parser
from dateutil import tz as dateutil_tz
from providers.solarconditions.http_solar_conditions_provider import HTTPSolarConditionsProvider
from providers.solarconditions.http_solar_conditions_provider import (
HTTPSolarConditionsProvider,
)
POLL_INTERVAL = 3600 # 1 hour
URL = "https://www.hamqsl.com/solarxml.php"
@@ -92,7 +95,8 @@ class HamQSL(HTTPSolarConditionsProvider):
"aurora_latitude": float_val("latdegree"),
"solar_wind": float_val("solarwind"),
"magnetic_field": float_val("magneticfield"),
"geomag_field": text("geomagfield").title()
"geomag_field": text("geomagfield")
.title()
.replace("Vr Quiet", "Very Quiet")
.replace("Unsettld", "Unsettled")
.replace("Min Strm", "Minor Storm")
@@ -102,8 +106,10 @@ class HamQSL(HTTPSolarConditionsProvider):
"geomag_noise": text("signalnoise"),
"hf_conditions": hf_conditions,
"vhf_conditions": {
"vhf_aurora_northern_hemi": (vhf_map.get(("vhf-aurora", "northern_hemi")) or "").title().replace(
"Lat Aur", "Latitude") or None,
"vhf_aurora_northern_hemi": (vhf_map.get(("vhf-aurora", "northern_hemi")) or "")
.title()
.replace("Lat Aur", "Latitude")
or None,
"es_2m_europe": vhf_map.get(("E-Skip", "europe")),
"es_4m_europe": vhf_map.get(("E-Skip", "europe_4m")),
"es_6m_europe": vhf_map.get(("E-Skip", "europe_6m")),
@@ -1,10 +1,10 @@
import logging
from datetime import datetime
from threading import Thread, Event
from threading import Event, Thread
import pytz
import requests
from requests.exceptions import ConnectionError, ReadTimeout, ConnectTimeout
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
from core.constants import HTTP_HEADERS
from providers.solarconditions.solar_conditions_provider import SolarConditionsProvider
@@ -22,8 +22,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
self._stop_event = Event()
def start(self):
logging.info(
f"Set up query of {self.name} solar conditions API every {self._poll_interval!s} seconds.")
logging.info(f"Set up query of {self.name} solar conditions API every {self._poll_interval!s} seconds.")
self._thread = Thread(target=self._run, name=f"HTTPSolarConditionsProvider-{self.name}")
self._thread.start()
+4 -4
View File
@@ -1,10 +1,10 @@
import logging
from datetime import datetime, timezone, timedelta
from threading import Thread, Event
from datetime import datetime, timedelta, timezone
from threading import Event, Thread
import pytz
import requests
from requests.exceptions import ConnectionError, ReadTimeout, ConnectTimeout
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
from core.constants import HTTP_HEADERS
from providers.solarconditions.ionosonde_utils import compute_band_states
@@ -119,7 +119,7 @@ class KC2GProp(SolarConditionsProvider):
except ConnectionError:
logging.warning("Connection error when accessing KC2G ionosonde API.")
except (ConnectTimeout, ReadTimeout):
logging.warning(f"Timeout when accessing KC2G ionosonde API.")
logging.warning("Timeout when accessing KC2G ionosonde API.")
except Exception:
self.status = "Error"
logging.exception("Exception in KC2G ionosonde data provider")
+22 -12
View File
@@ -2,7 +2,9 @@ import logging
import re
from datetime import datetime, timezone
from providers.solarconditions.http_solar_conditions_provider import HTTPSolarConditionsProvider
from providers.solarconditions.http_solar_conditions_provider import (
HTTPSolarConditionsProvider,
)
POLL_INTERVAL = 10800 # Every 3 hours
URL = "https://services.swpc.noaa.gov/text/3-day-forecast.txt"
@@ -32,13 +34,13 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
# Find the date header line by scanning the next few lines for month & day patterns
date_header_idx = None
for j in range(start_idx + 1, min(start_idx + 6, len(lines))):
if re.search(r'[A-Za-z]{3}\s+\d{2}', lines[j]):
if re.search(r"[A-Za-z]{3}\s+\d{2}", lines[j]):
date_header_idx = j
break
if date_header_idx is None:
logging.warning(f"NOAA 3-day forecast: could not find date header after '{section_header}'")
return None
date_matches = re.findall(r'([A-Za-z]{3})\s+(\d{2})', lines[date_header_idx])
date_matches = re.findall(r"([A-Za-z]{3})\s+(\d{2})", lines[date_header_idx])
if not date_matches:
logging.warning(f"NOAA 3-day forecast: no dates in header: {lines[date_header_idx]}")
return None
@@ -55,20 +57,20 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
# Parse data rows. Each non-empty line should have a text label followed by percentage values
result = {}
for line in lines[date_header_idx + 1:]:
for line in lines[date_header_idx + 1 :]:
line_stripped = line.strip()
if not line_stripped:
if result:
break
continue
pct_matches = list(re.finditer(r'\b(\d+)%', line_stripped))
pct_matches = list(re.finditer(r"\b(\d+)%", line_stripped))
if not pct_matches:
if result:
break
continue
# Row label is everything before the first percentage value
row_label = line_stripped[:line_stripped.index(pct_matches[0].group())].strip()
row_label = line_stripped[: line_stripped.index(pct_matches[0].group())].strip()
row_data = {}
for j, match in enumerate(pct_matches):
if j >= len(column_timestamps):
@@ -94,7 +96,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
# Extract the year from the header line, e.g. "NOAA Kp index breakdown Apr 2-Apr 4, 2026"
header_line = lines[start_idx]
year_match = re.search(r'\b(\d{4})\b', header_line)
year_match = re.search(r"\b(\d{4})\b", header_line)
if not year_match:
logging.warning(f"NOAA K-index forecast: could not extract year from: {header_line}")
return None
@@ -106,7 +108,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
return None
date_header_line = lines[start_idx + 2]
date_matches = re.findall(r'([A-Za-z]{3})\s+(\d{2})', date_header_line)
date_matches = re.findall(r"([A-Za-z]{3})\s+(\d{2})", date_header_line)
if not date_matches:
logging.warning(f"NOAA K-index forecast: could not parse date headers from: {date_header_line}")
return None
@@ -121,8 +123,8 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
# Parse each data row, e.g. "00-03UT 2.00 3.00 2.00"
k_index_forecast = {}
for line in lines[start_idx + 3:]:
time_match = re.match(r'^(\d{2})-(\d{2})UT\s+(.*)', line.strip())
for line in lines[start_idx + 3 :]:
time_match = re.match(r"^(\d{2})-(\d{2})UT\s+(.*)", line.strip())
if not time_match:
if k_index_forecast:
break
@@ -130,7 +132,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
start_hour = int(time_match.group(1))
# Split on 2 or more spaces so that e.g. "5.67 (G2)" stays as one token per column
raw_values = re.split(r' {2,}', time_match.group(3).strip())
raw_values = re.split(r" {2,}", time_match.group(3).strip())
for i, val in enumerate(raw_values):
if i >= len(column_dates):
@@ -142,7 +144,15 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
continue
date = column_dates[i]
start_dt = datetime(date.year, date.month, date.day, start_hour, 0, 0, tzinfo=timezone.utc)
start_dt = datetime(
date.year,
date.month,
date.day,
start_hour,
0,
0,
tzinfo=timezone.utc,
)
# Key the data dict by start time
key = start_dt.timestamp()
@@ -7,7 +7,7 @@ from core.data_store import DATA_STORE
class SolarConditionsProvider:
"""Generic solar conditions provider class. Subclasses of this query individual APIs for space weather and
propagation data."""
propagation data."""
def __init__(self, name, provider_config):
"""Constructor"""