Search code examples
pythonflaskdecoratorflask-restful

Is there a pythonic way to skip decoration on a subclass' method?


I have an class which decorates some methods using a decorator from another library. Specifically, the class subclasses flask-restful resources, decorates the http methods with httpauth.HTTPBasicAuth().login_required(), and does some sensible defaults on a model service.

On most subclasses I want the decorator applied; therefore I'd rather remove it than add it in the subclasses.

My thought is to have a private method which does the operations and a public method which is decorated. The effects of decoration can be avoided by overriding the public method to call the private one and not decorating this override. Mocked example below.

I am curious to know if there's a better way to do this. Is there a shortcut for 'cancelling decorators' in python that gives this effect?

Or can you recommend a better approach?

Some other questions have suitable answers for this, e.g. Is there a way to get the function a decorator has wrapped?. But my question is about broader design - i am interested in any pythonic way to run the operations in decorated methods without the effects of decoration. E.g. my example is one such way but there may be others.

def auth_required(fn):
    def new_fn(*args, **kwargs):
        print('Auth required for this resource...')
        fn(*args, **kwargs)
    return new_fn

class Resource:
    name = None

    @auth_required
    def get(self):
        self._get()

    def _get(self):
        print('Getting %s' %self.name)

class Eggs(Resource):
    name = 'Eggs'

class Spam(Resource):
    name = 'Spam'

    def get(self):
        self._get()
        # super(Spam, self)._get()

eggs = Eggs()
spam = Spam()

eggs.get()
# Auth required for this resource...
# Getting Eggs

spam.get()
# Getting Spam

Solution

  • Flask-HTTPAuth uses functools.wraps in the login_required decorator:

    def login_required(self, f):
        @wraps(f)
        def decorated(*args, **kwargs):
            ...
    

    From Python 3.2, as this calls update_wrapper, you can access the original function via __wrapped__:

    To allow access to the original function for introspection and other purposes (e.g. bypassing a caching decorator such as lru_cache()), this function automatically adds a __wrapped__ attribute to the wrapper that refers to the function being wrapped.

    If you're writing your own decorators, as in your example, you can also use @wraps to get the same functionality (as well as keeping the docstrings, etc.).

    See also Is there a way to get the function a decorator has wrapped?