importlib: _import_module_using_spec also imports parent modules
According to the Python import implementation:
73906d5c90/Lib/importlib/_bootstrap.py (L1308-L1311)
When we import a module we should also import its parents, and set the child as attribute of its parent.
This commit is contained in:
parent
70dd4b0880
commit
f4289181cb
|
@ -636,31 +636,30 @@ def _import_module_using_spec(
|
|||
|
||||
if spec_matches_module_path(spec, module_path):
|
||||
assert spec is not None
|
||||
# Attempt to import the parent module, seems is our responsibility:
|
||||
# https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311
|
||||
parent_module_name, _, name = module_name.rpartition(".")
|
||||
parent_module: Optional[ModuleType] = sys.modules.get(parent_module_name)
|
||||
if parent_module is None and parent_module_name:
|
||||
with contextlib.suppress(ModuleNotFoundError, ImportWarning):
|
||||
parent_module = importlib.import_module(parent_module_name)
|
||||
|
||||
# Find spec and import this module.
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = mod
|
||||
spec.loader.exec_module(mod) # type: ignore[union-attr]
|
||||
|
||||
# Set this module as an attribute of the parent module (#12194).
|
||||
if parent_module is not None:
|
||||
setattr(parent_module, name, mod)
|
||||
|
||||
if insert_modules:
|
||||
insert_missing_modules(sys.modules, module_name)
|
||||
_set_name_in_parent(mod)
|
||||
return mod
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _set_name_in_parent(module: ModuleType) -> None:
|
||||
"""
|
||||
Sets an attribute in the module's parent pointing to the module itself (#12194).
|
||||
|
||||
Based on https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1335-L1342.
|
||||
"""
|
||||
parent, _, name = module.__name__.rpartition(".")
|
||||
if not parent:
|
||||
return
|
||||
parent_module = sys.modules.get(parent)
|
||||
if parent_module is not None:
|
||||
setattr(sys.modules[parent], name, module)
|
||||
|
||||
|
||||
def spec_matches_module_path(
|
||||
module_spec: Optional[ModuleSpec], module_path: Path
|
||||
) -> bool:
|
||||
|
|
|
@ -1127,6 +1127,41 @@ def test_safe_exists(tmp_path: Path) -> None:
|
|||
|
||||
|
||||
def test_import_sets_module_as_attribute(pytester: Pytester) -> None:
|
||||
"""Unittest test for #12194."""
|
||||
pytester.path.joinpath("foo/bar/baz").mkdir(parents=True)
|
||||
pytester.path.joinpath("foo/__init__.py").touch()
|
||||
pytester.path.joinpath("foo/bar/__init__.py").touch()
|
||||
pytester.path.joinpath("foo/bar/baz/__init__.py").touch()
|
||||
pytester.syspathinsert()
|
||||
|
||||
# Import foo.bar.baz and ensure parent modules also ended up imported.
|
||||
baz = import_path(
|
||||
pytester.path.joinpath("foo/bar/baz/__init__.py"),
|
||||
mode=ImportMode.importlib,
|
||||
root=pytester.path,
|
||||
consider_namespace_packages=False,
|
||||
)
|
||||
assert baz.__name__ == "foo.bar.baz"
|
||||
foo = sys.modules["foo"]
|
||||
assert foo.__name__ == "foo"
|
||||
bar = sys.modules["foo.bar"]
|
||||
assert bar.__name__ == "foo.bar"
|
||||
|
||||
# Check parent modules have an attribute pointing to their children.
|
||||
assert bar.baz is baz
|
||||
assert foo.bar is bar
|
||||
|
||||
# Ensure we returned the "foo.bar" module cached in sys.modules.
|
||||
bar_2 = import_path(
|
||||
pytester.path.joinpath("foo/bar/__init__.py"),
|
||||
mode=ImportMode.importlib,
|
||||
root=pytester.path,
|
||||
consider_namespace_packages=False,
|
||||
)
|
||||
assert bar_2 is bar
|
||||
|
||||
|
||||
def test_import_sets_module_as_attribute_regression(pytester: Pytester) -> None:
|
||||
"""Regression test for #12194."""
|
||||
pytester.path.joinpath("foo/bar/baz").mkdir(parents=True)
|
||||
pytester.path.joinpath("foo/__init__.py").touch()
|
||||
|
|
Loading…
Reference in New Issue