Question

I have a great number of functions that runs as tests when trying to save the object, so I create a way to make it more convenient to register this methods as constraints (tests) to the object.

I'm confused about using decorators in my solution. What I was intending to do:

  • Create a decorator that add property to the method in the object
  • Decorate the methods that I want to mark as constraints.
  • Loop all the methods on my object and call to execute all that has the flag

Using the decorator don't work, but adding the flag by my self does.

Here is the code:

from functools import wrap

def constraint(func):
    @wraps(func)
    def inner(*args, **kwargs):
        func._is_constraint = True  # Here after returning the function I add a flag
        return func(*args, **kwargs)
    return inner

class Record(object):
    param1 = 100  # some defaults
    param2 = 20

    @constraint  # This dont works
    def check_test1(self):
        if param1 < 0:
            return 'No value less than zero'

    # This works fine
    def check_test2(self):
        if param2 < 0:
            return 'No value less than zero'
    check_test2._is_constraint = True

    def a_random_method(self):
        print 'some random thing'

So what I'm trying as:

>>> rec = Record()
>>> rec.param1 = -100
>>> rec.param2 = -100
>>> for prop in dir(rec):
...     if hasattr(getattr(rec, prop), '_is_constraint'):
...         constraint = getattr(rec, prop)
...             print prop, constraint()
...    
'check_param2: No value less than zero'
>>>

See that check_param1 was not executed.

So, how can I make it work using decorators? is possible?

I'm trying to use on this particular case https://gist.github.com/mariocesar/4684561

Was it helpful?

Solution

Well, first thing I see, you want to be adding the param to the function object in the decorator, not the closure; the closure gets called when the function is called from the decorated scope, so tagging it within the closure is doing it after the fact:

def constraint(func):
    func._is_constraint = True
    @wraps(func)
    def inner(*args, **kwargs):
        return func(*args, **kwargs)
    return inner

Also, if all you are doing is directly calling func within the closure without adding any functionality, just tagging it, the closure is completely superfluous:

 def constraint(func):
    func._is_constraint = True
    return func

In which case, you may be served better by a different pattern all together:

class ConstraintsType(type):

    def __new__(cls, name, bases, attrs):

        attrs['constraint_names'] = []
        for attr in attrs:
            if attr.startswith('constraint_'):
                attrs['constraint_names'].append(attr)
        return super(ConstraintsType, cls).__new__(cls, name, bases, attrs)

class Constraints(object):

    __metaclass__ = ConstraintsType

    @property
    def constraints(self):

        for name in self.constraint_names:
            yield getattr(self, name)

class Record(Constraints):

    def __init__(self, params=(100, 20)):

        self.params = params

    def constraint_greater_than_0(self, value):

        return value > 0

   def run(self):

       for index, value in enumerate(self.params):
           for func in self.constraints:
               if not func(value):
                   print 'param %d (%s) did not satisfy constraint %s' % (index, value, func.__name__)

Record().run()
for value_set in ((-100, -100), (0, 0), (-1,1), (1,-1)):
    Record(value_set).run()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top