Reputation: 598
I would like to implement a way to globally handle exceptions in a class coming through my application rather than handle them at the call site because it will cause a lot of duplicate code.
For example's sake say I have a class like so:
class handler():
def throws_value_error(self):
raise ValueError
def throws_not_implemented_error(self):
raise NotImplementedError
# ... Imagine there are hundreds of these
In this handler class I'd like all ValueError
and NotImplementedError
handled the same way - a message saying [ERROR]: ...
where ...
is the details from the particular instance.
Instead of reproducing a TON of:
try:
#...
except Exception as e:
# Catch the instances of the exception and handle them
# in more-or-less the same way here
I'd like to have a single method implementable on the class that takes care of this in general:
def exception_watcher(self):
# Big if...elif...else for handling these types of exceptions
Then, this method would be invoked when any Exception
inside the class is given.
My motivation is to use this to implement a generic sort of controller interface where a controller method could throw a subclass of an exception (for example - BadRequestException
which is subclassed from Error400Exception
). From there, the handler could dispatch a method to properly configure a response with an error code tailored to the type of exception, and additionally return a useful reply to the consumer.
Obviously this is doable without this dispatcher - but this way certainly saves a ton of duplicated code (and therefore bugs).
I've done a lot of digging around the internet and I haven't found anything. Admittedly I am borrowing this pattern from the Spring Boot Framework. I will also accept this being non-pythonic as an answer (as long as you can tell me how to do it right!)
Any help would be appreciated - thank you!
Upvotes: 1
Views: 457
Reputation: 7529
Modifying functions without altering their core functionality is exactly what decorators are for. Take a look at this example and try running it:
def catch_exception(func):
def wrapped(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except ValueError:
self.value_error_handler()
except NotImplementedError:
self.ni_error_handler()
return wrapped
class Handler:
def value_error_handler(self):
print("found value error")
def ni_error_handler(self):
print("found not implemented error")
@catch_exception
def value_error_raiser(self):
raise ValueError
@catch_exception
def ni_error_raiser(self):
raise NotImplementedError
h = Handler()
h.value_error_raiser()
h.ni_error_raiser()
Because of the @catch_exception
decorator, the decorated methods do not raise the errors shown when caled; instead, they call the respective method defined inside wrapped
's try/except block, printing a message.
While this is not exactly what you asked for, I believe this solution is more manageable overall. You can both choose exactly what methods should raise exceptions regularly and which should be handled by catch_exception
(by deciding which methods are decorated), and add future behaviour for other exception types by writing an extra except
block inside wrapped
and adding the respective method to the Handler
class.
Note that this is not a "clean" solution, since wrapped
expects to know that self
is an instance of Handler
, seeing as it calls a bunch of its methods, even though it is a function outside the Handler
class... But hey, that's Python's duck typing for you :-P
Upvotes: 4