I'm using pynic framework to handle my APIs endpoints, but I guess this would be the same logic with Flask or Django.
I've got a few endpoints, and I was wondering if there were anyway to handle all the exceptions at the same place.
For instance:
class Pnorm(Handler):
def post(self):
logger = logging.getLogger(constants.loggerName)
template_exception = "Exception {0} in class {1} ({2})."
try:
myJson = DoThings()
return myJson
except HTTP_400 as e:
logger.critical(message)
raise e
except Exception as e:
# unknown exception raise 500
logger.critical(message)
raise HTTP_500(message)
Is there anyway I can make all my endpoints handle the exceptions the same way or do I hacve to repeat my "exception block" at the end of each point ?
(I don't mean in the same class only but through the project.)
Cheers,
Julien
Edited:
My main class:
class app(WSGI):
DataStructureHelper.set_dsh()
setup_logging.setup_logging(logger_name=constants.loggerName, console_level=logging.INFO)
routes = [
('/allocator', Allocator()),
('/data/([0-9]+)/([0-9]+)/([0-9]+)/([0-9]+)', InstrumentData()),
('/pnorm', Pnorm()),
('/portfolios')
]
I think the right approach would be decorators, since it fits the needs perfectly. Following is working piece of code w.r.t flask.
A word of caution is you need return the control back to handlers from decorator.
from functools import wraps
from flask import Flask, request
app = Flask(__name__)
def http_error_codes(method_name):
#wraps(method_name)
def handle_exceptions(*args):
try:
print("Inside the exceptions")
return method_name(*args)
except Exception as e:
print("HAHAHAHA")
raise e
return handle_exceptions
def do_the_login():
return "Testing is fun"
def show_the_login_form():
raise ValueError('The day is too frabjous.')
#app.route('/login', methods=['GET', 'POST'])
#http_error_codes
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()
if __name__ == '__main__':
app.run()
Hope this helps
If you want to handle all the exceptions in a single place, you can keep an general exceptional block like as follows..
try:
#Code part may give error
except Exception:
#If error what to do..
Here Exception is the General class, which will handle all exceptions irrespective of the error.
Related
I am a newbie in python software development. I have created a script which includes many functions returning output of a request and handling the exceptions generated by them. One of the func. is as below:
def ApiResponse(emailHash):
r=None
errorMessage = ""
url = "https://dummy.com"
try:
r = requests.get(url, timeout = TIMEOUT_LIMIT)
except requests.exceptions.Timeout as e:
errorMessage = "Timeout Error"
except requests.ConnectionError as e:
errorMessage = "ConnectionError"
except requests.RequestException as e:
errorMessage = "Some other Request Exception"
return r
I have many such functions returning response for different requests. But, they all repeat the exception handling code. I want to handle exceptions separately in another function so that I don't have to repeat myself. I have tried stuff like passing on the variable "e" to other function and comparing it to the exception types and return different error messages accordingly but I cant seem to compare, lets say, e == requests.exceptions.Timeout.
What else can i do just to separate out that exception handling part? I aim to write a modular code!
You can use exception-decouple package for good readbility:
from exception_decouple import redirect_exceptions
def timeout_handler(emailHash, e):
return "Timeout Error"
def default_handler(emailHash, e):
return "Other Error"
#redirect_exceptions(timeout_handler, requests.exceptions.Timeout)
#redirect_exceptions(default_handler, requests.ConnectionError,
requests.RequestException)
def ApiResponse(emailHash):
r=None
url = "https://dummy.com"
r = requests.get(url, timeout = TIMEOUT_LIMIT)
return r
one of the ways I found was to compare the type of the error with the error. for example in the above case.
def exception_handler(e):
if type(e) is requests.exceptions.ConnectTimeout:
return "Timeout Error"
elif type(e) is requests.ConnectionError:
return "ConnectionError"
else:
return "Some other Request Exception"
this should work
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)
I have the following error handlers:
#api.errorhandler(DatabaseException)
def handle_database_exception(database_error):
return database_error.query_error, 400
#api.errorhandler(ValidationException)
def handle_validation_exception(validation_error):
return {'fields': validation_error.body_error}, 400
These are simple classes:
class ValidationException(Exception):
def __init__(self, body_error=None, missing_files=None):
Exception.__init__(self, "Validation failed")
self.body_error = body_error
if missing_files is not None:
for missing_file in missing_files:
self.body_error[missing_file] = ['required file']
class DatabaseException(Exception):
def __init__(self, details):
Exception.__init__(self, "Database Error")
self.query_error = details
Here's my problem:
If I call raise the DatabaseException in any of my routes, it falls through and I get a 500 template from flask.
The really funny thing is that the ValidationException, which was implemented earlier works just fine.
I went through in detail what's going on, when the ValidationException is raised, it goes through response.py and ends up in the error handler. I'm unfortunately can't comprehend everything that's going on deep inside flask, but in debug, the DatabaseException is certainly went on a different route.
I expect that the error handlers get called. If I raise a DatabaseException in one of my routes, it should get called.
Sorry, my answer was a bit strange. If you want to return a JSON response, you could do it like this.
class ValidationException(Exception):
def __init__(self, body_error=None, missing_files=None):
Exception.__init__(self, "Validation failed")
self.body_error = body_error
if missing_files is not None:
for missing_file in missing_files:
self.body_error[missing_file] = ['required file']
#api.errorhandler(ValidationException)
def handle_validation_exception(validation_error):
from flask import jsonify, make_response
return make_response(jsonify({'fields': validation_error.body_error}), 400)
This way is also possible.
#api.errorhandler(ValidationException)
def handle_validation_exception(validation_error):
return "{'fields': validation_error.body_error}", 400
The DatabaseException works fine and returns 400 Bad Request with a plaintext body.
Have fun ^2.
DatabaseException worked perfectly for me.
In the second solution, you try to return a Python dictionary. I assume that the missing quotes cause the error. Maybe you could use jsonify and make_response.
#api.errorhandler(DatabaseException)
def handle_validation_exception(validation_error):
from flask import jsonify, make_response
return make_response(jsonify({ 'fields': validation_error.query_error }), 400)
Have fun!
I have a python flask app, pseudocode shown below.
#app.route('/predict', methods=['POST'])
def transformation():
try:
var1 = function_1()
# do something with var1
var2 = function_2()
except Exception as e:
return jsonify({'message': '{}: {}'.format(type(e).__name__, e)}), 500
As you see, the POST call handler returns a generic exception message back to the client. But I want to customize those exception messages based on whether they are coming from function_1 or function_2
I looked into this thread and understand that it is possible to do below -
try:
#something1
#something2
except ExceptionType1:
#return xyz
except ExceptionType2:
#return abc
But how would it know that ExceptionType1 is coming from function_1() or function_2(). How should I pass exception from function_1() or function_2() to be caught in the main try-except block?
Given this simple Bottle code:
def bar(i):
if i%2 == 0:
return i
raise MyError
#route('/foo')
def foo():
try:
return bar()
except MyError as e:
response.status_code = e.pop('status_code')
return e
How would one write Bottle middleware so the same exception handling is done implicitly, so that code like this can work identically to above:
#route('/foo')
def foo():
return bar()
You can do this elegantly with a plugin leveraging abort:
from bottle import abort
def error_translation(func):
def wrapper(*args,**kwargs):
try:
func(*args,**kwargs)
except ValueError as e:
abort(400, e.message)
return wrapper
app.install(error_translation)
Bottle respect the wsgi spec. You can use a classic wsgi middleware
from bottle import route, default_app, run, request
# push an application in the AppStack
default_app.push()
#route('/foo')
def foo():
raise KeyError()
# error view
#route('/error')
def error():
return 'Sorry an error occured %(myapp.error)r' % request.environ
# get the bottle application. can be a Bottle() instance too
app = default_app.pop()
app.catchall = False
def error_catcher(environ, start_response):
# maybe better to fake the start_response callable but this work
try:
return app.wsgi(environ, start_response)
except Exception as e:
# redirect to the error view if an exception is raised
environ['PATH_INFO'] = '/error'
environ['myapp.error'] = e
return app.wsgi(environ, start_response)
# serve the middleware instead of the applicatio
run(app=error_catcher)
You can use this instead:
from bottle import error, run, route
#error(500)
def error_handler_500(error):
return json.dumps({"status": "error", "message": str(error.exception)})
#route("/")
def index():
a = {}
a['aaa']
run()