How to use decorators on overridable class methods - python

I have a custom class with multiple methods that all return a code. I would like standard logic that checks the returned code against a list of acceptable codes for that method and raises an error if it was not expected.
I thought a good way to achieve this was with a decorator:
from functools import wraps
def expected_codes(codes):
def decorator(f):
#wraps(f)
def wrapper(*args, **kwargs):
code = f(*args, **kwargs)
if code not in codes:
raise Exception(f"{code} not allowed!")
else:
return code
return wrapper
return decorator
then I have a class like so:
class MyClass:
#expected_codes(["200"])
def return_200_code(self):
return "200"
#expected_codes(["300"])
def return_300_code(self):
return "301" # Exception: 301 not allowed!
This works fine, however if I override the base class:
class MyNewClass:
#expected_codes(["300", "301"])
def return_300_code(self):
return super().return_300_code() # Exception: 301 not allowed!
I would have expected the above overriden method to return correctly instead of raise an Exception because of the overridden decorator.
From what I've gathered through reading, my desired approach won't work because the decorator is being evaluated at class definition- however I'm surprised there's not a way to achieve what I wanted. This is all in the context of a Django application and I thought Djangos method_decorator decorator might have taken care of this for me, but I think I have a fundamental misunderstanding of how that works.

TL;DR
Use the __wrapped__ attribute to ignore the parent's decorator:
class MyNewClass(MyClass):
#expected_codes(["300", "301"])
def return_300_code(self):
return super().return_300_code.__wrapped__(self) # No exception raised
Explanation
The #decorator syntax is equivalent to:
def f():
pass
f = decorator(f)
Therefore you can stack up decorators:
def decorator(f):
#wraps(f)
def wrapper(*args, **kwargs):
print(f"Calling {f.__name__}")
f(*args, **kwargs)
return wrapper
#decorator
def f():
print("Hi!")
#decorator
def g():
f()
g()
#Calling g
#Calling f
#Hi!
But if you want to avoid stacking up, the __wrapped__ attribute is your friend:
#decorator
def g():
f.__wrapped__()
g()
#Calling g
#Hi!
In short, if you call one of the decorated parent's method in a decorated method of the child class, decorators will stack up, not override one another.
So when you call super().return_300_code() you are calling the decorated method of the parent class which doesn't accept 301 as a valid code and will raise its own exception.
If you want to reuse the original parent's method, the one that simply returns 301 without checking, you can use the __wrapped__ attribute which gives access to the original function (before it was decorated):
class MyNewClass(MyClass):
#expected_codes(["300", "301"])
def return_300_code(self):
return super().return_300_code.__wrapped__(self) # No exception raised

Related

How to externalize the decorator in python flask application

I have written a python Flask application, which has a class and methods as below.
class PythonSample:
def method1():
pass # does something
def method2():
pass # does something
Now I have written another class which has decorator functions as below.
class PythonAuth:
def oauthAuth():
pass
Now I'm wiring oauthAuth decorator for all the methods of PythonSample class as below
import oauthAuth from PythonAuth
class PythonSample
#oauthAuth
def method1():
pass # does something
#oauthAuth
def method2():
pass # does something
Applying decorator at each method works fine.
Question: Instead of applying oauthAuth decorator to each of the methods. Is there a way to configure in python, as apply oauthAuth decorator to all the methods in a class and exclude certain methods.
Something like include auth for certain URLs and exclude authentication for certain urls
Please ignore the syntax of the python code here.
You can use a class decorator plus some magic.
Decorating Functions
Assume you have a decorator that just logs a string before calling the function.
def log(func):
def logged_func(*args, **kwargs):
print('logged')
func(*args, **kwargs)
return logged_func
Decorating classes
You can use the same trick, but with a class. log_all is a class decorator, cls is a class type. We use vars to walk the class dictionary, and look for methods by using callable(v). Decorate the method with log(v) and use setattr to change the cls definition to the new decorated method. Just like function decorators, return the class in the end.
def log_all(cls):
for k, v in vars(cls).items():
if callable(v):
setattr(cls, k, log(v))
return cls
I am ignoring k essentially, but k is the method name, you could leverage it to achieve your usage scenario.
Full code
Here is a full example, that should make some sense now.
def log(func):
def logged_func(*args, **kwargs):
print('logged')
func(*args, **kwargs)
return logged_func
def log_all(cls):
for k, v in vars(cls).items():
if callable(v):
setattr(cls, k, log(v))
return cls
#log_all
class A:
def method(self):
pass
Every method in class A should be decorated with the log decorator.
>>> a = A()
>>> a.method()
logged

decorator inside class Python

Sorry for my english. I want to create a decorator method that can check each step methods and write it db.
This is my method:
class Test:
#StepStatusManager.logger_steps("GET_LIST") # TypeError: logger_steps() missing 1 required positional argument: 'type'
def get_mails(self):
print("GET_MAIL")
This is my decorator class:
class StepStatusManager:
def __init__(self):
self.db = DB()
def logger_steps(self, type):
def logger_steps(func):
#functools.wraps(func)
def wrapper(*args):
try:
func(*args)
self.db.setStatus(type)
except BaseException as e:
print(e)
return wrapper
return logger_steps
You are trying to call the instance method, logger_steps, directly from the class StepStatusManager, and Python is taking the value "GET_LIST" as the self parameter instead of type. You should create an instance of StepStatusManager and then make the decorator calling the method of the instance instead. It can be as simple as:
manager = StepStatusManager()
class Test:
#manager.logger_steps("GET_LIST")
def get_mails(self):
print("GET_MAIL")
This is now creating an instance of the class and then calling the method on the instance, instead of trying to call the method directly from the class. You can now use manager to decorate as many methods as you want. Also, this would make all decorated methods use the same StepStatusManager, but if you want you can create different instances and use them to decorate different methods; that would allow you to use different self.db for different methods, if you need it.
Another approach could be having the db variable in the class, and make logger_steps a class method instead:
class StepStatusManager:
db = DB()
#classmethod
def logger_steps(cls, type):
def logger_steps(func):
#functools.wraps(func)
def wrapper(*args):
try:
func(*args)
cls.db.setStatus(type)
except BaseException as e:
print(e)
return wrapper
return logger_steps
class Test:
#StepStatusManager.logger_steps("GET_LIST")
def get_mails(self):
print("GET_MAIL")
Note however that this is less flexible, in that it will not allow you to have methods decorated with different managers, should you ever need to. Also, this is mostly equivalent to have, instead of a class, a StepStatusManager module, where db is a module variable and logger_steps is a module function, and that would probably clearer if you want this functionality:
# StepStatusManager.py
# ...
db = DB()
def logger_steps(type):
def logger_steps(func):
#functools.wraps(func)
def wrapper(*args):
try:
func(*args)
cls.db.setStatus(type)
except BaseException as e:
print(e)
return wrapper
return logger_steps
# test.py
import StepStatusManager
class Test:
#StepStatusManager.logger_steps("GET_LIST")
def get_mails(self):
print("GET_MAIL")
Again this is maybe more straightforward but less flexible as your first proposed class-based solution.
EDIT:
Just for completeness and comparison, here is yet another version, similar to the one with #classmethod, but using #staticmethod instead (to understand the subtle difference between these two decorators, check one of the many SO questions about it, e.g. What is the difference between #staticmethod and #classmethod? or Meaning of #classmethod and #staticmethod for beginner?):
class StepStatusManager:
db = DB()
#staticmethod
def logger_steps(type):
def logger_steps(func):
#functools.wraps(func)
def wrapper(*args):
try:
func(*args)
StepStatusManager.db.setStatus(type)
except BaseException as e:
print(e)
return wrapper
return logger_steps
class Test:
#StepStatusManager.logger_steps("GET_LIST")
def get_mails(self):
print("GET_MAIL")
As it frequently happens with #classmethod and #staticmethod, the difference is quite minimal. Their behavior might differ if you are using inheritance, or if you are using a metaclass, or a decorator, or something like that, but otherwise they pretty much the same.

How to deliver a class parameter to a decorator?

It is a Python problem.
I have write a decorator at a util file and then I defined a class with some member functions, one of the function I want to decorate with the decorator, and the decorator have a parameter to control the code running. When I decorate the member function, I just want to given a class member value or a class instance value, how to implements that?
Not exactly sure what you're asking since there's no sample code in your question, but here's a guess:
import functools
#from utilities import my_decorator # in-lined below for simplicity
def my_decorator(control):
def wrapper(function):
" Do something with function or before and after it returns. """
#functools.wraps(function)
def wrapped(*args, **kwargs):
print('doing something with {!r} control before calling {}()'.format(
control, function.__name__))
return function(*args, **kwargs)
return wrapped
return wrapper
class Test(object):
def foo(self):
print('in method foo')
#my_decorator('baz')
def bar(self):
print('in method bar')
test = Test()
test.foo()
test.bar()
Output:
in method foo
doing something with 'baz' control before calling bar()
in method bar

How can I extend a library's decorator?

I would like to extend a library's decorator. I know that I can just call both decorators:
#my_decorator
#lib_decorator
def func():
pass
But I would like to avoid having to pass #lib_decorator to each function each time. I would like my decorator to automatically decorate func() with lib_decorator. How can I do this? Can they be nested?
You can incorporate the lib's decorator within yours. For simple, argument-less decorators, it's rather straight-forward:
def my_decorator():
#lib_decorator # <--- Just include the lib's decorator here
def inner:
func(*args, **kwargs)
return inner
It's a bit trickier for decorators that have arguments. Just remember that your decorator is replacing the decorated function with the inner-most function. So that's the one you need to decorate. So if you call your decorator with args, e.g.
#my_decorator(arg)
def func():
pass
Then decorate the inner function with the lib decorator:
def my_decorator(arg):
def wrapper(func):
#lib_decorator # <--- Just include the lib's decorator here
def inner(*args, **kwargs):
func(*args, **kwargs)
return inner
return wrapper
Or, using the class form of the decorator function:
class my_decorator():
def __init__(self, arg):
pass
def __call__(self, func):
#lib_decorator # <--- Just include the lib's decorator here
def inner(*args, **kwargs):
func(*args, **kwargs)
return inner
You can easily transform a decoration like yours:
#my_decorator
#lib_decorator
def func():
pass
To this simpler decoration, using function composition:
my_composed_decorator = lambda func: my_decorator(lib_decorator(func))
#my_composed_decorator
def func():
pass

Difference between decorator classes and decorator functions

I guess that's how they are called, but I will give examples just in case.
Decorator class:
class decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print 'something'
self.func(*args, **kwargs)
Decorator function:
def decorator(func):
def wrapper(*args, **kwargs):
print 'something'
return func(*args, **kwargs)
return wrapper
Is using one or the other just a matter of taste? Is there any practical difference?
If you can write a function to implement your decorator you should prefer it. But not all decorators can easily be written as a function - for example when you want to store some internal state.
class counted(object):
""" counts how often a function is called """
def __init__(self, func):
self.func = func
self.counter = 0
def __call__(self, *args, **kwargs):
self.counter += 1
return self.func(*args, **kwargs)
#counted
def something():
pass
something()
print something.counter
I've seen people (including myself) go through ridiculous efforts to write decorators only with functions. I still have no idea why, the overhead of a class is usually totally negligible.
It is generally just a matter of taste. Most Python programs use duck typing and don't really care whether the thing they're calling is a function or an instance of some other type, so long as it is callable. And anything with a __call__() method is callable.
There are a few advantages to using function-style decorators:
Much cleaner when your decorator doesn't return a wrapper function (i.e., it returns the original function after doing something to it, such as setting an attribute).
No need to explicitly save the reference to the original function, as this is done by the closure.
Most of the tools that help you make decorators, such as functools.wraps() or Michele Simionato's signature-preserving decorator module, work with function-style decorators.
There may be some programs out there somewhere which don't use duck typing, but actually expect a function type, so returning a function to replace a function is theoretically "safer."
For these reasons, I use function-style decorators most of the time. As a counterexample, however, here is a recent instance in which the class-style decorator was more natural for me.
The proposed class decorator implementation has a slight difference with the function implementation : it will fail on methods
class Decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('something')
self.func(*args, **kwargs)
class A:
#Decorator
def mymethod(self):
print("method")
A().mymethod()
will raise TypeError: mymethod() missing 1 required positional argument: 'self'
To add support of methods, you need to implement the __get__
import types
class Decorator2(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('something')
self.func(*args, **kwargs)
def __get__(self, instance, owner):
if instance is None:
return self
return types.MethodType(self, instance)
class B:
#Decorator2
def mymethod(self):
print("method")
B().mymethod()
will output
class B:...
something
method
The reason it works is that when you access B().mymethod, the __get__ is called first and supplies the bound method. Then __call__ is called
To conclude, provided you define the __get__, class and function implementation can be used the same way. See python cookbook recipe 9.9 for more information.

Categories