mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-25 08:44:33 +00:00
157 lines
7.3 KiB
Python
157 lines
7.3 KiB
Python
from webserver.handlers.api.addspot import APISpotHandler
|
|
from webserver.handlers.api.alerts import APIAlertsHandler, APIAlertsStreamHandler
|
|
from webserver.handlers.api.compatibility.compatibility import (
|
|
CompatibilityWrapper,
|
|
RequestCompatibilityWrapper,
|
|
StreamCompatibilityWrapper,
|
|
rename_keys_and_values,
|
|
rename_query_params,
|
|
translate_json_body,
|
|
)
|
|
from webserver.handlers.api.dxstats import APIDxStatsHandler
|
|
from webserver.handlers.api.lookups import APILookupActivityRefHandler, APILookupCallHandler, APILookupGridHandler
|
|
from webserver.handlers.api.options import APIOptionsHandler
|
|
from webserver.handlers.api.solar_conditions import APISolarConditionsHandler
|
|
from webserver.handlers.api.spots import APISpotsHandler, APISpotsStreamHandler
|
|
from webserver.handlers.api.status import APIStatusHandler
|
|
|
|
# Query parameters renamed in v3
|
|
_V2_TO_V3_QUERY_PARAMS = {
|
|
"sig": "activity",
|
|
"needs_sig": "needs_activity",
|
|
"needs_sig_ref": "needs_activity_ref",
|
|
}
|
|
# Values of query parameters renamed in v3
|
|
_V2_TO_V3_QUERY_VALUES = {
|
|
"activity": {"NO_SIG": "NO_ACTIVITY"},
|
|
"fields": {"sig": "activities", "sig_refs": "activity_refs"},
|
|
}
|
|
# Keys of JSON objects in API responses renamed in v3
|
|
_V3_TO_V2_RESPONSE_KEYS = {
|
|
"activity": "sig",
|
|
"activity_refs": "sig_refs",
|
|
"activity_type": "sig_type",
|
|
"activities": "sigs",
|
|
"activity_ref_data_providers": "sig_ref_data_providers",
|
|
"activity_name": "sig_name",
|
|
}
|
|
# Values of JSON objects in API responses renamed in v3
|
|
_V3_TO_V2_RESPONSE_VALUES = {
|
|
"dx_location_source": {"ACTIVITY REF LOOKUP": "SIG REF LOOKUP"},
|
|
}
|
|
|
|
|
|
class V2CompatibilityWrapper(CompatibilityWrapper):
|
|
"""Translates v2 requests to v3 on the way in, and v3 responses to v2 on the way out. This logic captures the
|
|
majority of changes needed for each endpoint."""
|
|
|
|
def translate_request(self):
|
|
rename_query_params(self.request, _V2_TO_V3_QUERY_PARAMS, _V2_TO_V3_QUERY_VALUES)
|
|
super().translate_request()
|
|
|
|
def translate_response_object(self, obj):
|
|
obj = super().translate_response_object(obj)
|
|
return rename_keys_and_values(obj, _V3_TO_V2_RESPONSE_KEYS, _V3_TO_V2_RESPONSE_VALUES)
|
|
|
|
|
|
class V2SpotsAlertsCompatibilityWrapper(V2CompatibilityWrapper):
|
|
"""Extra translation for spots and alerts. In v3 these have a list of "activities" rather than a single activity,
|
|
so for v2 we collapse this back down to a single value using the first activity in the list. This must happen
|
|
before the generic key renaming, which would otherwise rename "activities" to "sigs" as it does for /options."""
|
|
|
|
def translate_response_object(self, obj):
|
|
return super().translate_response_object(collapse_activities(obj))
|
|
|
|
|
|
class V2APISpotsHandler(V2SpotsAlertsCompatibilityWrapper, RequestCompatibilityWrapper, APISpotsHandler):
|
|
"""No special handling for this, the compatibility wrapper will handle translation to and from the later version."""
|
|
|
|
|
|
class V2APISpotsStreamHandler(V2SpotsAlertsCompatibilityWrapper, StreamCompatibilityWrapper, APISpotsStreamHandler):
|
|
"""No special handling for this, the compatibility wrapper will handle translation to and from the later version."""
|
|
|
|
|
|
class V2APIAlertsHandler(V2SpotsAlertsCompatibilityWrapper, RequestCompatibilityWrapper, APIAlertsHandler):
|
|
"""No special handling for this, the compatibility wrapper will handle translation to and from the later version."""
|
|
|
|
|
|
class V2APIAlertsStreamHandler(V2SpotsAlertsCompatibilityWrapper, StreamCompatibilityWrapper, APIAlertsStreamHandler):
|
|
"""No special handling for this, the compatibility wrapper will handle translation to and from the later version."""
|
|
|
|
|
|
class V2APISolarConditionsHandler(V2CompatibilityWrapper, RequestCompatibilityWrapper, APISolarConditionsHandler):
|
|
"""No special handling for this, the compatibility wrapper will handle translation to and from the later version."""
|
|
|
|
|
|
class V2APIDxStatsHandler(V2CompatibilityWrapper, RequestCompatibilityWrapper, APIDxStatsHandler):
|
|
"""No special handling for this, the compatibility wrapper will handle translation to and from the later version."""
|
|
|
|
|
|
class V2APIOptionsHandler(V2CompatibilityWrapper, RequestCompatibilityWrapper, APIOptionsHandler):
|
|
"""No special handling for this, the compatibility wrapper will handle translation to and from the later version."""
|
|
|
|
|
|
class V2APIStatusHandler(V2CompatibilityWrapper, RequestCompatibilityWrapper, APIStatusHandler):
|
|
"""No special handling for this, the compatibility wrapper will handle translation to and from the later version."""
|
|
|
|
|
|
class V2APILookupCallHandler(V2CompatibilityWrapper, RequestCompatibilityWrapper, APILookupCallHandler):
|
|
"""No special handling for this, the compatibility wrapper will handle translation to and from the later version."""
|
|
|
|
|
|
class V2APILookupSigRefHandler(V2CompatibilityWrapper, RequestCompatibilityWrapper, APILookupActivityRefHandler):
|
|
"""No special handling for this, the compatibility wrapper will handle translation to and from the later version."""
|
|
|
|
|
|
class V2APILookupGridHandler(V2CompatibilityWrapper, RequestCompatibilityWrapper, APILookupGridHandler):
|
|
"""No special handling for this, the compatibility wrapper will handle translation to and from the later version."""
|
|
|
|
|
|
class V2APISpotHandler(V2CompatibilityWrapper, RequestCompatibilityWrapper, APISpotHandler):
|
|
"""Some special handling for add spot, because unlike the other calls we have to deal with a request body.
|
|
Because the definition of a spot changed (e.g. sig to activity) we need to apply the same translation to
|
|
spots that are coming in via the add spot call."""
|
|
|
|
def translate_request(self):
|
|
def translate(body):
|
|
if isinstance(body, dict) and isinstance(body.get("spot"), dict):
|
|
body["spot"] = self._translate_v2_spot(body["spot"])
|
|
return body
|
|
|
|
translate_json_body(self.request, translate)
|
|
super().translate_request()
|
|
|
|
|
|
def _translate_v2_spot(self, spot_data):
|
|
"""Translate a spot provided by a client calling the add spot method in v2 format into v3 format"""
|
|
|
|
spot_data = dict(spot_data)
|
|
if "sig" in spot_data:
|
|
sig = spot_data.pop("sig")
|
|
spot_data["activities"] = [sig] if sig else []
|
|
if "sig_refs" in spot_data:
|
|
spot_data["activity_refs"] = spot_data.pop("sig_refs")
|
|
if isinstance(spot_data.get("activity_refs"), list):
|
|
refs = []
|
|
for ref in spot_data["activity_refs"]:
|
|
if isinstance(ref, dict) and "sig" in ref:
|
|
ref = dict(ref)
|
|
ref["activity"] = ref.pop("sig")
|
|
refs.append(ref)
|
|
spot_data["activity_refs"] = refs
|
|
return spot_data
|
|
|
|
|
|
def collapse_activities(obj):
|
|
"""Utility method to replace the "activities" list in a spot or alert JSON object with a single "activity" value,
|
|
being the first activity in the list, or None if there are none. The object can be a single spot/alert dict, or a
|
|
list of them. Anything else is returned untouched. Used to translate v3's list of activities to the single sig
|
|
expected in v2 API calls."""
|
|
|
|
if isinstance(obj, list):
|
|
return [collapse_activities(i) for i in obj]
|
|
if isinstance(obj, dict) and "activities" in obj:
|
|
obj = dict(obj)
|
|
activities = obj.pop("activities")
|
|
obj["activity"] = activities[0] if activities else None
|
|
return obj |