Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect if method is decorated before invoking it

I wrote a Python flow control framework that works very similarly to unittest.TestCase: the user creates a class derived from the framework class, and then writes custom task_*(self) methods. The framework discovers them and runs them:

###################
# FRAMEWORK LIBRARY
###################
import functools

class SkipTask(BaseException):
    pass

def skip_if(condition):
    def decorator(task):
        @functools.wraps(task)
        def wrapper(self, *args, **kargs):
            if condition(self):
                raise SkipTask()
            return task(self, *args, **kargs)
        return wrapper
    return decorator

class MyFramework(object):
    def run(self):
        print "Starting task"
        try:
            self.task()
        except SkipTask:
            print "Skipped task"
        except Exception:
            print "Failed task"
            raise
        else:
            print "Finished task"

#############
# USER SCRIPT
#############
class MyUserClass(MyFramework):
    skip_flag = True

    @skip_if(lambda self: self.skip_flag)
    def task(self):
        print "Doing something"

if __name__ == '__main__':
    MyUserClass().run()

Output:

Starting task
Skipped task

I want to change the framework so that, whenever the condition of @skip_if is True, the wrapper does not print "Starting task". I tried this, but it doesn't work:

def skip_if(condition):
    def decorator(task):
        print "decorating "  + str(task)
        task.__skip_condition = condition
        return task
    return decorator

class MyFramework(object):
    def run(self):
        try:
            if self.task.__skip_condition():
                print "Skipped task"
                return
        except AttributeError:
            print str(self.task) + " is not decorated"
            pass

        print "Starting task"
        try:
            self.task()
        except Exception as e:
            print "Failed task: " + str(e)
            raise
        else:
            print "Finished task"

Output:

decorating <function task at 0x194fcd70>
<bound method MyUserClass.task of <__main__.MyUserClass object at 0x195010d0>> is not decorated
Starting task
Doing something
Finished task

Why wasn't the task skipped instead?

like image 482
crusaderky Avatar asked May 12 '15 11:05

crusaderky


1 Answers

You are using a double underscore name, which has undergone private name mangling in the run method.

When stepping through the debugger, I get:

AttributeError: "'function' object has no attribute '_MyFramework__skip_condition

Don't use double underscore names here; if you rename the function attribute to _skip_condition the code works (provided you bind the condition function or pass in self explicitly):

def skip_if(condition):
    def decorator(task):
        print "decorating "  + str(task)
        task._skip_condition = condition
        return task
    return decorator

class MyFramework(object):
    def run(self):
        try:
            if self.task._skip_condition(self):
                print "Skipped task"
                return
        except AttributeError:
            print str(self.task) + " is not decorated"
            pass

        print "Starting task"
        try:
            self.task()
        except Exception as e:
            print "Failed task: " + str(e)
            raise
        else:
            print "Finished task"

With these changes the output becomes:

decorating <function task at 0x1071a1b90>
Skipped task
like image 152
Martijn Pieters Avatar answered Oct 12 '22 09:10

Martijn Pieters