Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding an argument to a decorator

I have this decorator, used to decorate a django view when I do not want the view to be executed if the share argument is True (handled by middleware)

class no_share(object):
    def __init__(self, view):
        self.view = view

    def __call__(self, request, *args, **kwargs):
        """Don't let them in if it's shared"""

        if kwargs.get('shared', True):
            from django.http import Http404
            raise Http404('not availiable for sharing')

        return self.view(request, *args, **kwargs)

It currently works like this:

@no_share
def prefs(request, [...])

But I'm wanting to expand the functionality a little bit, so that it will work like this:

@no_share('prefs')
def prefs(request, [...])

My question is how can I modify this decorator class so that it accepts extra arguments?

like image 303
priestc Avatar asked Dec 05 '09 00:12

priestc


People also ask

Can decorator have arguments?

The decorator arguments are accessible to the inner decorator through a closure, exactly like how the wrapped() inner function can access f . And since closures extend to all the levels of inner functions, arg is also accessible from within wrapped() if necessary.

Can you give a good use case for using a decorator in Python?

When to Use a Decorator in Python. You'll use a decorator when you need to change the behavior of a function without modifying the function itself. A few good examples are when you want to add logging, test performance, perform caching, verify permissions, and so on.

What is the correct syntax for using a decorator?

Decorators use a special syntax in JavaScript, whereby they are prefixed with an @ symbol and placed immediately before the code being decorated.


2 Answers

I hope this article by Bruce Eckel helps.

Upd: According to the article your code will look like this:

class no_share(object):
    def __init__(self, arg1):
        self.arg1 = arg1

    def __call__(self, f):
        """Don't let them in if it's shared"""

        # Do something with the argument passed to the decorator.
        print 'Decorator arguments:', self.arg1

        def wrapped_f(request, *args, **kwargs):
            if kwargs.get('shared', True):
                from django.http import Http404
                raise Http404('not availiable for sharing')
            f(request, *args, **kwargs)            
        return wrapped_f

to be used as desired:

@no_share('prefs')
def prefs(request, [...])
like image 175
Li0liQ Avatar answered Oct 06 '22 00:10

Li0liQ


The Bruce Eckel article that Li0liQ mentioned should be helpful in figuring this out. Decorators with and with out arguments behave slightly differently. The big difference is that when you pass arguments the __call__ method gets called once on __init__ and it is supposed to return a function that will be called whenever the decorated function is called. When there are no arguments, the __call__ method gets called every time the decorated function is called.

What does this mean for you? The way that __init__ and __call__ are called for a @no_arg_decorator is different than they are called for a @decorator('with','args').

Here are two decorators that might do the trick for you. You can get away with just the @no_share_on(...) decorator as long as you always use it with the parentheses.

def sharing_check(view, attr_name, request, *args, **kwargs):
    if kwargs.get(attr_name, True):
        from django.http import Http404
        raise Http404('not availiable for sharing')

    return view(request, *args, **kwargs)

class no_share(object):
    """A decorator w/o arguments.  Usage:
    @no_share
    def f(request):
        ...
    """
    def __init__(self, view):
        self.view = view

    def __call__(self, request, *args, **kwargs):
        return sharing_check(self.view, 'sharing', request, *args, **kwargs)

class no_share_on(object):
    """A decorator w/ arguments.  Usage:
    @no_share_on('something')
    def f(request):
        ...
    --OR--
    @no_share_on()
    def g(request):
        ...
    """
    def __init__(self, attr_name='sharing'):
        self.attr_name = attr_name

    def  __call__(self, view):
        def wrapper_f(request, *args, **kwargs):
            return sharing_check(view, self.attr_name, request, *args, **kwargs)
like image 27
istruble Avatar answered Oct 05 '22 23:10

istruble