mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 14:27:42 +00:00
flynt pass to provide consistency to string formatters and concatenation
This commit is contained in:
+1
-1
@@ -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
|
||||
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"])
|
||||
return provider_class(config_providers_entry)
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ from data.sig import SIG
|
||||
SOFTWARE_VERSION = "2.0-pre"
|
||||
|
||||
# HTTP headers used for spot providers that use HTTP
|
||||
HTTP_HEADERS = {"User-Agent": "Spothole v" + SOFTWARE_VERSION + " (operated by " + SERVER_OWNER_CALLSIGN + ")"}
|
||||
HAMQTH_PRG = ("Spothole v" + SOFTWARE_VERSION + " operated by " + SERVER_OWNER_CALLSIGN).replace(" ", "_")
|
||||
HTTP_HEADERS = {"User-Agent": f"Spothole v{SOFTWARE_VERSION} (operated by {SERVER_OWNER_CALLSIGN})"}
|
||||
HAMQTH_PRG = f"Spothole v{SOFTWARE_VERSION} operated by {SERVER_OWNER_CALLSIGN}".replace(" ", "_")
|
||||
|
||||
# Special Interest Groups
|
||||
SIGS = [
|
||||
|
||||
+11
-11
@@ -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
|
||||
# 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:
|
||||
self._solar.add("solar_conditions", SolarConditions())
|
||||
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:
|
||||
self._status.add("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
|
||||
# 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()
|
||||
|
||||
# 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"
|
||||
# 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))
|
||||
|
||||
# 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
|
||||
# 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_clublogxml = diskcache.Cache(CACHE_DIR + "callsign_data_clublogxml")
|
||||
self.callsign_data_clublogapi = diskcache.Cache(CACHE_DIR + "callsign_data_clublogapi")
|
||||
self.callsign_data_qrz = diskcache.Cache(CACHE_DIR + "callsign_data_qrz")
|
||||
self.callsign_data_hamqth = diskcache.Cache(CACHE_DIR + "callsign_data_hamqth")
|
||||
self.callsign_data_countryfiles = diskcache.Cache(f"{CACHE_DIR}callsign_data_countryfiles")
|
||||
self.callsign_data_clublogxml = diskcache.Cache(f"{CACHE_DIR}callsign_data_clublogxml")
|
||||
self.callsign_data_clublogapi = diskcache.Cache(f"{CACHE_DIR}callsign_data_clublogapi")
|
||||
self.callsign_data_qrz = diskcache.Cache(f"{CACHE_DIR}callsign_data_qrz")
|
||||
self.callsign_data_hamqth = diskcache.Cache(f"{CACHE_DIR}callsign_data_hamqth")
|
||||
unique_keys = set()
|
||||
for c in [self.callsign_data_countryfiles, self.callsign_data_clublogxml, self.callsign_data_clublogapi,
|
||||
self.callsign_data_qrz, self.callsign_data_hamqth]:
|
||||
@@ -84,12 +84,12 @@ class DataStore:
|
||||
# specifically load these caches *last* so that any sigref and callsign data is already loaded from disk cache
|
||||
# before the spots and alerts are live in the system.
|
||||
self.spots = LiveDataCache(maxsize=self._MAX_SPOT_COUNT, ttl=MAX_SPOT_AGE,
|
||||
snapshot_dir=CACHE_DIR + "spots",
|
||||
snapshot_dir=f"{CACHE_DIR}spots",
|
||||
snapshot_interval_sec=self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC)
|
||||
logging.info(f"Loaded %d spots from a previous run.", len(self.spots.keys()))
|
||||
|
||||
self.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)
|
||||
logging.info(f"Loaded %d alerts from a previous run.", len(self.alerts.keys()))
|
||||
|
||||
|
||||
+1
-1
@@ -172,7 +172,7 @@ def wab_wai_square_to_lat_lon(ref):
|
||||
elif re.match(r"^W[AV][0-9]{2}$", ref):
|
||||
return utm_grid_square_to_lat_lon(ref)
|
||||
else:
|
||||
logging.warning("Invalid WAB/WAI square: " + ref)
|
||||
logging.warning(f"Invalid WAB/WAI square: {ref}")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -68,14 +68,14 @@ def get_sig_ref_info(sig, ref_id):
|
||||
if not sig_ref.name:
|
||||
sig_ref.name = sig_ref.id
|
||||
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
|
||||
|
||||
### ACTUAL LOOKUP ###
|
||||
#
|
||||
# OK, this is something we have to look up. Now check to see if our data store contains reference data and use
|
||||
# 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
|
||||
if 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)
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -21,4 +21,4 @@ def get_sig_name_from_comment_name(sig):
|
||||
|
||||
|
||||
# Regex matching any SIG's "comment name", i.e. how it may be referred to in spot comments
|
||||
ANY_SIG_REGEX = 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))})"
|
||||
|
||||
@@ -17,7 +17,7 @@ class URLDataCache(CachedSession):
|
||||
_lock = threading.Lock()
|
||||
|
||||
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))
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ def infer_mode_type_from_mode(mode):
|
||||
return "DATA"
|
||||
else:
|
||||
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
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -388,7 +388,7 @@ class Spot:
|
||||
if self.sig_refs and len(self.sig_refs) > 0:
|
||||
qth = self.sig_refs[0].id
|
||||
if self.sig_refs[0].name:
|
||||
qth += " " + self.sig_refs[0].name
|
||||
qth += f" {self.sig_refs[0].name}"
|
||||
self.dx_qth = qth
|
||||
else:
|
||||
self.dx_qth = dx_call_info.qth
|
||||
|
||||
@@ -24,7 +24,7 @@ class HTTPAlertProvider(AlertProvider):
|
||||
def start(self):
|
||||
# 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.
|
||||
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.start()
|
||||
|
||||
@@ -40,7 +40,7 @@ class HTTPAlertProvider(AlertProvider):
|
||||
def _poll(self):
|
||||
try:
|
||||
# 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))
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -52,7 +52,7 @@ class HTTPAlertProvider(AlertProvider):
|
||||
|
||||
self.status = "OK"
|
||||
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:
|
||||
self.status = "Error"
|
||||
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.")
|
||||
except Exception:
|
||||
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()
|
||||
self._stop_event.wait(timeout=1)
|
||||
|
||||
|
||||
@@ -49,9 +49,9 @@ class NG3K(HTTPAlertProvider):
|
||||
end_day = end_string.split(", ")[0].strip()
|
||||
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()
|
||||
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(
|
||||
tzinfo=pytz.UTC).timestamp()
|
||||
|
||||
@@ -78,8 +78,8 @@ class NG3K(HTTPAlertProvider):
|
||||
alert = Alert(source=self.name,
|
||||
dx_calls=dx_calls,
|
||||
dx_country=dx_country,
|
||||
freqs_modes=bands + (("; " + modes) if modes != "" else ""),
|
||||
comment=by + "; " + comment + "; " + qsl_info,
|
||||
freqs_modes=bands + (f"; {modes}" if modes != "" else ""),
|
||||
comment=f"{by}; {comment}; {qsl_info}",
|
||||
start_time=start_timestamp,
|
||||
end_time=end_timestamp,
|
||||
is_dxpedition=True)
|
||||
|
||||
@@ -43,7 +43,7 @@ class ParksNPeaks(HTTPAlertProvider):
|
||||
alert = Alert(source=self.name,
|
||||
source_id=source_alert["alID"],
|
||||
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"],
|
||||
sig_refs=sigrefs,
|
||||
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
|
||||
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
|
||||
# the alert list. Note that while ZLOTA has its own spots API, it doesn't have its own alerts API. So that
|
||||
|
||||
@@ -27,7 +27,7 @@ class POTA(HTTPAlertProvider):
|
||||
freqs_modes=source_alert["frequencies"],
|
||||
comment=source_alert["comments"],
|
||||
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"],
|
||||
"%Y-%m-%d%H:%M").replace(tzinfo=pytz.UTC).timestamp(),
|
||||
end_time=datetime.strptime(source_alert["endDate"] + source_alert["endTime"],
|
||||
|
||||
@@ -33,7 +33,7 @@ class SOTA(HTTPAlertProvider):
|
||||
freqs_modes=source_alert["frequency"],
|
||||
comment=source_alert["comments"],
|
||||
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)],
|
||||
start_time=datetime.strptime(source_alert["dateActivated"],
|
||||
"%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=pytz.UTC).timestamp(),
|
||||
|
||||
@@ -24,7 +24,7 @@ class WWFF(HTTPAlertProvider):
|
||||
alert = Alert(source=self.name,
|
||||
source_id=source_alert["id"],
|
||||
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"],
|
||||
sig_refs=[SIGRef(id=source_alert["reference"], sig="WWFF")],
|
||||
start_time=datetime.strptime(source_alert["utc_start"],
|
||||
|
||||
@@ -26,7 +26,7 @@ class ClublogXML(FileDownloadCallsignDataProvider):
|
||||
logging.warning(
|
||||
"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)
|
||||
|
||||
def _handle_file(self, path):
|
||||
|
||||
@@ -22,7 +22,7 @@ class FileDownloadCallsignDataProvider(CallsignDataProvider):
|
||||
self._poll_interval = poll_interval
|
||||
self._thread = None
|
||||
self._stop_event = Event()
|
||||
self._url_data_cache = URLDataCache("callsigndata_" + name)
|
||||
self._url_data_cache = URLDataCache(f"callsigndata_{name}")
|
||||
|
||||
if self.enabled:
|
||||
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
|
||||
# subsequent polls, so start() returns immediately and the application can continue starting.
|
||||
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.start()
|
||||
|
||||
@@ -48,7 +48,7 @@ class FileDownloadCallsignDataProvider(CallsignDataProvider):
|
||||
try:
|
||||
# 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.
|
||||
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)
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -61,7 +61,7 @@ class FileDownloadCallsignDataProvider(CallsignDataProvider):
|
||||
if ok:
|
||||
self.status = "OK"
|
||||
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:
|
||||
self.status = "Error"
|
||||
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}.")
|
||||
except Exception:
|
||||
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)
|
||||
|
||||
def _handle_file(self, path):
|
||||
|
||||
@@ -22,11 +22,11 @@ class HamQTH(APIQueryCallsignDataProvider):
|
||||
def __init__(self, provider_config):
|
||||
super().__init__("HamQTH", provider_config, DATA_STORE.callsign_data_hamqth)
|
||||
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")
|
||||
# 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.
|
||||
self._CREDENTIALS_CACHE = CachedSession(CACHE_DIR + "/urls/hamqth-creds",
|
||||
self._CREDENTIALS_CACHE = CachedSession(f"{CACHE_DIR}/urls/hamqth-creds",
|
||||
expire_after=timedelta(minutes=55))
|
||||
|
||||
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:
|
||||
try:
|
||||
session_data = self._CREDENTIALS_CACHE.get(
|
||||
self._HAMQTH_BASE_URL + "?u=" + urllib.parse.quote_plus(lookup_credentials.hamqth_username) +
|
||||
"&p=" + urllib.parse.quote_plus(lookup_credentials.hamqth_password),
|
||||
f"{self._HAMQTH_BASE_URL}?u={urllib.parse.quote_plus(lookup_credentials.hamqth_username)}&p={urllib.parse.quote_plus(lookup_credentials.hamqth_password)}",
|
||||
headers=HTTP_HEADERS).content
|
||||
dict_data = xmltodict.parse(session_data)
|
||||
if "session_id" in dict_data["HamQTH"]["session"]:
|
||||
@@ -74,8 +73,7 @@ class HamQTH(APIQueryCallsignDataProvider):
|
||||
for lookup_call in calls_to_try:
|
||||
try:
|
||||
response = self._URL_DATA_CACHE.get(
|
||||
self._HAMQTH_BASE_URL + "?id=" + session_id + "&callsign=" + urllib.parse.quote_plus(
|
||||
lookup_call) + "&prg=" + self._PRG, headers=HTTP_HEADERS, timeout=10)
|
||||
f"{self._HAMQTH_BASE_URL}?id={session_id}&callsign={urllib.parse.quote_plus(lookup_call)}&prg={self._PRG}", headers=HTTP_HEADERS, timeout=10)
|
||||
if response.ok:
|
||||
# Found data, convert it to our object and return it
|
||||
data = xmltodict.parse(response.content)["HamQTH"]["search"]
|
||||
|
||||
@@ -24,7 +24,7 @@ class QRZ(APIQueryCallsignDataProvider):
|
||||
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
|
||||
# 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))
|
||||
|
||||
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:
|
||||
try:
|
||||
login_response = self._CREDENTIALS_CACHE.get(
|
||||
self._QRZ_BASE_URL + "?username=" + urllib.parse.quote_plus(lookup_credentials.qrz_username) +
|
||||
"&password=" + urllib.parse.quote_plus(lookup_credentials.qrz_password) + "&agent=spothole",
|
||||
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",
|
||||
headers=HTTP_HEADERS).content
|
||||
login_data = xmltodict.parse(login_response)
|
||||
session = login_data.get("QRZDatabase", {}).get("Session", {})
|
||||
@@ -73,7 +72,7 @@ class QRZ(APIQueryCallsignDataProvider):
|
||||
for lookup_call in calls_to_try:
|
||||
try:
|
||||
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)
|
||||
if response.ok:
|
||||
qrz_response = xmltodict.parse(response.content).get("QRZDatabase", {})
|
||||
@@ -129,9 +128,9 @@ class QRZ(APIQueryCallsignDataProvider):
|
||||
if "fname" in data:
|
||||
name = data["fname"]
|
||||
if "nick" in data:
|
||||
name = name + " \"" + data["nick"] + "\""
|
||||
name = f"{name} \"{data['nick']}\""
|
||||
if "name" in data:
|
||||
name = name + " " + data["name"]
|
||||
name = f"{name} {data['name']}"
|
||||
|
||||
# Check for sensible latitudes
|
||||
lat = None
|
||||
|
||||
@@ -22,7 +22,7 @@ class ARLHS(FileDownloadSIGRefDataProvider):
|
||||
ref_id = row["ARLHS"]
|
||||
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None,
|
||||
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"] != "" else None,
|
||||
longitude=float(row["Longitude"]) if "Longitude" in row and row[
|
||||
|
||||
@@ -28,7 +28,7 @@ class DME(LocalFileSIGRefDataProvider):
|
||||
|
||||
ref = SIGRef(sig=self.SIG, id=ref_id,
|
||||
ref_type="Town",
|
||||
name=row["NOMBRE_ACTUAL"] + ", " + row["PROVINCIA"],
|
||||
name=f"{row['NOMBRE_ACTUAL']}, {row['PROVINCIA']}",
|
||||
latitude=latitude,
|
||||
longitude=longitude)
|
||||
if latitude and longitude:
|
||||
|
||||
@@ -21,13 +21,13 @@ class FileDownloadSIGRefDataProvider(SIGRefDataProvider):
|
||||
self._poll_interval = poll_interval
|
||||
self._thread = None
|
||||
self._stop_event = Event()
|
||||
self._url_data_cache = URLDataCache("sigrefdata_" + sig_name)
|
||||
self._url_data_cache = URLDataCache(f"sigrefdata_{sig_name}")
|
||||
|
||||
def start(self):
|
||||
# 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.
|
||||
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.start()
|
||||
|
||||
@@ -45,7 +45,7 @@ class FileDownloadSIGRefDataProvider(SIGRefDataProvider):
|
||||
try:
|
||||
# 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.
|
||||
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)
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -57,7 +57,7 @@ class FileDownloadSIGRefDataProvider(SIGRefDataProvider):
|
||||
|
||||
self.status = "OK"
|
||||
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:
|
||||
self.status = "Error"
|
||||
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}.")
|
||||
except Exception:
|
||||
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)
|
||||
|
||||
def _http_response_to_data(self, http_response):
|
||||
|
||||
@@ -21,7 +21,7 @@ class GMA(FileDownloadSIGRefDataProvider):
|
||||
ref_id = row["Reference"]
|
||||
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None,
|
||||
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"] != "" else None,
|
||||
longitude=float(row["Longitude"]) if "Longitude" in row and row[
|
||||
|
||||
@@ -22,7 +22,7 @@ class ILLW(FileDownloadSIGRefDataProvider):
|
||||
ref_id = row["ILLW"]
|
||||
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None,
|
||||
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"] != "" else None,
|
||||
longitude=float(row["Longitude"]) if "Longitude" in row and row[
|
||||
|
||||
@@ -27,7 +27,7 @@ class LLOTA(FileDownloadSIGRefDataProvider):
|
||||
|
||||
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=str(ref["name"]),
|
||||
ref_type="Lake",
|
||||
url="https://llota.app/list/ref/" + ref_id,
|
||||
url=f"https://llota.app/list/ref/{ref_id}",
|
||||
grid=grid,
|
||||
latitude=ll[0],
|
||||
longitude=ll[1]))
|
||||
|
||||
@@ -14,7 +14,7 @@ class LocalFileSIGRefDataProvider(SIGRefDataProvider):
|
||||
self._path = path
|
||||
|
||||
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:
|
||||
new_data = self._file_to_data(self._path)
|
||||
if new_data:
|
||||
@@ -23,10 +23,10 @@ class LocalFileSIGRefDataProvider(SIGRefDataProvider):
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
else:
|
||||
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:
|
||||
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):
|
||||
"""Load a file on the given path and turn it into SIG Ref data."""
|
||||
|
||||
@@ -21,7 +21,7 @@ class MOTA(FileDownloadSIGRefDataProvider):
|
||||
ref_id = row["Reference"]
|
||||
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None,
|
||||
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"] != "" else None,
|
||||
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_type="Park",
|
||||
url="https://parksnpeaks.org/getPark.php?actPark=" + ref_id,
|
||||
url=f"https://parksnpeaks.org/getPark.php?actPark={ref_id}",
|
||||
latitude=latitude,
|
||||
longitude=longitude)
|
||||
if latitude and longitude:
|
||||
|
||||
@@ -21,7 +21,7 @@ class POTA(FileDownloadSIGRefDataProvider):
|
||||
ref_id = row["reference"]
|
||||
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["name"] if "name" in row else None,
|
||||
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,
|
||||
latitude=float(row["latitude"]) if "latitude" in row and row[
|
||||
"latitude"] != "" else None,
|
||||
|
||||
@@ -38,7 +38,7 @@ class SIGRefDataProvider:
|
||||
# with transact() batches all writes together to save making thousands of individual sqlite writes
|
||||
with DATA_STORE.sigrefs.transact():
|
||||
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
|
||||
# within the first few minutes of startup, we need a way to abort this expensive process of filling up the
|
||||
|
||||
@@ -26,7 +26,7 @@ class SOTA(FileDownloadSIGRefDataProvider):
|
||||
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_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,
|
||||
longitude=longitude,
|
||||
altitude=altitude,
|
||||
|
||||
@@ -21,7 +21,7 @@ class Towers(FileDownloadSIGRefDataProvider):
|
||||
ref_id = row["Ref"]
|
||||
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Nazev"] if "Nazev" in row else None,
|
||||
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,
|
||||
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))
|
||||
|
||||
@@ -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,
|
||||
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,
|
||||
longitude=longitude,
|
||||
grid=grid))
|
||||
|
||||
@@ -20,10 +20,10 @@ class WOTA(FileDownloadSIGRefDataProvider):
|
||||
ref_id = feature["properties"]["wotaId"]
|
||||
# Fudge WOTA URLs. Outlying fell (LDO) URLs don't match their ID numbers but require 214 to be
|
||||
# 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-"):
|
||||
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,
|
||||
ref_type="Summit",
|
||||
|
||||
@@ -21,7 +21,7 @@ class WWBOTA(FileDownloadSIGRefDataProvider):
|
||||
ref_id = row["Reference"]
|
||||
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["Name"] if "Name" in row else None,
|
||||
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,
|
||||
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))
|
||||
|
||||
@@ -21,7 +21,7 @@ class WWFF(FileDownloadSIGRefDataProvider):
|
||||
ref_id = row["reference"]
|
||||
new_data.append(SIGRef(sig=self.SIG, id=ref_id, name=row["name"] if "name" in row else None,
|
||||
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[
|
||||
"iaruLocator"] != "-" else None,
|
||||
latitude=float(row["latitude"]) if "latitude" in row and row[
|
||||
|
||||
@@ -27,7 +27,7 @@ class ZLOTA(FileDownloadSIGRefDataProvider):
|
||||
|
||||
new_ref = SIGRef(sig=self.SIG, id=ref_id, name=ref["name"],
|
||||
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,
|
||||
longitude=longitude)
|
||||
|
||||
|
||||
@@ -72,11 +72,11 @@ class HamQSL(HTTPSolarConditionsProvider):
|
||||
tz_abbr = updated_str.split()[-1]
|
||||
timezone = dateutil_tz.gettz(tz_abbr)
|
||||
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})
|
||||
updated = dt.astimezone(pytz.UTC).timestamp()
|
||||
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 {
|
||||
|
||||
@@ -23,7 +23,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
|
||||
|
||||
def start(self):
|
||||
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.start()
|
||||
|
||||
@@ -38,7 +38,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
|
||||
|
||||
def _poll(self):
|
||||
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))
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -47,7 +47,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
|
||||
|
||||
self.status = "OK"
|
||||
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:
|
||||
self.status = "Error"
|
||||
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.")
|
||||
except Exception:
|
||||
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)
|
||||
|
||||
def _http_response_to_solar_conditions(self, http_response):
|
||||
|
||||
@@ -96,7 +96,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
header_line = lines[start_idx]
|
||||
year_match = re.search(r'\b(\d{4})\b', header_line)
|
||||
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
|
||||
year = int(year_match.group(1))
|
||||
|
||||
@@ -108,7 +108,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
date_header_line = lines[start_idx + 2]
|
||||
date_matches = re.findall(r'([A-Za-z]{3})\s+(\d{2})', date_header_line)
|
||||
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
|
||||
|
||||
column_dates = []
|
||||
|
||||
+10
-10
@@ -54,19 +54,19 @@ class DXCluster(SpotProvider):
|
||||
while not connected and self._running:
|
||||
try:
|
||||
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.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
|
||||
logging.info("DX Cluster " + self._hostname + " connected.")
|
||||
logging.info(f"DX Cluster {self._hostname} connected.")
|
||||
except ConnectionRefusedError:
|
||||
self.status = "Error"
|
||||
logging.warning("Connection refused to DX cluster " + self._hostname)
|
||||
logging.warning(f"Connection refused to DX cluster {self._hostname}")
|
||||
sleep(300)
|
||||
except Exception:
|
||||
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)
|
||||
|
||||
self.status = "Waiting for Data"
|
||||
@@ -91,25 +91,25 @@ class DXCluster(SpotProvider):
|
||||
|
||||
self.status = "OK"
|
||||
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:
|
||||
connected = False
|
||||
if self._running:
|
||||
self.status = "Restarting"
|
||||
logging.warning("Disconnected from DX Cluster " + self._hostname + ". Reconnecting...")
|
||||
logging.warning(f"Disconnected from DX Cluster {self._hostname}. Reconnecting...")
|
||||
sleep(5)
|
||||
else:
|
||||
logging.info("DX Cluster " + self._hostname + " shutting down...")
|
||||
logging.info(f"DX Cluster {self._hostname} shutting down...")
|
||||
self.status = "Shutting down"
|
||||
except Exception:
|
||||
connected = False
|
||||
if self._running:
|
||||
self.status = "Error"
|
||||
logging.exception("Exception in DX Cluster Provider (" + self._hostname + ")")
|
||||
logging.exception(f"Exception in DX Cluster Provider ({self._hostname})")
|
||||
sleep(5)
|
||||
else:
|
||||
logging.info("DX Cluster " + self._hostname + " shutting down...")
|
||||
logging.info(f"DX Cluster {self._hostname} shutting down...")
|
||||
self.status = "Shutting down"
|
||||
|
||||
self.status = "Disconnected"
|
||||
|
||||
@@ -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.")
|
||||
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):
|
||||
new_spots = []
|
||||
@@ -98,8 +98,7 @@ class GMA(HTTPSpotProvider):
|
||||
spot.sig_refs[0].sig = "MOTA"
|
||||
spot.sig = "MOTA"
|
||||
case _:
|
||||
logging.warning("GMA spot found with ref type " + ref_info[
|
||||
"reftype"] + ", developer needs to add support for this!")
|
||||
logging.warning(f"GMA spot found with ref type {ref_info['reftype']}, developer needs to add support for this!")
|
||||
spot.sig_refs[0].sig = ref_info["reftype"]
|
||||
spot.sig = ref_info["reftype"]
|
||||
|
||||
@@ -115,8 +114,7 @@ class GMA(HTTPSpotProvider):
|
||||
logging.warning(
|
||||
f"GMA API returned a malformed response when looking up ref {source_spot['REF']}")
|
||||
except:
|
||||
logging.exception("Exception when looking up " + self.REF_INFO_URL_ROOT + source_spot[
|
||||
"REF"] + ", ignoring this spot for now")
|
||||
logging.exception(f"Exception when looking up {self.REF_INFO_URL_ROOT}{source_spot['REF']}, ignoring this spot for now")
|
||||
else:
|
||||
logging.warning(f"The GMA API returned an unexpected response (HTTP {http_response.status_code}).")
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class HTTPSpotProvider(SpotProvider):
|
||||
def start(self):
|
||||
# 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.
|
||||
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.start()
|
||||
|
||||
@@ -50,7 +50,7 @@ class HTTPSpotProvider(SpotProvider):
|
||||
def _poll(self):
|
||||
try:
|
||||
# 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))
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -62,7 +62,7 @@ class HTTPSpotProvider(SpotProvider):
|
||||
|
||||
self.status = "OK"
|
||||
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:
|
||||
self.status = "Error"
|
||||
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.")
|
||||
except Exception:
|
||||
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)
|
||||
|
||||
def _http_response_to_spots(self, http_response):
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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))
|
||||
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}")
|
||||
|
||||
@@ -63,6 +63,6 @@ class POTA(HTTPSpotProvider):
|
||||
headers = {**HTTP_HEADERS, "Content-Type": "application/json"}
|
||||
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
|
||||
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:
|
||||
raise RuntimeError("Park reference is required for submitting POTA spots.")
|
||||
|
||||
@@ -45,15 +45,15 @@ class RBN(SpotProvider):
|
||||
while not connected and self._running:
|
||||
try:
|
||||
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.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
|
||||
logging.info("RBN port " + str(self._port) + " connected.")
|
||||
logging.info(f"RBN port {self._port!s} connected.")
|
||||
except Exception:
|
||||
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)
|
||||
|
||||
self.status = "Waiting for Data"
|
||||
@@ -78,25 +78,25 @@ class RBN(SpotProvider):
|
||||
|
||||
self.status = "OK"
|
||||
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:
|
||||
connected = False
|
||||
if self._running:
|
||||
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)
|
||||
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"
|
||||
except Exception:
|
||||
connected = False
|
||||
if self._running:
|
||||
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)
|
||||
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 = "Disconnected"
|
||||
|
||||
@@ -104,10 +104,10 @@ class SOTA(HTTPSpotProvider):
|
||||
"comments": spot.comment or "",
|
||||
"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"}
|
||||
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
|
||||
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:
|
||||
raise RuntimeError("Summit reference is required for submitting SOTA spots.")
|
||||
|
||||
@@ -22,7 +22,7 @@ class SSESpotProvider(SpotProvider):
|
||||
self._event_source = None
|
||||
|
||||
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._thread = Thread(target=self._run, name=f"SSESpotProvider-{self.name}")
|
||||
self._thread.daemon = True
|
||||
@@ -38,12 +38,12 @@ class SSESpotProvider(SpotProvider):
|
||||
event_source.close()
|
||||
except 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:
|
||||
self._thread.join(timeout=15)
|
||||
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):
|
||||
self.status = "Waiting for Data"
|
||||
@@ -58,7 +58,7 @@ class SSESpotProvider(SpotProvider):
|
||||
def _run(self):
|
||||
while not self._stop_event.is_set():
|
||||
try:
|
||||
logging.debug("Connecting to " + self.name + " spot API...")
|
||||
logging.debug(f"Connecting to {self.name} spot API...")
|
||||
self.status = "Connecting"
|
||||
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:
|
||||
@@ -76,17 +76,17 @@ class SSESpotProvider(SpotProvider):
|
||||
|
||||
self.status = "OK"
|
||||
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:
|
||||
logging.exception(
|
||||
"Exception processing message from SSE Spot Provider (" + self.name + ")")
|
||||
f"Exception processing message from SSE Spot Provider ({self.name})")
|
||||
finally:
|
||||
self._set_event_source(None)
|
||||
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception("Exception in SSE Spot Provider (" + self.name + ")")
|
||||
logging.exception(f"Exception in SSE Spot Provider ({self.name})")
|
||||
else:
|
||||
self.status = "Disconnected"
|
||||
self._stop_event.wait(timeout=5) # Wait before trying to reconnect
|
||||
|
||||
@@ -86,7 +86,7 @@ class Tiles(HTTPSpotProvider):
|
||||
response = requests.post(self.SUBMIT_URL, json=body, headers=headers, timeout=(5, 30))
|
||||
if not response.ok:
|
||||
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:
|
||||
raise RuntimeError("The Tiles on the Air API requires a mode to be set.")
|
||||
else:
|
||||
@@ -100,4 +100,4 @@ def strip_extra_decimal_points(s):
|
||||
parts = s.split('.', 1)
|
||||
if len(parts) == 1:
|
||||
return s
|
||||
return parts[0] + '.' + parts[1].replace('.', '')
|
||||
return f"{parts[0]}.{parts[1].replace('.', '')}"
|
||||
|
||||
@@ -35,11 +35,9 @@ class UKPacketNet(HTTPSpotProvider):
|
||||
|
||||
# First build a "full" comment combining some of the extra info
|
||||
comment = listed_port["comment"] if "comment" in listed_port else ""
|
||||
comment = (comment + " " + listed_port["mode"]) if "mode" in listed_port else comment
|
||||
comment = (comment + " " + listed_port[
|
||||
"modulation"]) if "modulation" in listed_port else comment
|
||||
comment = (comment + " " + str(
|
||||
listed_port["baud"]) + " baud") if "baud" in listed_port and listed_port[
|
||||
comment = f"{comment} {listed_port['mode']}" if "mode" in listed_port else comment
|
||||
comment = f"{comment} {listed_port['modulation']}" if "modulation" in listed_port else comment
|
||||
comment = f"{comment} {listed_port['baud']!s} baud" if "baud" in listed_port and listed_port[
|
||||
"baud"] > 0 else comment
|
||||
|
||||
# Get frequency from the comment if it's not set properly in the data structure. This is
|
||||
|
||||
@@ -22,7 +22,7 @@ class WebsocketSpotProvider(SpotProvider):
|
||||
self._last_event_id = None
|
||||
|
||||
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._thread = Thread(target=self._run, name=f"WebsocketSpotProvider-{self.name}")
|
||||
self._thread.daemon = True
|
||||
@@ -44,7 +44,7 @@ class WebsocketSpotProvider(SpotProvider):
|
||||
def _run(self):
|
||||
while not self._stopped:
|
||||
try:
|
||||
logging.debug("Connecting to " + self.name + " spot API...")
|
||||
logging.debug(f"Connecting to {self.name} spot API...")
|
||||
self.status = "Connecting"
|
||||
self._ws = create_connection(self._url, header=HTTP_HEADERS)
|
||||
self.status = "Connected"
|
||||
@@ -57,15 +57,15 @@ class WebsocketSpotProvider(SpotProvider):
|
||||
|
||||
self.status = "OK"
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
self.status = "Disconnected"
|
||||
sleep(5) # Wait before trying to reconnect
|
||||
|
||||
@@ -28,7 +28,7 @@ class XOTA(WebsocketSpotProvider):
|
||||
def _ws_message_to_spot(self, b):
|
||||
string = b.decode("utf-8")
|
||||
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,
|
||||
source_id=source_spot["id"],
|
||||
dx_call=source_spot["stationCallSign"].upper(),
|
||||
|
||||
@@ -22,13 +22,13 @@ class FileDownloadStaticDataProvider(StaticDataProvider):
|
||||
self._poll_interval = poll_interval
|
||||
self._thread = None
|
||||
self._stop_event = Event()
|
||||
self._url_data_cache = URLDataCache("staticdata_" + name)
|
||||
self._url_data_cache = URLDataCache(f"staticdata_{name}")
|
||||
|
||||
def start(self):
|
||||
# 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.
|
||||
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.start()
|
||||
|
||||
@@ -45,7 +45,7 @@ class FileDownloadStaticDataProvider(StaticDataProvider):
|
||||
try:
|
||||
# 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.
|
||||
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)
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -54,7 +54,7 @@ class FileDownloadStaticDataProvider(StaticDataProvider):
|
||||
if ok:
|
||||
self.status = "OK"
|
||||
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:
|
||||
self.status = "Error"
|
||||
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}.")
|
||||
except Exception:
|
||||
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)
|
||||
|
||||
def _handle_http_response(self, http_response):
|
||||
|
||||
@@ -15,19 +15,19 @@ class LocalFileStaticDataProvider(StaticDataProvider):
|
||||
self._stop = False
|
||||
|
||||
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:
|
||||
ok = self._load_data(self._path)
|
||||
if ok:
|
||||
self.status = "OK"
|
||||
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:
|
||||
self.status = "Error"
|
||||
logging.error("Failed to load data for " + self.name)
|
||||
logging.error(f"Failed to load data for {self.name}")
|
||||
except Exception:
|
||||
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):
|
||||
self._stop = True
|
||||
|
||||
@@ -119,13 +119,13 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
# Reject invalid-looking callsigns
|
||||
if not re.match(r"^[A-Za-z0-9/\-]*$", spot.dx_call):
|
||||
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("Content-Type", "application/json")
|
||||
return
|
||||
if not re.match(r"^[A-Za-z0-9/\-]*$", spot.de_call):
|
||||
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("Content-Type", "application/json")
|
||||
return
|
||||
@@ -134,7 +134,7 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
if infer_band_from_freq(spot.freq) == UNKNOWN_BAND:
|
||||
self.set_status(422)
|
||||
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("Content-Type", "application/json")
|
||||
return
|
||||
@@ -145,7 +145,7 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
spot.dx_grid.upper()):
|
||||
self.set_status(422)
|
||||
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("Content-Type", "application/json")
|
||||
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):
|
||||
self.set_status(422)
|
||||
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("Content-Type", "application/json")
|
||||
return
|
||||
@@ -208,13 +208,11 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
threading.Timer(1.0, provider.force_poll).start()
|
||||
except NotImplementedError as e:
|
||||
upstream_warning = str(e)
|
||||
except Exception as e:
|
||||
logging.warning("Failed to submit spot upstream to " + upstream_provider_name + ": " + str(e))
|
||||
upstream_warning = "Spot was saved locally but upstream submission to " + upstream_provider_name + " failed: " + str(
|
||||
e)
|
||||
except Exception:
|
||||
logging.exception(f"Failed to submit spot upstream to {upstream_provider_name}")
|
||||
upstream_warning = f"Spot was saved locally but upstream submission to {upstream_provider_name} failed."
|
||||
else:
|
||||
upstream_warning = "No enabled provider named '" + upstream_provider_name + "' supports upstream submission for " + (
|
||||
spot.sig if spot.sig else "") + " spots."
|
||||
upstream_warning = f"No enabled provider named '{upstream_provider_name}' supports upstream submission for {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
|
||||
# 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)
|
||||
|
||||
if upstream_warning:
|
||||
self.write(safe_json_dumps("Warning - " + upstream_warning))
|
||||
self.write(safe_json_dumps(f"Warning - {upstream_warning}"))
|
||||
self.set_status(201)
|
||||
else:
|
||||
self.write(safe_json_dumps("OK"))
|
||||
@@ -256,6 +254,6 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
data={"secret": RECAPTCHA_SECRET_KEY, "response": token},
|
||||
timeout=(5, 10))
|
||||
return response.ok and response.json().get("success", False)
|
||||
except Exception as e:
|
||||
logging.warning("reCAPTCHA verification request failed: " + str(e))
|
||||
except Exception:
|
||||
logging.exception(f"reCAPTCHA verification request failed")
|
||||
return False
|
||||
|
||||
@@ -55,7 +55,7 @@ class APIAlertsHandler(tornado.web.RequestHandler):
|
||||
self.write(safe_json_dumps(data))
|
||||
self.set_status(200)
|
||||
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)
|
||||
except Exception:
|
||||
logging.exception("Exception when handling client request to alerts API")
|
||||
|
||||
@@ -50,7 +50,7 @@ class APILookupCallHandler(tornado.web.RequestHandler):
|
||||
self.write(safe_json_dumps(callsign_data))
|
||||
|
||||
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)
|
||||
else:
|
||||
self.write(safe_json_dumps("Error - call must be provided"))
|
||||
@@ -99,10 +99,10 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
|
||||
|
||||
else:
|
||||
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)
|
||||
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)
|
||||
else:
|
||||
self.write(safe_json_dumps("Error - sig and id must be provided"))
|
||||
|
||||
@@ -55,7 +55,7 @@ class APISpotsHandler(tornado.web.RequestHandler):
|
||||
self.write(safe_json_dumps(data))
|
||||
self.set_status(200)
|
||||
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)
|
||||
except Exception:
|
||||
logging.exception("Excedption when handling client request to spots API")
|
||||
|
||||
@@ -76,13 +76,13 @@ class V1APISpotHandler(tornado.web.RequestHandler):
|
||||
# Reject invalid-looking callsigns
|
||||
if not re.match(r"^[A-Za-z0-9/\-]*$", spot.dx_call):
|
||||
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("Content-Type", "application/json")
|
||||
return
|
||||
if not re.match(r"^[A-Za-z0-9/\-]*$", spot.de_call):
|
||||
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("Content-Type", "application/json")
|
||||
return
|
||||
@@ -90,7 +90,7 @@ class V1APISpotHandler(tornado.web.RequestHandler):
|
||||
# Reject if frequency not in a known band
|
||||
if infer_band_from_freq(spot.freq) == UNKNOWN_BAND:
|
||||
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("Content-Type", "application/json")
|
||||
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})$",
|
||||
spot.dx_grid.upper()):
|
||||
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("Content-Type", "application/json")
|
||||
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):
|
||||
self.set_status(422)
|
||||
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("Content-Type", "application/json")
|
||||
return
|
||||
|
||||
@@ -15,7 +15,7 @@ class V1RedirectHandler(tornado.web.RequestHandler):
|
||||
async def _proxy(self, path):
|
||||
new_url = f"{self.request.protocol}://{self.request.host}/api/v2/{path}"
|
||||
if self.request.query:
|
||||
new_url += "?" + self.request.query
|
||||
new_url += f"?{self.request.query}"
|
||||
|
||||
client = AsyncHTTPClient()
|
||||
try:
|
||||
|
||||
@@ -31,6 +31,6 @@ class PageTemplateHandler(tornado.web.RequestHandler):
|
||||
page_requests_counter.inc()
|
||||
|
||||
# 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,
|
||||
web_ui_options=WEB_UI_OPTIONS, baseurl=BASE_URL, current_path=self.request.path)
|
||||
|
||||
+2
-2
@@ -141,8 +141,8 @@ class WebServer:
|
||||
log_function=request_log,
|
||||
debug=False)
|
||||
app.listen(self._port, xheaders=True)
|
||||
logging.info("Web server running on port " + str(WEB_SERVER_PORT))
|
||||
logging.info("You can access your copy of Spothole at " + BASE_URL)
|
||||
logging.info(f"Web server running on port {WEB_SERVER_PORT!s}")
|
||||
logging.info(f"You can access your copy of Spothole at {BASE_URL}")
|
||||
await self._shutdown_event.wait()
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ if __name__ == '__main__':
|
||||
|
||||
logging.info("Starting...")
|
||||
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
|
||||
signal.signal(signal.SIGINT, shutdown)
|
||||
|
||||
@@ -19,7 +19,7 @@ for dxcc in data["dxcc"]:
|
||||
draw = ImageDraw.Draw(image)
|
||||
draw.text((0, -10), flag, font=ImageFont.truetype("/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", 109),
|
||||
embedded_color=True)
|
||||
outfile = str(dxcc_id) + ".png"
|
||||
outfile = f"{dxcc_id!s}.png"
|
||||
image.save(outfile, "PNG")
|
||||
|
||||
image = Image.new("RGBA", (140, 110), (255, 0, 0, 0))
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
</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 () {
|
||||
$("#nav-link-add-spot").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/js/alerts.js?v=1786776159"></script>
|
||||
<script src="/static/js/alerts.js?v=1786776933"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-alerts").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1786776159"></script>
|
||||
<script src="/static/js/bands.js?v=1786776159"></script>
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1786776933"></script>
|
||||
<script src="/static/js/bands.js?v=1786776933"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-bands").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
{% extends "skeleton.html" %}
|
||||
{% 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/fontawesome-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;
|
||||
</script>
|
||||
|
||||
<script src="/static/js/utils.js?v=1786776159"></script>
|
||||
<script src="/static/js/ui-ham.js?v=1786776159"></script>
|
||||
<script src="/static/js/geo.js?v=1786776159"></script>
|
||||
<script src="/static/js/common.js?v=1786776159"></script>
|
||||
<script src="/static/js/utils.js?v=1786776932"></script>
|
||||
<script src="/static/js/ui-ham.js?v=1786776932"></script>
|
||||
<script src="/static/js/geo.js?v=1786776932"></script>
|
||||
<script src="/static/js/common.js?v=1786776932"></script>
|
||||
{% end %}
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
|
||||
@@ -284,7 +284,7 @@
|
||||
</div>
|
||||
|
||||
<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 () {
|
||||
$("#nav-link-conditions").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
+2
-2
@@ -112,8 +112,8 @@
|
||||
<script src="/static/vendor/js/leaflet-cqzones.js"></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/map.js?v=1786776159"></script>
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1786776933"></script>
|
||||
<script src="/static/js/map.js?v=1786776933"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-map").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -118,8 +118,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1786776159"></script>
|
||||
<script src="/static/js/spots.js?v=1786776159"></script>
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1786776932"></script>
|
||||
<script src="/static/js/spots.js?v=1786776932"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-spots").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/status.js?v=1786776159"></script>
|
||||
<script src="/static/js/status.js?v=1786776933"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$("#nav-link-status").addClass("active");
|
||||
|
||||
Reference in New Issue
Block a user