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
25 changes: 16 additions & 9 deletions Lib/runpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ def _run_module_code(code, init_globals=None,

# Helper to get the full name, spec and code for a module
def _get_module_details(mod_name, error=ImportError):
# name= is only accepted by ImportError and its subclasses.
kwargs = {"name": mod_name} if issubclass(error, ImportError) else {}
if mod_name.startswith("."):
raise error("Relative module names not supported")
raise error("Relative module names not supported", **kwargs)
pkg_name, _, _ = mod_name.rpartition(".")
if pkg_name:
# Try importing the parent to avoid catching initialization errors
Expand Down Expand Up @@ -136,30 +138,33 @@ def _get_module_details(mod_name, error=ImportError):
if mod_name.endswith(".py"):
msg += (f". Try using '{mod_name[:-3]}' instead of "
f"'{mod_name}' as the module name.")
raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
raise error(msg.format(mod_name, type(ex).__name__, ex),
**kwargs) from ex
if spec is None:
raise error("No module named %s" % mod_name)
raise error("No module named %s" % mod_name, **kwargs)
if spec.submodule_search_locations is not None:
if mod_name == "__main__" or mod_name.endswith(".__main__"):
raise error("Cannot use package as __main__ module")
raise error("Cannot use package as __main__ module", **kwargs)
try:
pkg_main_name = mod_name + ".__main__"
return _get_module_details(pkg_main_name, error)
except error as e:
if mod_name not in sys.modules:
raise # No module loaded; being a package is irrelevant
raise error(("%s; %r is a package and cannot " +
"be directly executed") %(e, mod_name))
"be directly executed") %(e, mod_name),
**kwargs)
loader = spec.loader
if loader is None:
raise error("%r is a namespace package and cannot be executed"
% mod_name)
% mod_name,
**kwargs)
try:
code = loader.get_code(mod_name)
except ImportError as e:
raise error(format(e)) from e
raise error(format(e), **kwargs) from e
if code is None:
raise error("No code object available for %s" % mod_name)
raise error("No code object available for %s" % mod_name, **kwargs)
return mod_name, spec, code

class _Error(Exception):
Expand Down Expand Up @@ -232,14 +237,16 @@ def _get_main_module_details(error=ImportError):
# Also moves the standard __main__ out of the way so that the
# preexisting __loader__ entry doesn't cause issues
main_name = "__main__"
kwargs = {"name": main_name} if issubclass(error, ImportError) else {}
saved_main = sys.modules[main_name]
del sys.modules[main_name]
try:
return _get_module_details(main_name)
except ImportError as exc:
if main_name in str(exc):
raise error("can't find %r module in %r" %
(main_name, sys.path[0])) from exc
(main_name, sys.path[0]),
**kwargs) from exc
raise
finally:
sys.modules[main_name] = saved_main
Expand Down
30 changes: 30 additions & 0 deletions Lib/test/test_runpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,25 @@ def test_invalid_names(self):
# Package without __main__.py
self.expect_import_error("multiprocessing")

def test_invalid_names_set_name_attribute(self):
cases = [
# (mod_name, expected_name) -- comment indicates raise site
("nonexistent_runpy_test_module",
"nonexistent_runpy_test_module"), # spec is None
("sys.imp.eric", "sys.imp.eric"), # find_spec error
(".relative_name", ".relative_name"), # relative name rejected
("sys", "sys"), # builtin: no code object
("multiprocessing", "multiprocessing"), # package without __main__
]
for mod_name, expected_name in cases:
with self.subTest(mod_name=mod_name):
try:
run_module(mod_name)
except ImportError as exc:
self.assertEqual(exc.name, expected_name)
else:
self.fail("Expected ImportError for %r" % mod_name)

def test_library_module(self):
self.assertEqual(run_module("runpy")["__name__"], "runpy")

Expand Down Expand Up @@ -714,6 +733,17 @@ def test_directory_error(self):
msg = "can't find '__main__' module in %r" % script_dir
self._check_import_error(script_dir, msg)

def test_directory_error_sets_name_attribute(self):
with temp_dir() as script_dir:
self._make_test_script(script_dir, 'not_main')
try:
run_path(script_dir)
except ImportError as exc:
self.assertEqual(exc.name, '__main__')
else:
self.fail("Expected ImportError for directory without "
"__main__.py")

def test_zipfile(self):
with temp_dir() as script_dir:
mod_name = '__main__'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix :func:`runpy.run_module` and :func:`runpy.run_path` to set the
:attr:`~ImportError.name` attribute on the :exc:`ImportError` they
raise.
Loading