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
10 changes: 6 additions & 4 deletions Doc/library/runpy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,9 @@ The :mod:`!runpy` module provides two functions:
For a simple script, the specified code is simply executed in a fresh
module namespace. For a valid :data:`sys.path` entry (typically a zipfile or
directory), the entry is first added to the beginning of ``sys.path``. The
function then looks for and executes a :mod:`__main__` module using the
updated path. Note that there is no special protection against invoking
an existing ``__main__`` entry located elsewhere on ``sys.path`` if
there is no such module at the specified location.
function then looks for and executes a :mod:`__main__` module in that
entry. :exc:`ImportError` is raised if there is no such module at the
specified location.

The optional dictionary argument *init_globals* may be used to pre-populate
the module's globals dictionary before the code is executed.
Expand Down Expand Up @@ -177,6 +176,9 @@ The :mod:`!runpy` module provides two functions:
.. versionchanged:: 3.15
``__cached__`` is no longer set.

.. versionchanged:: next
The ``__main__`` module is now searched only in *path_name*.

.. seealso::

:pep:`338` -- Executing modules as scripts
Expand Down
28 changes: 16 additions & 12 deletions Lib/runpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,22 +234,26 @@ def run_module(mod_name, init_globals=None,
def _get_main_module_details(error=ImportError):
# Helper that gives a nicer error message when attempting to
# execute a zipfile or directory by invoking __main__.py
# Also moves the standard __main__ out of the way so that the
# preexisting __loader__ entry doesn't cause issues
# The module is searched only in sys.path[0] -- the executed directory
# or zipfile -- not in the whole sys.path.
main_name = "__main__"
kwargs = {"name": main_name} if issubclass(error, ImportError) else {}
saved_main = sys.modules[main_name]
del sys.modules[main_name]
from pkgutil import get_importer
path_name = sys.path[0]
importer = get_importer(path_name)
spec = importer.find_spec(main_name) if importer is not None else None
if spec is None or spec.loader is None:
raise error("can't find %r module in %r" % (main_name, path_name),
**kwargs)
if spec.submodule_search_locations is not None:
raise error("Cannot use package as __main__ module", **kwargs)
try:
return _get_module_details(main_name)
code = spec.loader.get_code(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]),
**kwargs) from exc
raise
finally:
sys.modules[main_name] = saved_main
raise error(format(exc), **kwargs) from exc
if code is None:
raise error("No code object available for %s" % main_name, **kwargs)
return main_name, spec, code


def _get_code_from_file(fname, module):
Expand Down
13 changes: 5 additions & 8 deletions Lib/test/test_runpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
import warnings
from test.support import (
force_not_colorized_test_class,
infinite_recursion,
no_tracing,
requires_resource,
requires_subprocess,
verbose,
)
Expand Down Expand Up @@ -771,17 +768,17 @@ def test_zipfile_error(self):
msg = "can't find '__main__' module in %r" % zip_name
self._check_import_error(zip_name, msg)

@no_tracing
@requires_resource('cpu')
def test_main_recursion_error(self):
def test_main_no_recursion(self):
# gh-75221: __main__ is searched only in the executed directory or
# zipfile, so running a directory without __main__ from a zipfile's
# __main__ fails instead of recursing.
with temp_dir() as script_dir, temp_dir() as dummy_dir:
mod_name = '__main__'
source = ("import runpy\n"
"runpy.run_path(%r)\n") % dummy_dir
script_name = self._make_test_script(script_dir, mod_name, source)
zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name)
with infinite_recursion(25):
self.assertRaises(RecursionError, run_path, zip_name)
self.assertRaises(ImportError, run_path, zip_name)

def test_encoding(self):
with temp_dir() as script_dir:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:func:`runpy.run_path` and the execution of a directory or a zipfile now
search the ``__main__`` module only in that directory or zipfile. Previously
an existing ``__main__`` module located elsewhere on :data:`sys.path` could be
executed instead.
Loading