Repository: mkorpela/overrides

Issue:
Creating a method with the same name as a method in `EnforceOverridesMeta` raises
Because `register` is defined in `abc.ABCMeta` and `handle_special_value` is defined in `EnforceOverridesMeta`, the code incorrectly determines that classes that redefine these methods are missing an `@overrides` decorator. Instead, `EnforceOverridesMeta` should ignore methods that are missing an `@overrides` decorator if they are defined on `EnforceOverridesMeta`.

```python
class RegisterRepro(overrides.EnforceOverrides):
    def register(self):
        pass

# AssertionError: Method register overrides but does not have @overrides decorator
```

```python
class HandleSpecialValueRepro(overrides.EnforceOverrides):
    def handle_special_value(self):
        pass

# AssertionError: Method handle_special_value overrides but does not have @overrides decorator
```

Discussion:

Patch:
diff --git a/overrides/enforce.py b/overrides/enforce.py
index 83b82df..42c35c6 100644
--- a/overrides/enforce.py
+++ b/overrides/enforce.py
@@ -123,6 +123,11 @@ def ensure_return_type_compatibility(super_sig, sub_sig):
 
 class EnforceOverridesMeta(ABCMeta):
     def __new__(mcls, name, bases, namespace, **kwargs):
+        # Ignore any methods defined on the metaclass when enforcing overrides.
+        for method in dir(mcls):
+            if not method.startswith("__") and method != "mro":
+                setattr(getattr(mcls, method), "__ignored__", True)
+
         cls = super().__new__(mcls, name, bases, namespace, **kwargs)
         for name, value in namespace.items():
             # Actually checking the direct parent should be enough,
@@ -133,7 +138,7 @@ class EnforceOverridesMeta(ABCMeta):
             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):
+                if not base_class_method or not callable(base_class_method) or getattr(base_class_method, "__ignored__", False):
                     continue
                 assert (
                     is_override


Test patch:
diff --git a/tests/test_enforce.py b/tests/test_enforce.py
index c624be8..013077b 100644
--- a/tests/test_enforce.py
+++ b/tests/test_enforce.py
@@ -104,6 +104,16 @@ class EnforceTests(unittest.TestCase):
         self.assertNotEqual(ClassMethodOverrider.nonfinal_classmethod(),
                             Enforcing.nonfinal_classmethod())
 
+    def test_enforcing_when_metaclass_method_overridden(self):
+        class MetaClassMethodOverrider(Enforcing):
+            def register(self):
+                pass
+
+        with self.assertRaises(AssertionError):
+            class SubClass(MetaClassMethodOverrider):
+                def register(self):
+                    pass
+
     def test_ensure_compatible_when_compatible(self):
         def sup(a, /, b: str, c: int, *, d, e, **kwargs) -> object:
             pass
@@ -113,7 +123,6 @@ class EnforceTests(unittest.TestCase):
 
         ensure_compatible(sup, sub)
         
-
     def test_ensure_compatible_when_return_types_are_incompatible(self):
         def sup(x) -> int:
             pass