forked from dabeaz-course/python-mastery
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstructure.py
70 lines (55 loc) · 2 KB
/
structure.py
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
# structure.py
from validate import Validator, validated
class Structure:
_fields = ()
_types = ()
def __setattr__(self, name, value):
if name.startswith('_') or name in self._fields:
super().__setattr__(name, value)
else:
raise AttributeError('No attribute %s' % name)
def __repr__(self):
return '%s(%s)' % (type(self).__name__,
', '.join(repr(getattr(self, name)) for name in self._fields))
@classmethod
def from_row(cls, row):
rowdata = [ func(val) for func, val in zip(cls._types, row) ]
return cls(*rowdata)
@classmethod
def create_init(cls):
'''
Create an __init__ method from _fields
'''
args = ','.join(cls._fields)
code = f'def __init__(self, {args}):\n'
for name in cls._fields:
code += f' self.{name} = {name}\n'
locs = { }
exec(code, locs)
cls.__init__ = locs['__init__']
@classmethod
def __init_subclass__(cls):
# Apply the validated decorator to subclasses
validate_attributes(cls)
def validate_attributes(cls):
'''
Class decorator that scans a class definition for Validators
and builds a _fields variable that captures their definition order.
'''
validators = []
for name, val in vars(cls).items():
if isinstance(val, Validator):
validators.append(val)
# Apply validated decorator to any callable with annotations
elif callable(val) and val.__annotations__:
setattr(cls, name, validated(val))
# Collect all of the field names
cls._fields = tuple([v.name for v in validators])
# Collect type conversions. The lambda x:x is an identity
# function that's used in case no expected_type is found.
cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)
for v in validators ])
# Create the __init__ method
if cls._fields:
cls.create_init()
return cls