Should it be possible to wrap a decorated function with another decorator?
I want to wrap some server functions with a decorator that performs some tests, specifically a load of authentication/authorisation tests that just get a little laborious to write out not to mention adding voluminous lines of code.
I can of course do this (which is more explicit) :
@anvil.server.callable
def members_only():
if auth_and_auth() is False:
return False
# If here you are authed
for every function, but that’s not as neat as (something like) this, which doesn’t work :
@anvil.server.callable
@auth_and_auth
def members_only():
# This only runs if authed.
If I put the anvil.server.callable first, I get this (kind of expected) :
anvil.server.NoServerFunctionError:
No server function matching "auth_and_auth" has been registered
If I put it second, it just doesn’t run the decorator.
My inexperience with Python might be showing through here …
Hmmmm…
you seem to be doing exactly what I tried, yet it didn’t work for me. It just didn’t call the decorator. How interesting.
Thanks for sharing - I’m going to pick through that now…
EDIT - are you sure it’s working for you? I can’t get your app to run as it needs a modified users table so i can’t verify it. But I’m doing exactly what you’re doing and it’s just not running the decorator.
I really don’t get this.
Here’s my server module :
import anvil.server
def mydec(func):
def wrapper():
print("mydec before func")
func()
print("mydec after func")
return wrapper
@mydec
@anvil.server.callable
def myfunc():
print("Hi")
I’ve found a workaround, but I think this is just an academic curiosity
Don’t think it’s actually any easier or clearer than abandoning the decorator for inline code.
But, for the sake of it …
You can use what I call a “dispatcher”. So instead of calling a server function directly, you call the dispatcher with the actual function you want as a parameter (and parameters to the function as further parameters).
So, here’s what’s happening. When you add the @anvil.server.callable decorator to a function, by default it registers a server function with the name (strictly, __name__) of the function you decorated. So…
@anvil.server.callable
def my_func():
return 42
… registers a server function called “my_func”. Unfortunately this…
… registers a server function called “wrapper”, because that’s the name of the function passed to the callable decorator. And @anvil.server.callable must come first, otherwise we won’t call our custom decorator.
The solution here is to manually specify the name of the server function, like so:
As an alternative you could use the functools wraps module which will preserve the __name__ of a wrapped function
from functools import wraps
def my_decorator(f):
@wraps(f) # this will preserve the __name__ of the function when it's wrapped
def wrapper():
return 2 * f()
return wrapper
@anvil.server.callable # no need to manually set the name with functools wrap implemented
@my_decorator
def my_func():
return 42