Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions overrides/overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ def _overrides(
global_vars = getattr(method, "__globals__", None)
if global_vars is None:
global_vars = vars(sys.modules[method.__module__])
for super_class in _get_base_classes(sys._getframe(3), global_vars):
frame = sys._getframe(3)
for super_class in _get_base_classes(frame, global_vars, frame.f_locals):
if hasattr(super_class, method.__name__):
if check_at_runtime:

Expand Down Expand Up @@ -191,9 +192,9 @@ def _validate_method(method, super_class, check_signature):
ensure_signature_is_compatible(super_method, method, is_static)


def _get_base_classes(frame, namespace):
def _get_base_classes(frame, namespace, local_vars=None):
return [
_get_base_class(class_name_components, namespace)
_get_base_class(class_name_components, namespace, local_vars)
for class_name_components in _get_base_class_names(frame)
]

Expand Down Expand Up @@ -234,9 +235,12 @@ def _get_base_class_names(frame: FrameType) -> List[List[str]]:
return items


def _get_base_class(components, namespace):
def _get_base_class(components, namespace, local_vars=None):
try:
obj = namespace[components[0]]
if local_vars is not None and components[0] in local_vars:
obj = local_vars[components[0]]
else:
obj = namespace[components[0]]
except KeyError:
if isinstance(namespace["__builtins__"], dict):
obj = namespace["__builtins__"][components[0]]
Expand Down
39 changes: 38 additions & 1 deletion tests/test_special_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from concurrent.futures import Future
from typing import Callable, Any

from overrides import override
import pytest

from overrides import EnforceOverrides, override


class MyInterface(ABC):
Expand Down Expand Up @@ -41,3 +43,38 @@ def pop(
self, key: str, default: Any = DEFAULT, keep_as_dict: bool = False
) -> Any:
pass


def test_overriding_method_of_sibling_nested_class_is_fine():
# Regression test for https://github.com/mkorpela/overrides/issues/86
# `Bar` is only reachable as a local variable in the enclosing `Foo`
# class body, not as a module global, so resolving it requires
# consulting the calling frame's locals.
class Foo:
class Bar(EnforceOverrides):
def to_dict(self):
return {}

class Car(Bar):
@override
def to_dict(self):
return {"car": True}

assert Foo.Car().to_dict() == {"car": True}


def test_overriding_method_of_sibling_nested_class_still_validates():
# The base class resolved for the nested-class case must be the
# actual `Bar` class, not something incidentally found elsewhere, so
# a genuinely incompatible override still raises.
with pytest.raises(TypeError):

class Foo:
class Bar(EnforceOverrides):
def to_dict(self):
return {}

class Car(Bar):
@override
def not_a_base_method(self):
pass
Loading