From cee9188538185eef96b952f7049e17f43c8bb5f1 Mon Sep 17 00:00:00 2001 From: Ian Renton Date: Sun, 2 Aug 2026 12:38:33 +0100 Subject: [PATCH] Refactor of caching & data storage part 11 #118 --- config-example.yml | 7 ++- core/utils.py | 29 ++++++++++++- data/callsign.py | 2 +- .../api_query_callsign_data_provider.py | 25 +++++++++++ providers/callsigndata/clublogapi.py | 43 +++++++++++++++++++ providers/callsigndata/clublogxml.py | 20 +-------- providers/callsigndata/countryfiles.py | 20 +-------- .../file_download_callsign_data_provider.py | 3 +- 8 files changed, 108 insertions(+), 41 deletions(-) create mode 100644 providers/callsigndata/api_query_callsign_data_provider.py create mode 100644 providers/callsigndata/clublogapi.py diff --git a/config-example.yml b/config-example.yml index 158d966..cf46a15 100644 --- a/config-example.yml +++ b/config-example.yml @@ -257,11 +257,16 @@ callsign-data-providers: enabled: true - class: "ClublogXML" - enabled: false + enabled: true # API key for Clublog to look up information. Required in order to enable this provider. You will need to request # one via their helpdesk portal if you want to use callsign lookups from Clublog. clublog-api-key: "" + - class: "ClublogAPI" + enabled: true + # API key for Clublog to look up information. Required in order to enable this provider. + clublog-api-key: "" + # Maximum time to keep spots and alerts in the system before deleting them. By default, one hour for spots and one week # for alerts. diff --git a/core/utils.py b/core/utils.py index ee80d00..ff4f39e 100644 --- a/core/utils.py +++ b/core/utils.py @@ -5,6 +5,7 @@ from pyhamtools.frequency import freq_to_band from core.constants import UNKNOWN_BAND, BANDS, CW_MODES, PHONE_MODES, DATA_MODES, MODE_ALIASES, ALL_MODES from core.data_store import DATA_STORE +from data.callsign import Callsign def safe_json_dumps(obj): @@ -76,4 +77,30 @@ def get_flag_for_dxcc(dxcc): """Get an emoji flag for a given DXCC entity ID""" dxcc_data = DATA_STORE.dxcc_data[dxcc] if dxcc in DATA_STORE.dxcc_data else None - return dxcc_data["flag"] if dxcc_data else None \ No newline at end of file + return dxcc_data["flag"] if dxcc_data else None + + +def get_callsign_object_from_pyhamtools_callinfo(callsign, callinfo): + """Utility function to take the data provided by a PyHamTools CallInfo object and populate our own Callsign data + object from it""" + + home_call = callinfo.get_homecall(callsign) + data = callinfo.get_all() + + country = data["country"] if "country" in data else None + dxcc_id = data["adif"] if "adif" in data else None + continent = data["continent"] if "continent" in data else None + cq_zone = data["cqz"] if "cqz" in data else None + itu_zone = data["ituz"] if "ituz" in data else None + lat = float(data["latitude"]) if "latitude" in data else None + lon = float(data["longitude"]) if "longitude" in data else None + + return Callsign(call=callsign, + home_call=home_call, + country=country, + dxcc_id=dxcc_id, + continent=continent, + cq_zone=cq_zone, + itu_zone=itu_zone, + latitude=lat, + longitude=lon) diff --git a/data/callsign.py b/data/callsign.py index 0a7aef7..e7e0d44 100644 --- a/data/callsign.py +++ b/data/callsign.py @@ -11,7 +11,7 @@ class Callsign: # Callsign as spotted call: str # "Home" call, i.e. with any prefixes and suffixes stripped off - home_call: str + home_call: str | None = None # Operator name name : str | None = None # QTH (location), free text diff --git a/providers/callsigndata/api_query_callsign_data_provider.py b/providers/callsigndata/api_query_callsign_data_provider.py new file mode 100644 index 0000000..6d52358 --- /dev/null +++ b/providers/callsigndata/api_query_callsign_data_provider.py @@ -0,0 +1,25 @@ +import logging +from datetime import datetime +from threading import Thread, Event + +import pytz +from requests import ReadTimeout +from requests.exceptions import ConnectionError, ConnectTimeout + +from core.constants import HTTP_HEADERS +from core.url_data_cache import URLDataCache +from providers.callsigndata.callsign_data_provider import CallsignDataProvider + + +class APIQueryCallsignDataProvider(CallsignDataProvider): + """Generic callsign data provider class for providers that fetch their data from the web on-demand using an API.""" + + def __init__(self, name, provider_config): + """ Set up the provider.""" + super().__init__(name, provider_config) + + def start(self): + pass + + def stop(self): + pass diff --git a/providers/callsigndata/clublogapi.py b/providers/callsigndata/clublogapi.py new file mode 100644 index 0000000..34a2096 --- /dev/null +++ b/providers/callsigndata/clublogapi.py @@ -0,0 +1,43 @@ +import logging +from datetime import datetime + +import pytz +from pyhamtools import LookupLib, Callinfo + +from core.utils import get_callsign_object_from_pyhamtools_callinfo +from data.callsign import Callsign +from providers.callsigndata.api_query_callsign_data_provider import APIQueryCallsignDataProvider + + +class ClublogAPI(APIQueryCallsignDataProvider): + """Callsign data provider for Clublog's API.""" + + _callinfo = None + + def __init__(self, provider_config): + # API key required for this provider + self._api_key = provider_config.get("api-key", "") + if self._api_key != "": + lookuplib = LookupLib(lookuptype="clublogapi", apikey=self._api_key) + self._callinfo = Callinfo(lookuplib) + else: + provider_config["enabled"] = False + logging.warning( + "Clublog XML callsign data provider configured but no api key was provided, this has been disabled.") + + super().__init__("Clublog API", provider_config) + + + def lookup(self, callsign, lookup_credentials): + callsign_data = Callsign(call=callsign) + + try: + callsign_data = get_callsign_object_from_pyhamtools_callinfo(callsign, self._callinfo) + self.status = "OK" + self.last_update_time = datetime.now(pytz.UTC) + + except Exception as e: + self.status = "Error" + logging.error("Exception when looking up data from Clublog API", e, exc_info=True) + + return callsign_data diff --git a/providers/callsigndata/clublogxml.py b/providers/callsigndata/clublogxml.py index 2891b25..e4ba610 100644 --- a/providers/callsigndata/clublogxml.py +++ b/providers/callsigndata/clublogxml.py @@ -3,6 +3,7 @@ import logging from pyhamtools import LookupLib, Callinfo +from core.utils import get_callsign_object_from_pyhamtools_callinfo from data.callsign import Callsign from providers.callsigndata.file_download_callsign_data_provider import FileDownloadCallsignDataProvider @@ -48,21 +49,4 @@ class ClublogXML(FileDownloadCallsignDataProvider): return False def lookup(self, callsign, lookup_credentials): - # Lookup credentials are not required for this source. - # Lat/lon will only be centre of country or capital city from this source - ll = self._callinfo.get_lat_long(callsign) - lat = None - lon = None - if ll and "latitude" in ll and "longitude" in ll: - lat = float(ll["latitude"]) - lon = float(ll["longitude"]) - - return Callsign(call=callsign, - home_call=self._callinfo.get_homecall(callsign), - country=self._callinfo.get_country_name(callsign), - dxcc_id=self._callinfo.get_adif_id(callsign), - continent=self._callinfo.get_continent(callsign), - cq_zone=self._callinfo.get_cqz(callsign), - itu_zone=self._callinfo.get_ituz(callsign), - latitude=lat, - longitude=lon) + return get_callsign_object_from_pyhamtools_callinfo(callsign, self._callinfo) diff --git a/providers/callsigndata/countryfiles.py b/providers/callsigndata/countryfiles.py index 3a6d370..5da8dab 100644 --- a/providers/callsigndata/countryfiles.py +++ b/providers/callsigndata/countryfiles.py @@ -2,6 +2,7 @@ import logging from pyhamtools import LookupLib, Callinfo +from core.utils import get_callsign_object_from_pyhamtools_callinfo from data.callsign import Callsign from providers.callsigndata.file_download_callsign_data_provider import FileDownloadCallsignDataProvider @@ -28,21 +29,4 @@ class CountryFiles(FileDownloadCallsignDataProvider): return False def lookup(self, callsign, lookup_credentials): - # Lookup credentials are not required for this source. - # Lat/lon will only be centre of country or capital city from this source - ll = self._callinfo.get_lat_long(callsign) - lat = None - lon = None - if ll and "latitude" in ll and "longitude" in ll: - lat = float(ll["latitude"]) - lon = float(ll["longitude"]) - - return Callsign(call=callsign, - home_call=self._callinfo.get_homecall(callsign), - country=self._callinfo.get_country_name(callsign), - dxcc_id=self._callinfo.get_adif_id(callsign), - continent=self._callinfo.get_continent(callsign), - cq_zone=self._callinfo.get_cqz(callsign), - itu_zone=self._callinfo.get_ituz(callsign), - latitude=lat, - longitude=lon) + return get_callsign_object_from_pyhamtools_callinfo(callsign, self._callinfo) diff --git a/providers/callsigndata/file_download_callsign_data_provider.py b/providers/callsigndata/file_download_callsign_data_provider.py index 0c562ce..19585a3 100644 --- a/providers/callsigndata/file_download_callsign_data_provider.py +++ b/providers/callsigndata/file_download_callsign_data_provider.py @@ -12,8 +12,7 @@ from providers.callsigndata.callsign_data_provider import CallsignDataProvider class FileDownloadCallsignDataProvider(CallsignDataProvider): - """Generic static reference data provider class for providers that fetch their data from the web by downloading a - file.""" + """Generic callsign data provider class for providers that fetch their data from the web by downloading a file.""" def __init__(self, name, provider_config, url, cache_file_path, poll_interval): """ Set up the provider, note poll_interval is in *days*."""