Pass multiple arguments to a decorator in Python - 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.

Related

Python mixin/decorator/__metaclass__ for base class enhancement

I am implementing a content-aware caching system for a Django REST API. I would like to develop a component which can be added to existing views that would modify the behavior of the base class by checking the cache and falling back to the base class behavior on a miss.
basically, I have something like this:
class Base:
def get(self, request, *args, **kwargs):
....
return Response
class AnotherBase:
def get(self, request, *args, **kwargs):
....
return Response
class Derived(Base):
pass
class OtherDerived(AnotherBase):
pass
and my initial thought was to do something along the lines of
class Cacheable:
def get(self, request, *args, **kwargs):
cache_key = self.get_cache_key(request)
base_get = #.... and this is the problem
return cache.get(cache_key, base_get(request, *args, **kwargs))
def get_cache_key(self, request):
# .... do stuff
class Derived(Cacheable, Base):
pass
class AnotherDerived(Cacheable, AnotherBase):
pass
So clearly this doesn't work, as I don't know how, or if it's possible, or if it's advisable to access the sibling superclass(es) from a mixin.
My goal is an implementation that allows me to add caching behavior to existing views without touching the internals of the existing classes.
Given a view class, C, s.t. C.get(request, *args, **kwargs) -> Response, is there a function, F, s.t. F(C).get(... does the cache check before falling back to C.get? And in this quasi-formal notation, we'll say that adding a mixin to the leftmost parent class in the class definition counts as a function.
Is it more appropriate to use method decorators? or how would a class decorator work?
And then I've seen references to __metaclass__ in researching this, but I'm not clear on what that approach looks like.
This is Python 3.6
Simple example:
def Base:
def _get_data(self):
# get the data eg from database
return self._get_data_native()
def get(self, request, *args, **kwargs):
return Response(self._get_data())
def Cacheable(Base):
def _get_data(self):
# get from cache ...
result = ...
if result is None:
# or from base ...
result = ...
return result
def Derived(Cacheable):
def _get_data_native(self):
# get the data eg from database
...
By inheriting from Cacheable, you include the caching here, because _get_data is overwritten there.
For this problem, you don't need metaclasses or decorators, if you want to just add caching at one place.
Of course, a decorator could be used for including caching in an even more generic way.
See for example this answer: Is there a decorator to simply cache function return values?
The answer was a decorator and some Django-specific libraries.
from django.utils.decorators import method_decorator
from django.core.cache import cache
def cached_get(cache_key_func=None):
"""
Decorator to be applied via django.utils.decorators.method_decorator
Implements content-aware cache fetching by decorating the "get" method
on a django View
:param cache_key_func: a function of fn(request, *args, **kwargs) --> String
which determines the cache key for the request
"""
def decorator(func):
def cached_func(request, *args, **kwargs):
assert cache_key_func is not None, "cache_key_function is required"
key = cache_key_func(request, *args, **kwargs)
result = cache.get(key)
if result is None:
return func(request, *args, **kwargs)
return Response(result)
return cached_func
return decorator
#method_decorator(cached_get(cache_key_func=get_cache_key), name="get")
class SomeView(BaseView):
...
def get_cache_key(request):
# do arbitrary processing on request, the following is the naïve melody
key = urllib.urlencode(request.query_params)
return key
So the solution is to use Django's built-in method_decorator which applies its first argument, a decorator, to the decorated class's method, named by the second argument, name, to method_decorator. I define a higher-order function, cached_get, which takes another function as its argument, and returns a curried function (closure, so called). By calling this, with the function get_cache_key (and not, mind you, invoking that function) I have a decorator that will be applied to the 'get' method on SomeView.
The decorator itself is a straightforward Python decorator -- in this application, it is cached_func and the original, undecorated get method is func. Thus, cached_func replaces SomeView.get, so when SomeView.get is called, it first checks the cache, but falls back to the undecorated method on a miss.
I'm hopeful this approach provides a balance of generic applicability with content-aware key derivation.
My two cents:
You're walking into obscure territory here. Get familiar with all the related concepts, try a few, then decide.
Here is a good tutorial about metaclasses.
Here there's one about decorators.
I'm in no way affiliated to that site.

Calling login_required inside decorator

I'm trying to write a custom decorator, which will do some checks to see if a user has permission to access a page, but prior to that, the user needs to be authenticated. I thought of using Django's login_required decorator, and then doing my custom logic, however I can't seem to find any way to call the login_required decorator inside my own.
I do know that there are alternatives, like decorating my view like this:
#login_required
#my_custom_decorator
def my_view(request):
pass
Or checking for user.is_authenticated() inside my decorator:
def my_custom_decorator(view_func):
#wraps(view_func)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated():
redirect(...)
However I'd like to user Django's logic from login_required.
Is there any way to call a decorator inside a decorator, or any other way to implement my logic without using 2 separate decorators?
Your decorator returns a function, e.g.
def my_custom_decorator(view_func):
#wraps(view_func)
def wrapper(request, *args, **kwargs):
...
return wrapper
You can wrap that function in login_required before you return it:
def my_custom_decorator(view_func):
#wraps(view_func)
def wrapper(request, *args, **kwargs):
...
return login_required(wrapper)

passing function into a class and using it as a decorator of class methods

first I created some user management functions I want to use everywhere, and bound them to cherrypy, thinking I could import cherrypy elsewhere and they would be there. Other functions seem to import fine this way, when not used as decorators.
from user import validuser
cherrypy.validuser = validuser
del validuser
that didn't work, so next I tried passing the function into the class that is a section of my cherrypy site (/analyze) from the top level class of pages:
class Root:
analyze = Analyze(cherrypy.validuser) #maps to /analyze
And in the Analyze class, I referred to them. This works for normal functions but not for decorators. why not?
class Analyze:
def __init__(self, validuser):
self.validuser = validuser
#cherrypy.expose
#self.validuser(['uid'])
def index(self, **kw):
return analysis_panel.pick_data_sets(user_id=kw['uid'])
I'm stuck. How can I pass functions in and use them as decorators. I'd rather not wrap my functions like this:
return self.validuser(analysis_panel.pick_data_sets(user_id=kw['uid']),['uid'])
thanks.
ADDED/EDITED: here's what the decorator is doing, because as a separate issue, I don't think it is properly adding user_id into the kwargs
def validuser(old_function, fetch=['uid']):
def new_function(*args, **kw):
"... do stuff. decide is USER is logged in. return USER id or -1 ..."
if USER != -1 and 'uid' in fetch:
kw['uid'] = user_data['fc_uid']
return old_function(*args, **kw)
return new_function
only the kwargs that were passed in appear in the kwargs for the new_function. Anything I try to add isn't there. (what I'm doing appears to work here How can I pass a variable in a decorator to function's argument in a decorated function?)
The proper way in CherryPy to handle a situation like this is to have a tool and to enable that tool on the parts of your site that require authentication. Consider first creating this user-auth tool:
#cherrypy.tools.register('before_handler')
def validate_user():
if USER == -1:
return
cherrypy.request.uid = user_data['fc_uid']
Note that the 'register' decorator was added in CherryPy 5.5.0.
Then, wherever you wish to validate the user, either decorate the handler with the tool:
class Analyze:
#cherrypy.expose
#cherrypy.tools.validate_user()
def index(self):
return analysis_panel.pick_data_sets(user_id=cherrypy.request.uid)
Or in your cherrypy config, enable that tool:
config = {
'/analyze': {
'tools.validate_user.on': True,
},
}
The function/method is defined in the class, it doesn't make sense to decorate it with an instance variable because it won't be the same decorator for each instance.
You may consider using a property to create the decorated method when it is accessed:
#property
def index(self):
#cherrypy.expose
#self.validuser(['uid'])
def wrapped_index(**kw):
return analysis_panel.pick_data_sets(user_id=kw['uid'])
return wrapped_index
You may also consider trying to apply lru_cache to save the method for each instance but I'm not sure how to apply that with the property.

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'>

Django decorators with arguments for multiple views

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

Categories