mirror of
https://github.com/python/cpython.git
synced 2025-07-29 06:05:00 +00:00
Issue 4195: Restore the ability to execute packages with the -m switch (but this time in a way that leaves the import machinery in a valid state). (Original patch by Andi Vajda)
This commit is contained in:
parent
e0820e2ea7
commit
d39600e69f
6 changed files with 110 additions and 12 deletions
|
@ -28,17 +28,22 @@ The :mod:`runpy` module provides a single function:
|
||||||
mechanism (refer to PEP 302 for details) and then executed in a fresh module
|
mechanism (refer to PEP 302 for details) and then executed in a fresh module
|
||||||
namespace.
|
namespace.
|
||||||
|
|
||||||
|
If the supplied module name refers to a package rather than a normal module,
|
||||||
|
then that package is imported and the ``__main__`` submodule within that
|
||||||
|
package is then executed and the resulting module globals dictionary returned.
|
||||||
|
|
||||||
The optional dictionary argument *init_globals* may be used to pre-populate the
|
The optional dictionary argument *init_globals* may be used to pre-populate the
|
||||||
globals dictionary before the code is executed. The supplied dictionary will not
|
globals dictionary before the code is executed. The supplied dictionary will not
|
||||||
be modified. If any of the special global variables below are defined in the
|
be modified. If any of the special global variables below are defined in the
|
||||||
supplied dictionary, those definitions are overridden by the ``run_module``
|
supplied dictionary, those definitions are overridden by the ``run_module``
|
||||||
function.
|
function.
|
||||||
|
|
||||||
The special global variables ``__name__``, ``__file__``, ``__loader__`` and
|
The special global variables ``__name__``, ``__file__``, ``__loader__``,
|
||||||
``__builtins__`` are set in the globals dictionary before the module code is
|
``__builtins__`` and ``__package__`` are set in the globals dictionary before
|
||||||
executed.
|
the module code is executed.
|
||||||
|
|
||||||
``__name__`` is set to *run_name* if this optional argument is supplied, and the
|
``__name__`` is set to *run_name* if this optional argument is supplied, to
|
||||||
|
``mod_name + '.__main__'`` if the named module is a package and to the
|
||||||
*mod_name* argument otherwise.
|
*mod_name* argument otherwise.
|
||||||
|
|
||||||
``__loader__`` is set to the PEP 302 module loader used to retrieve the code for
|
``__loader__`` is set to the PEP 302 module loader used to retrieve the code for
|
||||||
|
@ -50,6 +55,9 @@ The :mod:`runpy` module provides a single function:
|
||||||
``__builtins__`` is automatically initialised with a reference to the top level
|
``__builtins__`` is automatically initialised with a reference to the top level
|
||||||
namespace of the :mod:`__builtin__` module.
|
namespace of the :mod:`__builtin__` module.
|
||||||
|
|
||||||
|
``__package__`` is set to *mod_name* if the named module is a package and to
|
||||||
|
``mod_name.rpartition('.')[0]`` otherwise.
|
||||||
|
|
||||||
If the argument *alter_sys* is supplied and evaluates to ``True``, then
|
If the argument *alter_sys* is supplied and evaluates to ``True``, then
|
||||||
``sys.argv[0]`` is updated with the value of ``__file__`` and
|
``sys.argv[0]`` is updated with the value of ``__file__`` and
|
||||||
``sys.modules[__name__]`` is updated with a temporary module object for the
|
``sys.modules[__name__]`` is updated with a temporary module object for the
|
||||||
|
@ -62,8 +70,15 @@ The :mod:`runpy` module provides a single function:
|
||||||
function from threaded code.
|
function from threaded code.
|
||||||
|
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
Added ability to execute packages by looking for a ``__main__`` submodule
|
||||||
|
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
:pep:`338` - Executing modules as scripts
|
:pep:`338` - Executing modules as scripts
|
||||||
PEP written and implemented by Nick Coghlan.
|
PEP written and implemented by Nick Coghlan.
|
||||||
|
|
||||||
|
:pep:`366` - Main module explicit relative imports
|
||||||
|
PEP written and implemented by Nick Coghlan.
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,12 @@ source.
|
||||||
the implementation may not always enforce this (e.g. it may allow you to
|
the implementation may not always enforce this (e.g. it may allow you to
|
||||||
use a name that includes a hyphen).
|
use a name that includes a hyphen).
|
||||||
|
|
||||||
|
Package names are also permitted. When a package name is supplied instead
|
||||||
|
of a normal module, the interpreter will execute ``<pkg>.__main__`` as
|
||||||
|
the main module. This behaviour is deliberately similar to the handling
|
||||||
|
of directories and zipfiles that are passed to the interpreter as the
|
||||||
|
script argument.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
This option cannot be used with builtin modules and extension modules
|
This option cannot be used with builtin modules and extension modules
|
||||||
|
@ -97,7 +103,7 @@ source.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
:func:`runpy.run_module`
|
:func:`runpy.run_module`
|
||||||
The actual implementation of this feature.
|
Equivalent functionality directly available to Python code
|
||||||
|
|
||||||
:pep:`338` -- Executing modules as scripts
|
:pep:`338` -- Executing modules as scripts
|
||||||
|
|
||||||
|
@ -106,6 +112,9 @@ source.
|
||||||
.. versionchanged:: 2.5
|
.. versionchanged:: 2.5
|
||||||
The named module can now be located inside a package.
|
The named module can now be located inside a package.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.7
|
||||||
|
Supply the package name to run a ``__main__`` submodule.
|
||||||
|
|
||||||
|
|
||||||
.. describe:: -
|
.. describe:: -
|
||||||
|
|
||||||
|
|
18
Lib/runpy.py
18
Lib/runpy.py
|
@ -80,13 +80,19 @@ def _get_module_details(mod_name):
|
||||||
if loader is None:
|
if loader is None:
|
||||||
raise ImportError("No module named %s" % mod_name)
|
raise ImportError("No module named %s" % mod_name)
|
||||||
if loader.is_package(mod_name):
|
if loader.is_package(mod_name):
|
||||||
raise ImportError(("%s is a package and cannot " +
|
if mod_name == "__main__" or mod_name.endswith(".__main__"):
|
||||||
"be directly executed") % mod_name)
|
raise ImportError(("Cannot use package as __main__ module"))
|
||||||
|
try:
|
||||||
|
pkg_main_name = mod_name + ".__main__"
|
||||||
|
return _get_module_details(pkg_main_name)
|
||||||
|
except ImportError, e:
|
||||||
|
raise ImportError(("%s; %r is a package and cannot " +
|
||||||
|
"be directly executed") %(e, mod_name))
|
||||||
code = loader.get_code(mod_name)
|
code = loader.get_code(mod_name)
|
||||||
if code is None:
|
if code is None:
|
||||||
raise ImportError("No code object available for %s" % mod_name)
|
raise ImportError("No code object available for %s" % mod_name)
|
||||||
filename = _get_filename(loader, mod_name)
|
filename = _get_filename(loader, mod_name)
|
||||||
return loader, code, filename
|
return mod_name, loader, code, filename
|
||||||
|
|
||||||
|
|
||||||
# XXX ncoghlan: Should this be documented and made public?
|
# XXX ncoghlan: Should this be documented and made public?
|
||||||
|
@ -101,12 +107,12 @@ def _run_module_as_main(mod_name, set_argv0=True):
|
||||||
__loader__
|
__loader__
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
loader, code, fname = _get_module_details(mod_name)
|
mod_name, loader, code, fname = _get_module_details(mod_name)
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
# Try to provide a good error message
|
# Try to provide a good error message
|
||||||
# for directories, zip files and the -m switch
|
# for directories, zip files and the -m switch
|
||||||
if set_argv0:
|
if set_argv0:
|
||||||
# For -m switch, just disply the exception
|
# For -m switch, just display the exception
|
||||||
info = str(exc)
|
info = str(exc)
|
||||||
else:
|
else:
|
||||||
# For directories/zipfiles, let the user
|
# For directories/zipfiles, let the user
|
||||||
|
@ -127,7 +133,7 @@ def run_module(mod_name, init_globals=None,
|
||||||
|
|
||||||
Returns the resulting top level namespace dictionary
|
Returns the resulting top level namespace dictionary
|
||||||
"""
|
"""
|
||||||
loader, code, fname = _get_module_details(mod_name)
|
mod_name, loader, code, fname = _get_module_details(mod_name)
|
||||||
if run_name is None:
|
if run_name is None:
|
||||||
run_name = mod_name
|
run_name = mod_name
|
||||||
pkg_name = mod_name.rpartition('.')[0]
|
pkg_name = mod_name.rpartition('.')[0]
|
||||||
|
|
|
@ -158,6 +158,16 @@ class CmdLineTest(unittest.TestCase):
|
||||||
self.assert_(printed_package in data)
|
self.assert_(printed_package in data)
|
||||||
self.assert_(printed_argv0 in data)
|
self.assert_(printed_argv0 in data)
|
||||||
|
|
||||||
|
def _check_import_error(self, script_name, expected_msg,
|
||||||
|
*cmd_line_switches):
|
||||||
|
run_args = cmd_line_switches + (script_name,)
|
||||||
|
exit_code, data = _run_python(*run_args)
|
||||||
|
if verbose:
|
||||||
|
print 'Output from test script %r:' % script_name
|
||||||
|
print data
|
||||||
|
print 'Expected output: %r' % expected_msg
|
||||||
|
self.assert_(expected_msg in data)
|
||||||
|
|
||||||
def test_basic_script(self):
|
def test_basic_script(self):
|
||||||
with temp_dir() as script_dir:
|
with temp_dir() as script_dir:
|
||||||
script_name = _make_test_script(script_dir, 'script')
|
script_name = _make_test_script(script_dir, 'script')
|
||||||
|
@ -182,6 +192,11 @@ class CmdLineTest(unittest.TestCase):
|
||||||
os.remove(script_name)
|
os.remove(script_name)
|
||||||
self._check_script(script_dir, compiled_name, script_dir, '')
|
self._check_script(script_dir, compiled_name, script_dir, '')
|
||||||
|
|
||||||
|
def test_directory_error(self):
|
||||||
|
with temp_dir() as script_dir:
|
||||||
|
msg = "can't find '__main__.py' in %r" % script_dir
|
||||||
|
self._check_import_error(script_dir, msg)
|
||||||
|
|
||||||
def test_zipfile(self):
|
def test_zipfile(self):
|
||||||
with temp_dir() as script_dir:
|
with temp_dir() as script_dir:
|
||||||
script_name = _make_test_script(script_dir, '__main__')
|
script_name = _make_test_script(script_dir, '__main__')
|
||||||
|
@ -195,6 +210,13 @@ class CmdLineTest(unittest.TestCase):
|
||||||
zip_name, run_name = _make_test_zip(script_dir, 'test_zip', compiled_name)
|
zip_name, run_name = _make_test_zip(script_dir, 'test_zip', compiled_name)
|
||||||
self._check_script(zip_name, run_name, zip_name, '')
|
self._check_script(zip_name, run_name, zip_name, '')
|
||||||
|
|
||||||
|
def test_zipfile_error(self):
|
||||||
|
with temp_dir() as script_dir:
|
||||||
|
script_name = _make_test_script(script_dir, 'not_main')
|
||||||
|
zip_name, run_name = _make_test_zip(script_dir, 'test_zip', script_name)
|
||||||
|
msg = "can't find '__main__.py' in %r" % zip_name
|
||||||
|
self._check_import_error(zip_name, msg)
|
||||||
|
|
||||||
def test_module_in_package(self):
|
def test_module_in_package(self):
|
||||||
with temp_dir() as script_dir:
|
with temp_dir() as script_dir:
|
||||||
pkg_dir = os.path.join(script_dir, 'test_pkg')
|
pkg_dir = os.path.join(script_dir, 'test_pkg')
|
||||||
|
@ -215,6 +237,47 @@ class CmdLineTest(unittest.TestCase):
|
||||||
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.test_pkg.script', zip_name)
|
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.test_pkg.script', zip_name)
|
||||||
self._check_script(launch_name, run_name, run_name, 'test_pkg.test_pkg')
|
self._check_script(launch_name, run_name, run_name, 'test_pkg.test_pkg')
|
||||||
|
|
||||||
|
def test_package(self):
|
||||||
|
with temp_dir() as script_dir:
|
||||||
|
pkg_dir = os.path.join(script_dir, 'test_pkg')
|
||||||
|
_make_test_pkg(pkg_dir)
|
||||||
|
script_name = _make_test_script(pkg_dir, '__main__')
|
||||||
|
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg')
|
||||||
|
self._check_script(launch_name, script_name,
|
||||||
|
script_name, 'test_pkg')
|
||||||
|
|
||||||
|
def test_package_compiled(self):
|
||||||
|
with temp_dir() as script_dir:
|
||||||
|
pkg_dir = os.path.join(script_dir, 'test_pkg')
|
||||||
|
_make_test_pkg(pkg_dir)
|
||||||
|
script_name = _make_test_script(pkg_dir, '__main__')
|
||||||
|
compiled_name = _compile_test_script(script_name)
|
||||||
|
os.remove(script_name)
|
||||||
|
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg')
|
||||||
|
self._check_script(launch_name, compiled_name,
|
||||||
|
compiled_name, 'test_pkg')
|
||||||
|
|
||||||
|
def test_package_error(self):
|
||||||
|
with temp_dir() as script_dir:
|
||||||
|
pkg_dir = os.path.join(script_dir, 'test_pkg')
|
||||||
|
_make_test_pkg(pkg_dir)
|
||||||
|
msg = ("'test_pkg' is a package and cannot "
|
||||||
|
"be directly executed")
|
||||||
|
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg')
|
||||||
|
self._check_import_error(launch_name, msg)
|
||||||
|
|
||||||
|
def test_package_recursion(self):
|
||||||
|
with temp_dir() as script_dir:
|
||||||
|
pkg_dir = os.path.join(script_dir, 'test_pkg')
|
||||||
|
_make_test_pkg(pkg_dir)
|
||||||
|
main_dir = os.path.join(pkg_dir, '__main__')
|
||||||
|
_make_test_pkg(main_dir)
|
||||||
|
msg = ("Cannot use package as __main__ module; "
|
||||||
|
"'test_pkg' is a package and cannot "
|
||||||
|
"be directly executed")
|
||||||
|
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg')
|
||||||
|
self._check_import_error(launch_name, msg)
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
test.test_support.run_unittest(CmdLineTest)
|
test.test_support.run_unittest(CmdLineTest)
|
||||||
|
|
|
@ -712,6 +712,7 @@ Lionel Ulmer
|
||||||
Roger Upole
|
Roger Upole
|
||||||
Michael Urman
|
Michael Urman
|
||||||
Hector Urtubia
|
Hector Urtubia
|
||||||
|
Andi Vajda
|
||||||
Atul Varma
|
Atul Varma
|
||||||
Dmitry Vasiliev
|
Dmitry Vasiliev
|
||||||
Alexandre Vassalotti
|
Alexandre Vassalotti
|
||||||
|
|
|
@ -149,6 +149,10 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #4195: The ``runpy`` module (and the ``-m`` switch) now support
|
||||||
|
the execution of packages by looking for and executing a ``__main__``
|
||||||
|
submodule when a package name is supplied.
|
||||||
|
|
||||||
- Issue #1731706: Call Tcl_ConditionFinalize for Tcl_Conditions that will
|
- Issue #1731706: Call Tcl_ConditionFinalize for Tcl_Conditions that will
|
||||||
not be used again (this requires Tcl/Tk 8.3.1), also fix a memory leak in
|
not be used again (this requires Tcl/Tk 8.3.1), also fix a memory leak in
|
||||||
Tkapp_Call when calling from a thread different than the one that created
|
Tkapp_Call when calling from a thread different than the one that created
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue