Decorator for validation - inefficient?
Arnaud Delobelle
arnodel at googlemail.com
Fri Oct 31 16:06:36 EDT 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