Source code for sigmaepsilon.core.acp
# -*- coding: utf-8 -*-
"""
Multiple solutions on how to enforce an abstract class property
on an object. Generic types like 'List[int]' are not allowed,
because the don't work well with the isinstance()-like methods.
"""
from typing import Callable
__all__ = ['abstract_class_property', 'setproperty']
[docs]def abstract_class_property(**kwargs) -> Callable:
"""
A decorator to achieve similar behaviour to the `property` decorator,
but for classes.
Example
-------
>>> class MyClasss:
>>> _my_property = 1
>>>
>>> @classproperty
>>> def prop(cls):
>>> return cls._my_property
"""
return abstract_class_property_B(**kwargs)
def setproperty(**kwargs):
def decorator(cls):
for key, value in kwargs.items():
setattr(cls, key, value)
return cls
return decorator
"""
Implementation A: class annotations solution using an object descriptor
and a wrapper class with classic pythonic manipulations.
"""
def abstract_class_property_A(**kwargs) -> Callable:
"""
Decorator function to decorate objects with abstract
class properties. Leaves behind another decorator
that takes a class as its input.
"""
def abstractor(WrappedClass):
class PropertyWrapper(WrappedClass):
"""
A Wrapper class to decorate objects with abstract class properties.
The class has a default dummy annotation, just so that we can append
the items of the input dictionary d to the class definition.
The dummy property is deleted at the end.
"""
_dummy_: None
def __init__(self, *args, **kwargs):
WrappedClass.__init__(self)
d_ = dict()
d_props = PropertyWrapper.__dict__.get('__annotations__', {})
for key, value in d_props.items():
d_[key] = value
d_self = self.__class__.__dict__.get('__annotations__', {})
for key, value in d_self.items():
d_[key] = value
for key in d_.keys():
if not hasattr(self, key):
raise AttributeError(f'required attribute {key} not present '
f'in {self.__class__}')
return
res = PropertyWrapper
d_ = res.__dict__['__annotations__']
for key, value in kwargs.items():
d_[key] = value
for key, value in WrappedClass.__dict__.get('__annotations__', {}).items():
d_[key] = value
del d_['_dummy_']
return res
return abstractor
"""
Implementation B: class annotations solution using an object descriptor and a
wrapper class using the __mro__ property of the class.
This implementation also checks the validity of the declaration.
"""
def abstract_class_property_B(**kwargs) -> Callable:
"""
Decorator function to decorate objects with abstract
class properties. Leaves behind another decorator
that takes a class as its input.
"""
def abstractor(WrappedClass):
class PropertyWrapper(WrappedClass):
"""
A Wrapper class to decorate objects with abstract class properties.
The class has a default dummy annotation, just so that we can append
the items of the input dictionary d to the class definition.
The dummy property is deleted at the end.
"""
_dummy_: None
def __init__(self, *args, **kwargs):
WrappedClass.__init__(self)
self.__check_absclsprops__()
return
@classmethod
def __absclsprops__(cls):
"""
Collects the abstracts class properties and their
expected types based on the MRO of the class.
"""
t = cls.__mro__
l = [ti.__dict__.get('__annotations__', {}) for ti in t]
res = dict()
for d in l:
for key, value in d.items():
res[key] = value
return res
def __check_absclsprops__(self):
"""
Checks if the instance has attributes according to
the anstract annotations. Returns True if every attribute is
a type-correct declaration.
"""
props = self.__class__.__absclsprops__()
for key, value in props.items():
if not hasattr(self, key):
raise AttributeError(f'required attribute {key} not present '
f'in {self.__class__}')
else:
if not isinstance(getattr(self, key), value):
raise TypeError(
'TypeError. key : {}, value = {}'.format(key, value))
return True
res = PropertyWrapper
d_ = res.__dict__['__annotations__']
for key, value in kwargs.items():
d_[key] = value
del d_['_dummy_']
return res
return abstractor