flynt pass to provide consistency to string formatters and concatenation

This commit is contained in:
Ian Renton
2026-08-15 07:55:32 +01:00
parent bff5b79f8f
commit 7391c28cd0
72 changed files with 184 additions and 193 deletions
+1 -1
View File
@@ -40,6 +40,6 @@ def create_provider_from_config(package, config_providers_entry):
package to look for it in, as there are several types of provider. e.g. package "providers.spot", where the config package to look for it in, as there are several types of provider. e.g. package "providers.spot", where the config
entry is for a POTA spot provider.""" entry is for a POTA spot provider."""
module = importlib.import_module(package + "." + config_providers_entry["class"].lower()) module = importlib.import_module(f"{package}.{config_providers_entry['class'].lower()}")
provider_class = getattr(module, config_providers_entry["class"]) provider_class = getattr(module, config_providers_entry["class"])
return provider_class(config_providers_entry) return provider_class(config_providers_entry)
+2 -2
View File
@@ -6,8 +6,8 @@ from data.sig import SIG
SOFTWARE_VERSION = "2.0-pre" SOFTWARE_VERSION = "2.0-pre"
# HTTP headers used for spot providers that use HTTP # HTTP headers used for spot providers that use HTTP
HTTP_HEADERS = {"User-Agent": "Spothole v" + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"} HTTP_HEADERS = {"User-Agent": f"Spothole v{SOFTWARE_VERSION} (operated by {SERVER_OWNER_CALLSIGN})"}
HAMQTH_PRG = ("Spothole v" + SOFTWARE_VERSION + " operated by " + SERVER_OWNER_CALLSIGN).replace(" ", "_") HAMQTH_PRG = f"Spothole v{SOFTWARE_VERSION} operated by {SERVER_OWNER_CALLSIGN}".replace(" ", "_")
# Special Interest Groups # Special Interest Groups
SIGS = [ SIGS = [
+11 -11
View File
@@ -46,34 +46,34 @@ class DataStore:
# Standard disk cache for solar data and status data, but each cache contains only a single object which we # Standard disk cache for solar data and status data, but each cache contains only a single object which we
# expose to the wider application # expose to the wider application
self._solar = diskcache.Cache(CACHE_DIR + "solar") self._solar = diskcache.Cache(f"{CACHE_DIR}solar")
if "solar_conditions" not in self._solar: if "solar_conditions" not in self._solar:
self._solar.add("solar_conditions", SolarConditions()) self._solar.add("solar_conditions", SolarConditions())
self.solar_conditions = self._solar.get("solar_conditions") self.solar_conditions = self._solar.get("solar_conditions")
self._status = diskcache.Cache(CACHE_DIR + "status") self._status = diskcache.Cache(f"{CACHE_DIR}status")
if "status_data" not in self._status: if "status_data" not in self._status:
self._status.add("status_data", {}) self._status.add("status_data", {})
self.status_data = self._status.get("status_data") self.status_data = self._status.get("status_data")
# Standard disk cache for static reference and SIG ref data. Separate provider threads will repopulate these on # Standard disk cache for static reference and SIG ref data. Separate provider threads will repopulate these on
# a regular basis but there's no need for a TTL since old data is better than no data. # a regular basis but there's no need for a TTL since old data is better than no data.
self.dxcc_data = diskcache.Cache(CACHE_DIR + "dxcc_data") self.dxcc_data = diskcache.Cache(f"{CACHE_DIR}dxcc_data")
self.regenerate_call_regex_to_dxcc_entity_map() self.regenerate_call_regex_to_dxcc_entity_map()
# For SIG reference data specifically, we need to key on both SIG *and* reference, and trying to do two layers # For SIG reference data specifically, we need to key on both SIG *and* reference, and trying to do two layers
# of dict in diskcache absolutely destroys performance with unpickling huge dicts, so we have an ugly "SIG:ref" # 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. # syntax for keys to keep it a single level.
self.sigrefs = diskcache.Cache(CACHE_DIR + "sigrefs") 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 %d SIG references.", len(self.sigrefs))
# Standard disk cache for callsign data. This data does have a TTL to trigger an occasional re-lookup. # 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 # Old data *is* better than no data, but we can't have a background thread re-looking-up every callsign
# we've seen, so we rely on them timing out and this triggering another lookup. # we've seen, so we rely on them timing out and this triggering another lookup.
self.callsign_data_countryfiles = diskcache.Cache(CACHE_DIR + "callsign_data_countryfiles") self.callsign_data_countryfiles = diskcache.Cache(f"{CACHE_DIR}callsign_data_countryfiles")
self.callsign_data_clublogxml = diskcache.Cache(CACHE_DIR + "callsign_data_clublogxml") self.callsign_data_clublogxml = diskcache.Cache(f"{CACHE_DIR}callsign_data_clublogxml")
self.callsign_data_clublogapi = diskcache.Cache(CACHE_DIR + "callsign_data_clublogapi") self.callsign_data_clublogapi = diskcache.Cache(f"{CACHE_DIR}callsign_data_clublogapi")
self.callsign_data_qrz = diskcache.Cache(CACHE_DIR + "callsign_data_qrz") self.callsign_data_qrz = diskcache.Cache(f"{CACHE_DIR}callsign_data_qrz")
self.callsign_data_hamqth = diskcache.Cache(CACHE_DIR + "callsign_data_hamqth") self.callsign_data_hamqth = diskcache.Cache(f"{CACHE_DIR}callsign_data_hamqth")
unique_keys = set() unique_keys = set()
for c in [self.callsign_data_countryfiles, self.callsign_data_clublogxml, self.callsign_data_clublogapi, for c in [self.callsign_data_countryfiles, self.callsign_data_clublogxml, self.callsign_data_clublogapi,
self.callsign_data_qrz, self.callsign_data_hamqth]: self.callsign_data_qrz, self.callsign_data_hamqth]:
@@ -84,12 +84,12 @@ class DataStore:
# specifically load these caches *last* so that any sigref and callsign data is already loaded from disk cache # 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. # before the spots and alerts are live in the system.
self.spots = LiveDataCache(maxsize=self._MAX_SPOT_COUNT, ttl=MAX_SPOT_AGE, self.spots = LiveDataCache(maxsize=self._MAX_SPOT_COUNT, ttl=MAX_SPOT_AGE,
snapshot_dir=CACHE_DIR + "spots", snapshot_dir=f"{CACHE_DIR}spots",
snapshot_interval_sec=self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC) snapshot_interval_sec=self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC)
logging.info(f"Loaded %d spots from a previous run.", len(self.spots.keys())) logging.info(f"Loaded %d spots from a previous run.", len(self.spots.keys()))
self.alerts = LiveDataCache(maxsize=self._MAX_ALERT_COUNT, ttl=MAX_ALERT_AGE, self.alerts = LiveDataCache(maxsize=self._MAX_ALERT_COUNT, ttl=MAX_ALERT_AGE,
snapshot_dir=CACHE_DIR + "alerts", snapshot_dir=f"{CACHE_DIR}alerts",
snapshot_interval_sec=self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC) snapshot_interval_sec=self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC)
logging.info(f"Loaded %d alerts from a previous run.", len(self.alerts.keys())) logging.info(f"Loaded %d alerts from a previous run.", len(self.alerts.keys()))
+1 -1
View File
@@ -172,7 +172,7 @@ def wab_wai_square_to_lat_lon(ref):
elif re.match(r"^W[AV][0-9]{2}$", ref): elif re.match(r"^W[AV][0-9]{2}$", ref):
return utm_grid_square_to_lat_lon(ref) return utm_grid_square_to_lat_lon(ref)
else: else:
logging.warning("Invalid WAB/WAI square: " + ref) logging.warning(f"Invalid WAB/WAI square: {ref}")
return None return None
+3 -3
View File
@@ -68,14 +68,14 @@ def get_sig_ref_info(sig, ref_id):
if not sig_ref.name: if not sig_ref.name:
sig_ref.name = sig_ref.id sig_ref.name = sig_ref.id
if sig_ref.name: if sig_ref.name:
sig_ref.url = "https://www.beachesontheair.com/beaches/" + sig_ref.name.lower().replace(" ", "-") sig_ref.url = f"https://www.beachesontheair.com/beaches/{sig_ref.name.lower().replace(' ', '-')}"
return sig_ref return sig_ref
### ACTUAL LOOKUP ### ### ACTUAL LOOKUP ###
# #
# OK, this is something we have to look up. Now check to see if our data store contains reference data and use # OK, this is something we have to look up. Now check to see if our data store contains reference data and use
# that. # that.
key = sig + ":" + ref_id key = f"{sig}:{ref_id}"
lookup_data = DATA_STORE.sigrefs.get(key) if key in DATA_STORE.sigrefs else None lookup_data = DATA_STORE.sigrefs.get(key) if key in DATA_STORE.sigrefs else None
if lookup_data: if lookup_data:
return lookup_data return lookup_data
@@ -86,7 +86,7 @@ def get_sig_ref_info(sig, ref_id):
logging.debug("%s database did not contain data for ref %s", sig, ref_id) logging.debug("%s database did not contain data for ref %s", sig, ref_id)
except Exception: except Exception:
logging.exception("Exception when looking up sig_ref info for " + sig + " ref " + ref_id) logging.exception(f"Exception when looking up sig_ref info for {sig} ref {ref_id}")
return sig_ref return sig_ref
+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 # Regex matching any SIG's "comment name", i.e. how it may be referred to in spot comments
ANY_SIG_REGEX = r"(" + r"|".join(n for s in SIGS for n in s.comment_names) + r")" ANY_SIG_REGEX = rf"({'|'.join((n for s in SIGS for n in s.comment_names))})"
+1 -1
View File
@@ -17,7 +17,7 @@ class URLDataCache(CachedSession):
_lock = threading.Lock() _lock = threading.Lock()
def __init__(self, name): def __init__(self, name):
super().__init__(CACHE_DIR + "urls/" + name, expire_after=timedelta(days=1), super().__init__(f"{CACHE_DIR}urls/{name}", expire_after=timedelta(days=1),
allowable_codes=(200, 400, 401, 403, 404)) allowable_codes=(200, 400, 401, 403, 404))
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
+1 -1
View File
@@ -39,7 +39,7 @@ def infer_mode_type_from_mode(mode):
return "DATA" return "DATA"
else: else:
if mode.upper() != "OTHER": if mode.upper() != "OTHER":
logging.warning("Found an unrecognised mode: " + mode + ". Developer should categorise this.") logging.warning(f"Found an unrecognised mode: {mode}. Developer should categorise this.")
return None return None
+1 -1
View File
@@ -388,7 +388,7 @@ class Spot:
if self.sig_refs and len(self.sig_refs) > 0: if self.sig_refs and len(self.sig_refs) > 0:
qth = self.sig_refs[0].id qth = self.sig_refs[0].id
if self.sig_refs[0].name: if self.sig_refs[0].name:
qth += " " + self.sig_refs[0].name qth += f" {self.sig_refs[0].name}"
self.dx_qth = qth self.dx_qth = qth
else: else:
self.dx_qth = dx_call_info.qth self.dx_qth = dx_call_info.qth
+4 -4
View File
@@ -24,7 +24,7 @@ class HTTPAlertProvider(AlertProvider):
def start(self): def start(self):
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between # Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
# subsequent polls, so start() returns immediately and the application can continue starting. # subsequent polls, so start() returns immediately and the application can continue starting.
logging.info("Set up query of " + self.name + " alert API every " + str(self._poll_interval) + " seconds.") logging.info(f"Set up query of {self.name} alert API every {self._poll_interval!s} seconds.")
self._thread = Thread(target=self._run, name=f"HTTPAlertProvider-{self.name}") self._thread = Thread(target=self._run, name=f"HTTPAlertProvider-{self.name}")
self._thread.start() self._thread.start()
@@ -40,7 +40,7 @@ class HTTPAlertProvider(AlertProvider):
def _poll(self): def _poll(self):
try: try:
# Request data from API # Request data from API
logging.debug("Polling " + self.name + " alert API...") logging.debug(f"Polling {self.name} alert API...")
http_response = requests.get(self._url, headers=HTTP_HEADERS, timeout=(5, 30)) http_response = requests.get(self._url, headers=HTTP_HEADERS, timeout=(5, 30))
# Check response code was good # Check response code was good
if http_response.ok: if http_response.ok:
@@ -52,7 +52,7 @@ class HTTPAlertProvider(AlertProvider):
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.debug("Received data from " + self.name + " alert API.") logging.debug(f"Received data from {self.name} alert API.")
else: else:
self.status = "Error" self.status = "Error"
logging.warning(f"HTTP {http_response.status_code} when calling {self.name} alerts API.") logging.warning(f"HTTP {http_response.status_code} when calling {self.name} alerts API.")
@@ -63,7 +63,7 @@ class HTTPAlertProvider(AlertProvider):
logging.warning(f"Timeout when accessing {self.name} alerts API.") logging.warning(f"Timeout when accessing {self.name} alerts API.")
except Exception: except Exception:
self.status = "Error" self.status = "Error"
logging.exception("Exception in HTTP JSON Alert Provider (" + self.name + ")") logging.exception(f"Exception in HTTP JSON Alert Provider ({self.name})")
# Brief pause on error before the next poll, but still respond promptly to stop() # Brief pause on error before the next poll, but still respond promptly to stop()
self._stop_event.wait(timeout=1) self._stop_event.wait(timeout=1)
+4 -4
View File
@@ -49,9 +49,9 @@ class NG3K(HTTPAlertProvider):
end_day = end_string.split(", ")[0].strip() end_day = end_string.split(", ")[0].strip()
end_mon = start_mon end_mon = start_mon
start_timestamp = datetime.strptime(start_year + " " + start_mon + " " + start_day, "%Y %b %d").replace( start_timestamp = datetime.strptime(f"{start_year} {start_mon} {start_day}", "%Y %b %d").replace(
tzinfo=pytz.UTC).timestamp() tzinfo=pytz.UTC).timestamp()
end_timestamp = datetime.strptime(end_year + " " + end_mon + " " + end_day + " 23:59", end_timestamp = datetime.strptime(f"{end_year} {end_mon} {end_day} 23:59",
"%Y %b %d %H:%M").replace( "%Y %b %d %H:%M").replace(
tzinfo=pytz.UTC).timestamp() tzinfo=pytz.UTC).timestamp()
@@ -78,8 +78,8 @@ class NG3K(HTTPAlertProvider):
alert = Alert(source=self.name, alert = Alert(source=self.name,
dx_calls=dx_calls, dx_calls=dx_calls,
dx_country=dx_country, dx_country=dx_country,
freqs_modes=bands + (("; " + modes) if modes != "" else ""), freqs_modes=bands + (f"; {modes}" if modes != "" else ""),
comment=by + "; " + comment + "; " + qsl_info, comment=f"{by}; {comment}; {qsl_info}",
start_time=start_timestamp, start_time=start_timestamp,
end_time=end_timestamp, end_time=end_timestamp,
is_dxpedition=True) is_dxpedition=True)
+2 -2
View File
@@ -43,7 +43,7 @@ class ParksNPeaks(HTTPAlertProvider):
alert = Alert(source=self.name, alert = Alert(source=self.name,
source_id=source_alert["alID"], source_id=source_alert["alID"],
dx_calls=[source_alert["CallSign"].upper()], dx_calls=[source_alert["CallSign"].upper()],
freqs_modes=source_alert["Freq"] + " " + source_alert["MODE"], freqs_modes=f"{source_alert['Freq']} {source_alert['MODE']}",
comment=source_alert["Comments"], comment=source_alert["Comments"],
sig_refs=sigrefs, sig_refs=sigrefs,
start_time=start_time, start_time=start_time,
@@ -51,7 +51,7 @@ class ParksNPeaks(HTTPAlertProvider):
# Log a warning for the developer if PnP gives us an unknown programme we've never seen before # Log a warning for the developer if PnP gives us an unknown programme we've never seen before
if sig and sig not in ["POTA", "SOTA", "WWFF", "SIOTA", "ZLOTA", "KRMNPA", "SANPCPA", "LLOTA", "QRP"]: if sig and sig not in ["POTA", "SOTA", "WWFF", "SIOTA", "ZLOTA", "KRMNPA", "SANPCPA", "LLOTA", "QRP"]:
logging.warning("PNP alert found with sig " + sig + ", developer needs to add support for this!") logging.warning(f"PNP alert found with sig {sig}, developer needs to add support for this!")
# If this is POTA, SOTA or WWFF data we already have it through other means, so ignore. Otherwise, add to # If this is POTA, SOTA or WWFF data we already have it through other means, so ignore. Otherwise, add to
# the alert list. Note that while ZLOTA has its own spots API, it doesn't have its own alerts API. So that # the alert list. Note that while ZLOTA has its own spots API, it doesn't have its own alerts API. So that
+1 -1
View File
@@ -27,7 +27,7 @@ class POTA(HTTPAlertProvider):
freqs_modes=source_alert["frequencies"], freqs_modes=source_alert["frequencies"],
comment=source_alert["comments"], comment=source_alert["comments"],
sig_refs=[SIGRef(id=source_alert["reference"], sig="POTA", name=source_alert["name"], sig_refs=[SIGRef(id=source_alert["reference"], sig="POTA", name=source_alert["name"],
url="https://pota.app/#/park/" + source_alert["reference"])], url=f"https://pota.app/#/park/{source_alert['reference']}")],
start_time=datetime.strptime(source_alert["startDate"] + source_alert["startTime"], start_time=datetime.strptime(source_alert["startDate"] + source_alert["startTime"],
"%Y-%m-%d%H:%M").replace(tzinfo=pytz.UTC).timestamp(), "%Y-%m-%d%H:%M").replace(tzinfo=pytz.UTC).timestamp(),
end_time=datetime.strptime(source_alert["endDate"] + source_alert["endTime"], end_time=datetime.strptime(source_alert["endDate"] + source_alert["endTime"],
+1 -1
View File
@@ -33,7 +33,7 @@ class SOTA(HTTPAlertProvider):
freqs_modes=source_alert["frequency"], freqs_modes=source_alert["frequency"],
comment=source_alert["comments"], comment=source_alert["comments"],
sig_refs=[ sig_refs=[
SIGRef(id=source_alert["associationCode"] + "/" + source_alert["summitCode"], sig="SOTA", SIGRef(id=f"{source_alert['associationCode']}/{source_alert['summitCode']}", sig="SOTA",
name=summit_name, activation_score=summit_points)], name=summit_name, activation_score=summit_points)],
start_time=datetime.strptime(source_alert["dateActivated"], start_time=datetime.strptime(source_alert["dateActivated"],
"%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=pytz.UTC).timestamp(), "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=pytz.UTC).timestamp(),
+1 -1
View File
@@ -24,7 +24,7 @@ class WWFF(HTTPAlertProvider):
alert = Alert(source=self.name, alert = Alert(source=self.name,
source_id=source_alert["id"], source_id=source_alert["id"],
dx_calls=[source_alert["activator_call"].upper()], dx_calls=[source_alert["activator_call"].upper()],
freqs_modes=source_alert["band"] + " " + source_alert["mode"], freqs_modes=f"{source_alert['band']} {source_alert['mode']}",
comment=source_alert["remarks"], comment=source_alert["remarks"],
sig_refs=[SIGRef(id=source_alert["reference"], sig="WWFF")], sig_refs=[SIGRef(id=source_alert["reference"], sig="WWFF")],
start_time=datetime.strptime(source_alert["utc_start"], start_time=datetime.strptime(source_alert["utc_start"],
+1 -1
View File
@@ -26,7 +26,7 @@ class ClublogXML(FileDownloadCallsignDataProvider):
logging.warning( logging.warning(
"Clublog XML callsign data provider configured but no api key was provided, this has been disabled.") "Clublog XML callsign data provider configured but no api key was provided, this has been disabled.")
super().__init__("Clublog XML", provider_config, self.DATA_URL + "?api=" + self._api_key, super().__init__("Clublog XML", provider_config, f"{self.DATA_URL}?api={self._api_key}",
self.CACHE_PATH_ZIPPED, self.POLL_INTERVAL_DAYS, DATA_STORE.callsign_data_clublogxml) self.CACHE_PATH_ZIPPED, self.POLL_INTERVAL_DAYS, DATA_STORE.callsign_data_clublogxml)
def _handle_file(self, path): def _handle_file(self, path):
@@ -22,7 +22,7 @@ class FileDownloadCallsignDataProvider(CallsignDataProvider):
self._poll_interval = poll_interval self._poll_interval = poll_interval
self._thread = None self._thread = None
self._stop_event = Event() self._stop_event = Event()
self._url_data_cache = URLDataCache("callsigndata_" + name) self._url_data_cache = URLDataCache(f"callsigndata_{name}")
if self.enabled: if self.enabled:
self.status = "Ready" self.status = "Ready"
@@ -31,7 +31,7 @@ class FileDownloadCallsignDataProvider(CallsignDataProvider):
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between # Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
# subsequent polls, so start() returns immediately and the application can continue starting. # subsequent polls, so start() returns immediately and the application can continue starting.
logging.info( logging.info(
"Set up query of " + self.name + " callsign reference data every " + str(self._poll_interval) + " days.") f"Set up query of {self.name} callsign reference data every {self._poll_interval!s} days.")
self._thread = Thread(target=self._run, name=f"FileDownloadCallsignDataProvider-{self.name}") self._thread = Thread(target=self._run, name=f"FileDownloadCallsignDataProvider-{self.name}")
self._thread.start() self._thread.start()
@@ -48,7 +48,7 @@ class FileDownloadCallsignDataProvider(CallsignDataProvider):
try: try:
# Request the file. Use the data cache (with a TTL of 1 day) here, not as the main mechanism for # Request the file. Use the data cache (with a TTL of 1 day) here, not as the main mechanism for
# caching, but just so continual restarts of the software during testing don't hammer the servers. # caching, but just so continual restarts of the software during testing don't hammer the servers.
logging.debug("Downloading " + self.name + " callsign reference data...") logging.debug(f"Downloading {self.name} callsign reference data...")
http_response = self._url_data_cache.get(self._url, headers=HTTP_HEADERS) http_response = self._url_data_cache.get(self._url, headers=HTTP_HEADERS)
# Check response code was good # Check response code was good
if http_response.ok: if http_response.ok:
@@ -61,7 +61,7 @@ class FileDownloadCallsignDataProvider(CallsignDataProvider):
if ok: if ok:
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.info("Updated callsign reference data from " + self.name) logging.info(f"Updated callsign reference data from {self.name}")
else: else:
self.status = "Error" self.status = "Error"
logging.warning(f"Error updating callsign reference data from {self.name}.") logging.warning(f"Error updating callsign reference data from {self.name}.")
@@ -78,7 +78,7 @@ class FileDownloadCallsignDataProvider(CallsignDataProvider):
logging.warning(f"Timeout when downloading callsign reference data from {self.name}.") logging.warning(f"Timeout when downloading callsign reference data from {self.name}.")
except Exception: except Exception:
self.status = "Error" self.status = "Error"
logging.exception("Exception in callsign reference data provider (" + self.name + ")") logging.exception(f"Exception in callsign reference data provider ({self.name})")
self._stop_event.wait(timeout=1) self._stop_event.wait(timeout=1)
def _handle_file(self, path): def _handle_file(self, path):
+4 -6
View File
@@ -22,11 +22,11 @@ class HamQTH(APIQueryCallsignDataProvider):
def __init__(self, provider_config): def __init__(self, provider_config):
super().__init__("HamQTH", provider_config, DATA_STORE.callsign_data_hamqth) super().__init__("HamQTH", provider_config, DATA_STORE.callsign_data_hamqth)
self._HAMQTH_BASE_URL = "https://www.hamqth.com/xml.php" self._HAMQTH_BASE_URL = "https://www.hamqth.com/xml.php"
self._PRG = ("Spothole v" + SOFTWARE_VERSION + " operated by " + SERVER_OWNER_CALLSIGN).replace(" ", "_") self._PRG = f"Spothole v{SOFTWARE_VERSION} operated by {SERVER_OWNER_CALLSIGN}".replace(" ", "_")
self._URL_DATA_CACHE = URLDataCache("hamqth") self._URL_DATA_CACHE = URLDataCache("hamqth")
# Separate URL cache for session key lookups. Once a session key is returned from logging in with a username # Separate URL cache for session key lookups. Once a session key is returned from logging in with a username
# and password, this is valid for an hour, so our cache stores this specifically for 55 minutes. # and password, this is valid for an hour, so our cache stores this specifically for 55 minutes.
self._CREDENTIALS_CACHE = CachedSession(CACHE_DIR + "/urls/hamqth-creds", self._CREDENTIALS_CACHE = CachedSession(f"{CACHE_DIR}/urls/hamqth-creds",
expire_after=timedelta(minutes=55)) expire_after=timedelta(minutes=55))
def _perform_new_lookup(self, callsign, lookup_credentials): def _perform_new_lookup(self, callsign, lookup_credentials):
@@ -44,8 +44,7 @@ class HamQTH(APIQueryCallsignDataProvider):
elif lookup_credentials.hamqth_username and lookup_credentials.hamqth_password: elif lookup_credentials.hamqth_username and lookup_credentials.hamqth_password:
try: try:
session_data = self._CREDENTIALS_CACHE.get( session_data = self._CREDENTIALS_CACHE.get(
self._HAMQTH_BASE_URL + "?u=" + urllib.parse.quote_plus(lookup_credentials.hamqth_username) + f"{self._HAMQTH_BASE_URL}?u={urllib.parse.quote_plus(lookup_credentials.hamqth_username)}&p={urllib.parse.quote_plus(lookup_credentials.hamqth_password)}",
"&p=" + urllib.parse.quote_plus(lookup_credentials.hamqth_password),
headers=HTTP_HEADERS).content headers=HTTP_HEADERS).content
dict_data = xmltodict.parse(session_data) dict_data = xmltodict.parse(session_data)
if "session_id" in dict_data["HamQTH"]["session"]: if "session_id" in dict_data["HamQTH"]["session"]:
@@ -74,8 +73,7 @@ class HamQTH(APIQueryCallsignDataProvider):
for lookup_call in calls_to_try: for lookup_call in calls_to_try:
try: try:
response = self._URL_DATA_CACHE.get( response = self._URL_DATA_CACHE.get(
self._HAMQTH_BASE_URL + "?id=" + session_id + "&callsign=" + urllib.parse.quote_plus( f"{self._HAMQTH_BASE_URL}?id={session_id}&callsign={urllib.parse.quote_plus(lookup_call)}&prg={self._PRG}", headers=HTTP_HEADERS, timeout=10)
lookup_call) + "&prg=" + self._PRG, headers=HTTP_HEADERS, timeout=10)
if response.ok: if response.ok:
# Found data, convert it to our object and return it # Found data, convert it to our object and return it
data = xmltodict.parse(response.content)["HamQTH"]["search"] data = xmltodict.parse(response.content)["HamQTH"]["search"]
+5 -6
View File
@@ -24,7 +24,7 @@ class QRZ(APIQueryCallsignDataProvider):
self._URL_DATA_CACHE = URLDataCache("qrz") self._URL_DATA_CACHE = URLDataCache("qrz")
# Separate URL cache for session key lookups. Once a session key is returned from logging in with a username # Separate URL cache for session key lookups. Once a session key is returned from logging in with a username
# and password, this is valid for an hour, so our cache stores this specifically for 55 minutes. # and password, this is valid for an hour, so our cache stores this specifically for 55 minutes.
self._CREDENTIALS_CACHE = CachedSession(CACHE_DIR + "/urls/qrz-creds", self._CREDENTIALS_CACHE = CachedSession(f"{CACHE_DIR}/urls/qrz-creds",
expire_after=timedelta(minutes=55)) expire_after=timedelta(minutes=55))
def _perform_new_lookup(self, callsign, lookup_credentials): def _perform_new_lookup(self, callsign, lookup_credentials):
@@ -42,8 +42,7 @@ class QRZ(APIQueryCallsignDataProvider):
elif lookup_credentials.qrz_username and lookup_credentials.qrz_password: elif lookup_credentials.qrz_username and lookup_credentials.qrz_password:
try: try:
login_response = self._CREDENTIALS_CACHE.get( login_response = self._CREDENTIALS_CACHE.get(
self._QRZ_BASE_URL + "?username=" + urllib.parse.quote_plus(lookup_credentials.qrz_username) + f"{self._QRZ_BASE_URL}?username={urllib.parse.quote_plus(lookup_credentials.qrz_username)}&password={urllib.parse.quote_plus(lookup_credentials.qrz_password)}&agent=spothole",
"&password=" + urllib.parse.quote_plus(lookup_credentials.qrz_password) + "&agent=spothole",
headers=HTTP_HEADERS).content headers=HTTP_HEADERS).content
login_data = xmltodict.parse(login_response) login_data = xmltodict.parse(login_response)
session = login_data.get("QRZDatabase", {}).get("Session", {}) session = login_data.get("QRZDatabase", {}).get("Session", {})
@@ -73,7 +72,7 @@ class QRZ(APIQueryCallsignDataProvider):
for lookup_call in calls_to_try: for lookup_call in calls_to_try:
try: try:
response = self._URL_DATA_CACHE.get( response = self._URL_DATA_CACHE.get(
self._QRZ_BASE_URL + "?s=" + session_key + "&callsign=" + urllib.parse.quote_plus(lookup_call), f"{self._QRZ_BASE_URL}?s={session_key}&callsign={urllib.parse.quote_plus(lookup_call)}",
headers=HTTP_HEADERS, timeout=10) headers=HTTP_HEADERS, timeout=10)
if response.ok: if response.ok:
qrz_response = xmltodict.parse(response.content).get("QRZDatabase", {}) qrz_response = xmltodict.parse(response.content).get("QRZDatabase", {})
@@ -129,9 +128,9 @@ class QRZ(APIQueryCallsignDataProvider):
if "fname" in data: if "fname" in data:
name = data["fname"] name = data["fname"]
if "nick" in data: if "nick" in data:
name = name + " \"" + data["nick"] + "\"" name = f"{name} \"{data['nick']}\""
if "name" in data: if "name" in data:
name = name + " " + data["name"] name = f"{name} {data['name']}"
# Check for sensible latitudes # Check for sensible latitudes
lat = None lat = None
+1 -1
View File
@@ -22,7 +22,7 @@ class ARLHS(FileDownloadSIGRefDataProvider):
ref_id = row["ARLHS"] ref_id = row["ARLHS"]
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None, new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None,
ref_type="Lighthouse", ref_type="Lighthouse",
url="https://www.cqgma.org/zinfo.php?ref=" + ref_id, url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}",
latitude=float(row["Latitude"]) if "Latitude" in row and row[ latitude=float(row["Latitude"]) if "Latitude" in row and row[
"Latitude"] != "" else None, "Latitude"] != "" else None,
longitude=float(row["Longitude"]) if "Longitude" in row and row[ longitude=float(row["Longitude"]) if "Longitude" in row and row[
+1 -1
View File
@@ -28,7 +28,7 @@ class DME(LocalFileSIGRefDataProvider):
ref = SIGRef(sig=self.SIG, id=ref_id, ref = SIGRef(sig=self.SIG, id=ref_id,
ref_type="Town", ref_type="Town",
name=row["NOMBRE_ACTUAL"] + ", " + row["PROVINCIA"], name=f"{row['NOMBRE_ACTUAL']}, {row['PROVINCIA']}",
latitude=latitude, latitude=latitude,
longitude=longitude) longitude=longitude)
if latitude and longitude: if latitude and longitude:
@@ -21,13 +21,13 @@ class FileDownloadSIGRefDataProvider(SIGRefDataProvider):
self._poll_interval = poll_interval self._poll_interval = poll_interval
self._thread = None self._thread = None
self._stop_event = Event() self._stop_event = Event()
self._url_data_cache = URLDataCache("sigrefdata_" + sig_name) self._url_data_cache = URLDataCache(f"sigrefdata_{sig_name}")
def start(self): def start(self):
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between # Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
# subsequent polls, so start() returns immediately and the application can continue starting. # subsequent polls, so start() returns immediately and the application can continue starting.
logging.info( logging.info(
"Set up query of " + self.sig_name + " SIG ref data every " + str(self._poll_interval) + " days.") f"Set up query of {self.sig_name} SIG ref data every {self._poll_interval!s} days.")
self._thread = Thread(target=self._run, name=f"FileDownloadSIGRefDataProvider-{self.sig_name}") self._thread = Thread(target=self._run, name=f"FileDownloadSIGRefDataProvider-{self.sig_name}")
self._thread.start() self._thread.start()
@@ -45,7 +45,7 @@ class FileDownloadSIGRefDataProvider(SIGRefDataProvider):
try: try:
# Request data from API. Use the data cache (with a TTL of 1 day) here, not as the main mechanism for # Request data from API. Use the data cache (with a TTL of 1 day) here, not as the main mechanism for
# caching, but just so continual restarts of the software during testing don't hammer the servers. # caching, but just so continual restarts of the software during testing don't hammer the servers.
logging.debug("Downloading " + self.sig_name + " SIG ref data...") logging.debug(f"Downloading {self.sig_name} SIG ref data...")
http_response = self._url_data_cache.get(self._url, headers=HTTP_HEADERS) http_response = self._url_data_cache.get(self._url, headers=HTTP_HEADERS)
# Check response code was good # Check response code was good
if http_response.ok: if http_response.ok:
@@ -57,7 +57,7 @@ class FileDownloadSIGRefDataProvider(SIGRefDataProvider):
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.debug("Received SIG ref data for " + self.sig_name) logging.debug(f"Received SIG ref data for {self.sig_name}")
else: else:
self.status = "Error" self.status = "Error"
logging.warning(f"HTTP {http_response.status_code} when downloading SIG ref data for {self.sig_name}.") logging.warning(f"HTTP {http_response.status_code} when downloading SIG ref data for {self.sig_name}.")
@@ -70,7 +70,7 @@ class FileDownloadSIGRefDataProvider(SIGRefDataProvider):
logging.warning(f"Timeout when downloading SIG ref data for {self.sig_name}.") logging.warning(f"Timeout when downloading SIG ref data for {self.sig_name}.")
except Exception: except Exception:
self.status = "Error" self.status = "Error"
logging.exception("Exception in HTTP SIG Ref Data Provider (" + self.sig_name + ")") logging.exception(f"Exception in HTTP SIG Ref Data Provider ({self.sig_name})")
self._stop_event.wait(timeout=1) self._stop_event.wait(timeout=1)
def _http_response_to_data(self, http_response): def _http_response_to_data(self, http_response):
+1 -1
View File
@@ -21,7 +21,7 @@ class GMA(FileDownloadSIGRefDataProvider):
ref_id = row["Reference"] ref_id = row["Reference"]
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None, new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None,
ref_type="Summit", ref_type="Summit",
url="https://www.cqgma.org/zinfo.php?ref=" + ref_id, url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}",
latitude=float(row["Latitude"]) if "Latitude" in row and row[ latitude=float(row["Latitude"]) if "Latitude" in row and row[
"Latitude"] != "" else None, "Latitude"] != "" else None,
longitude=float(row["Longitude"]) if "Longitude" in row and row[ longitude=float(row["Longitude"]) if "Longitude" in row and row[
+1 -1
View File
@@ -22,7 +22,7 @@ class ILLW(FileDownloadSIGRefDataProvider):
ref_id = row["ILLW"] ref_id = row["ILLW"]
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None, new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None,
ref_type="Lighthouse", ref_type="Lighthouse",
url="https://www.cqgma.org/zinfo.php?ref=" + ref_id, url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}",
latitude=float(row["Latitude"]) if "Latitude" in row and row[ latitude=float(row["Latitude"]) if "Latitude" in row and row[
"Latitude"] != "" else None, "Latitude"] != "" else None,
longitude=float(row["Longitude"]) if "Longitude" in row and row[ longitude=float(row["Longitude"]) if "Longitude" in row and row[
+1 -1
View File
@@ -27,7 +27,7 @@ class LLOTA(FileDownloadSIGRefDataProvider):
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=str(ref["name"]), new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=str(ref["name"]),
ref_type="Lake", ref_type="Lake",
url="https://llota.app/list/ref/" + ref_id, url=f"https://llota.app/list/ref/{ref_id}",
grid=grid, grid=grid,
latitude=ll[0], latitude=ll[0],
longitude=ll[1])) longitude=ll[1]))
@@ -14,7 +14,7 @@ class LocalFileSIGRefDataProvider(SIGRefDataProvider):
self._path = path self._path = path
def start(self): def start(self):
logging.debug("Loading " + self.sig_name + " SIG ref data from file.") logging.debug(f"Loading {self.sig_name} SIG ref data from file.")
try: try:
new_data = self._file_to_data(self._path) new_data = self._file_to_data(self._path)
if new_data: if new_data:
@@ -23,10 +23,10 @@ class LocalFileSIGRefDataProvider(SIGRefDataProvider):
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
else: else:
self.status = "Error" self.status = "Error"
logging.info("Failed to load SIG ref data for " + self.sig_name) logging.info(f"Failed to load SIG ref data for {self.sig_name}")
except Exception: except Exception:
self.status = "Error" self.status = "Error"
logging.exception("Exception in local file SIG Ref Data Provider (" + self.sig_name + ")") logging.exception(f"Exception in local file SIG Ref Data Provider ({self.sig_name})")
def _file_to_data(self, path): def _file_to_data(self, path):
"""Load a file on the given path and turn it into SIG Ref data.""" """Load a file on the given path and turn it into SIG Ref data."""
+1 -1
View File
@@ -21,7 +21,7 @@ class MOTA(FileDownloadSIGRefDataProvider):
ref_id = row["Reference"] ref_id = row["Reference"]
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None, new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None,
ref_type="Mill", ref_type="Mill",
url="https://www.cqgma.org/zinfo.php?ref=" + ref_id, url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}",
latitude=float(row["Latitude"]) if "Latitude" in row and row[ latitude=float(row["Latitude"]) if "Latitude" in row and row[
"Latitude"] != "" else None, "Latitude"] != "" else None,
longitude=float(row["Longitude"]) if "Longitude" in row and row[ longitude=float(row["Longitude"]) if "Longitude" in row and row[
@@ -39,7 +39,7 @@ class ParksNPeaksKMLSIGRefDataProvider(FileDownloadSIGRefDataProvider):
ref = SIGRef(sig=self.sig_name, id=ref_id, name=placemark.name, ref = SIGRef(sig=self.sig_name, id=ref_id, name=placemark.name,
ref_type="Park", ref_type="Park",
url="https://parksnpeaks.org/getPark.php?actPark=" + ref_id, url=f"https://parksnpeaks.org/getPark.php?actPark={ref_id}",
latitude=latitude, latitude=latitude,
longitude=longitude) longitude=longitude)
if latitude and longitude: if latitude and longitude:
+1 -1
View File
@@ -21,7 +21,7 @@ class POTA(FileDownloadSIGRefDataProvider):
ref_id = row["reference"] ref_id = row["reference"]
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["name"] if "name" in row else None, new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["name"] if "name" in row else None,
ref_type="Park", ref_type="Park",
url="https://pota.app/#/park/" + ref_id, url=f"https://pota.app/#/park/{ref_id}",
grid=row["grid"] if "grid" in row else None, grid=row["grid"] if "grid" in row else None,
latitude=float(row["latitude"]) if "latitude" in row and row[ latitude=float(row["latitude"]) if "latitude" in row and row[
"latitude"] != "" else None, "latitude"] != "" else None,
@@ -38,7 +38,7 @@ class SIGRefDataProvider:
# with transact() batches all writes together to save making thousands of individual sqlite writes # with transact() batches all writes together to save making thousands of individual sqlite writes
with DATA_STORE.sigrefs.transact(): with DATA_STORE.sigrefs.transact():
for d in new_data: for d in new_data:
DATA_STORE.sigrefs.set(self.sig_name + ":" + d.id, d) DATA_STORE.sigrefs.set(f"{self.sig_name}:{d.id}", d)
# For the big data sources, loading will take a few minutes. If we want to shut down the software neatly # For the big data sources, loading will take a few minutes. If we want to shut down the software neatly
# within the first few minutes of startup, we need a way to abort this expensive process of filling up the # within the first few minutes of startup, we need a way to abort this expensive process of filling up the
+1 -1
View File
@@ -26,7 +26,7 @@ class SOTA(FileDownloadSIGRefDataProvider):
altitude = float(row["AltM"]) if "AltM" in row and row["AltM"] != "" else None altitude = float(row["AltM"]) if "AltM" in row and row["AltM"] != "" else None
ref = SIGRef(sig=self.SIG, id=ref_id, name=row["SummitName"] if "SummitName" in row else None, ref = SIGRef(sig=self.SIG, id=ref_id, name=row["SummitName"] if "SummitName" in row else None,
ref_type="Summit", ref_type="Summit",
url="https://www.sotadata.org.uk/en/summit/" + ref_id, url=f"https://www.sotadata.org.uk/en/summit/{ref_id}",
latitude=latitude, latitude=latitude,
longitude=longitude, longitude=longitude,
altitude=altitude, altitude=altitude,
+1 -1
View File
@@ -21,7 +21,7 @@ class Towers(FileDownloadSIGRefDataProvider):
ref_id = row["Ref"] ref_id = row["Ref"]
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Nazev"] if "Nazev" in row else None, new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Nazev"] if "Nazev" in row else None,
ref_type="Tower", ref_type="Tower",
url="https://wwtota.com/seznam/karta_rozhledny.php?ref=" + ref_id, url=f"https://wwtota.com/seznam/karta_rozhledny.php?ref={ref_id}",
grid=row["Lokator"] if "Lokator" in row and row["Lokator"] != "" else None, grid=row["Lokator"] if "Lokator" in row and row["Lokator"] != "" else None,
latitude=float(row["Lat"]) if "Lat" in row and row["Lat"] != "" else None, latitude=float(row["Lat"]) if "Lat" in row and row["Lat"] != "" else None,
longitude=float(row["Lon"]) if "Lon" in row and row["Lon"] != "" else None)) longitude=float(row["Lon"]) if "Lon" in row and row["Lon"] != "" else None))
+1 -1
View File
@@ -38,7 +38,7 @@ class WCA(FileDownloadSIGRefDataProvider):
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["CLEAN NAME"] if "CLEAN NAME" in row else None, new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["CLEAN NAME"] if "CLEAN NAME" in row else None,
ref_type="Castle", ref_type="Castle",
url="https://www.cqgma.org/zinfo.php?ref=" + ref_id, url=f"https://www.cqgma.org/zinfo.php?ref={ref_id}",
latitude=latitude, latitude=latitude,
longitude=longitude, longitude=longitude,
grid=grid)) grid=grid))
+2 -2
View File
@@ -20,10 +20,10 @@ class WOTA(FileDownloadSIGRefDataProvider):
ref_id = feature["properties"]["wotaId"] ref_id = feature["properties"]["wotaId"]
# Fudge WOTA URLs. Outlying fell (LDO) URLs don't match their ID numbers but require 214 to be # Fudge WOTA URLs. Outlying fell (LDO) URLs don't match their ID numbers but require 214 to be
# added to them # added to them
url = "https://www.wota.org.uk/MM_" + ref_id url = f"https://www.wota.org.uk/MM_{ref_id}"
if ref_id.upper().startswith("LDO-"): if ref_id.upper().startswith("LDO-"):
number = int(ref_id.upper().replace("LDO-", "")) number = int(ref_id.upper().replace("LDO-", ""))
url = "https://www.wota.org.uk/MM_LDO-" + str(number + 214) url = f"https://www.wota.org.uk/MM_LDO-{number + 214!s}"
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=feature["properties"]["title"], url=url, new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=feature["properties"]["title"], url=url,
ref_type="Summit", ref_type="Summit",
+1 -1
View File
@@ -21,7 +21,7 @@ class WWBOTA(FileDownloadSIGRefDataProvider):
ref_id = row["Reference"] ref_id = row["Reference"]
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None, new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None,
ref_type="Bunker", ref_type="Bunker",
url="https://bunkerwiki.org/?s=" + ref_id if ref_id.startswith("B/G") else None, url=f"https://bunkerwiki.org/?s={ref_id}" if ref_id.startswith("B/G") else None,
grid=row["Locator"] if "Locator" in row and row["Locator"] != "" else None, grid=row["Locator"] if "Locator" in row and row["Locator"] != "" else None,
latitude=float(row["Lat"]) if "Lat" in row and row["Lat"] != "" else None, latitude=float(row["Lat"]) if "Lat" in row and row["Lat"] != "" else None,
longitude=float(row["Long"]) if "Long" in row and row["Long"] != "" else None)) longitude=float(row["Long"]) if "Long" in row and row["Long"] != "" else None))
+1 -1
View File
@@ -21,7 +21,7 @@ class WWFF(FileDownloadSIGRefDataProvider):
ref_id = row["reference"] ref_id = row["reference"]
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["name"] if "name" in row else None, new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["name"] if "name" in row else None,
ref_type="Park", ref_type="Park",
url="https://wwff.co/directory/?showRef=" + ref_id, url=f"https://wwff.co/directory/?showRef={ref_id}",
grid=row["iaruLocator"] if "iaruLocator" in row and row[ grid=row["iaruLocator"] if "iaruLocator" in row and row[
"iaruLocator"] != "-" else None, "iaruLocator"] != "-" else None,
latitude=float(row["latitude"]) if "latitude" in row and row[ latitude=float(row["latitude"]) if "latitude" in row and row[
+1 -1
View File
@@ -27,7 +27,7 @@ class ZLOTA(FileDownloadSIGRefDataProvider):
new_ref = SIGRef(sig=self.SIG, id=ref_id, name=ref["name"], new_ref = SIGRef(sig=self.SIG, id=ref_id, name=ref["name"],
ref_type=ref["asset_type"].title(), ref_type=ref["asset_type"].title(),
url="https://ontheair.nz/assets/" + ref_id.replace("/", "_"), url=f"https://ontheair.nz/assets/{ref_id.replace('/', '_')}",
latitude=latitude, latitude=latitude,
longitude=longitude) longitude=longitude)
+2 -2
View File
@@ -72,11 +72,11 @@ class HamQSL(HTTPSolarConditionsProvider):
tz_abbr = updated_str.split()[-1] tz_abbr = updated_str.split()[-1]
timezone = dateutil_tz.gettz(tz_abbr) timezone = dateutil_tz.gettz(tz_abbr)
if timezone is None: if timezone is None:
raise ValueError("Unknown timezone abbreviation: " + tz_abbr) raise ValueError(f"Unknown timezone abbreviation: {tz_abbr}")
dt = dateutil_parser.parse(updated_str, tzinfos={tz_abbr: timezone}) dt = dateutil_parser.parse(updated_str, tzinfos={tz_abbr: timezone})
updated = dt.astimezone(pytz.UTC).timestamp() updated = dt.astimezone(pytz.UTC).timestamp()
except (ValueError, IndexError): except (ValueError, IndexError):
logging.warning("HamQSL solar conditions API returned unrecognised timestamp format: " + updated_str) logging.warning(f"HamQSL solar conditions API returned unrecognised timestamp format: {updated_str}")
# Return the data ready to be put into the solar conditions object. # Return the data ready to be put into the solar conditions object.
return { return {
@@ -23,7 +23,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
def start(self): def start(self):
logging.info( logging.info(
"Set up query of " + self.name + " solar conditions API every " + str(self._poll_interval) + " seconds.") 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 = Thread(target=self._run, name=f"HTTPSolarConditionsProvider-{self.name}")
self._thread.start() self._thread.start()
@@ -38,7 +38,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
def _poll(self): def _poll(self):
try: try:
logging.debug("Polling " + self.name + " solar conditions API...") logging.debug(f"Polling {self.name} solar conditions API...")
http_response = requests.get(self._url, headers=HTTP_HEADERS, timeout=(5, 30)) http_response = requests.get(self._url, headers=HTTP_HEADERS, timeout=(5, 30))
# Check response code was good # Check response code was good
if http_response.ok: if http_response.ok:
@@ -47,7 +47,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.debug("Received data from " + self.name + " solar conditions API.") logging.debug(f"Received data from {self.name} solar conditions API.")
else: else:
self.status = "Error" self.status = "Error"
logging.warning(f"HTTP {http_response.status_code} when calling {self.name} solar conditions API.") logging.warning(f"HTTP {http_response.status_code} when calling {self.name} solar conditions API.")
@@ -58,7 +58,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
logging.warning(f"Timeout when accessing {self.name} solar conditions API.") logging.warning(f"Timeout when accessing {self.name} solar conditions API.")
except Exception: except Exception:
self.status = "Error" self.status = "Error"
logging.exception("Exception in HTTP Solar Conditions Provider (" + self.name + ")") logging.exception(f"Exception in HTTP Solar Conditions Provider ({self.name})")
self._stop_event.wait(timeout=1) self._stop_event.wait(timeout=1)
def _http_response_to_solar_conditions(self, http_response): def _http_response_to_solar_conditions(self, http_response):
@@ -96,7 +96,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
header_line = lines[start_idx] 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: if not year_match:
logging.warning("NOAA K-index forecast: could not extract year from: " + header_line) logging.warning(f"NOAA K-index forecast: could not extract year from: {header_line}")
return None return None
year = int(year_match.group(1)) year = int(year_match.group(1))
@@ -108,7 +108,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
date_header_line = lines[start_idx + 2] 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: if not date_matches:
logging.warning("NOAA K-index forecast: could not parse date headers from: " + date_header_line) logging.warning(f"NOAA K-index forecast: could not parse date headers from: {date_header_line}")
return None return None
column_dates = [] column_dates = []
+10 -10
View File
@@ -54,19 +54,19 @@ class DXCluster(SpotProvider):
while not connected and self._running: while not connected and self._running:
try: try:
self.status = "Connecting" self.status = "Connecting"
logging.info("DX Cluster " + self._hostname + " connecting...") logging.info(f"DX Cluster {self._hostname} connecting...")
self._telnet = telnetlib3.Telnet(self._hostname, self._port) self._telnet = telnetlib3.Telnet(self._hostname, self._port)
self._telnet.read_until(self._login_prompt.encode("latin-1")) self._telnet.read_until(self._login_prompt.encode("latin-1"))
self._telnet.write((self._login_callsign + "\n").encode("latin-1")) self._telnet.write(f"{self._login_callsign}\n".encode("latin-1"))
connected = True connected = True
logging.info("DX Cluster " + self._hostname + " connected.") logging.info(f"DX Cluster {self._hostname} connected.")
except ConnectionRefusedError: except ConnectionRefusedError:
self.status = "Error" self.status = "Error"
logging.warning("Connection refused to DX cluster " + self._hostname) logging.warning(f"Connection refused to DX cluster {self._hostname}")
sleep(300) sleep(300)
except Exception: except Exception:
self.status = "Error" self.status = "Error"
logging.exception("Exception while connecting to DX Cluster Provider (" + self._hostname + ").") logging.exception(f"Exception while connecting to DX Cluster Provider ({self._hostname}).")
sleep(5) sleep(5)
self.status = "Waiting for Data" self.status = "Waiting for Data"
@@ -91,25 +91,25 @@ class DXCluster(SpotProvider):
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.debug("Data received from DX Cluster " + self._hostname + ".") logging.debug(f"Data received from DX Cluster {self._hostname}.")
except EOFError: except EOFError:
connected = False connected = False
if self._running: if self._running:
self.status = "Restarting" self.status = "Restarting"
logging.warning("Disconnected from DX Cluster " + self._hostname + ". Reconnecting...") logging.warning(f"Disconnected from DX Cluster {self._hostname}. Reconnecting...")
sleep(5) sleep(5)
else: else:
logging.info("DX Cluster " + self._hostname + " shutting down...") logging.info(f"DX Cluster {self._hostname} shutting down...")
self.status = "Shutting down" self.status = "Shutting down"
except Exception: except Exception:
connected = False connected = False
if self._running: if self._running:
self.status = "Error" self.status = "Error"
logging.exception("Exception in DX Cluster Provider (" + self._hostname + ")") logging.exception(f"Exception in DX Cluster Provider ({self._hostname})")
sleep(5) sleep(5)
else: else:
logging.info("DX Cluster " + self._hostname + " shutting down...") logging.info(f"DX Cluster {self._hostname} shutting down...")
self.status = "Shutting down" self.status = "Shutting down"
self.status = "Disconnected" self.status = "Disconnected"
+3 -5
View File
@@ -27,7 +27,7 @@ class GMA(HTTPSpotProvider):
logging.warning("GMA spot provider configured but no api key was provided, this API will not be queried.") logging.warning("GMA spot provider configured but no api key was provided, this API will not be queried.")
self._url_data_cache = URLDataCache("GMA") self._url_data_cache = URLDataCache("GMA")
super().__init__("GMA", provider_config, self.SPOTS_URL + "?key=" + self._api_key, self.POLL_INTERVAL_SEC) super().__init__("GMA", provider_config, f"{self.SPOTS_URL}?key={self._api_key}", self.POLL_INTERVAL_SEC)
def _http_response_to_spots(self, http_response): def _http_response_to_spots(self, http_response):
new_spots = [] new_spots = []
@@ -98,8 +98,7 @@ class GMA(HTTPSpotProvider):
spot.sig_refs[0].sig = "MOTA" spot.sig_refs[0].sig = "MOTA"
spot.sig = "MOTA" spot.sig = "MOTA"
case _: case _:
logging.warning("GMA spot found with ref type " + ref_info[ logging.warning(f"GMA spot found with ref type {ref_info['reftype']}, developer needs to add support for this!")
"reftype"] + ", developer needs to add support for this!")
spot.sig_refs[0].sig = ref_info["reftype"] spot.sig_refs[0].sig = ref_info["reftype"]
spot.sig = ref_info["reftype"] spot.sig = ref_info["reftype"]
@@ -115,8 +114,7 @@ class GMA(HTTPSpotProvider):
logging.warning( logging.warning(
f"GMA API returned a malformed response when looking up ref {source_spot['REF']}") f"GMA API returned a malformed response when looking up ref {source_spot['REF']}")
except: except:
logging.exception("Exception when looking up " + self.REF_INFO_URL_ROOT + source_spot[ logging.exception(f"Exception when looking up {self.REF_INFO_URL_ROOT}{source_spot['REF']}, ignoring this spot for now")
"REF"] + ", ignoring this spot for now")
else: else:
logging.warning(f"The GMA API returned an unexpected response (HTTP {http_response.status_code}).") logging.warning(f"The GMA API returned an unexpected response (HTTP {http_response.status_code}).")
+4 -4
View File
@@ -26,7 +26,7 @@ class HTTPSpotProvider(SpotProvider):
def start(self): def start(self):
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between # Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
# subsequent polls, so start() returns immediately and the application can continue starting. # subsequent polls, so start() returns immediately and the application can continue starting.
logging.info("Set up query of " + self.name + " spot API every " + str(self._poll_interval) + " seconds.") logging.info(f"Set up query of {self.name} spot API every {self._poll_interval!s} seconds.")
self._thread = Thread(target=self._run, name=f"HTTPSpotProvider-{self.name}") self._thread = Thread(target=self._run, name=f"HTTPSpotProvider-{self.name}")
self._thread.start() self._thread.start()
@@ -50,7 +50,7 @@ class HTTPSpotProvider(SpotProvider):
def _poll(self): def _poll(self):
try: try:
# Request data from API # Request data from API
logging.debug("Polling " + self.name + " spot API...") logging.debug(f"Polling {self.name} spot API...")
http_response = requests.get(self._url, headers=HTTP_HEADERS, timeout=(5, 30)) http_response = requests.get(self._url, headers=HTTP_HEADERS, timeout=(5, 30))
# Check response code was good # Check response code was good
if http_response.ok: if http_response.ok:
@@ -62,7 +62,7 @@ class HTTPSpotProvider(SpotProvider):
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.debug("Received data from " + self.name + " spot API.") logging.debug(f"Received data from {self.name} spot API.")
else: else:
self.status = "Error" self.status = "Error"
logging.warning(f"HTTP {http_response.status_code} when calling {self.name} spot API.") logging.warning(f"HTTP {http_response.status_code} when calling {self.name} spot API.")
@@ -73,7 +73,7 @@ class HTTPSpotProvider(SpotProvider):
logging.warning(f"Timeout when accessing {self.name} spots API.") logging.warning(f"Timeout when accessing {self.name} spots API.")
except Exception: except Exception:
self.status = "Error" self.status = "Error"
logging.exception("Exception in HTTP Spot Provider (" + self.name + ")") logging.exception(f"Exception in HTTP Spot Provider ({self.name})")
self._stop_event.wait(timeout=1) self._stop_event.wait(timeout=1)
def _http_response_to_spots(self, http_response): def _http_response_to_spots(self, http_response):
+2 -2
View File
@@ -62,7 +62,7 @@ class ParksNPeaks(HTTPSpotProvider):
# Log a warning for the developer if PnP gives us an unknown programme we've never seen before # Log a warning for the developer if PnP gives us an unknown programme we've never seen before
if sig not in ["POTA", "SOTA", "WWFF", "SIOTA", "ZLOTA", "KRMNPA", "SANPCPA", "LLOTA"]: if sig not in ["POTA", "SOTA", "WWFF", "SIOTA", "ZLOTA", "KRMNPA", "SANPCPA", "LLOTA"]:
logging.warning("PNP spot found with sig " + sig + ", developer needs to add support for this!") logging.warning(f"PNP spot found with sig {sig}, developer needs to add support for this!")
# Add new spot to the list # Add new spot to the list
new_spots.append(spot) new_spots.append(spot)
@@ -91,4 +91,4 @@ class ParksNPeaks(HTTPSpotProvider):
} }
response = requests.post(self.SUBMIT_URL, json=body, headers=HTTP_HEADERS, timeout=(5, 30)) response = requests.post(self.SUBMIT_URL, json=body, headers=HTTP_HEADERS, timeout=(5, 30))
if not response.ok: if not response.ok:
raise RuntimeError("Parks N Peaks API returned " + str(response.status_code) + ": " + response.text) raise RuntimeError(f"Parks N Peaks API returned {response.status_code!s}: {response.text}")
+1 -1
View File
@@ -63,6 +63,6 @@ class POTA(HTTPSpotProvider):
headers = {**HTTP_HEADERS, "Content-Type": "application/json"} headers = {**HTTP_HEADERS, "Content-Type": "application/json"}
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30)) response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
if not response.ok: if not response.ok:
raise RuntimeError("POTA API returned " + str(response.status_code) + ": " + response.text) raise RuntimeError(f"POTA API returned {response.status_code!s}: {response.text}")
else: else:
raise RuntimeError("Park reference is required for submitting POTA spots.") raise RuntimeError("Park reference is required for submitting POTA spots.")
+9 -9
View File
@@ -45,15 +45,15 @@ class RBN(SpotProvider):
while not connected and self._running: while not connected and self._running:
try: try:
self.status = "Connecting" self.status = "Connecting"
logging.info("RBN port " + str(self._port) + " connecting...") logging.info(f"RBN port {self._port!s} connecting...")
self._telnet = telnetlib3.Telnet("telnet.reversebeacon.net", self._port) self._telnet = telnetlib3.Telnet("telnet.reversebeacon.net", self._port)
self._telnet.read_until("Please enter your call: ".encode("latin-1")) self._telnet.read_until("Please enter your call: ".encode("latin-1"))
self._telnet.write((SERVER_OWNER_CALLSIGN + "\n").encode("latin-1")) self._telnet.write(f"{SERVER_OWNER_CALLSIGN}\n".encode("latin-1"))
connected = True connected = True
logging.info("RBN port " + str(self._port) + " connected.") logging.info(f"RBN port {self._port!s} connected.")
except Exception: except Exception:
self.status = "Error" self.status = "Error"
logging.exception("Exception while connecting to RBN (port " + str(self._port) + ").") logging.exception(f"Exception while connecting to RBN (port {self._port!s}).")
sleep(5) sleep(5)
self.status = "Waiting for Data" self.status = "Waiting for Data"
@@ -78,25 +78,25 @@ class RBN(SpotProvider):
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.debug("Data received from RBN on port " + str(self._port) + ".") logging.debug(f"Data received from RBN on port {self._port!s}.")
except EOFError: except EOFError:
connected = False connected = False
if self._running: if self._running:
self.status = "Restarting" self.status = "Restarting"
logging.warning("Disconnected from RBN provider (port " + str(self._port) + "). Reconnecting...") logging.warning(f"Disconnected from RBN provider (port {self._port!s}). Reconnecting...")
sleep(5) sleep(5)
else: else:
logging.info("RBN provider (port " + str(self._port) + ") shutting down...") logging.info(f"RBN provider (port {self._port!s}) shutting down...")
self.status = "Shutting down" self.status = "Shutting down"
except Exception: except Exception:
connected = False connected = False
if self._running: if self._running:
self.status = "Error" self.status = "Error"
logging.exception("Exception in RBN provider (port " + str(self._port) + ")") logging.exception(f"Exception in RBN provider (port {self._port!s})")
sleep(5) sleep(5)
else: else:
logging.info("RBN provider (port " + str(self._port) + ") shutting down...") logging.info(f"RBN provider (port {self._port!s}) shutting down...")
self.status = "Shutting down" self.status = "Shutting down"
self.status = "Disconnected" self.status = "Disconnected"
+2 -2
View File
@@ -104,10 +104,10 @@ class SOTA(HTTPSpotProvider):
"comments": spot.comment or "", "comments": spot.comment or "",
"type": "TEST" # todo replatce with NORMAL/QRT once testing complete "type": "TEST" # todo replatce with NORMAL/QRT once testing complete
} }
headers = {**HTTP_HEADERS, "Authorization": "bearer " + access_token, "id_token": id_token, headers = {**HTTP_HEADERS, "Authorization": f"bearer {access_token}", "id_token": id_token,
"Content-Type": "application/json"} "Content-Type": "application/json"}
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30)) response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
if not response.ok: if not response.ok:
raise RuntimeError("SOTA API returned " + str(response.status_code) + ": " + response.text) raise RuntimeError(f"SOTA API returned {response.status_code!s}: {response.text}")
else: else:
raise RuntimeError("Summit reference is required for submitting SOTA spots.") raise RuntimeError("Summit reference is required for submitting SOTA spots.")
+7 -7
View File
@@ -22,7 +22,7 @@ class SSESpotProvider(SpotProvider):
self._event_source = None self._event_source = None
def start(self): def start(self):
logging.info("Set up SSE connection to " + self.name + " spot API.") logging.info(f"Set up SSE connection to {self.name} spot API.")
self._stop_event.clear() self._stop_event.clear()
self._thread = Thread(target=self._run, name=f"SSESpotProvider-{self.name}") self._thread = Thread(target=self._run, name=f"SSESpotProvider-{self.name}")
self._thread.daemon = True self._thread.daemon = True
@@ -38,12 +38,12 @@ class SSESpotProvider(SpotProvider):
event_source.close() event_source.close()
except Exception: except Exception:
logging.exception( logging.exception(
"Exception closing SSE connection for " + self.name + " during stop()") f"Exception closing SSE connection for {self.name} during stop()")
if self._thread: if self._thread:
self._thread.join(timeout=15) self._thread.join(timeout=15)
if self._thread.is_alive(): if self._thread.is_alive():
logging.warning(self.name + " SSE worker thread did not exit on time and will be killed.") logging.warning(f"{self.name} SSE worker thread did not exit on time and will be killed.")
def _on_open(self): def _on_open(self):
self.status = "Waiting for Data" self.status = "Waiting for Data"
@@ -58,7 +58,7 @@ class SSESpotProvider(SpotProvider):
def _run(self): def _run(self):
while not self._stop_event.is_set(): while not self._stop_event.is_set():
try: try:
logging.debug("Connecting to " + self.name + " spot API...") logging.debug(f"Connecting to {self.name} spot API...")
self.status = "Connecting" self.status = "Connecting"
with EventSource(self._url, headers=HTTP_HEADERS, latest_event_id=self._last_event_id, timeout=10, with EventSource(self._url, headers=HTTP_HEADERS, latest_event_id=self._last_event_id, timeout=10,
on_open=self._on_open, on_error=self._on_error) as event_source: on_open=self._on_open, on_error=self._on_error) as event_source:
@@ -76,17 +76,17 @@ class SSESpotProvider(SpotProvider):
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.debug("Received data from " + self.name + " spot API.") logging.debug(f"Received data from {self.name} spot API.")
except Exception: except Exception:
logging.exception( logging.exception(
"Exception processing message from SSE Spot Provider (" + self.name + ")") f"Exception processing message from SSE Spot Provider ({self.name})")
finally: finally:
self._set_event_source(None) self._set_event_source(None)
except Exception: except Exception:
self.status = "Error" self.status = "Error"
logging.exception("Exception in SSE Spot Provider (" + self.name + ")") logging.exception(f"Exception in SSE Spot Provider ({self.name})")
else: else:
self.status = "Disconnected" self.status = "Disconnected"
self._stop_event.wait(timeout=5) # Wait before trying to reconnect self._stop_event.wait(timeout=5) # Wait before trying to reconnect
+2 -2
View File
@@ -86,7 +86,7 @@ class Tiles(HTTPSpotProvider):
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30)) response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
if not response.ok: if not response.ok:
raise RuntimeError( raise RuntimeError(
"Tiles on the Air API returned " + str(response.status_code) + ": " + response.text) f"Tiles on the Air API returned {response.status_code!s}: {response.text}")
else: else:
raise RuntimeError("The Tiles on the Air API requires a mode to be set.") raise RuntimeError("The Tiles on the Air API requires a mode to be set.")
else: else:
@@ -100,4 +100,4 @@ def strip_extra_decimal_points(s):
parts = s.split('.', 1) parts = s.split('.', 1)
if len(parts) == 1: if len(parts) == 1:
return s return s
return parts[0] + '.' + parts[1].replace('.', '') return f"{parts[0]}.{parts[1].replace('.', '')}"
+3 -5
View File
@@ -35,11 +35,9 @@ class UKPacketNet(HTTPSpotProvider):
# First build a "full" comment combining some of the extra info # First build a "full" comment combining some of the extra info
comment = listed_port["comment"] if "comment" in listed_port else "" comment = listed_port["comment"] if "comment" in listed_port else ""
comment = (comment + " " + listed_port["mode"]) if "mode" in listed_port else comment comment = f"{comment} {listed_port['mode']}" if "mode" in listed_port else comment
comment = (comment + " " + listed_port[ comment = f"{comment} {listed_port['modulation']}" if "modulation" in listed_port else comment
"modulation"]) if "modulation" in listed_port else comment comment = f"{comment} {listed_port['baud']!s} baud" if "baud" in listed_port and listed_port[
comment = (comment + " " + str(
listed_port["baud"]) + " baud") if "baud" in listed_port and listed_port[
"baud"] > 0 else comment "baud"] > 0 else comment
# Get frequency from the comment if it's not set properly in the data structure. This is # Get frequency from the comment if it's not set properly in the data structure. This is
+5 -5
View File
@@ -22,7 +22,7 @@ class WebsocketSpotProvider(SpotProvider):
self._last_event_id = None self._last_event_id = None
def start(self): def start(self):
logging.info("Set up websocket connection to " + self.name + " spot API.") logging.info(f"Set up websocket connection to {self.name} spot API.")
self._stopped = False self._stopped = False
self._thread = Thread(target=self._run, name=f"WebsocketSpotProvider-{self.name}") self._thread = Thread(target=self._run, name=f"WebsocketSpotProvider-{self.name}")
self._thread.daemon = True self._thread.daemon = True
@@ -44,7 +44,7 @@ class WebsocketSpotProvider(SpotProvider):
def _run(self): def _run(self):
while not self._stopped: while not self._stopped:
try: try:
logging.debug("Connecting to " + self.name + " spot API...") logging.debug(f"Connecting to {self.name} spot API...")
self.status = "Connecting" self.status = "Connecting"
self._ws = create_connection(self._url, header=HTTP_HEADERS) self._ws = create_connection(self._url, header=HTTP_HEADERS)
self.status = "Connected" self.status = "Connected"
@@ -57,15 +57,15 @@ class WebsocketSpotProvider(SpotProvider):
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.debug("Received data from " + self.name + " spot API.") logging.debug(f"Received data from {self.name} spot API.")
except Exception: except Exception:
logging.exception( logging.exception(
"Exception processing message from Websocket Spot Provider (" + self.name + ")") f"Exception processing message from Websocket Spot Provider ({self.name})")
except Exception as e: except Exception as e:
self.status = "Error" self.status = "Error"
logging.exception("Exception in Websocket Spot Provider (" + self.name + ")", e) logging.exception(f"Exception in Websocket Spot Provider ({self.name})", e)
else: else:
self.status = "Disconnected" self.status = "Disconnected"
sleep(5) # Wait before trying to reconnect sleep(5) # Wait before trying to reconnect
+1 -1
View File
@@ -28,7 +28,7 @@ class XOTA(WebsocketSpotProvider):
def _ws_message_to_spot(self, b): def _ws_message_to_spot(self, b):
string = b.decode("utf-8") string = b.decode("utf-8")
source_spot = json.loads(string) source_spot = json.loads(string)
ref_id = self._sig_ref_prefix + " " + source_spot["reference"]["title"] ref_id = f"{self._sig_ref_prefix} {source_spot['reference']['title']}"
spot = Spot(source=self.name, spot = Spot(source=self.name,
source_id=source_spot["id"], source_id=source_spot["id"],
dx_call=source_spot["stationCallSign"].upper(), dx_call=source_spot["stationCallSign"].upper(),
@@ -22,13 +22,13 @@ class FileDownloadStaticDataProvider(StaticDataProvider):
self._poll_interval = poll_interval self._poll_interval = poll_interval
self._thread = None self._thread = None
self._stop_event = Event() self._stop_event = Event()
self._url_data_cache = URLDataCache("staticdata_" + name) self._url_data_cache = URLDataCache(f"staticdata_{name}")
def start(self): def start(self):
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between # Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
# subsequent polls, so start() returns immediately and the application can continue starting. # subsequent polls, so start() returns immediately and the application can continue starting.
logging.info( logging.info(
"Set up query of " + self.name + " static reference data every " + str(self._poll_interval) + " days.") f"Set up query of {self.name} static reference data every {self._poll_interval!s} days.")
self._thread = Thread(target=self._run, name=f"FileDownloadStaticDataProvider-{self.name}") self._thread = Thread(target=self._run, name=f"FileDownloadStaticDataProvider-{self.name}")
self._thread.start() self._thread.start()
@@ -45,7 +45,7 @@ class FileDownloadStaticDataProvider(StaticDataProvider):
try: try:
# Request data from API. Use the data cache (with a TTL of 1 day) here, not as the main mechanism for # Request data from API. Use the data cache (with a TTL of 1 day) here, not as the main mechanism for
# caching, but just so continual restarts of the software during testing don't hammer the servers. # caching, but just so continual restarts of the software during testing don't hammer the servers.
logging.debug("Downloading " + self.name + " static reference data...") logging.debug(f"Downloading {self.name} static reference data...")
http_response = self._url_data_cache.get(self._url, headers=HTTP_HEADERS) http_response = self._url_data_cache.get(self._url, headers=HTTP_HEADERS)
# Check response code was good # Check response code was good
if http_response.ok: if http_response.ok:
@@ -54,7 +54,7 @@ class FileDownloadStaticDataProvider(StaticDataProvider):
if ok: if ok:
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.info("Updated static reference data for " + self.name) logging.info(f"Updated static reference data for {self.name}")
else: else:
self.status = "Error" self.status = "Error"
logging.warning(f"HTTP {http_response.status_code} when downloading static reference data for {self.name}.") logging.warning(f"HTTP {http_response.status_code} when downloading static reference data for {self.name}.")
@@ -67,7 +67,7 @@ class FileDownloadStaticDataProvider(StaticDataProvider):
logging.warning(f"Timeout when downloading static reference data for {self.name}.") logging.warning(f"Timeout when downloading static reference data for {self.name}.")
except Exception: except Exception:
self.status = "Error" self.status = "Error"
logging.exception("Exception in HTTP static reference data provider (" + self.name + ")") logging.exception(f"Exception in HTTP static reference data provider ({self.name})")
self._stop_event.wait(timeout=1) self._stop_event.wait(timeout=1)
def _handle_http_response(self, http_response): def _handle_http_response(self, http_response):
@@ -15,19 +15,19 @@ class LocalFileStaticDataProvider(StaticDataProvider):
self._stop = False self._stop = False
def start(self): def start(self):
logging.debug("Loading " + self.name + " static reference data from file.") logging.debug(f"Loading {self.name} static reference data from file.")
try: try:
ok = self._load_data(self._path) ok = self._load_data(self._path)
if ok: if ok:
self.status = "OK" self.status = "OK"
self.last_update_time = datetime.now(pytz.UTC) self.last_update_time = datetime.now(pytz.UTC)
logging.info("Updated static reference data for " + self.name) logging.info(f"Updated static reference data for {self.name}")
else: else:
self.status = "Error" self.status = "Error"
logging.error("Failed to load data for " + self.name) logging.error(f"Failed to load data for {self.name}")
except Exception: except Exception:
self.status = "Error" self.status = "Error"
logging.exception("Exception in local file Static Data Provider (" + self.name + ")") logging.exception(f"Exception in local file Static Data Provider ({self.name})")
def stop(self): def stop(self):
self._stop = True self._stop = True
+12 -14
View File
@@ -119,13 +119,13 @@ class APISpotHandler(tornado.web.RequestHandler):
# Reject invalid-looking callsigns # Reject invalid-looking callsigns
if not re.match(r"^[A-Za-z0-9/\-]*$", spot.dx_call): if not re.match(r"^[A-Za-z0-9/\-]*$", spot.dx_call):
self.set_status(422) self.set_status(422)
self.write(safe_json_dumps("Error - '" + spot.dx_call + "' does not look like a valid callsign.")) self.write(safe_json_dumps(f"Error - '{spot.dx_call}' does not look like a valid callsign."))
self.set_header("Cache-Control", "no-store") self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
return return
if not re.match(r"^[A-Za-z0-9/\-]*$", spot.de_call): if not re.match(r"^[A-Za-z0-9/\-]*$", spot.de_call):
self.set_status(422) self.set_status(422)
self.write(safe_json_dumps("Error - '" + spot.de_call + "' does not look like a valid callsign.")) self.write(safe_json_dumps(f"Error - '{spot.de_call}' does not look like a valid callsign."))
self.set_header("Cache-Control", "no-store") self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
return return
@@ -134,7 +134,7 @@ class APISpotHandler(tornado.web.RequestHandler):
if infer_band_from_freq(spot.freq) == UNKNOWN_BAND: if infer_band_from_freq(spot.freq) == UNKNOWN_BAND:
self.set_status(422) self.set_status(422)
self.write( self.write(
safe_json_dumps("Error - Frequency of " + str(spot.freq / 1000.0) + "kHz is not in a known band.")) safe_json_dumps(f"Error - Frequency of {spot.freq / 1000.0!s}kHz is not in a known band."))
self.set_header("Cache-Control", "no-store") self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
return return
@@ -145,7 +145,7 @@ class APISpotHandler(tornado.web.RequestHandler):
spot.dx_grid.upper()): spot.dx_grid.upper()):
self.set_status(422) self.set_status(422)
self.write( self.write(
safe_json_dumps("Error - '" + spot.dx_grid + "' does not look like a valid Maidenhead grid.")) safe_json_dumps(f"Error - '{spot.dx_grid}' does not look like a valid Maidenhead grid."))
self.set_header("Cache-Control", "no-store") self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
return return
@@ -155,7 +155,7 @@ class APISpotHandler(tornado.web.RequestHandler):
spot.sig) and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id): spot.sig) and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id):
self.set_status(422) self.set_status(422)
self.write(safe_json_dumps( self.write(safe_json_dumps(
"Error - '" + spot.sig_refs[0].id + "' does not look like a valid reference for " + spot.sig + ".")) f"Error - '{spot.sig_refs[0].id}' does not look like a valid reference for {spot.sig}."))
self.set_header("Cache-Control", "no-store") self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
return return
@@ -208,13 +208,11 @@ class APISpotHandler(tornado.web.RequestHandler):
threading.Timer(1.0, provider.force_poll).start() threading.Timer(1.0, provider.force_poll).start()
except NotImplementedError as e: except NotImplementedError as e:
upstream_warning = str(e) upstream_warning = str(e)
except Exception as e: except Exception:
logging.warning("Failed to submit spot upstream to " + upstream_provider_name + ": " + str(e)) logging.exception(f"Failed to submit spot upstream to {upstream_provider_name}")
upstream_warning = "Spot was saved locally but upstream submission to " + upstream_provider_name + " failed: " + str( upstream_warning = f"Spot was saved locally but upstream submission to {upstream_provider_name} failed."
e)
else: else:
upstream_warning = "No enabled provider named '" + upstream_provider_name + "' supports upstream submission for " + ( upstream_warning = f"No enabled provider named '{upstream_provider_name}' supports upstream submission for {spot.sig if spot.sig else ''} spots."
spot.sig if spot.sig else "") + " spots."
# If we successfully submitted the spot upstream, don't add it direct to Spothole, otherwise it will be a # If we successfully submitted the spot upstream, don't add it direct to Spothole, otherwise it will be a
# duplicate with what immediately comes back from the API. But if we weren't asked to send it upstream, or # duplicate with what immediately comes back from the API. But if we weren't asked to send it upstream, or
@@ -224,7 +222,7 @@ class APISpotHandler(tornado.web.RequestHandler):
self._spots.set(spot.id, spot) self._spots.set(spot.id, spot)
if upstream_warning: if upstream_warning:
self.write(safe_json_dumps("Warning - " + upstream_warning)) self.write(safe_json_dumps(f"Warning - {upstream_warning}"))
self.set_status(201) self.set_status(201)
else: else:
self.write(safe_json_dumps("OK")) self.write(safe_json_dumps("OK"))
@@ -256,6 +254,6 @@ class APISpotHandler(tornado.web.RequestHandler):
data={"secret": RECAPTCHA_SECRET_KEY, "response": token}, data={"secret": RECAPTCHA_SECRET_KEY, "response": token},
timeout=(5, 10)) timeout=(5, 10))
return response.ok and response.json().get("success", False) return response.ok and response.json().get("success", False)
except Exception as e: except Exception:
logging.warning("reCAPTCHA verification request failed: " + str(e)) logging.exception(f"reCAPTCHA verification request failed")
return False return False
+1 -1
View File
@@ -55,7 +55,7 @@ class APIAlertsHandler(tornado.web.RequestHandler):
self.write(safe_json_dumps(data)) self.write(safe_json_dumps(data))
self.set_status(200) self.set_status(200)
except ValueError as e: except ValueError as e:
self.write(safe_json_dumps("Bad request - " + str(e))) self.write(safe_json_dumps(f"Bad request - {e!s}"))
self.set_status(400) self.set_status(400)
except Exception: except Exception:
logging.exception("Exception when handling client request to alerts API") logging.exception("Exception when handling client request to alerts API")
+3 -3
View File
@@ -50,7 +50,7 @@ class APILookupCallHandler(tornado.web.RequestHandler):
self.write(safe_json_dumps(callsign_data)) self.write(safe_json_dumps(callsign_data))
else: else:
self.write(safe_json_dumps("Error - '" + call + "' does not look like a valid callsign.")) self.write(safe_json_dumps(f"Error - '{call}' does not look like a valid callsign."))
self.set_status(422) self.set_status(422)
else: else:
self.write(safe_json_dumps("Error - call must be provided")) self.write(safe_json_dumps("Error - call must be provided"))
@@ -99,10 +99,10 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
else: else:
self.write(safe_json_dumps( self.write(safe_json_dumps(
"Error - '" + ref_id + "' does not look like a valid reference ID for " + sig + ".")) f"Error - '{ref_id}' does not look like a valid reference ID for {sig}."))
self.set_status(422) self.set_status(422)
else: else:
self.write(safe_json_dumps("Error - sig '" + sig + "' is not known.")) self.write(safe_json_dumps(f"Error - sig '{sig}' is not known."))
self.set_status(422) self.set_status(422)
else: else:
self.write(safe_json_dumps("Error - sig and id must be provided")) self.write(safe_json_dumps("Error - sig and id must be provided"))
+1 -1
View File
@@ -55,7 +55,7 @@ class APISpotsHandler(tornado.web.RequestHandler):
self.write(safe_json_dumps(data)) self.write(safe_json_dumps(data))
self.set_status(200) self.set_status(200)
except ValueError as e: except ValueError as e:
self.write(safe_json_dumps("Bad request - " + str(e))) self.write(safe_json_dumps(f"Bad request - {e!s}"))
self.set_status(400) self.set_status(400)
except Exception: except Exception:
logging.exception("Excedption when handling client request to spots API") logging.exception("Excedption when handling client request to spots API")
+5 -5
View File
@@ -76,13 +76,13 @@ class V1APISpotHandler(tornado.web.RequestHandler):
# Reject invalid-looking callsigns # Reject invalid-looking callsigns
if not re.match(r"^[A-Za-z0-9/\-]*$", spot.dx_call): if not re.match(r"^[A-Za-z0-9/\-]*$", spot.dx_call):
self.set_status(422) self.set_status(422)
self.write(safe_json_dumps("Error - '" + spot.dx_call + "' does not look like a valid callsign.")) self.write(safe_json_dumps(f"Error - '{spot.dx_call}' does not look like a valid callsign."))
self.set_header("Cache-Control", "no-store") self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
return return
if not re.match(r"^[A-Za-z0-9/\-]*$", spot.de_call): if not re.match(r"^[A-Za-z0-9/\-]*$", spot.de_call):
self.set_status(422) self.set_status(422)
self.write(safe_json_dumps("Error - '" + spot.de_call + "' does not look like a valid callsign.")) self.write(safe_json_dumps(f"Error - '{spot.de_call}' does not look like a valid callsign."))
self.set_header("Cache-Control", "no-store") self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
return return
@@ -90,7 +90,7 @@ class V1APISpotHandler(tornado.web.RequestHandler):
# Reject if frequency not in a known band # Reject if frequency not in a known band
if infer_band_from_freq(spot.freq) == UNKNOWN_BAND: if infer_band_from_freq(spot.freq) == UNKNOWN_BAND:
self.set_status(422) self.set_status(422)
self.write(safe_json_dumps("Error - Frequency of " + str(spot.freq / 1000.0) + "kHz is not in a known band.")) self.write(safe_json_dumps(f"Error - Frequency of {spot.freq / 1000.0!s}kHz is not in a known band."))
self.set_header("Cache-Control", "no-store") self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
return return
@@ -100,7 +100,7 @@ class V1APISpotHandler(tornado.web.RequestHandler):
r"^([A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}|[A-R]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2})$", r"^([A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}|[A-R]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2})$",
spot.dx_grid.upper()): spot.dx_grid.upper()):
self.set_status(422) self.set_status(422)
self.write(safe_json_dumps("Error - '" + spot.dx_grid + "' does not look like a valid Maidenhead grid.")) self.write(safe_json_dumps(f"Error - '{spot.dx_grid}' does not look like a valid Maidenhead grid."))
self.set_header("Cache-Control", "no-store") self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
return return
@@ -110,7 +110,7 @@ class V1APISpotHandler(tornado.web.RequestHandler):
spot.sig) and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id): spot.sig) and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id):
self.set_status(422) self.set_status(422)
self.write(safe_json_dumps( self.write(safe_json_dumps(
"Error - '" + spot.sig_refs[0].id + "' does not look like a valid reference for " + spot.sig + ".")) f"Error - '{spot.sig_refs[0].id}' does not look like a valid reference for {spot.sig}."))
self.set_header("Cache-Control", "no-store") self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
return return
+1 -1
View File
@@ -15,7 +15,7 @@ class V1RedirectHandler(tornado.web.RequestHandler):
async def _proxy(self, path): async def _proxy(self, path):
new_url = f"{self.request.protocol}://{self.request.host}/api/v2/{path}" new_url = f"{self.request.protocol}://{self.request.host}/api/v2/{path}"
if self.request.query: if self.request.query:
new_url += "?" + self.request.query new_url += f"?{self.request.query}"
client = AsyncHTTPClient() client = AsyncHTTPClient()
try: try:
+1 -1
View File
@@ -31,6 +31,6 @@ class PageTemplateHandler(tornado.web.RequestHandler):
page_requests_counter.inc() page_requests_counter.inc()
# Load named template, and provide variables used in templates # Load named template, and provide variables used in templates
self.render(self._template_name + ".html", software_version=SOFTWARE_VERSION, self.render(f"{self._template_name}.html", software_version=SOFTWARE_VERSION,
server_owner_callsign=SERVER_OWNER_CALLSIGN, allow_spotting=ALLOW_SPOTTING, server_owner_callsign=SERVER_OWNER_CALLSIGN, allow_spotting=ALLOW_SPOTTING,
web_ui_options=WEB_UI_OPTIONS, baseurl=BASE_URL, current_path=self.request.path) web_ui_options=WEB_UI_OPTIONS, baseurl=BASE_URL, current_path=self.request.path)
+2 -2
View File
@@ -141,8 +141,8 @@ class WebServer:
log_function=request_log, log_function=request_log,
debug=False) debug=False)
app.listen(self._port, xheaders=True) app.listen(self._port, xheaders=True)
logging.info("Web server running on port " + str(WEB_SERVER_PORT)) logging.info(f"Web server running on port {WEB_SERVER_PORT!s}")
logging.info("You can access your copy of Spothole at " + BASE_URL) logging.info(f"You can access your copy of Spothole at {BASE_URL}")
await self._shutdown_event.wait() await self._shutdown_event.wait()
+1 -1
View File
@@ -42,7 +42,7 @@ if __name__ == '__main__':
logging.info("Starting...") logging.info("Starting...")
logging.info( logging.info(
"This is Spothole version " + SOFTWARE_VERSION + ". This instance is run by " + SERVER_OWNER_CALLSIGN + ".") f"This is Spothole version {SOFTWARE_VERSION}. This instance is run by {SERVER_OWNER_CALLSIGN}.")
# Shut down gracefully on SIGINT # Shut down gracefully on SIGINT
signal.signal(signal.SIGINT, shutdown) signal.signal(signal.SIGINT, shutdown)
+1 -1
View File
@@ -19,7 +19,7 @@ for dxcc in data["dxcc"]:
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
draw.text((0, -10), flag, font=ImageFont.truetype("/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", 109), draw.text((0, -10), flag, font=ImageFont.truetype("/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", 109),
embedded_color=True) embedded_color=True)
outfile = str(dxcc_id) + ".png" outfile = f"{dxcc_id!s}.png"
image.save(outfile, "PNG") image.save(outfile, "PNG")
image = Image.new("RGBA", (140, 110), (255, 0, 0, 0)) image = Image.new("RGBA", (140, 110), (255, 0, 0, 0))
+1 -1
View File
@@ -76,7 +76,7 @@
</div> </div>
<script src="/static/js/add-spot.js?v=1786776159"></script> <script src="/static/js/add-spot.js?v=1786776933"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-add-spot").addClass("active"); $("#nav-link-add-spot").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -82,7 +82,7 @@
</div> </div>
<script src="/static/js/alerts.js?v=1786776159"></script> <script src="/static/js/alerts.js?v=1786776933"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-alerts").addClass("active"); $("#nav-link-alerts").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -79,8 +79,8 @@
</div> </div>
<script src="/static/js/spotsbandsandmap.js?v=1786776159"></script> <script src="/static/js/spotsbandsandmap.js?v=1786776933"></script>
<script src="/static/js/bands.js?v=1786776159"></script> <script src="/static/js/bands.js?v=1786776933"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-bands").addClass("active"); $("#nav-link-bands").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+5 -5
View File
@@ -1,6 +1,6 @@
{% extends "skeleton.html" %} {% extends "skeleton.html" %}
{% block head_extra %} {% block head_extra %}
<link rel="stylesheet" href="/static/css/style.css?v=1786776159" type="text/css"> <link rel="stylesheet" href="/static/css/style.css?v=1786776932" type="text/css">
<link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet"> <link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
<link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet"> <link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet">
<link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet"> <link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet">
@@ -15,10 +15,10 @@
window.fetchEventSource = fetchEventSource; window.fetchEventSource = fetchEventSource;
</script> </script>
<script src="/static/js/utils.js?v=1786776159"></script> <script src="/static/js/utils.js?v=1786776932"></script>
<script src="/static/js/ui-ham.js?v=1786776159"></script> <script src="/static/js/ui-ham.js?v=1786776932"></script>
<script src="/static/js/geo.js?v=1786776159"></script> <script src="/static/js/geo.js?v=1786776932"></script>
<script src="/static/js/common.js?v=1786776159"></script> <script src="/static/js/common.js?v=1786776932"></script>
{% end %} {% end %}
{% block body %} {% block body %}
<div class="container"> <div class="container">
+1 -1
View File
@@ -284,7 +284,7 @@
</div> </div>
<script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script> <script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script>
<script src="/static/js/conditions.js?v=1786776159"></script> <script src="/static/js/conditions.js?v=1786776933"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-conditions").addClass("active"); $("#nav-link-conditions").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -112,8 +112,8 @@
<script src="/static/vendor/js/leaflet-cqzones.js"></script> <script src="/static/vendor/js/leaflet-cqzones.js"></script>
<script src="/static/vendor/js/leaflet-workedallbritainireland.js" type="module"></script> <script src="/static/vendor/js/leaflet-workedallbritainireland.js" type="module"></script>
<script src="/static/js/spotsbandsandmap.js?v=1786776159"></script> <script src="/static/js/spotsbandsandmap.js?v=1786776933"></script>
<script src="/static/js/map.js?v=1786776159"></script> <script src="/static/js/map.js?v=1786776933"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-map").addClass("active"); $("#nav-link-map").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+2 -2
View File
@@ -118,8 +118,8 @@
</div> </div>
<script src="/static/js/spotsbandsandmap.js?v=1786776159"></script> <script src="/static/js/spotsbandsandmap.js?v=1786776932"></script>
<script src="/static/js/spots.js?v=1786776159"></script> <script src="/static/js/spots.js?v=1786776932"></script>
<script>$(document).ready(function () { <script>$(document).ready(function () {
$("#nav-link-spots").addClass("active"); $("#nav-link-spots").addClass("active");
}); <!-- highlight active page in nav --></script> }); <!-- highlight active page in nav --></script>
+1 -1
View File
@@ -86,7 +86,7 @@
</div> </div>
</div> </div>
<script src="/static/js/status.js?v=1786776159"></script> <script src="/static/js/status.js?v=1786776933"></script>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
$("#nav-link-status").addClass("active"); $("#nav-link-status").addClass("active");