mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 22:37:44 +00:00
Use ruff linter to fix issues and provide consistent formatting
This commit is contained in:
@@ -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
@@ -68,4 +68,4 @@ class CleanupTimer:
|
||||
|
||||
|
||||
# Global object
|
||||
CLEANUP_TIMER = CleanupTimer()
|
||||
CLEANUP_TIMER = CleanupTimer()
|
||||
|
||||
+5
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user