Skip to content
Merged
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: 13 additions & 1 deletion flowrep/models/parsers/object_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import ast
import builtins
import inspect
import sys
from collections.abc import Callable
from typing import Any

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


def get_scope(func: Callable[..., Any] | type[Any]) -> ScopeProxy:
return ScopeProxy(inspect.getmodule(func).__dict__ | vars(builtins))
module = inspect.getmodule(func)
if module is None:
module_name = getattr(func, "__module__", None)
if module_name is not None:
module = sys.modules.get(module_name)
if module is None:
raise ValueError(
f"Cannot determine the module for {func!r}. "
"inspect.getmodule() returned None and no resolvable __module__ "
"attribute was found."
)
return ScopeProxy(module.__dict__ | vars(builtins))


def resolve_attribute_to_object(attribute: str, scope: ScopeProxy | object) -> object:
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/models/parsers/test_object_scope.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import ast
import sys
import types
import unittest

from flowrep.models.parsers import object_scope
Expand Down Expand Up @@ -42,6 +44,36 @@ def test_includes_builtins(self):
self.assertIs(scope.int, int)
self.assertIs(scope.ValueError, ValueError)

def test_none_module_fallback_via_dunder_module(self):
"""When inspect.getmodule returns None but __module__ is set, fall back."""
mod = types.ModuleType("_test_dynamic_mod")
mod.__dict__["sentinel"] = object()
sys.modules["_test_dynamic_mod"] = mod
try:
func = types.FunctionType(
(lambda: None).__code__,
{},
"_test_func",
)
# Manually set __module__ but keep the function out of a real module
# so inspect.getmodule() returns None.
func.__module__ = "_test_dynamic_mod"
scope = object_scope.get_scope(func)
self.assertIs(scope.sentinel, mod.__dict__["sentinel"])
finally:
del sys.modules["_test_dynamic_mod"]

def test_no_resolvable_module_raises_value_error(self):
"""When neither inspect.getmodule nor __module__ resolves, raise ValueError."""
func = types.FunctionType(
(lambda: None).__code__,
{},
"_orphan_func",
)
func.__module__ = None # type: ignore[assignment]
with self.assertRaises(ValueError):
object_scope.get_scope(func)


class TestResolveSymbolToObject(unittest.TestCase):
def test_simple_name(self):
Expand Down
Loading