Initial commit
This commit is contained in:
91
Solutions/8_2/structure.py
Normal file
91
Solutions/8_2/structure.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# structure.py
|
||||
|
||||
from validate import Validator, validated
|
||||
from collections import ChainMap
|
||||
|
||||
class StructureMeta(type):
|
||||
@classmethod
|
||||
def __prepare__(meta, clsname, bases):
|
||||
return ChainMap({}, Validator.validators)
|
||||
|
||||
@staticmethod
|
||||
def __new__(meta, name, bases, methods):
|
||||
methods = methods.maps[0]
|
||||
return super().__new__(meta, name, bases, methods)
|
||||
|
||||
class Structure(metaclass=StructureMeta):
|
||||
_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))
|
||||
|
||||
def __iter__(self):
|
||||
for name in self._fields:
|
||||
yield getattr(self, name)
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, type(self)) and tuple(self) == tuple(other)
|
||||
|
||||
@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
|
||||
|
||||
def typed_structure(clsname, **validators):
|
||||
cls = type(clsname, (Structure,), validators)
|
||||
return cls
|
||||
Reference in New Issue
Block a user