I want to design an error handler function in view and use it in different main functions in view.
This error handler function will gather required error info, log the error to the system, and render those info to an error handler page.
Codes in view.py look like:
def error_manage(request, error_title, error_detail):
error_time = datetime.now()
info_table = {'error_title': error_title,
'error_detail': error_detail,
'error_time ': error_time ,
}
logger.error(str(error_detail))
return render(request,'home/error_manage.html', {'error_title': error_title,
'info_table ': display_table})
def my_page_1(request):
try:
var1 = my_calculation_func()
except Exception as ex:
error_manage(request, "Unexpected Error Happened.", ex)
var2 = my_calculation_func2()
var3 = my_calculation_func3()
return render(request,'home/my_page_1.html', {'var1': var1 ,'var2': var2 ,'var3': var3})
The problem here is that when the error occurs in var1, the program will jump into error_manage but will not stop in the return of this function and also will not successfully render into error_manage.html.
it will later jump out of this function and continue run var2 and var3 until it meets the final return in my_page_1().
I know I can simply put the return statement in error_manage to my_page_1 under Exception.
But this error handler will be used in many other functions in view.py and I don't want to repeat it over and over again.
Is there a good solution for this problem? Thank you.
Here's an example of the decorator approach:
class MyError(Exception):
pass
def error_handler(func):
def decorated(*args, **kwargs):
try:
return func(*args, **kwargs)
except MyError as e:
return f"Oh no! {e} happened!"
return decorated
#error_handler
def my_view(request):
if "error" in request:
raise MyError("request was rubbish")
return "Great success!"
for r in ["Fine", "no problem", "An error happened"]:
print(my_view(r))
So running this, we get
Great success!
Great success!
Oh no! request was rubbish happened!
So essentially we use the decorator to catch and handle MyError exceptions, other exceptions will not be handled and will bubble up out of the view as normal to be handled by django's [default] view error handling.
You should make a custom middleware to handle exceptions. See Writing your own middleware [Django docs]:
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_exception(request, exception):
error_time = datetime.now()
info_table = {
'error_detail': exception,
'error_time ': error_time ,
}
logger.error(str(exception))
return render(request,'home/error_manage.html', {'info_table ': display_table})
Now you need to add this middleware to the MIDDLEWARE list in the settings:
MIDDLEWARE = [
# Default middlewares
'path.to.MyMiddleware',
]
Now in your view you should simply not catch the Exception and let the middleware handle it.
Note: With this your approach of passing error_title won't work but you can try to re-raise a new exception from the old one if you
need some customization: raise Exception("Unexpected Error Happened.") from ex
Related
I'm currently refactoring a larger code base which uses Flask. I see a lot of duplication around parameter checks. It's similar to this:
#api.route("/foo/bar/<string:content_id>")
def get_foo(content_id):
try:
content = get_content(content_id)
except:
return jsonify({"status": 404, "error": "not found"}), 404
...
#api.route("/foo/bar/<string:content_id>/update")
def update_foo(content_id):
try: # This is exactly the same!
content = get_content(content_id) # It's just copy-pasted
except: # and this way a lot of duplication
return jsonify({"status": 404, "error": "not found"}), 404 # is added! I want to reduce that.
...
The checks are longer and there are way more of them, but I hope this illustrates the issue well.
Is it possible to factor this check out? Maybe by using a decorator? How would that look like?
What I want
I would love to have something like this:
def get_content_validator():
try:
content = get_content(content_id)
except:
return jsonify({"status": 404, "error": "not found"}), 404
return content
#api.route("/foo/bar/<get_content_validator(string:content_id)>")
def get_foo(content_id):
...
#api.route("/foo/bar/<get_content_validator(string:content_id)>/update")
def update_foo(content):
...
The issue with that is that in one case the value is returned and the normal execution of get_foo / update_foo is done and in other cases the function body of get_foo / update_foo is not executed. This could be possible by checking the return type. If it's a "response" return type, the response is given. In all other cases it's just returned. But that sounds as if it could result in other issues.
I'd just make a decorator:
from functools import wraps
def validated_content(func):
#wraps(func)
def wrapper(content_id, *args, **kwargs):
try:
content = get_content(content_id)
except:
return jsonify(...)
return func(content, *args, **kwargs)
return wrapper
And then your handlers become:
#api.route("...")
#validated_content
def get_foo(content):
# do stuff with content
I have exception handling code that is copied and pasted in multiple places, e.g.
def get(self, id):
try:
response = self.client_module_property.get(id)
except APIItemNotFoundException:
print("'{}' does not exist.".format(id), file=sys.stderr)
sys.exit(1)
except Exception as e:
print(
"Unknown error. To debug run with env var LOG_LEVEL=DEBUG",
file=sys.stderr,
)
_log.error(e)
sys.exit(1)
...
And ...
def delete(self, id):
try:
self.client_module_property.delete(id=id)
except APIItemNotFoundException:
print("'{}' does not exist".format(id), file=sys.stderr)
sys.exit(1)
except Exception as e:
print(
"Unknown error. To debug run with env var LOG_LEVEL=DEBUG",
file=sys.stderr,
)
_log.error(e)
sys.exit(1)
I have investigated decorators but they are NOT a good option for me because I'm introspecting the function parameters in some other code and the decorators change the method signature.
Is there another way I can extract the exception handling code to be reusable?
The comments in this question suggest that context manager could be used here, but I'm not clear what this would look like from browsing the python docs.
The solution needs to work on Python 2.7 and 3.x
In the end, I got this working with wrapt. I'm posting my solution in case it is useful for anyone else.
In the end, it was actually ok to reuse a decorator as long as I could get the method parameters.
My decorator:
#wrapt.decorator
def intercept_exception(wrapped, instance, args, kwargs):
"""Handle Exceptions."""
try:
return wrapped(*args, **kwargs)
except AssertionError as ae:
print(ae, file=sys.stderr)
sys.exit(1)
except (
APIException,
APIItemNotFoundException,
ContainerPlatformClientException,
) as e:
print(e.message, file=sys.stderr)
sys.exit(1)
except Exception as ge:
print(
"Unknown error. To debug run with env var LOG_LEVEL=DEBUG",
file=sys.stderr,
)
_log.error(ge)
sys.exit(1)
A decorated function:
#intercept_exception
def get(self, id):
response = self.client_module_property.get(id)
...
My client code needing to retrieve the method parameters
def get_metadata(self):
...
if six.PY2:
parameter_names = list(inspect.getargspec(function).args)
else:
parameter_names = list(
inspect.getfullargspec(function).args
)
...
I have a class with multiple functions. These functions will handle similar kinds of exceptions. Can I have a handler function and assign this to the functions.
At the end, I would want that there should be no exception handling in functions but on exception the control should go to this handler function.
Class Foo:
def a():
try:
some_code
except Exception1 as ex:
log error
mark file for error
other housekeeping
return some_status, ex.error
except Exception2 as ex:
log error
mark file for error
other housekeeping
return some_status, ex.error
Similarly, other functions would have the same kind of exceptions. I wanted to have all these exception handling in a separate method. Just that the functions should hand over the control to exception handler function.
I can think of calling every function from the wrapper handler function. But that looked very weird to me.
Class Foo:
def process_func(func, *args, **kwargs):
try:
func(*args, **kwargs)
except Exception1 as ex:
log error
mark file for error
other housekeeping
return some_status, ex.error
except Exception2 as ex:
log error
mark file for error
other housekeeping
return some_status, ex.error
def a(*args, **kwargs):
some_code
Is there a better way for doing this?
You can define a function decorator:
def process_func(func):
def wrapped_func(*args, **kwargs):
try:
func(*args, **kwargs)
except ...
return wrapped_func
And use as:
#process_func
def func(...):
...
So that func(...) is equivalent to process_func(func)(...), and errors are handled inside wrapped_func.
I got this-like hierarchy and similar code:
class FrontendException:
pass
class BackendException:
pass
class BackendRequest:
def exec():
raise BackendException()
class Frontend:
def cmd_a():
BackendRequest().exec()
def cmd_b():
BackendRequest().exec()
The goal is to make developer able to operate with Frontend objects and exceptions within functions cmd_x of Frontend.
Basicly, i need a place to handle common BackendException types, to raise FrontendException. For example:
class Frontend:
def cmd_a():
try:
BackendRequest().exec()
except BackendException as e:
raise FrontendException()
And this will be repeated in each cmd_x function! It's so ugly! And it operates with Backend things! I want to remove repeated exception handling.
Any suggestions?
Btw, my solution, but i find it ugly too, so view it after you'll try to suggest me something. Maybe you'll suggest me something about my solution.
class BaseFrontend:
def exec_request(req):
try:
return req.exec()
except BackendException as e:
raise FrontendException
class Frontend(BaseFrontend):
def cmd_a():
result self.exec_request(BackendRequest())
def cmd_b():
result self.exec_request(BackendRequest())
EDIT: Ok, yes, i know, i dont need to create a lot of classes to build simple API. But, let's see what i need in the result:
class APIManager:
def cmd_a(): ...
def cmd_b(): ...
This manager needs to access HTTP REST service to perform each command. So, if i'll get an error during REST request, i'll need to raise exception APIManagerException - i can't leave raw pycurl exception, beacause APIManager user don't knows what pycurl is, he will be confused with getting pycurl error if he will give wrong ID as argument of cmd_x.
So i need to raise informative exceptions for some common cases. Let it be just one exception - APIManagerException. But i dont want to repeat try...except block each time, in each command, to each pycurl request. In fact, i want to process some errors in commands(functions cmd_x), not to parse pycurl errors.
You can create a decorator that wraps all Frontend calls, catches BackendExceptions, and raises FrontendException if they are thrown. (Honestly, though, it's not clear why Frontend and Backend are classes and not a set of functions.) See below:
class FrontendException:
pass
class BackendException:
pass
class BackendRequest:
def exec():
raise BackendException()
class Frontend:
def back_raiser(func):
def wrapped(*args, **kwargs):
try:
func(*args, **kwargs)
except BackendException:
raise FrontendException
return wrapped
#back_raiser
def cmd_a():
BackendRequest().exec()
#back_raiser
def cmd_b():
BackendRequest().exec()
I am currently using the following to raise a HTTP bad request:
raise tornado.web.HTTPError(400)
which returns a html output:
<html><title>400: Bad Request</title><body>400: Bad Request</body></html>
Is it possible to return just the HTTP response code with a custom body?
You may simulate RequestHandler.send_error method:
class MyHandler(tornado.web.RequestHandler):
def get(self):
self.clear()
self.set_status(400)
self.finish("<html><body>My custom body</body></html>")
Tornado calls RequestHandler.write_error to output errors, so an alternative to VisioN's approach would be override it as suggested by the Tornado docs. The advantage to this approach is that it will allow you to raise HTTPError as before.
The source for RequestHandler.write_error is here. Below you can see an example of a simple modification of write_error that will change the set the status code and change the output if you provide a reason in kwargs.
def write_error(self, status_code, **kwargs):
if self.settings.get("serve_traceback") and "exc_info" in kwargs:
# in debug mode, try to send a traceback
self.set_header('Content-Type', 'text/plain')
for line in traceback.format_exception(*kwargs["exc_info"]):
self.write(line)
self.finish()
else:
self.set_status(status_code)
if kwargs['reason']:
self.finish(kwargs['reason'])
else:
self.finish("<html><title>%(code)d: %(message)s</title>"
"<body>%(code)d: %(message)s</body></html>" % {
"code": status_code,
"message": self._reason,
})
It's better to use the standard interface and define your custom message on the HTTPError.
raise tornado.web.HTTPError(status_code=code, log_message=custom_msg)
You can then parse the error in your RequestHandler and check for the message:
class CustomHandler(tornado.web.RequestHandler):
def write_error(self, status_code, **kwargs):
err_cls, err, traceback = kwargs['exc_info']
if err.log_message and err.log_message.startswith(custom_msg):
self.write("<html><body><h1>Here be dragons</h1></body></html>")
def write_error(self, status_code, **kwargs):
#Function to display custom error page defined in the handler.
#Over written from base handler.
data = {}
data['code'] = status_code
data['message'] = httplib.responses[status_code]
# your other conditions here to create data dict
self.write(TEMPLATES.load('error.html').generate(data=data))
when ever self.send_error() call is initiated write_error() function is called by the request handler. So you can create your custom error data dict here and render it to your custom error page.
http.responses[status_code] returns the error code text like "page not found" based on the status code.
Also you can override get_error_html method in your handler.
For example:
import tornado.web
class CustomHandler(tornado.web.RequestHandler):
def get_error_html(self, status_code, **kwargs);
self.write("<html><body><h1>404!</h1></body></html>")
...
def get(self):
...
This exchange clarifies some of the approaches suggested here, and discounts the reason keyword (which I was thinking about trying).
Q: (by mrtn)
"I want to use raise tornado.web.HTTPError(400, reason='invalid request') to pass a custom reason to the error response, and I hope to do this by overriding the write_error (self, status_code, **kwargs) method.
"But it seems that I can only access self._reason inside write_error, which is not what I want. I also tried kwargs['reason'] but that does not exist."
A: (by Tornado lead developer #bendarnell)
"The exception that exposed the error is available to write_error as an exc_info triple in the keyword arguments. You can access the reason field with something like this:
if "exc_info" in kwargs:
e = kwargs["exc_info"][1]
if isinstance(e, tornado.web.HTTPError):
reason = e.reason
"But note that the reason field is essentially deprecated (it is not present in HTTP/2), so it's probably not the best way to do whatever you're trying to do here (HTTPError's log_message field is a little better, but still not ideal). Just raise your own exception instead of using HTTPError; your write_error override can use self.set_status(400) when it sees the right kind of exception."
For json error response i use follow template:
Request handler:
import json
from tornado.web import RequestHandler
from src.lib.errors import HTTPBadRequest
class JsonHandler(RequestHandler):
def prepare(self):
content_type = ''
if "Content-Type" in self.request.headers:
content_type = self.request.headers['Content-Type']
if content_type == 'application/json':
try:
self.request.body = json.loads(self.request.body.decode('utf-8'))
except ValueError:
raise HTTPBadRequest
def write_error(self, *args, **kwargs):
err_cls, err, traceback = kwargs['exc_info']
self.set_status(err.status_code)
if err.description:
self.write_json(err.description)
self.finish()
def set_default_headers(self):
self.set_header('Content-Type', 'application/json')
def write_json(self, response):
self.write(json.dumps(response))
Errors handler:
from typing import Any
from tornado import httputil
class BaseHTTPError(Exception):
def __init__(
self, status_code: int = 500, description=None, *args: Any, **kwargs: Any
) -> None:
if description is None:
description = {}
self.status_code = status_code
self.description = description
self.args = args
self.kwargs = kwargs
def __str__(self) -> str:
message = "HTTP %d: %s" % (
self.status_code,
httputil.responses.get(self.status_code, "Unknown"),
)
return message
class HTTPBadRequest(BaseHTTPError):
def __init__(self, *args, **kwargs):
super().__init__(status_code=400, description={"error": "Bad Request"}, *args, **kwargs)