60 lines
1.7 KiB
Python
60 lines
1.7 KiB
Python
from collections import ChainMap
|
|
|
|
from validate import Validator, validated
|
|
|
|
|
|
def validate_attributes(cls):
|
|
validators = []
|
|
for name, val in vars(cls).items():
|
|
if isinstance(val, Validator):
|
|
validators.append(val)
|
|
elif callable(val) and val.__annotations__:
|
|
setattr(cls, name, validated(val))
|
|
cls._fields = tuple(v.name for v in validators)
|
|
cls._types = tuple(getattr(v, 'expected_type', lambda x: x) for v in validators)
|
|
if cls._fields:
|
|
cls.create_init()
|
|
return cls
|
|
|
|
|
|
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 = ()
|
|
|
|
def __init_subclass__(cls):
|
|
validate_attributes(cls)
|
|
|
|
def __repr__(self):
|
|
args = map(lambda field: f"{field}={getattr(self, field)!r}", self._fields)
|
|
return f"{type(self).__name__}({', '.join(args)})"
|
|
|
|
def __setattr__(self, name, value):
|
|
if name in self._fields or name[0] == '_':
|
|
super().__setattr__(name, value)
|
|
else:
|
|
raise AttributeError(f"No attribute {name}")
|
|
|
|
@classmethod
|
|
def create_init(cls):
|
|
code = f'def __init__(self, {", ".join(cls._fields)}):\n'
|
|
for name in cls._fields:
|
|
code += f' self.{name} = {name}\n'
|
|
locs = {}
|
|
exec(code, locs)
|
|
cls.__init__ = locs['__init__']
|
|
|
|
|
|
def typed_structure(clsname, **validators):
|
|
cls = type(clsname, (Structure,), validators)
|
|
return cls
|