From 7c79392f4d9366d85b4e9d4f18cd2fbc0a82d443 Mon Sep 17 00:00:00 2001 From: Julian Graham Date: Thu, 19 Oct 2023 17:58:30 -0400 Subject: [PATCH] fix: Move DRF custom exception handler to `exceptions` + support more types --- twitch_hdt_ebs/exceptions.py | 40 ++++++++++++++++++++++++++++++++++++ twitch_hdt_ebs/settings.py | 2 +- twitch_hdt_ebs/views.py | 16 --------------- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/twitch_hdt_ebs/exceptions.py b/twitch_hdt_ebs/exceptions.py index 437c24f..8064bd0 100644 --- a/twitch_hdt_ebs/exceptions.py +++ b/twitch_hdt_ebs/exceptions.py @@ -1,5 +1,10 @@ +import logging + +import requests +from hearthsim.instrumentation.django_influxdb import write_point from rest_framework import status from rest_framework.exceptions import APIException +from rest_framework.views import exception_handler class TwitchAPITimeout(APIException): @@ -20,3 +25,38 @@ class BadGateway(APIException): class BadTwitchResponse(BadGateway): default_detail = "Twitch returned an invalid response." default_code = "bad_twitch_response" + + +def untapped_django_exception_handler(exc, context): + """A Django REST Framework "custom exception handler" for translating additional types. + + This implementation attempts to convert upstream exception types corresponding to HTTP + 502 (Bad gateway) and HTTP 504 (Gateway timeout) before delegating to the default + exception handler implementation. + + See also + https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling + + :param exc: + :param context: + """ + + if isinstance(exc, requests.Timeout): + effective_exc = TwitchAPITimeout() + elif ( + isinstance(exc, requests.ConnectionError) or + isinstance(exc, requests.HTTPError) + ): + effective_exc = BadGateway() + else: + effective_exc = exc + + detail = getattr(exc, "detail", {}) + + logger = logging.getLogger("twitch_hdt_ebs") + logger.error("Got exception %r, detail=%r", exc, detail) + + if detail and isinstance(detail, dict): + write_point("api_error", {"count": 1}, error=detail.get("error", "unknown")) + + return exception_handler(effective_exc, context) diff --git a/twitch_hdt_ebs/settings.py b/twitch_hdt_ebs/settings.py index bc50fed..1b7db2e 100644 --- a/twitch_hdt_ebs/settings.py +++ b/twitch_hdt_ebs/settings.py @@ -138,7 +138,7 @@ def get_deployment_uuid(): # Disable DRF browsable API (it requires templates to be setup) REST_FRAMEWORK = { "DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer", ), - "EXCEPTION_HANDLER": "twitch_hdt_ebs.views.exception_handler", + "EXCEPTION_HANDLER": "twitch_hdt_ebs.exceptions.untapped_django_exception_handler", } # DRF CORS handling diff --git a/twitch_hdt_ebs/views.py b/twitch_hdt_ebs/views.py index 582b406..4d66d0b 100644 --- a/twitch_hdt_ebs/views.py +++ b/twitch_hdt_ebs/views.py @@ -1,7 +1,6 @@ import base64 import hashlib import json -import logging import string from typing import Any, Dict, List, Optional @@ -520,18 +519,3 @@ def get(self, request, user_id) -> Response: class PingView(View): def get(self, request): return HttpResponse("OK", content_type="text/plain") - - -def exception_handler(exc, context): - from rest_framework.views import exception_handler as original_handler - - response = original_handler(exc, context) - detail = getattr(exc, "detail", {}) - - logger = logging.getLogger("twitch_hdt_ebs") - logger.error("Got exception %r, detail=%r", exc, detail) - - if detail and isinstance(detail, dict): - write_point("api_error", {"count": 1}, error=detail.get("error", "unknown")) - - return response