Strict adherence to canonical ActivityNames, to avoid the problem where Spothole was giving out activities like ["Towers", "TOWERS"]. All uses of ActivityName should now be canonical not arbitrary strings, and attempts to convert unknown strings to activitynames will be logged for me to check out.

This commit is contained in:
Ian Renton
2026-09-25 09:16:53 +01:00
parent 7417140a3d
commit 760c2412d3
17 changed files with 144 additions and 145 deletions
+18 -10
View File
@@ -8,10 +8,11 @@ import pytz
from pyhamtools.locator import latlong_to_locator, locator_to_latlong
from core.activity_lookup_helper import populate_missing_activity_ref_info
from core.activity_utils import get_icon_for_activity
from core.activity_utils import get_activity_by_name, get_icon_for_activity
from core.call_lookup_helper import get_call_info
from core.enums import Continent
from core.enums import ActivityName, Continent
from core.utils import get_flag_for_dxcc
from data.activity_ref import ActivityRef
logger = logging.getLogger(__name__)
@@ -70,9 +71,9 @@ class Alert:
# e.g. a POTA and WWFF dual activation. This is a list so we can maintain the order items were added, but needs to
# be set-like to avoid dupes, and there's no Python class that handles that properly. So we use a list, but handle
# the uniqueness logic manually, so you must use add_activity() to add to it instead of adding directly.
activities: list = field(default_factory=list)
activities: list[ActivityName] = field(default_factory=list)
# Activity references. We allow multiple here for e.g. n-fer activations, unlike ADIF SIG_INFO.
activity_refs: list = field(default_factory=list)
activity_refs: list[ActivityRef] = field(default_factory=list)
# Timing info
@@ -96,9 +97,11 @@ class Alert:
icon: str | None = None
def __post_init__(self):
"""Normalise the activities list, removing any duplicates while keeping the order."""
"""Normalise the activities list, converting any activity names provided as strings to their canonical
ActivityName (dropping any we don't know about) and removing any duplicates while keeping the order."""
self.activities = list(dict.fromkeys(self.activities)) if self.activities else []
found_activities = [get_activity_by_name(activity) for activity in self.activities or []]
self.activities = list(dict.fromkeys(found.name for found in found_activities if found))
def infer_missing(self, credentials=None):
"""Infer missing parameters where possible"""
@@ -155,7 +158,7 @@ class Alert:
# Add the activities of any activity refs we have to the alert's list of activities.
for activity_ref in self.activity_refs:
if activity_ref and activity_ref.activity:
if activity_ref:
self.add_activity(activity_ref.activity)
# DX Grid to lat/lon and vice versa in case one is missing
@@ -197,11 +200,16 @@ class Alert:
except Exception:
logger.exception("Exception while inferring missing data from spot")
def add_activity(self, activity):
def add_activity(self, activity: ActivityName | None):
"""Add an activity to the activities list, so long as it's not blank and not already there. The list is kept in
insertion order, so the first activity added is treated as the "primary" one."""
insertion order, so the first activity added is treated as the "primary" one. Only canonical ActivityNames are
accepted otherwise we risk sending unknown stuff to API clients."""
if activity and activity not in self.activities:
if not activity:
return
if not isinstance(activity, ActivityName):
raise TypeError(f"add_activity() requires an ActivityName, got {type(activity).__name__} {activity!r}")
if activity not in self.activities:
self.activities.append(activity)
def to_json(self):