Welcome, guest | Sign In | My Account | Store | Cart

The following recipe builds on the recipe "Using one method as accessor for multiple attributes" (Python Cookbook, p.752) which in turn seems to be based on http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205126. It adds another argument which provides methods used to validate the value of a property. The interface is modeled after Ian Bicking's FormEncode package (http://formencode.org/). In fact the validators of the FormEncode package can be used as shown in the code below.

Python, 93 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import formencode.validators

class Property(object):
    def __init__(self,name,fget=getattr,fset=setattr,fdel=delattr,               
                doc=None,validator=None):

        self.name = name
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        self.__doc__ = doc or ""
        # the validator is an addition to the original recipe.
        self.validator = validator

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self

        if self.fget is None:
            raise AttributeError,"attribute is write-only"

        # from_python()
        return self.validator.from_python(self.fget(obj,self.name))

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError,"attribute is read-only"

        # to_python()
        self.fset(obj, self.name, self.validator.to_python(value))

    def __delete__(self,obj):
        if self.fdel is None:
            raise AttributeError,"attribute cannot be deleted"
        self.fdel(obj, self.name)


class IntValidator(object):
    """Validate a properties values.

    A validator must provide two methods:
    to_python -- used by __set__ to validate/convert the value. 
    from_python -- used by __get__ to validate/convert the value.

    This is modeled after the FormEncode package.
    """

    def __init__(self,min=None,max=None):
       self.min = min
       self.max = max

    def to_python(self,value):
       """Validate/convert value before setting the property.

       The value must be an integer and between self.min and
       self.max inclusive (if not None).
       """

       if (self.min is not None) and value < self.min:
           raise ValueError,"%s is less than %s" % (value,self.min)

       if (self.max is not None) and value > self.max:
           raise ValueError,"%s is greater than %s" % (value,self.max)

       return int(value)

    def from_python(self,value):
       """Validate/convert value when getting it.

       Usually we simply return the value as is.
       """
       return value


class MyClass(object):
    def __init__(self,x,r):
        self.x = x
        self.r = r

    # x must be an integer less than 10
    x = Property("_x",validator=IntValidator(max=9))

    # r must be an upper case character, we use the FormEncode
    # validator here.
    r = Property("_r",validator=formencode.validators.Regex(r"^[A-Z]+$"))


if __name__ == "__main__":
    c = MyClass(1,"A")  # this is fine.
    c.x = 9             # this is fine.
    c.r = "B"           # this is fine.
    c.x = 10            # this will raise a ValueError.
    c.r = "c"           # this will raise formencode.api.Invalid.

The naming of the to_python and from_python methods is probably not the best choice but it makes the FormEncode validators fit in nicely. The to_python method does the actual checking of the value, while in most cases from_python will simply return the value as is. Both can be used to convert a value between internal and external representation, compressing and decompressing text for example.

Created by Markus Juenemann on Tue, 15 Apr 2008 (PSF)
Python recipes (4591)
Markus Juenemann's recipes (1)

Required Modules

Other Information and Tasks