Skip to content

Commit

Permalink
final Ensured always, TypeError raised from metaclass
Browse files Browse the repository at this point in the history
  • Loading branch information
mkorpela committed Oct 9, 2022
1 parent 38b2851 commit 89ca2d2
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 32 deletions.
48 changes: 27 additions & 21 deletions overrides/enforce.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,45 @@ def __new__(mcls, name, bases, namespace, **kwargs):

cls = super().__new__(mcls, name, bases, namespace, **kwargs)
for name, value in namespace.items():
# Actually checking the direct parent should be enough,
# otherwise the error would have emerged during the parent class checking
if name.startswith("__"):
continue
value = mcls.handle_special_value(value)
is_override = getattr(value, "__override__", False)
for base in bases:
base_class_method = getattr(base, name, False)
if (
mcls._check_if_overrides_final_method(name, bases)
if not name.startswith("__"):
value = mcls._handle_special_value(value)
mcls._check_if_overrides_without_overrides_decorator(name, value, bases)
return cls

@staticmethod
def _check_if_overrides_without_overrides_decorator(name, value, bases):
is_override = getattr(value, "__override__", False)
for base in bases:
base_class_method = getattr(base, name, False)
if (
not base_class_method
or not callable(base_class_method)
or getattr(base_class_method, "__ignored__", False)
):
continue
assert (
is_override
), "Method %s overrides but does not have @overrides decorator" % (name)
# `__finalized__` is added by `@final` decorator
assert not getattr(base_class_method, "__finalized__", False), (
"Method %s is finalized in %s, it cannot be overridden"
% (base_class_method, base,)
)
return cls
):
continue
if not is_override:
raise TypeError(f"Method {name} overrides but does not have @overrides decorator")
@staticmethod
def _check_if_overrides_final_method(name, bases):
for base in bases:
base_class_method = getattr(base, name, False)
# `__finalized__` is added by `@final` decorator
if getattr(base_class_method, "__finalized__", False):
raise TypeError(
f"Method {base_class_method} is finalized in {base}, it cannot be overridden"
)

@staticmethod
def handle_special_value(value):
def _handle_special_value(value):
if isinstance(value, classmethod) or isinstance(value, staticmethod):
value = value.__get__(None, dict)
elif isinstance(value, property):
value = value.fget
return value



class EnforceOverrides(metaclass=EnforceOverridesMeta):
"Use this as the parent class for your custom classes"
pass
24 changes: 13 additions & 11 deletions tests/test_enforce__py38.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ class Enforcing(EnforceOverrides):
def finality(self):
return "final"


@final
def __and__(self, other):
return True

def nonfinal1(self, param: int) -> str:
return "super1"

Expand Down Expand Up @@ -47,26 +52,23 @@ def nonfinal1(self, param: int) -> str:
self.assertEqual(sc.classVariableIsOk, "OK!")

def test_enforcing_when_finality_broken(self):
try:

with self.assertRaises(TypeError):
class BrokesFinality(Enforcing):
def finality(self):
return "NEVER HERE"

raise RuntimeError("Should not go here")
except AssertionError:
pass
def test_trying_to_override_final_magic_method(self):
with self.assertRaises(TypeError):
class FinalMagicOverrides(Enforcing):
def __and__(self, other):
return False

def test_enforcing_when_none_explicit_override(self):
try:

with self.assertRaises(TypeError):
class Overrider(Enforcing):
def nonfinal2(self):
return "NEVER HERE EITHER"

raise RuntimeError("Should not go here")
except AssertionError:
pass

def test_enforcing_when_property_overriden(self):
class PropertyOverrider(Enforcing):
Expand Down Expand Up @@ -116,7 +118,7 @@ class MetaClassMethodOverrider(Enforcing):
def register(self):
pass

with self.assertRaises(AssertionError):
with self.assertRaises(TypeError):

class SubClass(MetaClassMethodOverrider):
def register(self):
Expand Down

0 comments on commit 89ca2d2

Please sign in to comment.