Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle network errors via translation in a DRF custom exception handler #19

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions twitch_hdt_ebs/exceptions.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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)
2 changes: 1 addition & 1 deletion twitch_hdt_ebs/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 0 additions & 16 deletions twitch_hdt_ebs/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import base64
import hashlib
import json
import logging
import string
from typing import Any, Dict, List, Optional

Expand Down Expand Up @@ -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
Loading