Skip to content

Commit 62fa0e4

Browse files
authored
Merge pull request #187 from pyiron/copilot/sub-pr-186
Fix AttributeError in get_scope when inspect.getmodule() returns None
2 parents 66ff2ae + 1f66017 commit 62fa0e4

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

flowrep/models/parsers/object_scope.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import ast
44
import builtins
55
import inspect
6+
import sys
67
from collections.abc import Callable
78
from typing import Any
89

@@ -45,7 +46,18 @@ def fork(self) -> ScopeProxy:
4546

4647

4748
def get_scope(func: Callable[..., Any] | type[Any]) -> ScopeProxy:
48-
return ScopeProxy(inspect.getmodule(func).__dict__ | vars(builtins))
49+
module = inspect.getmodule(func)
50+
if module is None:
51+
module_name = getattr(func, "__module__", None)
52+
if module_name is not None:
53+
module = sys.modules.get(module_name)
54+
if module is None:
55+
raise ValueError(
56+
f"Cannot determine the module for {func!r}. "
57+
"inspect.getmodule() returned None and no resolvable __module__ "
58+
"attribute was found."
59+
)
60+
return ScopeProxy(module.__dict__ | vars(builtins))
4961

5062

5163
def resolve_attribute_to_object(attribute: str, scope: ScopeProxy | object) -> object:

tests/unit/models/parsers/test_object_scope.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import ast
2+
import sys
3+
import types
24
import unittest
35

46
from flowrep.models.parsers import object_scope
@@ -42,6 +44,36 @@ def test_includes_builtins(self):
4244
self.assertIs(scope.int, int)
4345
self.assertIs(scope.ValueError, ValueError)
4446

47+
def test_none_module_fallback_via_dunder_module(self):
48+
"""When inspect.getmodule returns None but __module__ is set, fall back."""
49+
mod = types.ModuleType("_test_dynamic_mod")
50+
mod.__dict__["sentinel"] = object()
51+
sys.modules["_test_dynamic_mod"] = mod
52+
try:
53+
func = types.FunctionType(
54+
(lambda: None).__code__,
55+
{},
56+
"_test_func",
57+
)
58+
# Manually set __module__ but keep the function out of a real module
59+
# so inspect.getmodule() returns None.
60+
func.__module__ = "_test_dynamic_mod"
61+
scope = object_scope.get_scope(func)
62+
self.assertIs(scope.sentinel, mod.__dict__["sentinel"])
63+
finally:
64+
del sys.modules["_test_dynamic_mod"]
65+
66+
def test_no_resolvable_module_raises_value_error(self):
67+
"""When neither inspect.getmodule nor __module__ resolves, raise ValueError."""
68+
func = types.FunctionType(
69+
(lambda: None).__code__,
70+
{},
71+
"_orphan_func",
72+
)
73+
func.__module__ = None # type: ignore[assignment]
74+
with self.assertRaises(ValueError):
75+
object_scope.get_scope(func)
76+
4577

4678
class TestResolveSymbolToObject(unittest.TestCase):
4779
def test_simple_name(self):

0 commit comments

Comments
 (0)