Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applying a decorator to every method in a class?

I have decorator @login_testuser applied to method test_1():

class TestCase(object):
    @login_testuser
    def test_1(self):
        print "test_1()"

Is there a way I can apply @login_testuser on every method of the class prefixed with "test_"?

In other words, the decorator would apply to test_1(), test_2() methods below, but not on setUp().

class TestCase(object):
    def setUp(self):
        pass

    def test_1(self):
        print "test_1()"

    def test_2(self):
        print "test_2()"
like image 432
Jeff Bauer Avatar asked Feb 10 '10 14:02

Jeff Bauer


People also ask

Can decorator be used in a class?

In Python, decorators can be either functions or classes. In both cases, decorating adds functionality to existing functions.

How do you apply a decorator to a class method in Python?

To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function.

How do you use the class method decorator?

In Python, the @classmethod decorator is used to declare a method in the class as a class method that can be called using ClassName. MethodName() . The class method can also be called using an object of the class. The @classmethod is an alternative of the classmethod() function.

What is a class method decorator?

The @classmethod decorator is a built-in function decorator which is an expression that gets evaluated after your function is defined. The result of that evaluation shadows your function definition. A class method receives the class as the implicit first argument, just like an instance method receives the instance.


3 Answers

In Python 2.6, a class decorator is definitely the way to go. e.g., here's a pretty general one for these kind of tasks:

import inspect

def decallmethods(decorator, prefix='test_'):
    def dectheclass(cls):
        for name, m in inspect.getmembers(cls, inspect.isfunction):
            if name.startswith(prefix):
                setattr(cls, name, decorator(m))
        return cls
    return dectheclass


@decallmethods(login_testuser)
class TestCase(object):
    def setUp(self):
        pass

    def test_1(self):
        print("test_1()")

    def test_2(self):
        print("test_2()")

will get you what you desire. In Python 2.5 or worse, the @decallmethods syntax doesn't work for class decoration, but with otherwise exactly the same code you can replace it with the following statement right after the end of the class TestCase statement:

TestCase = decallmethods(login_testuser)(TestCase)
like image 112
Alex Martelli Avatar answered Oct 12 '22 05:10

Alex Martelli


Sure. Iterate all attributes of the class. Check each one for being a method and if the name starts with "test_". Then replace it with the function returned from your decorator

Something like:

from inspect import ismethod, getmembers
for name, obj in getmembers(TestCase, ismethod):
   if name.startswith("test_"):
       setattr(TestCase, name, login_testuser(obj))
like image 38
mthurlin Avatar answered Oct 12 '22 04:10

mthurlin


Are you sure you wouldn't be better off by putting login_testuser's code into setUp instead? That's what setUp is for: it's run before every test method.

like image 34
Ned Batchelder Avatar answered Oct 12 '22 06:10

Ned Batchelder