mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 22:37:44 +00:00
Use ruff linter to fix issues and provide consistent formatting
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user