Decorator for validation - inefficient?

Arnaud Delobelle arnodel at googlemail.com
Fri Oct 31 21:06:36 CET 2008


On Oct 31, 6:26 pm, Bryan <bryanv... at gmail.com> wrote:
> I want my business objects to be able to do this:
> class Person(base):
>
>     def __init__(self):
>         self.name = None
>
>     @base.validator
>     def validate_name(self):
>         if not self.name: return ['Name cannot be empty']
>
> p = Person()
> print p.invalid  # Prints ['Name cannot be empty']
> p.name = 'foo'
> print p.invalid  # Prints []
> print bool(p.invalid)  # Prints False
>
> The invalid attribute and validator decorator would be in the base
> class:
> class base(object):
>
>     @staticmethod  # so child can say: @base.validator
>     def validator(func):
>         """Mark the function as a validator."""
>         func._validator = True
>         return func
>
>     def _get_invalid(self):
>     """Collect all validation results from registered validators"""
>         result = []
>         for attrName in dir(self):
>             # Prevent recursive calls
>             if attrName == 'get_invalid' or attrName == 'invalid':
> continue
>
>             attr =  eval('self.' + attrName)  # Get attribute
>
>             if str(type(attr)) == "<type 'instancemethod'>":  # Check
> if is function
>                 if hasattr(attr, '_validator'):  # Check if function
> is a validator
>                     valerr = attr()  # Get result of validation
>                     # Validation result can be a single string, list
> of strings, or None.
>                     # If the validation fails, it will be a string or
> list of strings
>                     # which describe what the validation errors are.
>                     # If the validation succeeds, None is returned.
>                     if type(valerr) == type([]):
>                         for err in valerr:
>                             result.append(err)
>                     else:
>                         if valerr != None: result.append(valerr)
>         return result  # List of validation error strings
>     invalid = property(_get_invalid, None, None, "List of validation
> errors")  # Read-only, so no fset or fdel
>
> I don't really like the _get_invalid() logic that reflects over each
> attribute and does ugly string comparisons.  Is there a cleaner/more
> pythonic way to do this reflection?
>
> Also, I am using a decorator to simply mark a function as being a
> validator.  This works, but I must enumerate all of the object's
> attributes to find all the validators.  My original plan was to use a
> decorator to "register" a function as being a validator.  Then the
> _get_invalid() call would only need to enumerate the registered
> functions.  I ran into problems when I couldn't figure out how to use
> a decorator to store a function in an attribute of the function's
> class:
> # Decorator to register function as a validator
> def validator(func):
>     """Save func in a list of validator functions on the object that
> contains func"""
>     self._validation_functions.append(func)  # ERROR: Cannot access
> self this way
>     return func
>
> I appreciate your feedback.  I am relatively new to python but have
> become completely enamored by it after looking for alternatives to the
> MS languages I use to develop business apps.

Hi

I suggest a simpler approach

class Base(object):
    @property
    def invalid(self):
        invalid = []
        for field in self.fields:
            validate_field = getattr(self, 'validate_' + field)
            if validate_field:
                errors = validate_field()
                if errors:
                    invalid.extend(errors)
        return invalid

class Person(Base):
    fields = 'name', 'age'
    def __init__(self):
        self.name = None
        self.age = None
    def validate_name(self):
        if not self.name: return ['Name cannot be empty']
    def validate_age(self):
        if self.age is None: return ['Age cannot be empty']
        if not isinstance(self.age, int): return ['Age must be a
number']

Then:

>>> p=Person()
>>> p.invalid
['Name cannot be empty', 'Age cannot be empty']
>>> p.name='Lorenzo'
>>> p.invalid
['Age cannot be empty']
>>> p.age='green'
>>> p.invalid
['Age must be a number']
>>> p.age=12
>>> p.invalid
[]
>>>

HTH

--
Arnaud




More information about the Python-list mailing list