How can I catch a 4XX series raise in inner function of an API in Django rest framework?
from rest_framework.exceptions import ValidationError
class DummyView(APIView):
def get(self, request, id):
if id==something:
dummy_function_1(id)
else:
dummy_function_2(id)
return Response()
def dummy_function_1():
try:
validate_1(id)
except ValidationError:
raise ValidationError()
#do something with id
return id
When I send a HTTP GET request, I receive a 5XX series error if exception occurs. I want to get 400 Bad Request error in response.
After lots of effort and I don't know why,
If I specify the exception type of inner function, I will get 5XX series error.
so in dummy function I just wrote :
def dummy_function_1():
try:
validate_1(id)
except Exception: # just exception, Not ValidationError or other exception
raise ValidationError()
I could get 4XX series error
Try this:
def dummy_function_1():
try:
validate_1(id)
except Exception: # just exception, Not ValidationError or other exceptions
raise ValidationError()
Related
im making a python script that can manage my google projects.
im having a insue with one part
when i try to exclude the project its can return to me many errors.
i did a peace of code to get this exception:
try:
# Initialize request argument(s)
request = DeleteProjectRequest(
name=project,
)
self.project_manager.delete_project(request=request)
except PermissionDenied as exc:
# GCP returns PermissionDenied whether we actually does
# not have permissions to perform the get_project call
# or when the project does not exist. Due to this reason,
# the PermissionDenied exception catch won't be deterministic.
logger.error(f"Project '{project_id}' does not exist", exc)
return False
i need to get the error message of all types of errors
i changed except PermissionDenied as exc: for except Exception as exc:
and it works but i need to call the logger only if the error is PermissionDenied and in all cases i need to call another function passing the message as parameter like it return_to_db(error_message)
my question is. how can i run only the logger if the error is PermissionDenied?
You can also catch multiple Exceptions by adding additional blocks, though it will choose the first isinstance() match (so if you put Exception first, it will be selected instead, while TypeError would be continued past)
try:
self.project_manager.delete_project(
request=DeleteProjectRequest(name=project))
except PermissionDenied as exc:
# GCP returns PermissionDenied whether we actually does
# not have permissions to perform the get_project call
# or when the project does not exist. Due to this reason,
# the PermissionDenied exception catch won't be deterministic.
logger.error(f"Project '{project_id}' does not exist", exc)
except Exception:
# FIXME other handling to go here
pass # fall to return False
else: # didn't raise
return True
# opportunity for finally: block here too
# if any Exception was raised, continue to return False
return False
You can add a condition of the instance type of the current exception in Python, example :
try:
# Initialize request argument(s)
request = DeleteProjectRequest(
name=project,
)
self.project_manager.delete_project(request=request)
except Exception as exc:
if isinstance(exc, PermissionDenied):
logger.error(f"Project '{project_id}' does not exist", exc)
return False
As expected, the logger is executed only if the exception instance is PermissionDenied.
I have an exception that looks like this:
exc = ProtocolError('Connection Aborted', BadStatusLine('No status line received'))
how can I access the No status line received part?
Here's the example situation:
def some_function():
raise ProtocolError('Connection Aborted', BadStatusLine('No status line received'))
def some_other_function():
try:
some_function()
except Exception as exc:
if exc.message:
details = exc.message
else:
details = exc
In the code above I'm trying to check if the returned exception has a message and if so I'm supposed to write it into the database, however when I call the exc.message it returns an empty string, and when I call exc it returns:
bson.errors.InvalidDocument: cannot encode object:
ProtocolError('Connection Aborted', BadStatusLine('No status linereceived',)), of type: <class 'urllib3.exceptions.ProtocolError'>
so I cant write it into the database since its a type Exception not string, what I need to do is to see if the returned Exception has another nested Exception in it and get it's message.
I wasn't able to find the exact optimal way to fetch the inner message or exception but for quick assistance i wrote a utility function which by using regular expressions will return you inner exceptions or messages complete code is following
from urllib3.exceptions import ProtocolError
from http.client import BadStatusLine
import re
def FetchInnerExceptions(exc):
result = []
messages = str(exc).split(',')
for msg in messages:
m = re.search('''(?<=')\s*[^']+?\s*(?=')''', msg)
if m is not None or m != '':
result.append(m.group().strip())
return result
def some_function():
raise ProtocolError('Connection Aborted', BadStatusLine('No status line received'))
def some_other_function():
try:
some_function()
except Exception as exc:
e = FetchInnerExceptions(exc)
print(e) #dumps all array or use index e[1] for your required message
some_other_function()
I'm using django and vue to write a Programmer. Could I raise an exception as a http response, so I can raise the exception anywhere, and do not need to catch it in the django view function, and then reassemble it into a new http response.
Pseudocode
try:
a = ['0']
b = a[2]
except IndexError as e:
raise ExceptionAsHttpResponse(status=404, reason='haha') # Not implemented, hope to get your help.
after the raise ExceptionAsHttpResponse, the frontend can just accquire the status and reason
Yes you can, you can simply use Http404. For example:
from django.http import Http404
try:
a = ['0']
b = a[2]
except IndexError as e:
raise Http404('your reason')
But that is in general should not be practiced, because http response should be generated from view. If you have helper functions or service methods, then it is better to raise a generic error(ie IndexError) from there and catch them in view, then render a error response.
This is an example of my exception handling in a django project:
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except requests.exceptions.ConnectionError as e:
context = {'error_message': 'Could not connect to your box because the host is unknown.'}
return render(request, 'box/error.html', context)
except requests.exceptions.RequestException as e:
context = {'error_message': 'Could not connect to your box because of an unknown error.'}
return render(request, 'box/error.html', context)
There is only two excepts now, but it should be more for the several request exceptions. But already the view method is bloated up by this. Is there a way to forward the except handling to a separate error method?
There is also the problem, that I need here to call the render message for each except, I would like to avoid that.
And here I also repeat for each except "could not connect to your box because", that should be set once when there appeared any exception.
I can solve it by something like this:
try:
box.connect()
except Exception as e:
return error_handling(request, e)
-
def error_handling(request, e):
if type(e).__name__ == requests.exceptions.ConnectionError.__name__:
context = {'error_message': 'Could not connect to your box because the host is unknown.'}
elif type(e).__name__ == requests.exceptions.RequestException.__name__:
context = {'error_message': 'Could not connect to your box because of an unknown error.'}
else:
context = {'error_message': 'There was an unkown error, sorry.'}
return render(request, 'box/error.html', context)
and I could of course improve the error message thing then. But overall, is it a pythonic way to handle exceptions with if/else? For example I could not catch RequestException here if ConnectionError is thrown, so I would need to catch each requests error, that looks more like an ugly fiddling...
This is a use case for decorators. If it's something more general that applies to all views (say, error logging), you can use the Django exception middleware hook, but that doesn't seem to be the case here.
With respect to the repetitive error string problem, the Pythonic way to solve it is to have a constant base string with {replaceable_parts} inserted, so that later on you can .format() them.
With this, say we have the following file decorators.py:
import functools
from django.shortcuts import render
from requests.exceptions import ConnectionError, RequestException
BASE_ERROR_MESSAGE = 'Could not connect to your box because {error_reason}'
def handle_view_exception(func):
"""Decorator for handling exceptions."""
#functools.wraps(func)
def wrapper(request, *args, **kwargs):
try:
response = func(request, *args, **kwargs)
except RequestException as e:
error_reason = 'of an unknown error.'
if isinstance(e, ConnectionError):
error_reason = 'the host is unknown.'
context = {
'error_message': BASE_ERROR_MESSAGE.format(error_reason=error_reason),
}
response = render(request, 'box/error.html', context)
return response
return wrapper
We're using the fact that ConnectionError is a subclass of RequestException in the requests library. We could also do a dictionary with the exception classes as keys, but the issue here is that this won't handle exception class inheritance, which is the kind of omission that generates subtle bugs later on. The isinstance function is a more reliable way of doing this check.
If your exception tree keeps growing, you can keep adding if statements. In case that starts to get unwieldy, I recommend looking here, but I'd say it's a code smell to have that much branching in error handling.
Then in your views:
from .decorators import handle_view_exception
#handle_view_exception
def boxinfo(request, url: str):
box = get_box(url)
box.connect()
...
That way the error handling logic is completely separate from your views, and best of all, it's reusable.
could you have something like this:
views.py
EXCEPTION_MAP = {
ConnectionError: "Could not connect to your box because the host is unknown.",
RequestException: "Could not connect to your box because of an unknown error.",
}
UNKNOWN_EXCEPTION_MESSAGE = "Failed due to an unknown error."
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except (ConnectionError, RequestException) as e:
message = EXCEPTION_MAP.get(type(e)) or UNKNOWN_EXCEPTION_MESSAGE
context = {'error_message': message}
return render(request, 'box/error.html', context)
You could then just expand the EXCEPTION_MAP and the except () for any other known exception types you're expecting to catch?
if you want to reduce the duplication of "Could not connect to your box because ...
You could maybe do:
views.py
BASE_ERROR_STRING = "Could not connect to your box because {specific}"
EXCEPTION_MAP = {
ConnectionError: "the host is unknown.",
RequestException: "of an unknown error.",
}
UNKNOWN_EXCEPTION_MESSAGE = "Failed due to an unknown error."
def boxinfo(request, url: str):
box = get_box(url)
try:
box.connect()
except (ConnectionError, RequestException) as e:
specific_message = EXCEPTION_MAP.get(type(e))
if specific_message:
message = BASE_ERROR_STRING.format(specific=specific_message)
else:
message = UNKNOWN_EXCEPTION_MESSAGE
context = {'error_message': message}
return render(request, 'box/error.html', context)
There are three situations when I need to handle exceptions.
When data validation raised exception
When library/module functions raised exceptions (e.g. database connection abort)
When business logic raises exception such as 500, 503, 401, 403 and 404
def library_func():
try:
...
except HTTPException:
raise TwitterServiceException("Twitter is down!")
#view_config(route_name="home", renderer="json")
#validator
#authorization
def home_view(request):
try:
tweets = library_func()
return {"tweets": tweets}
except TwitterServiceException as e:
LOG.critical(e.msg)
raise ParnterServcieError(e.msg) # this is probably a 503 error
def validator(args):
# I will show the high level of this decorator
try:
decode input as JSON
verify data format
except ValueError as err:
error = {'error': "Missing required parameters."}
except json.JSONDecodeError as err:
error = {'error': "Failed to decode the incoming JSON payload."}
if error is not None:
return HTTPBadRequest(body=json.dumps(error),
content_type='application/json')
def authorization(args):
# very similar to validator except it performs authorization and if failed
# 401 is raised with some helpful message.
The doc suggests Custom Exception Views. In my PoC above, I will tie ParnterServcieError as one. I can even generalize HTTPBadRequest and all praymid.httpexceptions using custom exception so that I no longer need to repeat json.dumps and content_type. I can set a boilerplate error body before I return request.response object.
Idea:
#view_config(context=ParnterServcieError)
def 503_service_error_view(e, request):
request.response.status = 503
request.response.json_body = {"error": e.msg}
return request.response
I can generalize one for all uncaught, unspecified exceptions (which results in 500 Internal Server Error) called 500_internal_server_error_view.
Does this seem sane and clean to people? Is my way of handling high and low level of exceptions proper and Pythonic?
I applied this strategy to ToDoPyramid and could encapsulate error handling in a single custom exception view that was repeated multiple times in the application before. Until you could even improve it, you got a great idea. Pyramid rocks.
References
Catching database connection error in ToDoPyramid