mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-24 16:24:32 +00:00
First attempt at converting "sig" to "activity" for v3
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import json
|
||||
|
||||
import tornado
|
||||
|
||||
from core.utils import safe_json_dumps
|
||||
from webserver.handlers.api.addspot import APISpotHandler
|
||||
from webserver.handlers.api.alerts import APIAlertsHandler, APIAlertsStreamHandler
|
||||
from webserver.handlers.api.lookups import APILookupActivityRefHandler
|
||||
from webserver.handlers.api.options import APIOptionsHandler
|
||||
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": "activity", "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"},
|
||||
}
|
||||
|
||||
|
||||
def _translate_v2_query_params(handler):
|
||||
"""Rename any v2 query parameters (and values) in the request to their v3 equivalents, so the v3 handler can
|
||||
understand them."""
|
||||
|
||||
for arguments in (handler.request.arguments, handler.request.query_arguments):
|
||||
for v2_name, v3_name in _V2_TO_V3_QUERY_PARAMS.items():
|
||||
if v2_name in arguments:
|
||||
arguments[v3_name] = arguments.pop(v2_name)
|
||||
for name, value_map in _V2_TO_V3_QUERY_VALUES.items():
|
||||
if name in arguments:
|
||||
arguments[name] = [
|
||||
",".join(value_map.get(item.strip(), item) for item in v.decode("utf-8").split(",")).encode("utf-8")
|
||||
for v in arguments[name]
|
||||
]
|
||||
|
||||
|
||||
def _translate_v3_response_object(obj):
|
||||
"""Rename keys and values in an object from their v3 names to their v2 names."""
|
||||
|
||||
if isinstance(obj, dict):
|
||||
translated = {}
|
||||
for k, v in obj.items():
|
||||
if k in _V3_TO_V2_RESPONSE_VALUES and isinstance(v, str):
|
||||
v = _V3_TO_V2_RESPONSE_VALUES[k].get(v, v)
|
||||
translated[_V3_TO_V2_RESPONSE_KEYS.get(k, k)] = _translate_v3_response_object(v)
|
||||
return translated
|
||||
if isinstance(obj, list):
|
||||
return [_translate_v3_response_object(i) for i in obj]
|
||||
return obj
|
||||
|
||||
|
||||
def translate_v3_response(chunk):
|
||||
"""Translate a JSON string output by a v3 handler into its v2 equivalent"""
|
||||
|
||||
if not isinstance(chunk, str):
|
||||
return chunk
|
||||
try:
|
||||
return safe_json_dumps(_translate_v3_response_object(json.loads(chunk)))
|
||||
except ValueError:
|
||||
return chunk
|
||||
|
||||
|
||||
def translate_v2_spot(spot_data):
|
||||
"""Translate a spot provided by a client in v2 format into v3 format"""
|
||||
|
||||
spot_data = dict(spot_data)
|
||||
if "sig" in spot_data:
|
||||
spot_data["activity"] = spot_data.pop("sig")
|
||||
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
|
||||
|
||||
|
||||
class _V2ResponseTranslationMixin:
|
||||
"""Mixin for request handlers that translates v2 query params to v3 on the way in, and v3 JSON responses to v2 on
|
||||
the way out"""
|
||||
|
||||
def prepare(self):
|
||||
_translate_v2_query_params(self)
|
||||
super().prepare()
|
||||
|
||||
def write(self, chunk):
|
||||
super().write(translate_v3_response(chunk))
|
||||
|
||||
|
||||
class _V2StreamTranslationMixin:
|
||||
"""Mixin for SSE handlers that translates v2 query params to v3 on the way in, and v3 JSON messages to v2 on the way
|
||||
out"""
|
||||
|
||||
def prepare(self):
|
||||
_translate_v2_query_params(self)
|
||||
super().prepare()
|
||||
|
||||
def write_message(self, name=None, msg=True, wait=None, evt_id=None):
|
||||
if not name:
|
||||
msg = translate_v3_response(msg)
|
||||
return super().write_message(name=name, msg=msg, wait=wait, evt_id=evt_id)
|
||||
|
||||
|
||||
class V2APISpotsHandler(_V2ResponseTranslationMixin, APISpotsHandler):
|
||||
"""API request handler for /api/v2/spots (GET). Included in Spothole v3 for backwards compatibility."""
|
||||
|
||||
|
||||
class V2APISpotsStreamHandler(_V2StreamTranslationMixin, APISpotsStreamHandler):
|
||||
"""API request handler for /api/v2/spots/stream (SSE). Included in Spothole v3 for backwards compatibility."""
|
||||
|
||||
|
||||
class V2APIAlertsHandler(_V2ResponseTranslationMixin, APIAlertsHandler):
|
||||
"""API request handler for /api/v2/alerts (GET). Included in Spothole v3 for backwards compatibility."""
|
||||
|
||||
|
||||
class V2APIAlertsStreamHandler(_V2StreamTranslationMixin, APIAlertsStreamHandler):
|
||||
"""API request handler for /api/v2/alerts/stream (SSE). Included in Spothole v3 for backwards compatibility."""
|
||||
|
||||
|
||||
class V2APIOptionsHandler(_V2ResponseTranslationMixin, APIOptionsHandler):
|
||||
"""API request handler for /api/v2/options (GET). Included in Spothole v3 for backwards compatibility."""
|
||||
|
||||
|
||||
class V2APIStatusHandler(_V2ResponseTranslationMixin, APIStatusHandler):
|
||||
"""API request handler for /api/v2/status (GET). Included in Spothole v3 for backwards compatibility."""
|
||||
|
||||
|
||||
class V2APILookupSigRefHandler(_V2ResponseTranslationMixin, APILookupActivityRefHandler):
|
||||
"""API request handler for /api/v2/lookup/sigref (GET). Included in Spothole v3 for backwards compatibility. This
|
||||
is the v2 equivalent of /api/v3/lookup/activityref."""
|
||||
|
||||
|
||||
class V2APISpotHandler(APISpotHandler):
|
||||
"""API request handler for /api/v2/spot (POST). Included in Spothole v3 for backwards compatibility. Translates the
|
||||
spot in the request body from v2 to v3 format. The response is a plain status message so needs no translation."""
|
||||
|
||||
def post(self):
|
||||
# Translate the request body if we can. If the body is empty or invalid JSON, leave it alone and let the v3
|
||||
# handler return the appropriate error.
|
||||
try:
|
||||
json_body = tornado.escape.json_decode(self.request.body)
|
||||
if isinstance(json_body, dict) and isinstance(json_body.get("spot"), dict):
|
||||
json_body["spot"] = translate_v2_spot(json_body["spot"])
|
||||
self.request.body = json.dumps(json_body).encode("utf-8")
|
||||
except ValueError:
|
||||
pass
|
||||
super().post()
|
||||
Reference in New Issue
Block a user