Django decorators with arguments for multiple views - python

Iam using django decorators in my project.
Iam using multiple views with arguments and i need to call 1 decorator.
I want only one view to call with its arguments once. But the decorators giving the values of every views wherever i used the decorator.
I want the argument belong to the particular views which i called.
My views and decorator as:
def d(msg='my default message'):
def decorator(func):
print msg
def newfn(request, **kwargs):
return func(request, **kwargs)
return newfn
return decorator
#d('This is working')
def company_add(request):
return ...
#d('Dont come')
def company_list(request, comp_id = None):
return ...
If i call company_add views, Iam getting Output as :
This is working
Dont come
But my expected result is
This is working.
Anyone help me to print only the argument belong to the particular views.

When you wrap function with #d(arg), you actually run the body of the d function with msg=arg before running decorated function and of course print the msg. You can place the print statement somewhere else, for example:
def d(msg='my default message'):
def decorator(func):
def newfn(request, **kwargs):
print msg
return func(request, **kwargs)
return newfn
return decorator

The solution is to move print msg to the scope of the newfn wrapper. When you call the decorator with an argument specified, it executes and results in the behavior described above.
def d(msg='my default message'):
def decorator(func):
def newfn(request, **kwargs):
print msg # The message should be printed here.
return func(request, **kwargs)
return newfn
return decorator

Related

Pass multiple arguments to a decorator in Python

I am trying to build a flask app which will be having RBAC feature. For this I have written a decorator which is working fine but it can only take one argument meaning only one access level(e.g WRITE, READ, admin etc), but I want to pass multiple arguments to it. I have tried passing a list but its not taking it. I have never worked with decorators so need help with it. Thanks.
def permission_required(permission):
def decorator(f):
#wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.can(permission):
abort(403)
return f(*args, **kwargs)
return decorated_function
return decorator
def admin_required(f):
return permission_required(Permission.ADMIN)(f)
I as passing it like this:
#role_needed(Permission.VIEW), but I want to have this #role_needed(Permission.VIEW, Permission.WRITE)
My permission class is like this:
class Permission:
VIEW = 'Vew'
WRITE = 'Write'
ADMIN = 'admin'
First, I'd advise that you have a look at some tutorial on decorators, they are pretty cool and you definitely need to understand the basics if you want to use flask. I personally quite like this RealPython tutorial.
Second, you have two solutions : either default second argument or argument packing.
def permission_required(permission1, permission2=None):
...
or
def permission_required(*perms):
...
I personaly way prefer the second option.
Example:
def permission_required(*perms):
def decorator(f):
#wraps(f)
def decorated_function(*args, **kwargs):
for perm in perms:
if not current_user.can(perm):
abort(403)
return f(*args, **kwargs)
return decorated_function
return decorator
I think you missed the point that decorators are just usual functions, taking a function in argument and another one, the later being by design a wrapper around the original one. In your case, permission_required is a decorator factory, that can be used to specialize a decorator based on input arguments. So all you need to do is to allow passing any number of arguments to your decorator factory:
def role_needed(*permissions):
def decorator(f):
#wraps(f)
def decorated_function(*args, **kwargs):
nonlocal permissions # Just to make sure `permission` is available in this scope
# Implement here how to deal with permissions
return f(*args, **kwargs)
return decorated_function
return decorator
which can be called as intended:
#role_needed(Permission.VIEW, Permission.WRITE, ...)
In the function, permissions will store the input Permission as a Python tuple object.

How to use decorators on overridable class methods

I have a custom class with multiple methods that all return a code. I would like standard logic that checks the returned code against a list of acceptable codes for that method and raises an error if it was not expected.
I thought a good way to achieve this was with a decorator:
from functools import wraps
def expected_codes(codes):
def decorator(f):
#wraps(f)
def wrapper(*args, **kwargs):
code = f(*args, **kwargs)
if code not in codes:
raise Exception(f"{code} not allowed!")
else:
return code
return wrapper
return decorator
then I have a class like so:
class MyClass:
#expected_codes(["200"])
def return_200_code(self):
return "200"
#expected_codes(["300"])
def return_300_code(self):
return "301" # Exception: 301 not allowed!
This works fine, however if I override the base class:
class MyNewClass:
#expected_codes(["300", "301"])
def return_300_code(self):
return super().return_300_code() # Exception: 301 not allowed!
I would have expected the above overriden method to return correctly instead of raise an Exception because of the overridden decorator.
From what I've gathered through reading, my desired approach won't work because the decorator is being evaluated at class definition- however I'm surprised there's not a way to achieve what I wanted. This is all in the context of a Django application and I thought Djangos method_decorator decorator might have taken care of this for me, but I think I have a fundamental misunderstanding of how that works.
TL;DR
Use the __wrapped__ attribute to ignore the parent's decorator:
class MyNewClass(MyClass):
#expected_codes(["300", "301"])
def return_300_code(self):
return super().return_300_code.__wrapped__(self) # No exception raised
Explanation
The #decorator syntax is equivalent to:
def f():
pass
f = decorator(f)
Therefore you can stack up decorators:
def decorator(f):
#wraps(f)
def wrapper(*args, **kwargs):
print(f"Calling {f.__name__}")
f(*args, **kwargs)
return wrapper
#decorator
def f():
print("Hi!")
#decorator
def g():
f()
g()
#Calling g
#Calling f
#Hi!
But if you want to avoid stacking up, the __wrapped__ attribute is your friend:
#decorator
def g():
f.__wrapped__()
g()
#Calling g
#Hi!
In short, if you call one of the decorated parent's method in a decorated method of the child class, decorators will stack up, not override one another.
So when you call super().return_300_code() you are calling the decorated method of the parent class which doesn't accept 301 as a valid code and will raise its own exception.
If you want to reuse the original parent's method, the one that simply returns 301 without checking, you can use the __wrapped__ attribute which gives access to the original function (before it was decorated):
class MyNewClass(MyClass):
#expected_codes(["300", "301"])
def return_300_code(self):
return super().return_300_code.__wrapped__(self) # No exception raised

How to determine the class defining a method through introspection

I'm building a rate-limiting decorator in flask using redis stores that will recognize different limits on different endpoints. (I realize there are a number of rate-limiting decorators out there, but my use case is different enough that it made sense to roll my own.)
Basically the issue I'm having is ensuring that the keys I store in redis are class-specific. I'm using the blueprint pattern in flask, which basically works like this:
class SomeEndpoint(MethodView):
def get(self):
# Respond to get request
def post(self):
# Respond to post request
The issue here is that I want to be able to rate limit the post method of these classes without adding any additional naming conventions. In my mind the best way to do this would be something like this:
class SomeEndpoint(MethodView):
#RateLimit # Access SomeEndpoint class name
def post(self):
# Some response
but within the decorator, only the post function is in scope. How would I get back to the SomeEndpoint class given the post function? This is the basic layout of the decorator. That might be confusing, so here's a more concrete example of the decorator.
class RateLimit(object):
"""
The base decorator for app-specific rate-limiting.
"""
def __call__(self, f):
def endpoint(*args, **kwargs):
print class_backtrack(f) # Should print SomeEnpoint
return f(*args, **kwargs)
return endpoint
basically looking for what that class_backtrack function looks like. I've looked through the inspect module, but I haven't found anything that seems to accomplish this.
You can decorate the entire class instead of just the methods:
def wrap(Class, method):
def wrapper(self, *args, **kwargs):
print Class
return method(self, *args, **kwargs)
return method.__class__(wrapper, None, Class)
def rate_limit(*methods):
def decorator(Class):
for method_name in methods:
method = getattr(Class, method_name)
setattr(Class, method_name, wrap(Class, method))
return Class
return decorator
#rate_limit('post')
class SomeEndpoint(object):
def post(self):
pass
class Subclass(SomeEndpoint):
pass
a = Subclass()
a.post()
# prints <class 'SomeEndpoint'>

Pass parameters to a decorator for a class method is to be decorated

I am trying to define a decorator in flask which will finally be decorating class methods passing parameters of that class instance. Here is an example what I really want to do.
from functools import wraps
def user_permission(myname):
def decorator(f):
#wraps(f)
def decorated(*args,**argws):
if myname == 'My Name':
return f(*args,**argws)
else:
return "Not Permitted"
return decorated
return decorator
And my manager class is defined as:
class Manager(flask.views.MethodView):
def __init__(self,name):
self.name = name
#user_permission(self.my_name)
def post(self):
return "Response"
def get(self):
return "Response"
What I am trying to do is pass the class variables to the decorator. Yes "self" is not defined at that point but "#decorator.user_permission(self.my_name)" is what I am trying actually because I am yet not solved with my problem.
I couldn't find solution from HERE.
Does anybody knows about these stuffs please?
As you say, self is not defined at that point. This could never work, as a decorator is executed when the class is defined, whereas you want something to run when the instance method is called.
However I think you're really overcomplicating this. self is passed to the method itself. So there's no reason to try and make it a parameter to the decorator, since the decorator has access to the method arguments. This would be much simpler:
from functools import wraps
def user_permission():
#wraps(f)
def decorated(self, *args, **kwargs):
if self.myname == 'My Name':
return f(self, *args, **kwargs)
else:
return "Not Permitted"
return decorated
self is just an argument. You don't need a decorator factory.
def user_permission(f):
#wraps(f)
def decorated(self, *args, **kw):
if self.myname == 'My Name':
return f(self, *args, **kw)
else:
return "Not Permitted"
return decorated

Accessing self within decorator function within a Python class

I've got a decorator function I'm applying to functions within a class. The decorator is supposed to do a check against an _api member variable. However, I get a global name 'self' is not defined error when I try to do this. What's the right way?
def requires_api(fn):
def wrapped(*args, **kwargs):
if self._api is not None:
return fn(*args, **kwargs)
else:
return None
return wrapped
#requires_api
def do_something(self):
...
The reference to your instance is in your wrapper function's *args so the name self isn't avaialble. Use args[0], or just rewrite it as:
def wrapper(self, *args, **kwargs):
# ...
Naturally, if you do this, your call through to the wrapped function should also include self.
Change
if self._api is not None:
to
if hasattr(args[0], '_api'):
args[0] has self. hasattr is the right way to check for an attribute.
If you try to access an attribute that does not exist, (if self._api does not exist) you will generate an AttributeError.

Categories