I'm interested in creating a @classproperty decorator that can be chained together with the @abstractmethod decorator on a user-defined ABC class.
The desired functionality is:
- Raise
TypeErrorwhen attempting to access the abstract class property from a non-instantiatedABCclass. - Raise
TypeErrorwhen attempting to instantiate a concrete subclass that does not have the abstract class property implemented. - Raise
TypeErrorwhen attempting to access the abstract class property from a non-instantiated concrete subclass.
The TypeError referred to above should be the same type error coming from @abstractmethod (Ex. TypeError: Can't instantiate abstract class ConcreteNotImplemented with abstract methods abstract_class_property)
from abc import ABC, abstractmethod
class classproperty(property):
"""
A decorator that decorates class attributes. This means that it works off classes attributes instead of instance variables.
See Also:
property
References:
SqlAlchemy
"""
def __init__(self, fget, *arg, **kw):
super(classproperty, self).__init__(fget, *arg, **kw)
self.__doc__ = fget.__doc__
def __get__(desc, self, cls):
return desc.fget(cls)
__isabstractmethod__ = property(lambda self: object(), lambda self, v: None, lambda self: None) # Potentially some manipulation here??
class MyAbstractClass(ABC):
@classproperty
def non_abstract_class_property(self) -> str:
"""Example of classproperty with no abstract method. Works as desired."""
return "value 1"
@classproperty
@abstractmethod
def abstract_class_property(self) -> str:
"""
Desired Syntax - Wrong Functionality The @abstractmethod has no effect on this classproperty and subclass is allowed to be instantiated and accessed.
"""
class ConcreteNotImplemented(MyAbstractClass):
"""
Expected that 'MyAbstractClass' would force me to implement 'abstract_class_property' and raise the abstractmethod TypeError:
(TypeError: Can't instantiate abstract class ConcreteNotImplemented with abstract methods abstract_class_property)
but does not and simply returns None.
"""
print("MyAbstractBaseClass TESTS")
print(MyAbstractClass.non_abstract_class_property)
print(MyAbstractClass.abstract_class_property) # Expected: TypeError (case 1)
print()
print("ConcreteNotImplemented TESTS")
print(ConcreteNotImplemented.non_abstract_class_property)
print(ConcreteNotImplemented().abstract_class_property) # Expected: TypeError (case 2)
print(ConcreteNotImplemented.abstract_class_property) # Expected: TypeError (case 3)
print(ConcreteNotImplemented()) # Expected: TypeError (symptom of case 1)
Output:
MyAbstractBaseClass TESTS
value 1
None
ConcreteNotImplemented TESTS
value 1
None
None
<__main__.ConcreteNotImplemented object at 0x000001693BB79730>
After poking around in the Python property implementation, I found __isabstractmethod__ and threw that on my classproperty implementation hoping that it would resolve some of the desired functionality. Potentially some more manipulation would get me closer.