I wrote a decorator that looks like this
def login_required_message(*args, **kwargs):
kwargs.setdefault('message', "You must be logged in to do that.")
return _user_passes_test_message(lambda u: u.is_authenticated(), *args, **kwargs)
But when I try to use it without the () at the end, it fails, unless I rewrite it like this:
def login_required_message(function=None, *args, **kwargs):
kwargs.setdefault('message', "You must be logged in to do that.")
decorator = _user_passes_test_message(lambda u: u.is_authenticated(), *args, **kwargs)
if function: return decorator(function)
else: return decorator
Then the () is optional. So, how do I encapsulate this "optional" functionality into a decorator so that I can decorate my decorators to allow no arguments?
I actually recently wrote a blog post about this - at least, I think it addresses what you're trying to do. For posterity, here's what I came up with:
def opt_arguments(func):
def meta_wrapper(*args, **kwargs):
if len(args) == 1 and callable(args[0]):
return func(args[0])
else:
def meta_func(inner_func):
return func(inner_func, *args, **kwargs)
return meta_func
return meta_wrapper
By the way, in the process of trying to figure out how to do it, I came to the conclusion that this is one of those things that is almost always more complication than necessary ;-)
This answer is inspired by recipe 9.6 of the Python Cookbook
def mydecorator_with_opt_args(func=None, arg1="arg1-default", arg2="arg2-default"):
if func is None:
return partial(mydecorator_with_opt_args, arg1=arg1, arg2=arg2)
#wraps(func)
def wrapper(*args, **kwargs):
print(f"Do something with {arg1} and {arg2}")
return func(*args, **kwargs)
return wrapper
To use it :
#mydecorator_with_opt_args(arg1="arg1-set", arg2="arg2-set")
def passer():
pass
passer()
will output
"Do something with arg1-set and arg2-set"
#mydecorator_with_opt_args
def passer2():
pass
passer2()
will output
"Do something with arg1-default and arg2-default"
To understand what happens if you specify no args (passer2):
Putting a decorator is the same as writing passer2 = mydecorator_with_opt_args(passer2) which means the if func is None is ignored and you apply the decorator with the default arguments
However if you specify args (passer), Putting a decorator is the same as writing passer = mydecorator_with_opt_args(arg1="arg1-set", arg2="arg2-set")(passer)
mydecorator_with_opt_args(arg1="arg1-set", arg2="arg2-set") returns partial(mydecorator_with_opt_args, arg1="arg1-set", arg2="arg2-set") and then partial(mydecorator_with_opt_args, arg1="arg1-set", arg2="arg2-set")(passer) is equivalent to mydecorator_with_opt_args(func=passer, arg1="arg1-set", arg2="arg2-set")
Note that you need to specify the keyword arguments.
Related
I would like to improve the socketio.event decorator to make it print the event fired and its parameters.
I have a Manager class which has a self.sio: socketio.Server attribute. I try to define a new decorator as a Manager method such as it returns a function decorated by self.sio.eventand which also prints its data. I have tried this solution, but it does not work :
def event(self, func):
#self.sio.event
def wrapper(*args, **kwargs):
print(f'[{func.__name__}] : {args} {kwargs}')
func(*args, **kwargs)
return wrapper
Any recommendation ?
I think something like this should work for you:
def event(self, func):
def wrapper(*args, **kwargs):
print(f'[{func.__name__}] : {args} {kwargs}')
return func(*args, **kwargs)
return self.sio.on(func.__name__, wrapper)
You can't really use the #sio.event on the wrapper, because then the event that will be configured is going to be named wrapper. My solution uses the #sio.on decorator, which accepts the event name explicitly.
Here is my code:
name = "Arthur"
message = "Hello!"
def decorate(func_to_decorate):
def wrap(*args, **kwargs):
print ("........................")
func_to_decorate(*args, **kwargs)
print ("........................")
return wrap
#decorate
def send_message(your_name="unassigned", your_message="blank"):
print(your_name)
print(your_message)
send_message(name, message)
My error is in line 20:
send_message(name, message)
TypeError: 'NoneType' object is not callable
My understanding is that the wrapper is "replacing" itself with the function immediately following the decorator. This seems work when I am not passing arguments to the function being decorated, but not with the decorator present.
There are two things wrong with your decorator.
First, there's an indentation mistake:
def decorate(func_to_decorate):
def wrap(*args, **kwargs):
print ("........................")
func_to_decorate(*args, **kwargs)
print ("........................")
return wrap
That return wrap is part of the wrap function body, not part of the decorate function body. So, decorate has no return statement, which means it returns None. Hence the error you see: the decorator is in fact "replacing" the wrapped function with the wrapper it returns—but that wrapper is None, so you end up trying to call None as a function.
And meanwhile, you seem to understand that wrap should return something, but that something definitely shouldn't be itself. Usually, what you want to return is the result of the wrapped function (or some post-processed version of that result). In your test, you're only trying to wrap up a function that's used only for side-effects, not to mention that you never get to call the wrapper because of your first problem, so you wouldn't notice this problem yet, but you still want to fix it.
So:
def decorate(func_to_decorate):
def wrap(*args, **kwargs):
print ("........................")
retval = func_to_decorate(*args, **kwargs)
print ("........................")
return retval
return wrap
I'm trying to replace the marshal_with decorator from flask-restful with a decorator that does something before calling marshal_with. My approach is to try to implement a new decorator that wraps marshal_with.
My code looks like:
from flask.ext.restful import marshal_with as restful_marshal_with
def marshal_with(fields, envelope=None):
def wrapper(f):
print("Do something with fields and envelope")
#wraps(f)
def inner(*args, **kwargs):
restful_marshal_with(f(*args, **kwargs))
return inner
return wrapper
Unfortunately this seems to break things... no error messages but my API returns a null response when it shouldn't be. Any insights on what I'm doing wrong?
I don't know the specifics of marshal_with, but it's entirely possible to use multiple decorators on a single function. For instance:
def decorator_one(func):
def inner(*args, **kwargs):
print("I'm decorator one")
func(*args, **kwargs)
return inner
def decorator_two(text):
def wrapper(func):
def inner(*args, **kwargs):
print(text)
func(*args, **kwargs)
return inner
return wrapper
#decorator_one
#decorator_two("I'm decorator two")
def some_function(a, b):
print(a, b, a+b)
some_function(4, 7)
The output this gives is:
I'm decorator one
I'm decorator two
4 7 11
You can modify this little script by adding print statements after each inner function call to see the exact flow control between each decorator as well.
I was doing a couple things wrong here, first, failing to return the output of restful_marshal_with as jonrsharpe pointed out, secondly, failing to understand a decorator written as a class instead of a function, and how to properly pass values to it. The correct code ended up being:
def marshal_with(fields, envelope=None):
def wrapper(f):
print("Do something with fields and envelope")
#wraps(f)
def inner(*args, **kwargs):
rmw = restful_marshal_with(fields, envelope)
return rmw(f)(*args, **kwargs)
return inner
return wrapper
As you can see, in addition to not returning rmw(), I needed to properly initialize the request_marshal_with class before calling it. Finally, it is important to remember that decorators return functions, therefore the arguments of the original function should be passed to the return value of rmw(f), hence the statement return rmw(f)(*args, **kwargs). This is perhaps more apparent if you take a look at the flask_restful.marshal_with code here.
I'm trying to write a function decorator that wraps functions. If the function is called with dry=True, the function should simply print its name and arguments. If it's called with dry=False, or without dry as an argument, it should run normally.
I have a toy working with:
from functools import wraps
def drywrap(func):
#wraps(func)
def dryfunc(*args, **kwargs):
dry = kwargs.get('dry')
if dry is not None and dry:
print("Dry run {} with args {} {}".format(func.func_name, args, kwargs))
else:
if dry is not None:
del kwargs['dry']
return func(*args, **kwargs)
return dryfunc
#drywrap
def a(something):
# import pdb; pdb.set_trace()
print("a")
print(something)
a('print no dry')
a('print me', dry=False)
a('print me too', dry=True)
...but when I put this into my application, the print statement is never run even when dry=True in the arguments. I've also tried:
from functools import wraps
def drywrap(func):
#wraps(func)
def dryfunc(*args, **kwargs):
def printfunc(*args, **kwargs):
print("Dry run {} with args {} {}".format(func.func_name,args,kwargs))
dry = kwargs.get('dry')
if dry is not None and dry:
return printfunc(*args, **kwargs)
else:
if dry is not None:
del kwargs['dry']
return func(*args, **kwargs)
return dryfunc
I should note that as part of my applications, the drywrap function is part of utils file that is imported before being used...
src/build/utils.py <--- has drywrap
src/build/build.py
src/build/plugins/subclass_build.py <--- use drywrap via "from build.utils import drywrap"
The following github project provides "a useful dry-run decorator for python" that allows checking "the basic logic of our programs without running certain operations that are lengthy and cause side effects."
see https://github.com/haarcuba/dryable
This feels like a solution.
def drier(dry=False):
def wrapper(func):
def inner_wrapper(*arg,**kwargs):
if dry:
print(func.__name__)
print(arg)
print(kwargs)
else:
return func(*arg,**kwargs)
return inner_wrapper
return wrapper
#drier(True)
def test(name):
print("hello "+name)
test("girish")
#drier()
def test2(name,last):
print("hello {} {}".format(name,last))
But as you can see even when you dont have any arguments you have to give #drier() instead of #drier .
Note : Using python 3.4.1
Please consider the following simplified example:
permitted = True
class is_allowed(object):
def __init__(self, some_arg):
# this is actually needed in the complete code
self.some_arg = some_arg
def __call__(self, f):
if permitted == False:
raise Exception("not authenticated to do that")
def wrapped_f(*args, **kwargs):
f(*args, **kwargs)
return wrapped_f
#is_allowed("blah")
def print_hi():
print("hi")
print_hi()
permitted = False
print_hi()
I guess the problem is that the decorator is only called once when the function print_hi() is defined. Because of that the change of the global variable has no effect. Is there any way to circumwent this behaviour?
Move the check inside of the wrapped_f
def __call__(self, f):
def wrapped_f(*args, **kwargs):
if not permitted:
raise Exception("not authenticated to do that")
f(*args, **kwargs)
return wrapped_f
Outside of the wrapped_f, it is checked at the creation of the function. Inside, it becomes part of the body of the new callable, which means it will be checked each time there is a call.
You want to realize that wrapped_f will be called instead of print_hi, so you want any behavior that should be included in the function to go inside of that.