Use a decorator as a wrapper - python

I have a decorator that I use on a class method. However I would like to use the same decorator but as a wrapper instead.
For instance this is how I use the decorator:
myDecorators.py
def authenticate(method):
def authenticate_and_call(service_instance, *args, **kwargs):
print("Authentification success")
#access to service_instance is needed in the decorator
print(service_instance.config)
return method(service_instance, *args, **kwargs)
return authenticate_and_call
myClass.py
from myDecorators import authenticate
class MyService:
def __init__(self, config):
self.config = config
#authenticate #I dont want to use a decorator here
def execute(self):
print(self.config)
print("MyService is executed with authentication")
What I would like to do is something like:
service_callers.py
from myClass import MyService
from myDecorators import authenticate
#use the decorator as a wrapper
def execute_service_with_authentification():
service = MyService({"foo":"bar"})
authenticate(service.execute)(service)
execute_service_with_authentification()
This returns the following error:
File "c:\temp\test\myDecorators.py", line 4, in authenticate_and_call
return method(service_instance, *args, **kwargs)
TypeError: execute() takes exactly 1 argument (2 given)

First remove the #authenticate decorator from your MyService.execute definiton. Then when you need to use the authenticate decorator just wrap your call as: authenticate(service.execute)().
Make sure you change your decorator as well - you're not passing the first argument as self:
def authenticate(method):
def authenticate_and_call(*args, **kwargs):
print("Attempting authentication on object: {}".format(method.__self__))
return method(*args, **kwargs)
return authenticate_and_call

Related

Class method as decorator in python

So, im writing a library for appium tests.
I have a main class that look like this:
class APP():
def __init__(self):
self.variable1 = 1
self.current_view = "main_screen"
def do_operation_A(self):
self.open_side_menu()
do_something
self.current_view = "side_menu"
def do_operation_B(self):
self.open_side_menu()
do_something_else
self.current_view = "side_menu"
def set_landscape(self):
self.open_settings_menu()
configure_landscape
self.current_view = "settings_menu"
The class has a lot of operations so i can do things like app.do_operation_A() or app.set_landscape() without having to first go to each menu manually (resolved inside the class)
To reduce this i want to implement a decorator to do something like this if possible:
class APP():
def __init__(self):
self.variable1 = 1
self.current_view = "main_screen"
#DEFINE_DECORATOR_HERE
#side_menu
def do_operation_A(self):
do_something
#side_menu
def do_operation_B(self):
do_something_else
#settings_menu
def set_landscape(self):
configure_landscape
So i want to implement this decorators to navigate to the corresponding view and also change that variable that i use to check some things in other functions. I have seen some examples with functools.wraps but is not clear to me of how to implement the decorator inside the class to be able to modify this self variables.
Any help?
Using a decorator means that you "wrap" your other function, i.e. you call the decorator and then call the function from inside the decorator.
E.g.:
import functools
def outer(func):
#functools.wraps(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
Upon defining the function, the decorator will be called, returning the inner function.
Whenever you call func, you will in reality call inner, which runs it's own code, including calling the original func function.
So for your use case, you should be able to create decorators similar to:
def settings_menu(func):
#functools.wraps(func)
def inner(self, *args, **kwargs):
self.open_settings_menu()
self.current_view = "settings_menu"
return func(self, *args, **kwargs)
return inner
So a decorator is basically a function that returns another function, right?
def side_menu(func):
def wrapper():
return func()
return wrapper
The wrapper, returned by side_menu, will be called whenever App().do_operationA is called. And whenever that method is called, self is always the first argument. Or rather, the first argument is the instance of App, but whatever. So we could do:
def side_menu(func):
def wrapper(self, *args, **kwargs):
self.open_menu()
func(self, *args, **kwargs)
return wrapper
Now, you don't want the method to present itself as wrapper - you like the name do_operationA. That's where #functools.wraps comes in, it makes things look and work right when decorating.
def side_menu(func):
#functools.wraps
def wrapper(self, *args, **kwargs):
self.open_menu()
func(self, *args, **kwargs)
return wrapper

Implementing __getattr__ inside a class method such as __call__

So I've created a module inspired heavily by amoffat's sh module, where I can import shell programs as functions; unlike sh, my module can do something like git(C = path).commit(m = message) directly, by returning the module class itself as a partial: return partial(bakery, self.program). However, I've lost the ability to run something like ls() without a placeholder method like ls._(), which doesn't look as good. The code in the latter: return output_as_list(args, kwargs).
from functools import partial
def __getattr__(name):
if name == "__path__":
raise AttributeError
return bakery(name)
class bakery:
def __init__(self, program):
self.program = program
def __getattr__(self, subcommand):
return subcommand
#property
def __call__(self):
return partial(bakery, self.program)
My question is this:
Is there a way to tell __call__ you're accessing a dynamic attribute, using a __getattr__ inside it, for example, to implement both the git(C = path).commit(m = message) and ls() scenarios? Or to conditionally return a partial or an output list depending on whether an attribute of __call__ is being accessed?
Edit:
I was wondering if something similar to this might work?
def __call__(self, *args, **kwargs):
class inner_class:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def __getattr__(self, subcommand):
return partial(bakery, self.program)
def __call__(self, *args, **kwargs):
return bakery(*self.args, **self.kwargs)._get_output_as_list(*args, **kwargs)
return inner_class(self.program, *args, **kwargs)
Edit 2:
I suppose I could just convert the individual __call__ functions to subclasses, and import from whichever is necessary.

How to Create a Decorator Class that takes Arguments while Using `functools.wraps` and Preserving Access to the Decorated Instance

1. My Requirements
The Decorator Class should use functools.wraps so it has proper introspection and organization for later.
Access to the decorated instance should be possible.
In the example below, I do it by passing a wrapped_self argument to the __call__ method.
As the title states, the Decorator Class must have parameters that you can tune for for each method.
2. An Example of What It Would Look Like
The ideal situation should look something like this:
class A():
def __init__(self):
...
#LoggerDecorator(logger_name='test.log')
def do_something(self):
...
with the Decorator Class being, so far (basic logger decorator based on a recipe coming from David Beazley's Python Cookbook):
class LoggerDecorator():
def __init__(self, func, logger_name):
wraps(func)(self)
self.logger_name = logger_name
def config_logger(self):
... # for example, uses `self.logger_name` to configure the decorator
def __call__(self, wrapped_self, *args, **kwargs):
self.config_logger()
wrapped_self.logger = self.logger
func_to_return = self.__wrapped__(wrapped_self, *args, **kwargs)
return func_to_return
def __get__(self, instance, cls):
if instance is None:
return self
else:
return types.MethodType(self, instance)
3. How Do I Fix It?
The error I'm getting refers to __init__ not recognizing a third argument apparently:
TypeError: __init__() missing 1 required positional argument: 'func'
It has been suggested to me that I should be putting func in the __call__ method. However, if I put it there as a parameter, wrapped_self isn't properly read as a parameter and I get this error:
__call__() missing 1 required positional argument: 'wrapped_self'
I've tried many things to fix this issue, including: putting wraps(func)(self) inside __call__; and many variations of this very close but not quite filling all of the requirements solution (the problem with it is that I can't seem to be able to access wrapped_self anymore).
Since you're implementing a decorator that takes parameters, the __init__ method of LoggerDecorator should take only the parameters that configures the decorator, while the __call__ method instead should become the actual decorator that returns a wrapper function:
class LoggerDecorator():
def __init__(self, logger_name):
self.logger_name = logger_name
self.config_logger()
def __call__(self, func):
#wraps(func)
def wrapper(wrapped_self, *args, **kwargs):
wrapped_self.logger = self.logger
func_to_return = func(wrapped_self, *args, **kwargs)
return func_to_return
return wrapper
from functools import wraps
class LoggerDecorator:
def __init__(self, logger):
self.logger = logger
def __call__(self, func, *args, **kwargs):
print func, args, kwargs
# do processing
return func
#LoggerDecorator('lala')
def a():
print 1
The above should work as expected. If you're planning to call the decorator using keyword arguments you can remove the logger from __init__ and use **kwargs which will return a dict of the passed keywork arguments.

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

Python decorator on instance method

Anyone know what is wrong with this code?
def paginated_instance_method(default_page_size=25):
def wrap(func):
#functools.wraps(func)
def inner(self, page=1, page_size=default_page_size, *args, **kwargs):
objects = func(self=self, *args, **kwargs)
return _paginate(objects, page, page_size)
return inner
return wrap
class Event(object):
...
#paginated_instance_method
def get_attending_users(self, *args, **kwargs):
return User.objects.filter(pk__in=self.attending_list)
I get the following error:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/Users/zarathustra/Virtual_Envs/hinge/hinge_services/hinge/api/decorators.py", line 108, in wrap
def inner(self, page=1, page_size=default_page_size, *args, **kwargs):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'Event' object has no attribute '__name__'
The reason why I thought this would work is because, through trial and error, I got the following decorator working like a charm for classmethods:
def paginated_class_method(default_page_size=25):
def wrap(func):
#functools.wraps(func)
def inner(cls, page=1, page_size=default_page_size, *args, **kwargs):
objects = func(cls=cls, *args, **kwargs)
return _paginate(objects, page, page_size)
return inner
return wrap
paginated_instance_method is not a decorator, it is a function that returns a decorator. So
#paginated_instance_method()
def get_attending_users(self, *args, **kwargs):
(Note the parentheses.)
Your decorator has an extra level of indirection which is throwing things off. When you do this:
#paginated_instance_method
def get_attending_users(self, *args, **kwargs):
return User.objects.filter(pk__in=self.attending_list)
You are doing this:
def get_attending_users(self, *args, **kwargs):
return User.objects.filter(pk__in=self.attending_list)
get_attending_users = paginated_instance_method(get_attending_users)
That is what decorators do. Note that paginated_instance_method is called with get_attending_users as its argument. That means that in your decorator, the argument default_page_size is set to the function get_attending_users. Your decorator returns the function wrap, so get_attending_users is set to that wrap function.
Then when you then call Event().get_attending_users() it calls wrap(self), where self is your Event instance. wrap is expecting the argument to be a function, and tries to return a new function wrapping that function. But the argument isn't a function, it's an Event object, so functools.wrap fails when trying to wrap it.
I have a hunch that what you're trying to do is this:
#paginated_instance_method()
def get_attending_users(self, *args, **kwargs):
return User.objects.filter(pk__in=self.attending_list)
That is, you want paginated_instance_method to take an argument. But even if you want to use the default value of that argument, you still have to actually call paginated_instance_method. Otherwise you just pass the method as the argument, which is not what paginated_instance_method is expecting.
The reason it "worked" for a classmethod is that a classmethod takes the class as the first argument, and a class (unlike an instance) does have a __name__ attribute. However, I suspect that if you test it further you'll find it's not really doing what you want it to do, as it's still wrapping the class rather than the method.
This is really easy, but tricky at first view. Look at pep 318.
#dec2
#dec1
def func(arg1, arg2, ...):
pass
This is equivalent to:
def func(arg1, arg2, ...):
pass
func = dec2(dec1(func))
You have an extra wrapper, which takes a decorator's args to use it in the wrapped functions (closure design pattern). So your decorator will look like this:
#dec(arg=True)
def func(arg1, arg2, ...):
pass
Equivalent to:
def func(arg1, arg2, ...):
pass
func = dec(arg=True)(func)

Categories