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
+1 -1
View File
@@ -27,4 +27,4 @@ def get_call_info(callsign, lookup_credentials):
if callsign_data.fully_populated():
break
return callsign_data
return callsign_data
+1 -1
View File
@@ -68,4 +68,4 @@ class CleanupTimer:
# Global object
CLEANUP_TIMER = CleanupTimer()
CLEANUP_TIMER = CleanupTimer()
+5 -2
View File
@@ -7,7 +7,8 @@ import yaml
# Check you have a config file
if not os.path.isfile("config.yml"):
logging.error(
"Your config file is missing. Ensure you have copied config-example.yml to config.yml and updated it according to your needs.")
"Your config file is missing. Ensure you have copied config-example.yml to config.yml and updated it according to your needs."
)
exit()
# Load config
@@ -30,7 +31,9 @@ LOG_LEVEL = config.get("log_level", "INFO")
LOG_WEB_REQUESTS = config.get("log_web_requests", False)
WEB_UI_OPTIONS["qrz_enabled"] = any(p["class"] == "QRZ" and p["enabled"] for p in config["callsign_data_providers"])
WEB_UI_OPTIONS["hamqth_enabled"] = any(p["class"] == "HamQTH" and p["enabled"] for p in config["callsign_data_providers"])
WEB_UI_OPTIONS["hamqth_enabled"] = any(
p["class"] == "HamQTH" and p["enabled"] for p in config["callsign_data_providers"]
)
WEB_UI_OPTIONS["recaptcha_site_key"] = RECAPTCHA_SITE_KEY
WEB_UI_OPTIONS["allow_upstream_spotting"] = ALLOW_SPOTTING and ALLOW_UPSTREAM_SPOTTING
+169 -28
View File
@@ -11,36 +11,176 @@ HAMQTH_PRG = f"Spothole v{SOFTWARE_VERSION} operated by {SERVER_OWNER_CALLSIGN}"
# Special Interest Groups
SIGS = [
SIG(name="POTA", comment_names=["POTA"], description="Parks on the Air", ref_regex=r"[A-Z]{2}\-\d{4,5}|K\-TEST"),
SIG(name="SOTA", comment_names=["SOTA"], description="Summits on the Air", ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}"),
SIG(name="WWFF", comment_names=["WWFF"], description="World Wide Flora & Fauna", ref_regex=r"[A-Z0-9]{1,3}FF\-\d{4}"),
SIG(name="GMA", comment_names=["GMA"], description="Global Mountain Activity", ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}"),
SIG(name="WWBOTA", comment_names=["WWBOTA", "BOTA"], description="Worldwide Bunkers on the Air", ref_regex=r"B\/[A-Z0-9]{1,3}\-\d{3,4}"),
SIG(name="HEMA", comment_names=["HEMA"], description="HuMPs Excluding Marilyns Award", ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{3}\-\d{3}"),
SIG(name="IOTA", comment_names=["IOTA"], description="Islands on the Air", ref_regex=r"[A-Z]{2}\-\d{3}"),
SIG(name="MOTA", comment_names=["MOTA"], description="Mills on the Air", ref_regex=r"X\d{4,6}"),
SIG(name="ARLHS", comment_names=["ARLHS"], description="Amateur Radio Lighthouse Society", ref_regex=r"[A-Z]{3}[\- ]\d{3,4}"),
SIG(name="ILLW", comment_names=["ILLW"], description="International Lighthouse & Lightship Weekend", ref_regex=r"[A-Z]{2}\d{4}"),
SIG(name="SIOTA", comment_names=["SIOTA"], description="Silos on the Air", ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d"),
SIG(name="WCA", comment_names=["WCA"], description="World Castles Award", ref_regex=r"[A-Z0-9]{1,3}\-\d{5}"),
SIG(name="ZLOTA", comment_names=["ZLOTA"], description="New Zealand on the Air", ref_regex=r"ZL[A-Z]/[A-Z]{2}\-\d{3,4}"),
SIG(name="WOTA", comment_names=["WOTA"], description="Wainwrights on the Air", ref_regex=r"[A-Z]{3}-[0-9]{2}"),
SIG(
name="POTA",
comment_names=["POTA"],
description="Parks on the Air",
ref_regex=r"[A-Z]{2}\-\d{4,5}|K\-TEST",
),
SIG(
name="SOTA",
comment_names=["SOTA"],
description="Summits on the Air",
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
),
SIG(
name="WWFF",
comment_names=["WWFF"],
description="World Wide Flora & Fauna",
ref_regex=r"[A-Z0-9]{1,3}FF\-\d{4}",
),
SIG(
name="GMA",
comment_names=["GMA"],
description="Global Mountain Activity",
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{2}\-\d{3}",
),
SIG(
name="WWBOTA",
comment_names=["WWBOTA", "BOTA"],
description="Worldwide Bunkers on the Air",
ref_regex=r"B\/[A-Z0-9]{1,3}\-\d{3,4}",
),
SIG(
name="HEMA",
comment_names=["HEMA"],
description="HuMPs Excluding Marilyns Award",
ref_regex=r"[A-Z0-9]{1,3}\/[A-Z]{3}\-\d{3}",
),
SIG(
name="IOTA",
comment_names=["IOTA"],
description="Islands on the Air",
ref_regex=r"[A-Z]{2}\-\d{3}",
),
SIG(
name="MOTA",
comment_names=["MOTA"],
description="Mills on the Air",
ref_regex=r"X\d{4,6}",
),
SIG(
name="ARLHS",
comment_names=["ARLHS"],
description="Amateur Radio Lighthouse Society",
ref_regex=r"[A-Z]{3}[\- ]\d{3,4}",
),
SIG(
name="ILLW",
comment_names=["ILLW"],
description="International Lighthouse & Lightship Weekend",
ref_regex=r"[A-Z]{2}\d{4}",
),
SIG(
name="SIOTA",
comment_names=["SIOTA"],
description="Silos on the Air",
ref_regex=r"[A-Z]{2}\-[A-Z]{3}\d",
),
SIG(
name="WCA",
comment_names=["WCA"],
description="World Castles Award",
ref_regex=r"[A-Z0-9]{1,3}\-\d{5}",
),
SIG(
name="ZLOTA",
comment_names=["ZLOTA"],
description="New Zealand on the Air",
ref_regex=r"ZL[A-Z]/[A-Z]{2}\-\d{3,4}",
),
SIG(
name="WOTA",
comment_names=["WOTA"],
description="Wainwrights on the Air",
ref_regex=r"[A-Z]{3}-[0-9]{2}",
),
SIG(name="BOTA", comment_names=[], description="Beaches on the Air"),
SIG(name="KRMNPA", comment_names=["KRMNPA"], description="Keith Roget Memorial National Parks Award", ref_regex=r"VKFF\-\d{4}"),
SIG(name="SANPCPA", comment_names=["SANPCPA"], description="South Australian National Parks and Conservation Parks Award", ref_regex=r"VKFF\-\d{4}"),
SIG(name="LLOTA", comment_names=["LLOTA"], description="Lagos y Lagunas on the Air", ref_regex=r"LL[A-Z]{2}\-\d{4}"),
SIG(name="Towers", comment_names=["TOTA"], description="Towers on the Air", ref_regex=r"[A-Z]{2,3}R\-\d{4}"),
SIG(name="Tiles", comment_names=[], description="Tiles on the Air", ref_regex=r"[A-Za-z]{2}[0-9]{2}[A-Za-z]{2}"),
SIG(name="WAB", comment_names=["WAB"], description="Worked All Britain", ref_regex=r"[A-Z]{1,2}[0-9]{2}"),
SIG(name="WAI", comment_names=["WAI"], description="Worked All Ireland", ref_regex=r"[A-Z][0-9]{2}"),
SIG(name="DME", comment_names=["DME"], description="Diplomas de Municipios Españoles", ref_regex=r"\d{4,5}"),
SIG(name="Toilets", comment_names=[], description="Toilets on the Air", ref_regex=r"T\-[0-9]{2}")
SIG(
name="KRMNPA",
comment_names=["KRMNPA"],
description="Keith Roget Memorial National Parks Award",
ref_regex=r"VKFF\-\d{4}",
),
SIG(
name="SANPCPA",
comment_names=["SANPCPA"],
description="South Australian National Parks and Conservation Parks Award",
ref_regex=r"VKFF\-\d{4}",
),
SIG(
name="LLOTA",
comment_names=["LLOTA"],
description="Lagos y Lagunas on the Air",
ref_regex=r"LL[A-Z]{2}\-\d{4}",
),
SIG(
name="Towers",
comment_names=["TOTA"],
description="Towers on the Air",
ref_regex=r"[A-Z]{2,3}R\-\d{4}",
),
SIG(
name="Tiles",
comment_names=[],
description="Tiles on the Air",
ref_regex=r"[A-Za-z]{2}[0-9]{2}[A-Za-z]{2}",
),
SIG(
name="WAB",
comment_names=["WAB"],
description="Worked All Britain",
ref_regex=r"[A-Z]{1,2}[0-9]{2}",
),
SIG(
name="WAI",
comment_names=["WAI"],
description="Worked All Ireland",
ref_regex=r"[A-Z][0-9]{2}",
),
SIG(
name="DME",
comment_names=["DME"],
description="Diplomas de Municipios Españoles",
ref_regex=r"\d{4,5}",
),
SIG(
name="Toilets",
comment_names=[],
description="Toilets on the Air",
ref_regex=r"T\-[0-9]{2}",
),
]
# Modes. Note "DIGI" and "DIGITAL" are also supported but are normalised into "DATA".
CW_MODES = ["CW"]
PHONE_MODES = ["PHONE", "SSB", "USB", "LSB", "AM", "FM", "DV", "DMR", "DSTAR", "C4FM", "FUSION", "M17"]
DATA_MODES = ["DATA", "FT8", "FT4", "RTTY", "SSTV", "JS8", "HELL", "PSK", "OLIVIA", "PKT", "MSK144"]
PHONE_MODES = [
"PHONE",
"SSB",
"USB",
"LSB",
"AM",
"FM",
"DV",
"DMR",
"DSTAR",
"C4FM",
"FUSION",
"M17",
]
DATA_MODES = [
"DATA",
"FT8",
"FT4",
"RTTY",
"SSTV",
"JS8",
"HELL",
"PSK",
"OLIVIA",
"PKT",
"MSK144",
]
ALL_MODES = CW_MODES + PHONE_MODES + DATA_MODES
MODE_TYPES = ["CW", "PHONE", "DATA"]
SSB_SUB_MODES = ["USB", "LSB"]
@@ -58,7 +198,7 @@ MODE_ALIASES = {
"MFSK": "FSK",
"MFSK32": "FSK",
"DIGI": "DATA",
"DIGITAL": "DATA"
"DIGITAL": "DATA",
}
# Band definitions
@@ -88,7 +228,8 @@ BANDS = [
Band(name="10GHz", start_freq=10000000000, end_freq=10500000000),
Band(name="24GHz", start_freq=24000000000, end_freq=24050000000),
Band(name="47GHz", start_freq=47000000000, end_freq=47200000000),
Band(name="76GHz", start_freq=75500000000, end_freq=81500000000)]
Band(name="76GHz", start_freq=75500000000, end_freq=81500000000),
]
UNKNOWN_BAND = Band(name="Unknown", start_freq=0, end_freq=0)
# Continents
@@ -106,5 +247,5 @@ PROPAGATION_MODES = {
"MS": "Meteor scatter",
"RS": "Rain scatter",
"AS": "Aircraft scatter",
"SAT": "Satellite"
"SAT": "Satellite",
}
+15 -6
View File
@@ -15,7 +15,6 @@ class DataProviders:
self.sig_ref_data_providers = []
self.callsign_data_providers = []
def setup(self):
for entry in config["spot_providers"]:
self.spot_providers.append(create_provider_from_config("providers.spot", entry))
@@ -34,7 +33,7 @@ class DataProviders:
def start_providers(providers, provider_type):
"""Helper method to activate enabled providers in the list."""
logging.info(f"Starting %s providers...", provider_type)
logging.info(f"Starting {provider_type} providers...")
for p in providers:
if p.enabled:
p.start()
@@ -43,11 +42,20 @@ class DataProviders:
# Start data providers before spot/alert providers so the lookup data is there already for incoming spots.
# Each category is fired off after a small delay to give the rest of Spothole chance to start up.
threading.Timer(5.0, lambda: self.start_providers(self.static_data_providers, "static data")).start()
threading.Timer(10.0, lambda: self.start_providers(self.callsign_data_providers, "callsign data")).start()
threading.Timer(
10.0,
lambda: self.start_providers(self.callsign_data_providers, "callsign data"),
).start()
threading.Timer(15.0, lambda: self.start_providers(self.spot_providers, "spot")).start()
threading.Timer(20.0, lambda: self.start_providers(self.alert_providers, "alert")).start()
threading.Timer(25.0, lambda: self.start_providers(self.solar_condition_providers, "solar condition")).start()
threading.Timer(30.0, lambda: self.start_providers(self.sig_ref_data_providers, "SIG ref data")).start()
threading.Timer(
25.0,
lambda: self.start_providers(self.solar_condition_providers, "solar condition"),
).start()
threading.Timer(
30.0,
lambda: self.start_providers(self.sig_ref_data_providers, "SIG ref data"),
).start()
def stop(self):
for sp in self.spot_providers:
@@ -69,5 +77,6 @@ class DataProviders:
if cdp.enabled:
cdp.stop()
# Global object
DATA_PROVIDERS = DataProviders()
DATA_PROVIDERS = DataProviders()
+26 -14
View File
@@ -4,7 +4,7 @@ from pathlib import Path
import diskcache
from core.config import MAX_SPOT_AGE, MAX_ALERT_AGE
from core.config import MAX_ALERT_AGE, MAX_SPOT_AGE
from core.live_data_cache import LiveDataCache
from data.solar_conditions import SolarConditions
@@ -64,7 +64,7 @@ class DataStore:
# of dict in diskcache absolutely destroys performance with unpickling huge dicts, so we have an ugly "SIG:ref"
# syntax for keys to keep it a single level.
self.sigrefs = diskcache.Cache(f"{CACHE_DIR}sigrefs")
logging.info(f"Loaded data for %d SIG references.", len(self.sigrefs))
logging.info(f"Loaded data for {len(self.sigrefs)} SIG references.")
# Standard disk cache for callsign data. This data does have a TTL to trigger an occasional re-lookup.
# Old data *is* better than no data, but we can't have a background thread re-looking-up every callsign
@@ -75,23 +75,34 @@ class DataStore:
self.callsign_data_qrz = diskcache.Cache(f"{CACHE_DIR}callsign_data_qrz")
self.callsign_data_hamqth = diskcache.Cache(f"{CACHE_DIR}callsign_data_hamqth")
unique_keys = set()
for c in [self.callsign_data_countryfiles, self.callsign_data_clublogxml, self.callsign_data_clublogapi,
self.callsign_data_qrz, self.callsign_data_hamqth]:
for c in [
self.callsign_data_countryfiles,
self.callsign_data_clublogxml,
self.callsign_data_clublogapi,
self.callsign_data_qrz,
self.callsign_data_hamqth,
]:
unique_keys.update(c)
logging.info(f"Loaded data for %d callsigns.", len(unique_keys))
logging.info(f"Loaded data for {len(unique_keys)} callsigns.")
# Special caches for spots and alerts, which have TTL and write snapshots to disk at an interval. We
# specifically load these caches *last* so that any sigref and callsign data is already loaded from disk cache
# before the spots and alerts are live in the system.
self.spots = LiveDataCache(maxsize=self._MAX_SPOT_COUNT, ttl=MAX_SPOT_AGE,
snapshot_dir=f"{CACHE_DIR}spots",
snapshot_interval_sec=self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC)
logging.info(f"Loaded %d spots from a previous run.", len(self.spots.keys()))
self.spots = LiveDataCache(
maxsize=self._MAX_SPOT_COUNT,
ttl=MAX_SPOT_AGE,
snapshot_dir=f"{CACHE_DIR}spots",
snapshot_interval_sec=self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC,
)
logging.info(f"Loaded {len(self.spots.keys())} spots from a previous run.")
self.alerts = LiveDataCache(maxsize=self._MAX_ALERT_COUNT, ttl=MAX_ALERT_AGE,
snapshot_dir=f"{CACHE_DIR}alerts",
snapshot_interval_sec=self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC)
logging.info(f"Loaded %d alerts from a previous run.", len(self.alerts.keys()))
self.alerts = LiveDataCache(
maxsize=self._MAX_ALERT_COUNT,
ttl=MAX_ALERT_AGE,
snapshot_dir=f"{CACHE_DIR}alerts",
snapshot_interval_sec=self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC,
)
logging.info(f"Loaded {len(self.alerts.keys())} alerts from a previous run.")
def regenerate_call_regex_to_dxcc_entity_map(self):
"""DXCC entity data from K0SWE includes a regex which we can use to match a callsign, and determine which DXCC
@@ -118,5 +129,6 @@ class DataStore:
self.callsign_data_qrz.close()
self.callsign_data_hamqth.close()
# Global object
DATA_STORE = DataStore()
DATA_STORE = DataStore()
+2 -2
View File
@@ -113,8 +113,8 @@ def lat_lon_for_grid_sw_corner_plus_size(grid):
while block * 2 < length:
if block % 2 == 0:
# Letters in this block
lon_cell_no = ord(grid[block * 2]) - ord('A')
lat_cell_no = ord(grid[block * 2 + 1]) - ord('A')
lon_cell_no = ord(grid[block * 2]) - ord("A")
lat_cell_no = ord(grid[block * 2 + 1]) - ord("A")
# Bail if the values aren't in range. Allowed values are A-R (0-17) for the first letter block, or
# A-X (0-23) thereafter.
max_cell_no = 17 if block == 0 else 23
+3 -4
View File
@@ -34,8 +34,7 @@ class LiveDataCache:
try:
callback(value)
except Exception:
logging.exception("Listener raised an exception for key %s", key)
logging.exception(f"Listener raised an exception for key {key}")
def get(self, key, default=None):
with self._lock:
@@ -71,7 +70,7 @@ class LiveDataCache:
try:
self._disk_cache.set("snapshot", data)
except Exception as e:
logging.exception("Failed to write snapshot to %s", self._snapshot_dir, e)
logging.exception(f"Failed to write snapshot to {self._snapshot_dir}")
def _load_snapshot(self):
data = self._disk_cache.get("snapshot")
@@ -84,7 +83,7 @@ class LiveDataCache:
# Only restore entries that would still be within TTL
if now - saved_at < self._ttl:
self._cache[key] = value
logging.info("Loaded snapshot from %s", self._snapshot_dir)
logging.info(f"Loaded snapshot from {self._snapshot_dir}")
def _start_periodic_snapshot(self, interval):
def loop():
+11 -17
View File
@@ -1,4 +1,10 @@
from prometheus_client import CollectorRegistry, generate_latest, Counter, disable_created_metrics, Gauge
from prometheus_client import (
CollectorRegistry,
Counter,
Gauge,
disable_created_metrics,
generate_latest,
)
disable_created_metrics()
# Prometheus metrics registry
@@ -9,25 +15,13 @@ page_requests_counter = Counter(
"Total number of page requests received",
registry=registry,
)
api_requests_counter = Counter(
"spothole_api_requests",
"Total number of API requests received",
registry=registry
)
spots_gauge = Gauge(
"spothole_spots",
"Number of spots currently in the software",
registry=registry
)
alerts_gauge = Gauge(
"spothole_alerts",
"Number of alerts currently in the software",
registry=registry
)
api_requests_counter = Counter("spothole_api_requests", "Total number of API requests received", registry=registry)
spots_gauge = Gauge("spothole_spots", "Number of spots currently in the software", registry=registry)
alerts_gauge = Gauge("spothole_alerts", "Number of alerts currently in the software", registry=registry)
memory_use_gauge = Gauge(
"spothole_memory_usage_bytes",
"Current memory usage of the software in bytes",
registry=registry
registry=registry,
)
+2 -2
View File
@@ -1,6 +1,6 @@
import logging
from pyhamtools.locator import locator_to_latlong, latlong_to_locator
from pyhamtools.locator import latlong_to_locator, locator_to_latlong
from core.data_store import DATA_STORE
from core.geo_utils import wab_wai_square_to_lat_lon
@@ -83,7 +83,7 @@ def get_sig_ref_info(sig, ref_id):
else:
# Maybe a super new reference we don't know about yet, but more likely a typo or a test reference,
# just silently ignore it.
logging.debug("%s database did not contain data for ref %s", sig, ref_id)
logging.debug(f"{sig} database did not contain data for ref {ref_id}")
except Exception:
logging.exception(f"Exception when looking up sig_ref info for {sig} ref {ref_id}")
+1 -1
View File
@@ -21,4 +21,4 @@ def get_sig_name_from_comment_name(sig):
# Regex matching any SIG's "comment name", i.e. how it may be referred to in spot comments
ANY_SIG_REGEX = rf"({'|'.join((n for s in SIGS for n in s.comment_names))})"
ANY_SIG_REGEX = rf"({'|'.join(n for s in SIGS for n in s.comment_names)})"
+101 -46
View File
@@ -1,6 +1,6 @@
import os
from datetime import datetime
from threading import Thread, Event
from threading import Event, Thread
import psutil
import pytz
@@ -10,7 +10,7 @@ from core.config import SERVER_OWNER_CALLSIGN
from core.constants import SOFTWARE_VERSION
from core.data_providers import DATA_PROVIDERS
from core.data_store import DATA_STORE
from core.prometheus_metrics_handler import memory_use_gauge, spots_gauge, alerts_gauge
from core.prometheus_metrics_handler import alerts_gauge, memory_use_gauge, spots_gauge
from server.webserver import WEB_SERVER
@@ -55,55 +55,110 @@ class StatusReporter:
DATA_STORE.status_data["num_spots"] = len(DATA_STORE.spots.values())
DATA_STORE.status_data["num_alerts"] = len(DATA_STORE.alerts.values())
DATA_STORE.status_data["spot_providers"] = list(
map(lambda p: {"name": p.name, "enabled": p.enabled,
"enabled_by_default_in_web_ui": p.enabled_by_default_in_web_ui, "status": p.status,
"last_updated": p.last_update_time.replace(
tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0,
"last_spot": p.last_spot_time.replace(
tzinfo=pytz.UTC).timestamp() if p.last_spot_time.year > 2000 else 0},
DATA_PROVIDERS.spot_providers))
map(
lambda p: {
"name": p.name,
"enabled": p.enabled,
"enabled_by_default_in_web_ui": p.enabled_by_default_in_web_ui,
"status": p.status,
"last_updated": p.last_update_time.replace(tzinfo=pytz.UTC).timestamp()
if p.last_update_time.year > 2000
else 0,
"last_spot": p.last_spot_time.replace(tzinfo=pytz.UTC).timestamp()
if p.last_spot_time.year > 2000
else 0,
},
DATA_PROVIDERS.spot_providers,
)
)
DATA_STORE.status_data["alert_providers"] = list(
map(lambda p: {"name": p.name, "enabled": p.enabled, "status": p.status,
"last_updated": p.last_update_time.replace(
tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0},
DATA_PROVIDERS.alert_providers))
map(
lambda p: {
"name": p.name,
"enabled": p.enabled,
"status": p.status,
"last_updated": p.last_update_time.replace(tzinfo=pytz.UTC).timestamp()
if p.last_update_time.year > 2000
else 0,
},
DATA_PROVIDERS.alert_providers,
)
)
DATA_STORE.status_data["solar_condition_providers"] = list(
map(lambda p: {"name": p.name, "enabled": p.enabled, "status": p.status,
"last_updated": p.last_update_time.replace(
tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0},
DATA_PROVIDERS.solar_condition_providers))
map(
lambda p: {
"name": p.name,
"enabled": p.enabled,
"status": p.status,
"last_updated": p.last_update_time.replace(tzinfo=pytz.UTC).timestamp()
if p.last_update_time.year > 2000
else 0,
},
DATA_PROVIDERS.solar_condition_providers,
)
)
DATA_STORE.status_data["static_data_providers"] = list(
map(lambda p: {"name": p.name, "enabled": p.enabled, "status": p.status,
"last_updated": p.last_update_time.replace(
tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0},
DATA_PROVIDERS.static_data_providers))
map(
lambda p: {
"name": p.name,
"enabled": p.enabled,
"status": p.status,
"last_updated": p.last_update_time.replace(tzinfo=pytz.UTC).timestamp()
if p.last_update_time.year > 2000
else 0,
},
DATA_PROVIDERS.static_data_providers,
)
)
DATA_STORE.status_data["sig_ref_data_providers"] = list(
map(lambda p: {"sig_name": p.sig_name, "enabled": p.enabled, "status": p.status,
"last_updated": p.last_update_time.replace(
tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0,
"reference_count": p.reference_count},
DATA_PROVIDERS.sig_ref_data_providers))
map(
lambda p: {
"sig_name": p.sig_name,
"enabled": p.enabled,
"status": p.status,
"last_updated": p.last_update_time.replace(tzinfo=pytz.UTC).timestamp()
if p.last_update_time.year > 2000
else 0,
"reference_count": p.reference_count,
},
DATA_PROVIDERS.sig_ref_data_providers,
)
)
DATA_STORE.status_data["callsign_data_providers"] = list(
map(lambda p: {"name": p.name, "enabled": p.enabled, "status": p.status,
"last_updated": p.last_update_time.replace(
tzinfo=pytz.UTC).timestamp() if p.last_update_time.year > 2000 else 0,
"lookup_count": p.lookup_count},
DATA_PROVIDERS.callsign_data_providers))
DATA_STORE.status_data["cleanup"] = {"status": CLEANUP_TIMER.status,
"last_ran": CLEANUP_TIMER.last_cleanup_time.replace(
tzinfo=pytz.UTC).timestamp() if CLEANUP_TIMER.last_cleanup_time else 0}
DATA_STORE.status_data["webserver"] = {"status": WEB_SERVER.web_server_metrics["status"],
"last_api_access": WEB_SERVER.web_server_metrics[
"last_api_access_time"].replace(
tzinfo=pytz.UTC).timestamp() if WEB_SERVER.web_server_metrics[
"last_api_access_time"] else 0,
"api_access_count": WEB_SERVER.web_server_metrics["api_access_counter"],
"last_page_access": WEB_SERVER.web_server_metrics[
"last_page_access_time"].replace(
tzinfo=pytz.UTC).timestamp() if WEB_SERVER.web_server_metrics[
"last_page_access_time"] else 0,
"page_access_count": WEB_SERVER.web_server_metrics[
"page_access_counter"]}
map(
lambda p: {
"name": p.name,
"enabled": p.enabled,
"status": p.status,
"last_updated": p.last_update_time.replace(tzinfo=pytz.UTC).timestamp()
if p.last_update_time.year > 2000
else 0,
"lookup_count": p.lookup_count,
},
DATA_PROVIDERS.callsign_data_providers,
)
)
DATA_STORE.status_data["cleanup"] = {
"status": CLEANUP_TIMER.status,
"last_ran": CLEANUP_TIMER.last_cleanup_time.replace(tzinfo=pytz.UTC).timestamp()
if CLEANUP_TIMER.last_cleanup_time
else 0,
}
DATA_STORE.status_data["webserver"] = {
"status": WEB_SERVER.web_server_metrics["status"],
"last_api_access": WEB_SERVER.web_server_metrics["last_api_access_time"]
.replace(tzinfo=pytz.UTC)
.timestamp()
if WEB_SERVER.web_server_metrics["last_api_access_time"]
else 0,
"api_access_count": WEB_SERVER.web_server_metrics["api_access_counter"],
"last_page_access": WEB_SERVER.web_server_metrics["last_page_access_time"]
.replace(tzinfo=pytz.UTC)
.timestamp()
if WEB_SERVER.web_server_metrics["last_page_access_time"]
else 0,
"page_access_count": WEB_SERVER.web_server_metrics["page_access_counter"],
}
# Update Prometheus metrics
memory_use_gauge.set(psutil.Process(os.getpid()).memory_info().rss)
+6 -3
View File
@@ -17,9 +17,12 @@ class URLDataCache(CachedSession):
_lock = threading.Lock()
def __init__(self, name):
super().__init__(f"{CACHE_DIR}urls/{name}", expire_after=timedelta(days=1),
allowable_codes=(200, 400, 401, 403, 404))
super().__init__(
f"{CACHE_DIR}urls/{name}",
expire_after=timedelta(days=1),
allowable_codes=(200, 400, 401, 403, 404),
)
def get(self, *args, **kwargs):
with self._lock:
return super().get(*args, **kwargs)
return super().get(*args, **kwargs)
+40 -16
View File
@@ -4,7 +4,15 @@ import simplejson
from pyhamtools.frequency import freq_to_band
from pyhamtools.locator import latlong_to_locator
from core.constants import UNKNOWN_BAND, BANDS, CW_MODES, PHONE_MODES, DATA_MODES, MODE_ALIASES, ALL_MODES
from core.constants import (
ALL_MODES,
BANDS,
CW_MODES,
DATA_MODES,
MODE_ALIASES,
PHONE_MODES,
UNKNOWN_BAND,
)
from core.data_store import DATA_STORE
from data.callsign import Callsign
@@ -63,11 +71,25 @@ def infer_mode_from_frequency(freq):
# a spot at 7074.5 kHz will be indicated as LSB, even though it's clearly in the FT8 range. Future updates
# might include other common digimode centres of activity here, but this achieves the main goal of keeping
# large numbers of clearly-FT* spots off the list of people filtering out digimodes.
if (7074 <= khz < 7077) or (10136 <= khz < 10139) or (14074 <= khz < 14077) or (18100 <= khz < 18103) or (
21074 <= khz < 21077) or (24915 <= khz < 24918) or (28074 <= khz < 28077):
if (
(7074 <= khz < 7077)
or (10136 <= khz < 10139)
or (14074 <= khz < 14077)
or (18100 <= khz < 18103)
or (21074 <= khz < 21077)
or (24915 <= khz < 24918)
or (28074 <= khz < 28077)
):
mode = "FT8"
if (7047.5 <= khz < 7050.5) or (10140 <= khz < 10143) or (14080 <= khz < 14083) or (
18104 <= khz < 18107) or (21140 <= khz < 21143) or (24919 <= khz < 24922) or (28180 <= khz < 28183):
if (
(7047.5 <= khz < 7050.5)
or (10140 <= khz < 10143)
or (14080 <= khz < 14083)
or (18104 <= khz < 18107)
or (21140 <= khz < 21143)
or (24919 <= khz < 24922)
or (28180 <= khz < 28183)
):
mode = "FT4"
return mode
except KeyError:
@@ -100,17 +122,19 @@ def get_callsign_object_from_pyhamtools_callinfo(callsign, callinfo):
if lat and lon:
grid = latlong_to_locator(lat, lon)
return Callsign(call=callsign,
home_call=home_call,
country=country,
dxcc_id=dxcc_id,
continent=continent,
cq_zone=cq_zone,
itu_zone=itu_zone,
latitude=lat,
longitude=lon,
grid=grid,
location_source="DXCC")
return Callsign(
call=callsign,
home_call=home_call,
country=country,
dxcc_id=dxcc_id,
continent=continent,
cq_zone=cq_zone,
itu_zone=itu_zone,
latitude=lat,
longitude=lon,
grid=grid,
location_source="DXCC",
)
except (KeyError, ValueError):
# Unknown callsign, can't look anything up, return a Callsign object with basic data so that gets cached