0
votes

I feel like I have a pretty good grasp on using decorators when dealing with regular functions, but between using methods of base classes for decorators in derived classes, and passing parameters to said decorators, I cannot figure out what to do next. Here is a snippet of code.

class ValidatedObject:
    ...

    def apply_validation(self, field_name, code):
        def wrap(self, f):
            self._validations.append(Validation(field_name, code, f))
            return f
        return wrap


class test(ValidatedObject):
    ....
    @apply_validation("_name", "oh no!")
    def name_validation(self, name):
        return name == "jacob"

If I try this as is, I get an "apply_validation" is not found.
If I try it with @self.apply_validation I get a "self" isn't found.
I've also been messing around with making apply_validation a class method without success.

Would someone please explain what I'm doing wrong, and the best way to fix this? Thank you.

2
Does each instance need its own Validation object for each method? - user2357112 supports Monica
@user2357112 I was thinking it would just be one Validation object per test object. The Validation object contains a list of all the validations test, and when you call a method to run all the validations, it just goes through the list. - contrapsych
@jacobcase94 check my answer. Does that help? I don't know whats ValidatedObject is so I have tried to be as simple as I can. Or if I have misunderstood your question? - Aamir Adnan
Should ValidatedObject subclasses inherit validations from each other? That is, if a class test2 inherits from test in your example code, should test2._validations include the Validation instance created for "_name" or should it leave that to other inherited methods? - Blckknght
@Blckknght that would be preferable. - contrapsych

2 Answers

1
votes

The issue you're having is that apply_validation is a method, which means you need to call it on an instance of ValidatedObject. Unfortunately, at the time it is being called (during the definition of the test class), there is no appropriate instance available. You need a different approach.

The most obvious one is to use a metaclass that searches through its instance dictionaries (which are really class dictionaries) and sets up the _validations variable based on what it finds. You can still use a decorator, but it probably should be a global function, or perhaps a static method, and it will need to work differently. Here's some code, that uses a metaclass and a decorator that adds function attributes:

class ValidatedMeta(type):
    def __new__(meta, name, bases, dct):
        validations = [Validation(f._validation_field_name, f._validation_code, f)
                       for f in dct.values if hasattr(f._validation_field_name)]
        dct["_validations"] = validations
        super(ValidatedMeta, meta).__new__(meta, name, bases, dct)

def apply_validation(field_name, code):
    def decorator(f):
        f._validation_field_name = field_name
        f._validation_code = code
        return f
    return decorator

def ValidatedObject(metaclass=ValidatedMeta):
    pass

class test(ValidatedObject):
    @apply_validation("_name", "oh no!")
    def name_validation(self, name):
        return name == "jacob"

After this code runs, test._validations will be [Validation("_name", "oh no!", test.name_validation)]. Note that the method that is be passed to Validation is unbound, so you'll need to pass it a self argument yourself when you call it (or perhaps drop the self argument and change the decorator created in apply_validation to return staticmethod(f)).

This code may not do what you want if you have validation methods defined at several levels of an inheritance hierarchy. The metaclass as written above only checks the immediate class's dict for methods with the appropriate attributes. If you need it include inherited methods in _validations too, you may need to modify the logic in ValidatedMeta.__new__. Probably the easiest way to go is to look for _validations attributes in the bases and concatenate the lists together.

1
votes

Just an example for using decorators on class method:

from functools import wraps

def VALIDATE(dec):
    @wraps(dec)
    def _apply_validation(self, name):
        self.validate(name)
        return dec(self, name)
    return _apply_validation

class A:
    def validate(self, name):
        if name != "aamir":
            raise Exception, 'Invalid name "%s"' % name

class B(A):

    @VALIDATE
    def name_validation(self, name):
        return name

b = B()
b.name_validation('jacob') # should raise exception