from collections import ChainMap from .validate import Validator, validated __all__ = ['Structure'] 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"{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}") 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 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__'] @classmethod def from_row(cls, row): return cls(*[func(val) for func, val in zip(cls._types, row)]) def typed_structure(clsname, **validators): cls = type(clsname, (Structure,), validators) return cls