Use ruff linter to fix issues and provide consistent formatting

This commit is contained in:
Ian Renton
2026-08-15 08:25:54 +01:00
parent 7391c28cd0
commit af3f82c14d
121 changed files with 1989 additions and 996 deletions
+39 -24
View File
@@ -14,8 +14,7 @@ from core.config import ALLOW_SPOTTING, ALLOW_UPSTREAM_SPOTTING, RECAPTCHA_SECRE
from core.constants import UNKNOWN_BAND
from core.prometheus_metrics_handler import api_requests_counter
from core.sig_utils import get_ref_regex_for_sig
from core.utils import infer_band_from_freq
from core.utils import safe_json_dumps
from core.utils import infer_band_from_freq, safe_json_dumps
from data.spot import Spot
from providers.spot.spot_provider import SpotProvider
@@ -25,7 +24,12 @@ RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"
class APISpotHandler(tornado.web.RequestHandler):
"""API request handler for /api/v2/spot (POST)"""
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
def __init__(
self,
application: "Application",
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._spots = None
self._web_server_metrics = None
self._spot_providers = None
@@ -53,7 +57,7 @@ class APISpotHandler(tornado.web.RequestHandler):
return
# Reject if format not json
if not self.request.headers.get('Content-Type', '').startswith("application/json"):
if not self.request.headers.get("Content-Type", "").startswith("application/json"):
self.set_status(415)
self.write(safe_json_dumps("Error - request Content-Type must be application/json"))
self.set_header("Cache-Control", "no-store")
@@ -82,13 +86,10 @@ class APISpotHandler(tornado.web.RequestHandler):
upstream_credentials = handling.get("upstream_credentials", {})
captcha_token = handling.get("captcha_token", None)
# Spothole v2.0 release only: deny upstream spotting. Spothole API breaking changes were in v2.0 but
# functionality is not ready yet. TODO
submit_upstream = False
# Verify CAPTCHA if required
if RECAPTCHA_SECRET_KEY:
if not captcha_token:
@@ -111,7 +112,8 @@ class APISpotHandler(tornado.web.RequestHandler):
if not spot.time or not spot.dx_call or not spot.freq or not spot.de_call:
self.set_status(422)
self.write(
safe_json_dumps("Error - 'time', 'dx_call', 'freq' and 'de_call' must be provided as a minimum."))
safe_json_dumps("Error - 'time', 'dx_call', 'freq' and 'de_call' must be provided as a minimum.")
)
self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json")
return
@@ -133,29 +135,37 @@ class APISpotHandler(tornado.web.RequestHandler):
# Reject if frequency not in a known band
if infer_band_from_freq(spot.freq) == UNKNOWN_BAND:
self.set_status(422)
self.write(
safe_json_dumps(f"Error - Frequency of {spot.freq / 1000.0!s}kHz is not in a known band."))
self.write(safe_json_dumps(f"Error - Frequency of {spot.freq / 1000.0!s}kHz is not in a known band."))
self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json")
return
# Reject if grid formatting incorrect
if spot.dx_grid and not re.match(
r"^([A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}|[A-R]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2})$",
spot.dx_grid.upper()):
r"^([A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}|[A-R]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2})$",
spot.dx_grid.upper(),
):
self.set_status(422)
self.write(
safe_json_dumps(f"Error - '{spot.dx_grid}' does not look like a valid Maidenhead grid."))
self.write(safe_json_dumps(f"Error - '{spot.dx_grid}' does not look like a valid Maidenhead grid."))
self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json")
return
# Reject if sig_ref format incorrect for sig
if spot.sig and spot.sig_refs and len(spot.sig_refs) > 0 and spot.sig_refs[0].id and get_ref_regex_for_sig(
spot.sig) and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id):
if (
spot.sig
and spot.sig_refs
and len(spot.sig_refs) > 0
and spot.sig_refs[0].id
and get_ref_regex_for_sig(spot.sig)
and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id)
):
self.set_status(422)
self.write(safe_json_dumps(
f"Error - '{spot.sig_refs[0].id}' does not look like a valid reference for {spot.sig}."))
self.write(
safe_json_dumps(
f"Error - '{spot.sig_refs[0].id}' does not look like a valid reference for {spot.sig}."
)
)
self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json")
return
@@ -185,7 +195,8 @@ class APISpotHandler(tornado.web.RequestHandler):
if not spot.dx_grid and upstream_provider_name == "Tiles":
self.set_status(422)
self.write(
safe_json_dumps("Error - a grid reference is required to submit upstream to Tiles on the Air."))
safe_json_dumps("Error - a grid reference is required to submit upstream to Tiles on the Air.")
)
self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json")
return
@@ -210,7 +221,9 @@ class APISpotHandler(tornado.web.RequestHandler):
upstream_warning = str(e)
except Exception:
logging.exception(f"Failed to submit spot upstream to {upstream_provider_name}")
upstream_warning = f"Spot was saved locally but upstream submission to {upstream_provider_name} failed."
upstream_warning = (
f"Spot was saved locally but upstream submission to {upstream_provider_name} failed."
)
else:
upstream_warning = f"No enabled provider named '{upstream_provider_name}' supports upstream submission for {spot.sig if spot.sig else ''} spots."
@@ -250,10 +263,12 @@ class APISpotHandler(tornado.web.RequestHandler):
"""Verify a Google reCAPTCHA v2 token. Returns True if valid."""
try:
response = requests.post(RECAPTCHA_VERIFY_URL,
data={"secret": RECAPTCHA_SECRET_KEY, "response": token},
timeout=(5, 10))
response = requests.post(
RECAPTCHA_VERIFY_URL,
data={"secret": RECAPTCHA_SECRET_KEY, "response": token},
timeout=(5, 10),
)
return response.ok and response.json().get("success", False)
except Exception:
logging.exception(f"reCAPTCHA verification request failed")
logging.exception("reCAPTCHA verification request failed")
return False
+19 -10
View File
@@ -17,7 +17,12 @@ from data.lookup_credentials import extract_credentials
class APIAlertsHandler(tornado.web.RequestHandler):
"""API request handler for /api/v2/alerts"""
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
def __init__(
self,
application: "Application",
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._alerts = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
@@ -82,8 +87,7 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
def custom_headers(self):
"""Custom headers to avoid e.g. nginx reverse proxy from buffering SSE data"""
return {"Cache-Control": "no-store",
"X-Accel-Buffering": "no"}
return {"Cache-Control": "no-store", "X-Accel-Buffering": "no"}
def open(self):
try:
@@ -142,10 +146,10 @@ def get_alert_list_with_filters(all_alerts, query):
a = all_alerts.get(k)
if a is not None:
alerts.append(a)
alerts = sorted(alerts, key=lambda alert: (alert.start_time if alert and alert.start_time else 0))
alerts = sorted(alerts, key=lambda alert: alert.start_time if alert and alert.start_time else 0)
alerts = list(filter(lambda alert: alert_allowed_by_query(alert, query), alerts))
if "limit" in query.keys():
alerts = alerts[:int(query.get("limit"))]
alerts = alerts[: int(query.get("limit"))]
return alerts
@@ -164,8 +168,11 @@ def alert_allowed_by_query(alert, query):
# Check the duration if end_time is provided. If end_time is not provided, assume the activation is
# "short", i.e. it always passes this check. If dxpeditions_skip_max_duration_check is true and
# the alert is a dxpedition, it also always passes the check.
if alert.is_dxpedition and (query.get(
"dxpeditions_skip_max_duration_check").upper() == "TRUE" if "dxpeditions_skip_max_duration_check" in query.keys() else False):
if alert.is_dxpedition and (
query.get("dxpeditions_skip_max_duration_check").upper() == "TRUE"
if "dxpeditions_skip_max_duration_check" in query.keys()
else False
):
continue
if alert.end_time and alert.start_time and alert.end_time - alert.start_time > max_duration:
return False
@@ -192,8 +199,10 @@ def alert_allowed_by_query(alert, query):
return False
case "text_includes":
text_includes = query.get(k).strip()
if (not alert.dx_call or text_includes.upper() not in alert.dx_call.upper()) \
and (not alert.comment or text_includes.upper() not in alert.comment.upper()) \
and (not alert.freqs_modes or text_includes.upper() not in alert.freqs_modes.upper()):
if (
(not alert.dx_call or text_includes.upper() not in alert.dx_call.upper())
and (not alert.comment or text_includes.upper() not in alert.comment.upper())
and (not alert.freqs_modes or text_includes.upper() not in alert.freqs_modes.upper())
):
return False
return True
+12 -4
View File
@@ -21,7 +21,12 @@ BANDS_SET = frozenset(BANDS)
class APIDxStatsHandler(tornado.web.RequestHandler):
"""API request handler for /api/v2/dxstats"""
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
def __init__(
self,
application: "Application",
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._spots = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
@@ -46,12 +51,15 @@ class APIDxStatsHandler(tornado.web.RequestHandler):
continue
if not spot.time or spot.time < one_hour_ago:
continue
if spot.de_continent in CONTINENTS_SET and spot.dx_continent in CONTINENTS_SET and spot.band in BANDS_SET:
if (
spot.de_continent in CONTINENTS_SET
and spot.dx_continent in CONTINENTS_SET
and spot.band in BANDS_SET
):
counts[spot.de_continent, spot.dx_continent, spot.band] += 1
result = {
de: {dx: {band: counts[de, dx, band] for band in BANDS} for dx in CONTINENTS}
for de in CONTINENTS
de: {dx: {band: counts[de, dx, band] for band in BANDS} for dx in CONTINENTS} for de in CONTINENTS
}
self.write(json.dumps(result))
+32 -11
View File
@@ -10,7 +10,11 @@ from tornado.web import Application
from core.call_lookup_helper import get_call_info
from core.constants import SIGS
from core.geo_utils import lat_lon_for_grid_sw_corner_plus_size, lat_lon_to_cq_zone, lat_lon_to_itu_zone
from core.geo_utils import (
lat_lon_for_grid_sw_corner_plus_size,
lat_lon_to_cq_zone,
lat_lon_to_itu_zone,
)
from core.prometheus_metrics_handler import api_requests_counter
from core.sig_lookup_helper import populate_missing_sig_ref_info
from core.sig_utils import get_ref_regex_for_sig
@@ -22,7 +26,12 @@ from data.sig_ref import SIGRef
class APILookupCallHandler(tornado.web.RequestHandler):
"""API request handler for /api/v2/lookup/call"""
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
def __init__(
self,
application: "Application",
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
@@ -42,7 +51,7 @@ class APILookupCallHandler(tornado.web.RequestHandler):
query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
# The "call" query param must exist and look like a callsign
if "call" in query_params.keys():
if "call" in query_params:
call = str(query_params.get("call")).upper()
if re.match(r"^[A-Z0-9/\-]*$", call):
credentials = extract_credentials(self.request.headers)
@@ -68,7 +77,12 @@ class APILookupCallHandler(tornado.web.RequestHandler):
class APILookupSIGRefHandler(tornado.web.RequestHandler):
"""API request handler for /api/v2/lookup/sigref"""
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
def __init__(
self,
application: "Application",
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
@@ -89,7 +103,7 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
# "sig" and "id" query params must exist, SIG must be known, and if we have a reference regex for that SIG,
# the provided id must match it.
if "sig" in query_params.keys() and "id" in query_params.keys():
if "sig" in query_params and "id" in query_params:
sig = str(query_params.get("sig")).upper()
ref_id = str(query_params.get("id")).upper()
if sig in list(map(lambda p: p.name.upper(), SIGS)):
@@ -98,8 +112,9 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
self.write(safe_json_dumps(data))
else:
self.write(safe_json_dumps(
f"Error - '{ref_id}' does not look like a valid reference ID for {sig}."))
self.write(
safe_json_dumps(f"Error - '{ref_id}' does not look like a valid reference ID for {sig}.")
)
self.set_status(422)
else:
self.write(safe_json_dumps(f"Error - sig '{sig}' is not known."))
@@ -120,7 +135,12 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
class APILookupGridHandler(tornado.web.RequestHandler):
"""API request handler for /api/v2/lookup/grid"""
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
def __init__(
self,
application: "Application",
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
@@ -140,7 +160,7 @@ class APILookupGridHandler(tornado.web.RequestHandler):
query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
# "grid" query param must exist.
if "grid" in query_params.keys():
if "grid" in query_params:
grid = str(query_params.get("grid")).upper()
lat, lon, lat_cell_size, lon_cell_size = lat_lon_for_grid_sw_corner_plus_size(grid)
if lat is not None and lon is not None and lat_cell_size is not None and lon_cell_size is not None:
@@ -154,7 +174,7 @@ class APILookupGridHandler(tornado.web.RequestHandler):
"latitude": center_lat,
"longitude": center_lon,
"cq_zone": center_cq_zone,
"itu_zone": center_itu_zone
"itu_zone": center_itu_zone,
},
"southwest": {
"latitude": lat,
@@ -163,7 +183,8 @@ class APILookupGridHandler(tornado.web.RequestHandler):
"northeast": {
"latitude": lat + lat_cell_size,
"longitude": lon + lon_cell_size,
}}
},
}
self.write(safe_json_dumps(response))
else:
+56 -20
View File
@@ -7,8 +7,15 @@ import tornado
from tornado import httputil
from tornado.web import Application
from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, PROPAGATION_MODES
from core.config import ALLOW_SPOTTING, MAX_SPOT_AGE
from core.constants import (
ALL_MODES,
BANDS,
CONTINENTS,
MODE_TYPES,
PROPAGATION_MODES,
SIGS,
)
from core.prometheus_metrics_handler import api_requests_counter
from core.utils import safe_json_dumps
@@ -16,7 +23,12 @@ from core.utils import safe_json_dumps
class APIOptionsHandler(tornado.web.RequestHandler):
"""API request handler for /api/v2/options"""
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
def __init__(
self,
application: "Application",
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._status_data = None
self._web_server_metrics = None
self._spot_providers = None
@@ -49,13 +61,35 @@ class APIOptionsHandler(tornado.web.RequestHandler):
# Spot/alert sources are filtered for only ones that are enabled in config, no point letting the user toggle
# things that aren't even available.
spot_providers: list = list(
map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["spot_providers"])))
map(
lambda p: p["name"],
filter(lambda p: p["enabled"], self._status_data["spot_providers"]),
)
)
alert_providers = list(
map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["alert_providers"])))
map(
lambda p: p["name"],
filter(lambda p: p["enabled"], self._status_data["alert_providers"]),
)
)
callsign_data_providers = list(
map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["callsign_data_providers"])))
map(
lambda p: p["name"],
filter(
lambda p: p["enabled"],
self._status_data["callsign_data_providers"],
),
)
)
spot_providers_enabled_by_default = list(
map(lambda p: p["name"], filter(lambda p: p["enabled"] and p["enabled_by_default_in_web_ui"], self._status_data["spot_providers"])))
map(
lambda p: p["name"],
filter(
lambda p: p["enabled"] and p["enabled_by_default_in_web_ui"],
self._status_data["spot_providers"],
),
)
)
# If spotting to this server is enabled, "API" is another valid spot source even though it does not come from
# one of our providers.
@@ -63,19 +97,21 @@ class APIOptionsHandler(tornado.web.RequestHandler):
spot_providers.append("API")
spot_providers_enabled_by_default.append("API")
options = {"bands": BANDS,
"modes": ALL_MODES,
"mode_types": MODE_TYPES,
"sigs": SIGS,
"spot_providers": spot_providers,
"spot_providers_enabled_by_default": spot_providers_enabled_by_default,
"alert_providers": alert_providers,
"callsign_data_providers": callsign_data_providers,
"continents": CONTINENTS,
"propagation_modes": list(PROPAGATION_MODES.values()),
"max_spot_age": MAX_SPOT_AGE,
"spot_allowed": ALLOW_SPOTTING,
"spot_submit_providers": spot_submit_providers}
options = {
"bands": BANDS,
"modes": ALL_MODES,
"mode_types": MODE_TYPES,
"sigs": SIGS,
"spot_providers": spot_providers,
"spot_providers_enabled_by_default": spot_providers_enabled_by_default,
"alert_providers": alert_providers,
"callsign_data_providers": callsign_data_providers,
"continents": CONTINENTS,
"propagation_modes": list(PROPAGATION_MODES.values()),
"max_spot_age": MAX_SPOT_AGE,
"spot_allowed": ALLOW_SPOTTING,
"spot_submit_providers": spot_submit_providers,
}
self.write(safe_json_dumps(options))
self.set_status(200)
+6 -1
View File
@@ -14,7 +14,12 @@ from core.utils import safe_json_dumps
class APISolarConditionsHandler(tornado.web.RequestHandler):
"""API request handler for /api/v2/solar"""
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
def __init__(
self,
application: "Application",
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._solar_conditions = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
+12 -7
View File
@@ -17,7 +17,12 @@ from data.lookup_credentials import extract_credentials
class APISpotsHandler(tornado.web.RequestHandler):
"""API request handler for /api/v2/spots"""
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
def __init__(
self,
application: "Application",
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._spots = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
@@ -82,8 +87,7 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
def custom_headers(self):
"""Custom headers to avoid e.g. nginx reverse proxy from buffering SSE data"""
return {"Cache-Control": "no-store",
"X-Accel-Buffering": "no"}
return {"Cache-Control": "no-store", "X-Accel-Buffering": "no"}
def open(self):
"""Called once on the client opening a connection, set things up"""
@@ -145,10 +149,10 @@ def get_spot_list_with_filters(all_spots, query):
s = all_spots.get(k)
if s is not None:
spots.append(s)
spots = sorted(spots, key=lambda spot: (spot.time if spot and spot.time else 0), reverse=True)
spots = sorted(spots, key=lambda spot: spot.time if spot and spot.time else 0, reverse=True)
spots = list(filter(lambda spot: spot_allowed_by_query(spot, query), spots))
if "limit" in query.keys():
spots = spots[:int(query.get("limit"))]
spots = spots[: int(query.get("limit"))]
# Ensure only the latest spot of each callsign-SSID combo is present in the list. This relies on the
# list being in reverse time order, so if any future change allows re-ordering the list, that should
@@ -245,8 +249,9 @@ def spot_allowed_by_query(spot, query):
return False
case "text_includes":
text_includes = query.get(k).strip()
if (not spot.dx_call or text_includes.upper() not in spot.dx_call.upper()) \
and (not spot.comment or text_includes.upper() not in spot.comment.upper()):
if (not spot.dx_call or text_includes.upper() not in spot.dx_call.upper()) and (
not spot.comment or text_includes.upper() not in spot.comment.upper()
):
return False
case "allow_qrt":
# If false, spots that are flagged as QRT are not returned.
+6 -1
View File
@@ -14,7 +14,12 @@ from core.utils import safe_json_dumps
class APIStatusHandler(tornado.web.RequestHandler):
"""API request handler for /api/v2/status"""
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
def __init__(
self,
application: "Application",
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._status_data = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
+27 -10
View File
@@ -12,14 +12,19 @@ from core.config import ALLOW_SPOTTING
from core.constants import UNKNOWN_BAND
from core.prometheus_metrics_handler import api_requests_counter
from core.sig_utils import get_ref_regex_for_sig
from core.utils import safe_json_dumps, infer_band_from_freq
from core.utils import infer_band_from_freq, safe_json_dumps
from data.spot import Spot
class V1APISpotHandler(tornado.web.RequestHandler):
"""API request handler for /api/v1/spot (POST). Included in early Spothole v2 for backwards compatibility."""
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
def __init__(
self,
application: "Application",
request: httputil.HTTPServerRequest,
**kwargs: Any,
):
self._spots = None
self._web_server_metrics = None
super().__init__(application, request, **kwargs)
@@ -45,7 +50,7 @@ class V1APISpotHandler(tornado.web.RequestHandler):
return
# Reject if format not json
if not self.request.headers.get('Content-Type', '').startswith("application/json"):
if not self.request.headers.get("Content-Type", "").startswith("application/json"):
self.set_status(415)
self.write(safe_json_dumps("Error - request Content-Type must be application/json"))
self.set_header("Cache-Control", "no-store")
@@ -68,7 +73,9 @@ class V1APISpotHandler(tornado.web.RequestHandler):
# Reject if no timestamp, frequency, dx_call or de_call
if not spot.time or not spot.dx_call or not spot.freq or not spot.de_call:
self.set_status(422)
self.write(safe_json_dumps("Error - 'time', 'dx_call', 'freq' and 'de_call' must be provided as a minimum."))
self.write(
safe_json_dumps("Error - 'time', 'dx_call', 'freq' and 'de_call' must be provided as a minimum.")
)
self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json")
return
@@ -97,8 +104,9 @@ class V1APISpotHandler(tornado.web.RequestHandler):
# Reject if grid formatting incorrect
if spot.dx_grid and not re.match(
r"^([A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}|[A-R]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2})$",
spot.dx_grid.upper()):
r"^([A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}|[A-R]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2})$",
spot.dx_grid.upper(),
):
self.set_status(422)
self.write(safe_json_dumps(f"Error - '{spot.dx_grid}' does not look like a valid Maidenhead grid."))
self.set_header("Cache-Control", "no-store")
@@ -106,11 +114,20 @@ class V1APISpotHandler(tornado.web.RequestHandler):
return
# Reject if sig_ref format incorrect for sig
if spot.sig and spot.sig_refs and len(spot.sig_refs) > 0 and spot.sig_refs[0].id and get_ref_regex_for_sig(
spot.sig) and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id):
if (
spot.sig
and spot.sig_refs
and len(spot.sig_refs) > 0
and spot.sig_refs[0].id
and get_ref_regex_for_sig(spot.sig)
and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id)
):
self.set_status(422)
self.write(safe_json_dumps(
f"Error - '{spot.sig_refs[0].id}' does not look like a valid reference for {spot.sig}."))
self.write(
safe_json_dumps(
f"Error - '{spot.sig_refs[0].id}' does not look like a valid reference for {spot.sig}."
)
)
self.set_header("Cache-Control", "no-store")
self.set_header("Content-Type", "application/json")
return
+6 -2
View File
@@ -35,7 +35,11 @@ class V1RedirectHandler(tornado.web.RequestHandler):
if isinstance(response.headers, HTTPHeaders):
for name, value in response.headers.get_all():
# Let Tornado recompute these for the outgoing response
if name.lower() not in ("content-length", "transfer-encoding", "connection"):
if name.lower() not in (
"content-length",
"transfer-encoding",
"connection",
):
self.add_header(name, value)
if response.body:
self.write(response.body)
@@ -54,4 +58,4 @@ class V1RedirectHandler(tornado.web.RequestHandler):
await self._proxy(path)
async def patch(self, path):
await self._proxy(path)
await self._proxy(path)
+2 -1
View File
@@ -13,6 +13,7 @@ class V1APISpotsHandler(APISpotsHandler):
chunk = _GRID_SOURCE_RE.sub('"dx_location_source": "SPOT"', chunk)
super().write(chunk)
class V1APISpotsStreamHandler(APISpotsStreamHandler):
"""API request handler for /api/v1/spots/stream (SSE). Included in early Spothole v2 for backwards compatibility."""
@@ -24,4 +25,4 @@ class V1APISpotsStreamHandler(APISpotsStreamHandler):
for k, v in kwargs.items():
if isinstance(v, str) and '"dx_location_source"' in v:
kwargs[k] = _GRID_SOURCE_RE.sub('"dx_location_source": "SPOT"', v)
super().write_message(*args, **kwargs)
super().write_message(*args, **kwargs)