mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-25 08:44:33 +00:00
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:
+30
-18
@@ -5,6 +5,7 @@ import re
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta
|
||||
from math import isnan
|
||||
from typing import cast
|
||||
|
||||
import pytz
|
||||
from pyhamtools.locator import latlong_to_locator, locator_to_latlong
|
||||
@@ -129,9 +130,9 @@ class Spot:
|
||||
# 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
|
||||
|
||||
@@ -161,14 +162,22 @@ class Spot:
|
||||
def __post_init__(self):
|
||||
"""Normalise fields that don't survive a plain dict to Spot conversion. This is used in the "add spot" API
|
||||
endpoint where the client is submitting JSON, and we want to recreate a full Spot object, including nested
|
||||
objects such as the activity_refs list, and de-duplicating the activities list."""
|
||||
objects such as the activity_refs list, and de-duplicating the activities list. Activity names provided as
|
||||
strings are converted to their canonical ActivityName. Any activities, or activity refs, for an activity we
|
||||
don't know about are dropped."""
|
||||
|
||||
self.activities = list(dict.fromkeys(self.activities)) if self.activities else []
|
||||
if self.activity_refs:
|
||||
self.activity_refs = [
|
||||
activity_ref if isinstance(activity_ref, ActivityRef) else ActivityRef(**activity_ref)
|
||||
for activity_ref in self.activity_refs
|
||||
]
|
||||
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))
|
||||
# When created from JSON, activity refs arrive as dicts rather than ActivityRef objects.
|
||||
activity_refs = []
|
||||
for activity_ref in cast("list[ActivityRef | dict]", self.activity_refs or []):
|
||||
if isinstance(activity_ref, ActivityRef):
|
||||
activity_refs.append(activity_ref)
|
||||
elif found_activity := get_activity_by_name(activity_ref.get("activity")):
|
||||
activity_ref_data = dict(activity_ref)
|
||||
activity_ref_data["activity"] = found_activity.name
|
||||
activity_refs.append(ActivityRef(**activity_ref_data))
|
||||
self.activity_refs = activity_refs
|
||||
|
||||
def infer_missing(self, credentials=None):
|
||||
"""Infer missing parameters where possible"""
|
||||
@@ -274,13 +283,12 @@ class Spot:
|
||||
|
||||
# Add the activities of any activity refs we have to the top-level activities list.
|
||||
for activity_ref in self.activity_refs:
|
||||
if activity_ref.activity:
|
||||
self.add_activity(activity_ref.activity.upper())
|
||||
self.add_activity(activity_ref.activity)
|
||||
|
||||
# See if we already have an activity reference, but the comment looks like it contains more for the same
|
||||
# activity. This should catch e.g. POTA comments like "2-fer: GB-0001 GB-0002".
|
||||
if self.comment and self.activity_refs and self.activity_refs[0].activity:
|
||||
activity = self.activity_refs[0].activity.upper()
|
||||
if self.comment and self.activity_refs:
|
||||
activity = self.activity_refs[0].activity
|
||||
regex = get_ref_regex_for_activity(activity)
|
||||
if regex:
|
||||
all_comment_ref_matches = re.finditer(r"(?<!\w)(" + regex + r")(?!\w)", self.comment, re.IGNORECASE)
|
||||
@@ -476,7 +484,7 @@ class Spot:
|
||||
# Determine a "QTH" string. If we have an activity ref, pick the first one and turn it into a suitable
|
||||
# string, otherwise see what they have set on an online lookup service.
|
||||
if self.activity_refs:
|
||||
qth = self.activity_refs[0].id
|
||||
qth = self.activity_refs[0].id or ""
|
||||
if self.activity_refs[0].name:
|
||||
qth += f" {self.activity_refs[0].name}"
|
||||
self.dx_qth = qth
|
||||
@@ -539,11 +547,16 @@ class Spot:
|
||||
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):
|
||||
@@ -555,7 +568,6 @@ class Spot:
|
||||
"""Append an activity ref to the list, so long as it's not already there."""
|
||||
|
||||
new_activity_ref.id = new_activity_ref.id.strip().upper()
|
||||
new_activity_ref.activity = new_activity_ref.activity.strip().upper()
|
||||
if new_activity_ref.id == "":
|
||||
return
|
||||
for activity_ref in self.activity_refs:
|
||||
|
||||
Reference in New Issue
Block a user