# pxy_routing/api/views.py from __future__ import annotations import uuid from rest_framework.decorators import api_view, throttle_classes from rest_framework.response import Response from rest_framework import status from rest_framework.throttling import ScopedRateThrottle from pxy_routing.services.factory import get_routing_provider def _err(code: str, message: str, hint: str | None = None, http_status: int = 400): return Response( { "ok": False, "code": code, "message": message, "hint": hint, "trace_id": str(uuid.uuid4()), }, status=http_status, ) @api_view(["GET"]) @throttle_classes([ScopedRateThrottle]) def routing_health(request): routing_health.throttle_scope = "routing_health" # DRF scoped throttle provider = get_routing_provider() try: info = provider.health() or {} ok = bool(info.get("ok", False)) return Response( { "ok": ok, "provider": info.get("provider"), **info, # includes base_url/profile/reason } ) except Exception as e: return _err( code="routing_health_error", message="Routing health check failed", hint=str(e), http_status=status.HTTP_502_BAD_GATEWAY, ) @api_view(["POST"]) @throttle_classes([ScopedRateThrottle]) def routing_isochrone(request): routing_isochrone.throttle_scope = "routing_isochrone" # DRF scoped throttle data = request.data or {} center = data.get("center") minutes = data.get("minutes") if not isinstance(center, (list, tuple)) or len(center) != 2: return _err("invalid", "center must be [lat, lon]", http_status=status.HTTP_400_BAD_REQUEST) try: minutes = int(minutes) except Exception: return _err("invalid", "minutes must be an integer", http_status=status.HTTP_400_BAD_REQUEST) provider = get_routing_provider() try: feat = provider.isochrone(tuple(center), minutes) return Response(feat) except Exception as e: # Map to a consistent envelope for upstream/provider issues return _err( code="routing_error", message="Failed to compute isochrone", hint=str(e), http_status=status.HTTP_502_BAD_GATEWAY, ) # Backward-compatible aliases expected by urls.py health = routing_health isochrone = routing_isochrone