16

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
Community
  • 1
  • 1
Jotham Apaloo
  • 648
  • 7
  • 10
  • 2
    Possible duplicate of [Is there a way to get the function a decorator has wrapped?](http://stackoverflow.com/questions/1545178/is-there-a-way-to-get-the-function-a-decorator-has-wrapped) – Oin Feb 15 '16 at 16:21

3 Answers3

9

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?

Community
  • 1
  • 1
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • That works nicely - though I may need an alternative when there's more decoration. Answer accepted and question updated with usage – Jotham Apaloo Feb 03 '16 at 00:06
  • @JothamApaloo as long as they're all `@wraps`, you can just keep going until you reach something that doesn't have that attribute! Note that you shouldn't put answers in the question. – jonrsharpe Feb 03 '16 at 07:18
  • Thanks @jonrsharpe. I was thinking along the lines of canceling only `@with_required` but letting other ones persist. I'll deal with it when need comes and maybe update this. Also, I wanted to put the answer in a comment but it didn't format correctly. Is the protocol to just add it as an answer, then? I guess that's obvious, since its an 'answer', bear with me and verify :) – Jotham Apaloo Feb 03 '16 at 15:01
2

Another common option is to have the decorated function keep a copy of the original function that can be accessed:

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

Now, for any function that has been decorated, you can access its original_fn attribute to get a handle to the original, un-decorated function.

In that case, you could define some type of dispatcher that either makes plain function calls (when you are happy with the decorator behavior) or makes calls to thing.original_fn when you prefer to avoid the decorator behavior.

Your proposed method is also a valid way to structure it, and whether my suggestion is "better" depends on the rest of the code you're dealing with, who needs to read it, and other kinds of trade-offs.

ely
  • 74,674
  • 34
  • 147
  • 228
0

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?

Use the undecorated library. It digs through all the decorators and returns just the original function. The docs should be self-explanatory, basically you just call: undecorated(your_decorated_function)

Oin
  • 6,951
  • 2
  • 31
  • 55