-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaccessor.py
More file actions
134 lines (106 loc) · 4.38 KB
/
Copy pathaccessor.py
File metadata and controls
134 lines (106 loc) · 4.38 KB
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
__author__ = "Lars van Gemerden"
__date__ = "$11-Feb-2015 12:41:24$"
from inspect import isfunction
_marker = object()
class accessor(object):
'''
descriptor to be able to validate type and value of attributes on setting;
- initial value can be set
- type and initial value can be functions,
- validation can depend on object for which the attribute is set
- see below for usage
'''
def __init__(self, type_, init=_marker, valid=None):
self.__type = type_
self.__init = init
self.__valid = valid
def _type(self, obj):
if isfunction(self.__type):
return self.__type(obj)
return self.__type
def _init(self, obj):
if isfunction(self.__init):
return self.__init(obj)
return self.__init
def __get__(self, obj, cls=None):
if obj is None:
return self
try:
return obj.__dict__[self.name]
except KeyError:
raise AttributeError("'%s' object has no attribute '%s'" % (cls.__name__, self.name))
def __set__(self, obj, value):
obj.__dict__[self.name] = self._validate(obj, value)
def _validate(self, obj, value):
if value is None or value is _marker:
return value
if not isinstance(value, self._type(obj)):
raise ValueError("incorrect type for '%s' in '%s'" % (self.name, type(obj).__name__))
if self.__valid and not self.__valid(obj, value):
raise ValueError("invalid value for '%s' in '%s'" % (self.name, type(obj).__name__))
return value
def set_init(self, obj):
if self.__init is not _marker:
setattr(obj, self.name, self._init(obj))
class accessor_meta(type):
'''adds names of accessors to a class attribute __names__ for faster iteration and
adds name to accessor to simplify use of accessors in classes'''
def __init__(cls, name, bases, cls_dict):
for name, attr in cls_dict.iteritems():
if isinstance(attr, accessor):
attr.name = name
super(accessor_meta, cls).__init__(name, bases, cls_dict)
cls.__names__ = set()
for c in cls.mro():
for name, attr in c.__dict__.iteritems():
if isinstance(attr, accessor):
cls.__names__.add(name)
class Validated(object):
'''subclasses of Validated can only have accessor managed attributes
and all accessor managed attributes are type checked and can be validated'''
__metaclass__ = accessor_meta
def __init__(self, **kw):
for name in self.__class__.__names__:
if name in kw:
setattr(self, name, kw.pop(name))
else:
getattr(self.__class__, name).set_init(self)
super(Validated, self).__init__(**kw)
def __setattr__(self, name, value):
if name not in self.__class__.__names__:
raise AttributeError("attribute '%s' in '%s' cannot be set; it has no accessor" % (name, self.__class__.__name__))
super(Validated, self).__setattr__(name, value)
def __str__(self):
return "{cls}({attrs})".format(cls=self.__class__.__name__,
attrs=", ".join("{k}: {v}".format(k=k, v=getattr(self, k, None)) for k in self.__class__.__names__))
if __name__ == "__main__":
class Person(Validated):
name = accessor(str)
age = accessor(int, init=0, valid=lambda s, v: v>=0)
spouse = accessor(lambda s: Person, valid=lambda s, v: s.valid_spouse(v))
def valid_spouse(self, obj):
return self is not obj and self.age>=18 and obj.age>=18
bob = Person(name="bob")
bob.age = 26
ann = Person(name="ann", age=21)
mia = Person(name="mia", age=14)
bob.spouse = ann
print bob
print ann
print mia
try:
bob.name = 3 #wrong type
except ValueError as e:
print e.message
try:
err = Person(name="err", age=-1) #age must be >=0
except ValueError as e:
print e.message
try:
bob.hair = "brown" #"hair" is not a controled attribute
except AttributeError as e:
print e.message
try:
ann.spouse = mia #mia is underaged
except ValueError as e:
print e.message