Skip to content

Commit

Permalink
v2.12.3
Browse files Browse the repository at this point in the history
  • Loading branch information
Paebbels committed Feb 18, 2023
2 parents 78d6b58 + c417e44 commit 44cace3
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 25 deletions.
2 changes: 1 addition & 1 deletion pyTooling/Common/__init__.py
Expand Up @@ -37,7 +37,7 @@
__email__ = "Paebbels@gmail.com"
__copyright__ = "2017-2022, Patrick Lehmann"
__license__ = "Apache License, Version 2.0"
__version__ = "2.12.2"
__version__ = "2.12.3"
__keywords__ = ["decorators", "meta classes", "exceptions", "platform", "versioning", "licensing", "overloading",
"singleton", "tree", "graph", "timer", "data structure", "setuptools", "wheel", "installation",
"packaging", "path", "generic path", "generic library", "url"]
Expand Down
41 changes: 29 additions & 12 deletions pyTooling/MetaClasses/__init__.py
Expand Up @@ -37,7 +37,7 @@
import threading
from functools import wraps
from inspect import signature, Parameter
from types import MethodType
from types import MethodType, FunctionType
from typing import Any, Tuple, List, Dict, Callable, Type, TypeVar

try:
Expand Down Expand Up @@ -247,17 +247,21 @@ def __new__(
if useSlots:
members['__slots__'] = self.__getSlots(baseClasses, members)

# Compute abstract methods
abstractMethods, members = self._checkForAbstractMethods(baseClasses, members)

# Create a new class
newClass = type.__new__(self, className, baseClasses, members)

# Search in inheritance tree for abstract methods
newClass.__abstractMethods__ = self._checkForAbstractMethods(baseClasses, members)
newClass.__abstractMethods__ = abstractMethods
newClass.__isAbstract__ = self._wrapNewMethodIfAbstract(newClass)
newClass.__isSingleton__ = self._wrapNewMethodIfSingleton(newClass, singleton)

return newClass

@classmethod
def _checkForAbstractMethods(metacls, baseClasses: Tuple[type], members: Dict[str, Any]) -> Tuple[str, ...]:
def _checkForAbstractMethods(metacls, baseClasses: Tuple[type], members: Dict[str, Any]) -> Tuple[Dict[str, Callable], Dict[str, Any]]:
"""
Check if the current class contains abstract methods and return a tuple of them.
Expand All @@ -268,19 +272,33 @@ def _checkForAbstractMethods(metacls, baseClasses: Tuple[type], members: Dict[st
:param members: The dictionary of members for the constructed class.
:returns: A tuple of abstract method's names.
"""
abstractMethods = set()
abstractMethods = {}
if baseClasses:
for baseClass in baseClasses:
if hasattr(baseClass, "__abstractMethods__"):
abstractMethods = abstractMethods.union(baseClass.__abstractMethods__)
for key, value in baseClass.__abstractMethods__.items():
abstractMethods[key] = value

for base in baseClasses:
for key, value in base.__dict__.items():
if (key in abstractMethods and isinstance(value, FunctionType) and
not (hasattr(value, "__abstract__") or hasattr(value, "__mustOverride__"))):
def outer(method):
@wraps(method)
def inner(cls, *args, **kwargs):
return method(cls, *args, **kwargs)

return inner

members[key] = outer(value)

for memberName, member in members.items():
if hasattr(member, "__abstract__") or hasattr(member, "__mustOverride__"):
abstractMethods.add(memberName)
if ((hasattr(member, "__abstract__") and member.__abstract__) or (hasattr(member, "__mustOverride__") and member.__mustOverride__)):
abstractMethods[memberName] = member
elif memberName in abstractMethods:
abstractMethods.remove(memberName)
del abstractMethods[memberName]

return tuple(abstractMethods)
return abstractMethods, members

@staticmethod
def _wrapNewMethodIfSingleton(newClass, singleton: bool) -> bool:
Expand Down Expand Up @@ -364,9 +382,8 @@ def _wrapNewMethodIfAbstract(newClass) -> bool:

@OriginalFunction(oldnew)
@wraps(oldnew)
def new(cls, *args, **kwargs):
formattedMethodNames = "', '".join(newClass.__abstractMethods__)
raise AbstractClassError(f"Class '{cls.__name__}' is abstract. The following methods: '{formattedMethodNames}' need to be overridden in a derived class.")
def new(cls, *_, **__):
raise AbstractClassError(f"""Class '{cls.__name__}' is abstract. The following methods: '{"', '".join(newClass.__abstractMethods__)}' need to be overridden in a derived class.""")

new.__raises_abstract_class_error__ = True

Expand Down
37 changes: 25 additions & 12 deletions tests/unit/MetaClasses/Abstract.py
Expand Up @@ -43,8 +43,8 @@


class NormalBase(metaclass=ExtendedType):
def NormalMethod(self):
pass
def NormalMethod(self) -> bool:
return True


class NormalClass(NormalBase):
Expand All @@ -53,41 +53,50 @@ class NormalClass(NormalBase):

class AbstractBase(metaclass=ExtendedType):
@abstractmethod
def AbstractMethod(self):
pass
def AbstractMethod(self) -> bool:
return False


class AbstractClass(AbstractBase):
pass


class DerivedAbstractBase(AbstractBase):
def AbstractMethod(self):
super().AbstractMethod()
def AbstractMethod(self) -> bool:
return super().AbstractMethod()


class DoubleDerivedAbstractBase(DerivedAbstractBase):
pass


class DerivedAbstractClass(AbstractClass):
def AbstractMethod(self):
super().AbstractMethod()
def AbstractMethod(self) -> bool:
return super().AbstractMethod()


class Mixin:
def AbstractMethod(self) -> bool:
return True


class MultipleInheritance(AbstractBase, Mixin):
pass


class MustOverrideBase(metaclass=ExtendedType):
@mustoverride
def MustOverrideMethod(self):
pass
def MustOverrideMethod(self) -> bool:
return False


class MustOverrideClass(MustOverrideBase):
pass


class DerivedMustOverrideClass(MustOverrideBase):
def MustOverrideMethod(self):
super().MustOverrideMethod()
def MustOverrideMethod(self) -> bool:
return super().MustOverrideMethod()


class Abstract(TestCase):
Expand Down Expand Up @@ -135,6 +144,10 @@ def test_DerivedAbstractClass(self) -> None:

self.assertEqual("Method 'AbstractMethod' is abstract and needs to be overridden in a derived class.", str(ExceptionCapture.exception))

def test_MultipleInheritance(self) -> None:
derived = MultipleInheritance()
derived.AbstractMethod()

def test_MustOverrideBase(self) -> None:
with self.assertRaises(AbstractClassError) as ExceptionCapture:
MustOverrideBase()
Expand Down

0 comments on commit 44cace3

Please sign in to comment.