Python Decorator with Arguments only called once - python

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.

Related

Why is this function decorator failing?

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

Wrapping a decorator, with arguments

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.

Python decorator for function completion

Is it possible to develop a decorator to determine if a function successfully completed without crashing ? Something like the decorator below but it must detect if the function ran successfully.
def functionStartDecorator():
print("Function has started")
def decoratorWrapper(functionToBeDecorated):
def wrapper(*args, **kwargs):
#Todo log function has run
return functionToBeDecorated(*args, **kwargs)
return wrapper
return decoratorWrapper
As said in comments, simplest would be to wrap your function in a try/except. If your function returns nothing and simply operate by side-effect, it should be straightforward to have the decorated function return the status of that function run:
def tryfn(f):
def decorated(*args, **kwargs):
try:
f(*args, **kwargs)
return 'Ran successfully'
except Exception as e:
return 'Error: {}'.format(e)
return decorated
You can then tweak the exact return type of decorated: maybe return a boolean, maybe append the status of the function to the return of f, maybe log things... The core principle will probably still be around a try/except within the decorated function. For instance, if you want to return both your return value (or None if it failed) and whether this was a success:
def tryfn(f):
def decorated(*args, **kwargs):
try:
res = f(*args, **kwargs)
return True, res
except:
return False, None
return decorated
Note that in this case, you're losing the information about the exact error, so you might want to expand the return to include success, failure, error, etc... that becomes a trade-off between convenience and completeness, which will depend on your exact problem.
I did something like this. Do you see any issues with this code ?
def functionStartDecorator():
def decoratorWrapper(functionToBeDecorated):
def wrapper(*args, **kwargs):
try:
print("Function has started")
result=functionToBeDecorated(*args, **kwargs)
print("Function has complete")
return result
except:
print ("Function failed")
return wrapper
return decoratorWrapper
#functionStartDecorator()
def simplefunc():
return "somthing"

How can I get a Python decorator to run after the decorated function has completed?

I want to use a decorator to handle auditing of various functions (mainly Django view functions, but not exclusively). In order to do this I would like to be able to audit the function post-execution - i.e. the function runs as normal, and if it returns without an exception, then the decorator logs the fact.
Something like:
#audit_action(action='did something')
def do_something(*args, **kwargs):
if args[0] == 'foo':
return 'bar'
else:
return 'baz'
Where audit_action would only run after the function has completed.
Decorators usually return a wrapper function; just put your logic in the wrapper function after invoking the wrapped function.
def audit_action(action):
def decorator_func(func):
def wrapper_func(*args, **kwargs):
# Invoke the wrapped function first
retval = func(*args, **kwargs)
# Now do something here with retval and/or action
print('In wrapper_func, handling action {!r} after wrapped function returned {!r}'.format(action, retval))
return retval
return wrapper_func
return decorator_func
So audit_action(action='did something') is a decorator factory that returns a scoped decorator_func, which is used to decorate your do_something (do_something = decorator_func(do_something)).
After decorating, your do_something reference has been replaced by wrapper_func. Calling wrapper_func() causes the original do_something() to be called, and then your code in the wrapper func can do things.
The above code, combined with your example function, gives the following output:
>>> do_something('foo')
In wrapper_func, handling action 'did something' after wrapped function returned 'bar'
'bar'
Your decorator can handle it here itself, like
def audit_action(function_to_decorate):
def wrapper(*args, **kw):
# Calling your function
output = function_to_decorate(*args, **kw)
# Below this line you can do post processing
print "In Post Processing...."
return output
return wrapper

No Argument Decorator Decorator?

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.

Categories