Skip to content

Commit 72ea5d2

Browse files
author
nick.coghlan
committed
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)
git-svn-id: http://svn.python.org/projects/python/trunk@69419 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent 7eeb630 commit 72ea5d2

File tree

6 files changed

+110
-12
lines changed

6 files changed

+110
-12
lines changed

Doc/library/runpy.rst

+20-5
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,22 @@ The :mod:`runpy` module provides a single function:
2828
mechanism (refer to PEP 302 for details) and then executed in a fresh module
2929
namespace.
3030

31+
If the supplied module name refers to a package rather than a normal module,
32+
then that package is imported and the ``__main__`` submodule within that
33+
package is then executed and the resulting module globals dictionary returned.
34+
3135
The optional dictionary argument *init_globals* may be used to pre-populate the
3236
globals dictionary before the code is executed. The supplied dictionary will not
3337
be modified. If any of the special global variables below are defined in the
3438
supplied dictionary, those definitions are overridden by the ``run_module``
3539
function.
3640

37-
The special global variables ``__name__``, ``__file__``, ``__loader__`` and
38-
``__builtins__`` are set in the globals dictionary before the module code is
39-
executed.
41+
The special global variables ``__name__``, ``__file__``, ``__loader__``,
42+
``__builtins__`` and ``__package__`` are set in the globals dictionary before
43+
the module code is executed.
4044

41-
``__name__`` is set to *run_name* if this optional argument is supplied, and the
45+
``__name__`` is set to *run_name* if this optional argument is supplied, to
46+
``mod_name + '.__main__'`` if the named module is a package and to the
4247
*mod_name* argument otherwise.
4348

4449
``__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:
5055
``__builtins__`` is automatically initialised with a reference to the top level
5156
namespace of the :mod:`__builtin__` module.
5257

58+
``__package__`` is set to *mod_name* if the named module is a package and to
59+
``mod_name.rpartition('.')[0]`` otherwise.
60+
5361
If the argument *alter_sys* is supplied and evaluates to ``True``, then
5462
``sys.argv[0]`` is updated with the value of ``__file__`` and
5563
``sys.modules[__name__]`` is updated with a temporary module object for the
@@ -62,8 +70,15 @@ The :mod:`runpy` module provides a single function:
6270
function from threaded code.
6371

6472

73+
.. versionchanged:: 2.7
74+
Added ability to execute packages by looking for a ``__main__`` submodule
75+
76+
6577
.. seealso::
6678

6779
:pep:`338` - Executing modules as scripts
68-
PEP written and implemented by Nick Coghlan.
80+
PEP written and implemented by Nick Coghlan.
81+
82+
:pep:`366` - Main module explicit relative imports
83+
PEP written and implemented by Nick Coghlan.
6984

Doc/using/cmdline.rst

+10-1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ source.
7878
the implementation may not always enforce this (e.g. it may allow you to
7979
use a name that includes a hyphen).
8080

81+
Package names are also permitted. When a package name is supplied instead
82+
of a normal module, the interpreter will execute ``<pkg>.__main__`` as
83+
the main module. This behaviour is deliberately similar to the handling
84+
of directories and zipfiles that are passed to the interpreter as the
85+
script argument.
86+
8187
.. note::
8288

8389
This option cannot be used with builtin modules and extension modules
@@ -97,7 +103,7 @@ source.
97103

98104
.. seealso::
99105
:func:`runpy.run_module`
100-
The actual implementation of this feature.
106+
Equivalent functionality directly available to Python code
101107

102108
:pep:`338` -- Executing modules as scripts
103109

@@ -106,6 +112,9 @@ source.
106112
.. versionchanged:: 2.5
107113
The named module can now be located inside a package.
108114

115+
.. versionchanged:: 2.7
116+
Supply the package name to run a ``__main__`` submodule.
117+
109118

110119
.. describe:: -
111120

Lib/runpy.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,19 @@ def _get_module_details(mod_name):
8080
if loader is None:
8181
raise ImportError("No module named %s" % mod_name)
8282
if loader.is_package(mod_name):
83-
raise ImportError(("%s is a package and cannot " +
84-
"be directly executed") % mod_name)
83+
if mod_name == "__main__" or mod_name.endswith(".__main__"):
84+
raise ImportError(("Cannot use package as __main__ module"))
85+
try:
86+
pkg_main_name = mod_name + ".__main__"
87+
return _get_module_details(pkg_main_name)
88+
except ImportError, e:
89+
raise ImportError(("%s; %r is a package and cannot " +
90+
"be directly executed") %(e, mod_name))
8591
code = loader.get_code(mod_name)
8692
if code is None:
8793
raise ImportError("No code object available for %s" % mod_name)
8894
filename = _get_filename(loader, mod_name)
89-
return loader, code, filename
95+
return mod_name, loader, code, filename
9096

9197

9298
# XXX ncoghlan: Should this be documented and made public?
@@ -101,12 +107,12 @@ def _run_module_as_main(mod_name, set_argv0=True):
101107
__loader__
102108
"""
103109
try:
104-
loader, code, fname = _get_module_details(mod_name)
110+
mod_name, loader, code, fname = _get_module_details(mod_name)
105111
except ImportError as exc:
106112
# Try to provide a good error message
107113
# for directories, zip files and the -m switch
108114
if set_argv0:
109-
# For -m switch, just disply the exception
115+
# For -m switch, just display the exception
110116
info = str(exc)
111117
else:
112118
# For directories/zipfiles, let the user
@@ -127,7 +133,7 @@ def run_module(mod_name, init_globals=None,
127133
128134
Returns the resulting top level namespace dictionary
129135
"""
130-
loader, code, fname = _get_module_details(mod_name)
136+
mod_name, loader, code, fname = _get_module_details(mod_name)
131137
if run_name is None:
132138
run_name = mod_name
133139
pkg_name = mod_name.rpartition('.')[0]

Lib/test/test_cmd_line_script.py

+63
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,16 @@ def _check_script(self, script_name, expected_file,
158158
self.assert_(printed_package in data)
159159
self.assert_(printed_argv0 in data)
160160

161+
def _check_import_error(self, script_name, expected_msg,
162+
*cmd_line_switches):
163+
run_args = cmd_line_switches + (script_name,)
164+
exit_code, data = _run_python(*run_args)
165+
if verbose:
166+
print 'Output from test script %r:' % script_name
167+
print data
168+
print 'Expected output: %r' % expected_msg
169+
self.assert_(expected_msg in data)
170+
161171
def test_basic_script(self):
162172
with temp_dir() as script_dir:
163173
script_name = _make_test_script(script_dir, 'script')
@@ -182,6 +192,11 @@ def test_directory_compiled(self):
182192
os.remove(script_name)
183193
self._check_script(script_dir, compiled_name, script_dir, '')
184194

195+
def test_directory_error(self):
196+
with temp_dir() as script_dir:
197+
msg = "can't find '__main__.py' in %r" % script_dir
198+
self._check_import_error(script_dir, msg)
199+
185200
def test_zipfile(self):
186201
with temp_dir() as script_dir:
187202
script_name = _make_test_script(script_dir, '__main__')
@@ -195,6 +210,13 @@ def test_zipfile_compiled(self):
195210
zip_name, run_name = _make_test_zip(script_dir, 'test_zip', compiled_name)
196211
self._check_script(zip_name, run_name, zip_name, '')
197212

213+
def test_zipfile_error(self):
214+
with temp_dir() as script_dir:
215+
script_name = _make_test_script(script_dir, 'not_main')
216+
zip_name, run_name = _make_test_zip(script_dir, 'test_zip', script_name)
217+
msg = "can't find '__main__.py' in %r" % zip_name
218+
self._check_import_error(zip_name, msg)
219+
198220
def test_module_in_package(self):
199221
with temp_dir() as script_dir:
200222
pkg_dir = os.path.join(script_dir, 'test_pkg')
@@ -215,6 +237,47 @@ def test_module_in_subpackage_in_zipfile(self):
215237
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.test_pkg.script', zip_name)
216238
self._check_script(launch_name, run_name, run_name, 'test_pkg.test_pkg')
217239

240+
def test_package(self):
241+
with temp_dir() as script_dir:
242+
pkg_dir = os.path.join(script_dir, 'test_pkg')
243+
_make_test_pkg(pkg_dir)
244+
script_name = _make_test_script(pkg_dir, '__main__')
245+
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg')
246+
self._check_script(launch_name, script_name,
247+
script_name, 'test_pkg')
248+
249+
def test_package_compiled(self):
250+
with temp_dir() as script_dir:
251+
pkg_dir = os.path.join(script_dir, 'test_pkg')
252+
_make_test_pkg(pkg_dir)
253+
script_name = _make_test_script(pkg_dir, '__main__')
254+
compiled_name = _compile_test_script(script_name)
255+
os.remove(script_name)
256+
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg')
257+
self._check_script(launch_name, compiled_name,
258+
compiled_name, 'test_pkg')
259+
260+
def test_package_error(self):
261+
with temp_dir() as script_dir:
262+
pkg_dir = os.path.join(script_dir, 'test_pkg')
263+
_make_test_pkg(pkg_dir)
264+
msg = ("'test_pkg' is a package and cannot "
265+
"be directly executed")
266+
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg')
267+
self._check_import_error(launch_name, msg)
268+
269+
def test_package_recursion(self):
270+
with temp_dir() as script_dir:
271+
pkg_dir = os.path.join(script_dir, 'test_pkg')
272+
_make_test_pkg(pkg_dir)
273+
main_dir = os.path.join(pkg_dir, '__main__')
274+
_make_test_pkg(main_dir)
275+
msg = ("Cannot use package as __main__ module; "
276+
"'test_pkg' is a package and cannot "
277+
"be directly executed")
278+
launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg')
279+
self._check_import_error(launch_name, msg)
280+
218281

219282
def test_main():
220283
test.test_support.run_unittest(CmdLineTest)

Misc/ACKS

+1
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,7 @@ Lionel Ulmer
712712
Roger Upole
713713
Michael Urman
714714
Hector Urtubia
715+
Andi Vajda
715716
Atul Varma
716717
Dmitry Vasiliev
717718
Alexandre Vassalotti

Misc/NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ Core and Builtins
149149
Library
150150
-------
151151

152+
- Issue #4195: The ``runpy`` module (and the ``-m`` switch) now support
153+
the execution of packages by looking for and executing a ``__main__``
154+
submodule when a package name is supplied.
155+
152156
- Issue #1731706: Call Tcl_ConditionFinalize for Tcl_Conditions that will
153157
not be used again (this requires Tcl/Tk 8.3.1), also fix a memory leak in
154158
Tkapp_Call when calling from a thread different than the one that created

0 commit comments

Comments
 (0)