CL40
CL40

Reputation: 598

Class-wide exception handler

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

Answers (1)

jfaccioni
jfaccioni

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

Related Questions