[Python-checkins] r59288 - in python/trunk: Lib/runpy.py Lib/test/test_cmd_line_script.py Lib/test/test_pkg.py Lib/test/test_runpy.py Misc/NEWS Objects/moduleobject.c Python/import.c

nick.coghlan python-checkins at python.org
Mon Dec 3 13:55:18 CET 2007


Author: nick.coghlan
Date: Mon Dec  3 13:55:17 2007
New Revision: 59288

Modified:
   python/trunk/Lib/runpy.py
   python/trunk/Lib/test/test_cmd_line_script.py
   python/trunk/Lib/test/test_pkg.py
   python/trunk/Lib/test/test_runpy.py
   python/trunk/Misc/NEWS
   python/trunk/Objects/moduleobject.c
   python/trunk/Python/import.c
Log:
Implement PEP 366

Modified: python/trunk/Lib/runpy.py
==============================================================================
--- python/trunk/Lib/runpy.py	(original)
+++ python/trunk/Lib/runpy.py	Mon Dec  3 13:55:17 2007
@@ -23,19 +23,20 @@
 
 def _run_code(code, run_globals, init_globals=None,
               mod_name=None, mod_fname=None,
-              mod_loader=None):
+              mod_loader=None, pkg_name=None):
     """Helper for _run_module_code"""
     if init_globals is not None:
         run_globals.update(init_globals)
     run_globals.update(__name__ = mod_name,
                        __file__ = mod_fname,
-                       __loader__ = mod_loader)
+                       __loader__ = mod_loader,
+                       __package__ = pkg_name)
     exec code in run_globals
     return run_globals
 
 def _run_module_code(code, init_globals=None,
                     mod_name=None, mod_fname=None,
-                    mod_loader=None):
+                    mod_loader=None, pkg_name=None):
     """Helper for run_module"""
     # Set up the top level namespace dictionary
     temp_module = imp.new_module(mod_name)
@@ -49,7 +50,8 @@
     sys.modules[mod_name] = temp_module
     try:
         _run_code(code, mod_globals, init_globals,
-                    mod_name, mod_fname, mod_loader)
+                    mod_name, mod_fname,
+                    mod_loader, pkg_name)
     finally:
         sys.argv[0] = saved_argv0
         if restore_module:
@@ -95,11 +97,12 @@
            __loader__
     """
     loader, code, fname = _get_module_details(mod_name)
+    pkg_name = mod_name.rpartition('.')[0]
     main_globals = sys.modules["__main__"].__dict__
     if set_argv0:
         sys.argv[0] = fname
     return _run_code(code, main_globals, None,
-                     "__main__", fname, loader)
+                     "__main__", fname, loader, pkg_name)
 
 def run_module(mod_name, init_globals=None,
                run_name=None, alter_sys=False):
@@ -110,13 +113,14 @@
     loader, code, fname = _get_module_details(mod_name)
     if run_name is None:
         run_name = mod_name
+    pkg_name = mod_name.rpartition('.')[0]
     if alter_sys:
         return _run_module_code(code, init_globals, run_name,
-                                fname, loader)
+                                fname, loader, pkg_name)
     else:
         # Leave the sys module alone
-        return _run_code(code, {}, init_globals,
-                         run_name, fname, loader)
+        return _run_code(code, {}, init_globals, run_name,
+                         fname, loader, pkg_name)
 
 
 if __name__ == "__main__":

Modified: python/trunk/Lib/test/test_cmd_line_script.py
==============================================================================
--- python/trunk/Lib/test/test_cmd_line_script.py	(original)
+++ python/trunk/Lib/test/test_cmd_line_script.py	Mon Dec  3 13:55:17 2007
@@ -35,15 +35,15 @@
     finally:
         shutil.rmtree(dirname)
 
-test_source = ("""\
+test_source = """\
 # Script may be run with optimisation enabled, so don't rely on assert
 # statements being executed
 def assertEqual(lhs, rhs):
     if lhs != rhs:
-        raise AssertionError("%r != %r" % (lhs, rhs))
+        raise AssertionError('%r != %r' % (lhs, rhs))
 def assertIdentical(lhs, rhs):
     if lhs is not rhs:
-        raise AssertionError("%r is not %r" % (lhs, rhs))
+        raise AssertionError('%r is not %r' % (lhs, rhs))
 # Check basic code execution
 result = ['Top level assignment']
 def f():
@@ -53,17 +53,18 @@
 # Check population of magic variables
 assertEqual(__name__, '__main__')
 print '__file__==%r' % __file__
+print '__package__==%r' % __package__
 # Check the sys module
 import sys
 assertIdentical(globals(), sys.modules[__name__].__dict__)
 print 'sys.argv[0]==%r' % sys.argv[0]
-""")
+"""
 
-def _make_test_script(script_dir, script_basename):
-    script_filename = script_basename+os.extsep+"py"
+def _make_test_script(script_dir, script_basename, source=test_source):
+    script_filename = script_basename+os.extsep+'py'
     script_name = os.path.join(script_dir, script_filename)
-    script_file = open(script_name, "w")
-    script_file.write(test_source)
+    script_file = open(script_name, 'w')
+    script_file.write(source)
     script_file.close()
     return script_name
 
@@ -76,71 +77,108 @@
     return compiled_name
 
 def _make_test_zip(zip_dir, zip_basename, script_name):
-    zip_filename = zip_basename+os.extsep+"zip"
+    zip_filename = zip_basename+os.extsep+'zip'
     zip_name = os.path.join(zip_dir, zip_filename)
     zip_file = zipfile.ZipFile(zip_name, 'w')
     zip_file.write(script_name, os.path.basename(script_name))
     zip_file.close()
     # if verbose:
     #    zip_file = zipfile.ZipFile(zip_name, 'r')
-    #    print "Contents of %r:" % zip_name
+    #    print 'Contents of %r:' % zip_name
     #    zip_file.printdir()
     #    zip_file.close()
     return zip_name
 
+def _make_test_pkg(pkg_dir):
+    os.mkdir(pkg_dir)
+    _make_test_script(pkg_dir, '__init__', '')
+
+# There's no easy way to pass the script directory in to get
+# -m to work (avoiding that is the whole point of making
+# directories and zipfiles executable!)
+# So we fake it for testing purposes with a custom launch script
+launch_source = """\
+import sys, os.path, runpy
+sys.path[0:0] = os.path.dirname(__file__)
+runpy._run_module_as_main(%r)
+"""
+
+def _make_launch_script(script_dir, script_basename, module_name):
+    return _make_test_script(script_dir, script_basename,
+                             launch_source % module_name)
+
 class CmdLineTest(unittest.TestCase):
-    def _check_script(self, script_name, expected_file, expected_argv0):
-        exit_code, data = _run_python(script_name)
+    def _check_script(self, script_name, expected_file,
+                            expected_argv0, expected_package,
+                            *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 'Output from test script %r:' % script_name
             print data
         self.assertEqual(exit_code, 0)
         printed_file = '__file__==%r' % expected_file
         printed_argv0 = 'sys.argv[0]==%r' % expected_argv0
+        printed_package = '__package__==%r' % expected_package
+        if verbose:
+            print 'Expected output:'
+            print printed_file
+            print printed_package
+            print printed_argv0
         self.assert_(printed_file in data)
+        self.assert_(printed_package in data)
         self.assert_(printed_argv0 in data)
 
     def test_basic_script(self):
         with temp_dir() as script_dir:
-            script_name = _make_test_script(script_dir, "script")
-            self._check_script(script_name, script_name, script_name)
+            script_name = _make_test_script(script_dir, 'script')
+            self._check_script(script_name, script_name, script_name, None)
 
     def test_script_compiled(self):
         with temp_dir() as script_dir:
-            script_name = _make_test_script(script_dir, "script")
+            script_name = _make_test_script(script_dir, 'script')
             compiled_name = _compile_test_script(script_name)
             os.remove(script_name)
-            self._check_script(compiled_name, compiled_name, compiled_name)
+            self._check_script(compiled_name, compiled_name, compiled_name, None)
 
     def test_directory(self):
         with temp_dir() as script_dir:
-            script_name = _make_test_script(script_dir, "__main__")
-            self._check_script(script_dir, script_name, script_dir)
+            script_name = _make_test_script(script_dir, '__main__')
+            self._check_script(script_dir, script_name, script_dir, '')
 
     def test_directory_compiled(self):
         with temp_dir() as script_dir:
-            script_name = _make_test_script(script_dir, "__main__")
+            script_name = _make_test_script(script_dir, '__main__')
             compiled_name = _compile_test_script(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_zipfile(self):
         with temp_dir() as script_dir:
-            script_name = _make_test_script(script_dir, "__main__")
-            zip_name = _make_test_zip(script_dir, "test_zip", script_name)
-            self._check_script(zip_name, None, zip_name)
+            script_name = _make_test_script(script_dir, '__main__')
+            zip_name = _make_test_zip(script_dir, 'test_zip', script_name)
+            self._check_script(zip_name, None, zip_name, '')
 
     def test_zipfile_compiled(self):
         with temp_dir() as script_dir:
-            script_name = _make_test_script(script_dir, "__main__")
+            script_name = _make_test_script(script_dir, '__main__')
             compiled_name = _compile_test_script(script_name)
-            zip_name = _make_test_zip(script_dir, "test_zip", compiled_name)
-            self._check_script(zip_name, None, zip_name)
+            zip_name = _make_test_zip(script_dir, 'test_zip', compiled_name)
+            self._check_script(zip_name, None, zip_name, '')
+
+    def test_module_in_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, 'script')
+            launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg.script')
+            self._check_script(launch_name, script_name,
+                               script_name, 'test_pkg')
 
 
 def test_main():
     test.test_support.run_unittest(CmdLineTest)
     test.test_support.reap_children()
 
-if __name__ == "__main__":
+if __name__ == '__main__':
     test_main()

Modified: python/trunk/Lib/test/test_pkg.py
==============================================================================
--- python/trunk/Lib/test/test_pkg.py	(original)
+++ python/trunk/Lib/test/test_pkg.py	Mon Dec  3 13:55:17 2007
@@ -188,11 +188,13 @@
         import t5
         self.assertEqual(fixdir(dir(t5)),
                          ['__doc__', '__file__', '__name__',
-                          '__path__', 'foo', 'string', 't5'])
+                          '__package__', '__path__', 'foo', 'string', 't5'])
         self.assertEqual(fixdir(dir(t5.foo)),
-                         ['__doc__', '__file__', '__name__', 'string'])
+                         ['__doc__', '__file__', '__name__', '__package__',
+                          'string'])
         self.assertEqual(fixdir(dir(t5.string)),
-                         ['__doc__', '__file__', '__name__', 'spam'])
+                         ['__doc__', '__file__', '__name__','__package__',
+                          'spam'])
 
     def test_6(self):
         hier = [
@@ -208,14 +210,14 @@
         import t6
         self.assertEqual(fixdir(dir(t6)),
                          ['__all__', '__doc__', '__file__',
-                          '__name__', '__path__'])
+                          '__name__', '__package__', '__path__'])
         s = """
             import t6
             from t6 import *
             self.assertEqual(fixdir(dir(t6)),
                              ['__all__', '__doc__', '__file__',
-                              '__name__', '__path__', 'eggs',
-                              'ham', 'spam'])
+                              '__name__', '__package__', '__path__',
+                              'eggs', 'ham', 'spam'])
             self.assertEqual(dir(), ['eggs', 'ham', 'self', 'spam', 't6'])
             """
         self.run_code(s)
@@ -241,17 +243,19 @@
         t7, sub, subsub = None, None, None
         import t7 as tas
         self.assertEqual(fixdir(dir(tas)),
-                         ['__doc__', '__file__', '__name__', '__path__'])
+                         ['__doc__', '__file__', '__name__',
+                          '__package__', '__path__'])
         self.failIf(t7)
         from t7 import sub as subpar
         self.assertEqual(fixdir(dir(subpar)),
-                         ['__doc__', '__file__', '__name__', '__path__'])
+                         ['__doc__', '__file__', '__name__',
+                          '__package__', '__path__'])
         self.failIf(t7)
         self.failIf(sub)
         from t7.sub import subsub as subsubsub
         self.assertEqual(fixdir(dir(subsubsub)),
-                         ['__doc__', '__file__', '__name__', '__path__',
-                          'spam'])
+                         ['__doc__', '__file__', '__name__',
+                         '__package__', '__path__', 'spam'])
         self.failIf(t7)
         self.failIf(sub)
         self.failIf(subsub)

Modified: python/trunk/Lib/test/test_runpy.py
==============================================================================
--- python/trunk/Lib/test/test_runpy.py	(original)
+++ python/trunk/Lib/test/test_runpy.py	Mon Dec  3 13:55:17 2007
@@ -5,7 +5,12 @@
 import sys
 import tempfile
 from test.test_support import verbose, run_unittest, forget
-from runpy import _run_code, _run_module_code, _run_module_as_main, run_module
+from runpy import _run_code, _run_module_code, run_module
+
+# Note: This module can't safely test _run_module_as_main as it
+# runs its tests in the current process, which would mess with the
+# real __main__ module (usually test.regrtest)
+# See test_cmd_line_script for a test that executes that code path
 
 # Set up the test code and expected results
 
@@ -36,6 +41,7 @@
         self.failUnless(d["__name__"] is None)
         self.failUnless(d["__file__"] is None)
         self.failUnless(d["__loader__"] is None)
+        self.failUnless(d["__package__"] is None)
         self.failUnless(d["run_argv0"] is saved_argv0)
         self.failUnless("run_name" not in d)
         self.failUnless(sys.argv[0] is saved_argv0)
@@ -45,13 +51,15 @@
         name = "<Nonsense>"
         file = "Some other nonsense"
         loader = "Now you're just being silly"
+        package = '' # Treat as a top level module
         d1 = dict(initial=initial)
         saved_argv0 = sys.argv[0]
         d2 = _run_module_code(self.test_source,
                               d1,
                               name,
                               file,
-                              loader)
+                              loader,
+                              package)
         self.failUnless("result" not in d1)
         self.failUnless(d2["initial"] is initial)
         self.failUnless(d2["result"] == self.expected_result)
@@ -62,6 +70,7 @@
         self.failUnless(d2["__file__"] is file)
         self.failUnless(d2["run_argv0"] is file)
         self.failUnless(d2["__loader__"] is loader)
+        self.failUnless(d2["__package__"] is package)
         self.failUnless(sys.argv[0] is saved_argv0)
         self.failUnless(name not in sys.modules)
 
@@ -164,7 +173,7 @@
             self._del_pkg(pkg_dir, depth, mod_name)
         if verbose: print "Module executed successfully"
 
-    def _add_relative_modules(self, base_dir, depth):
+    def _add_relative_modules(self, base_dir, source, depth):
         if depth <= 1:
             raise ValueError("Relative module test needs depth > 1")
         pkg_name = "__runpy_pkg__"
@@ -190,7 +199,7 @@
         if verbose: print "  Added nephew module:", nephew_fname
 
     def _check_relative_imports(self, depth, run_name=None):
-        contents = """\
+        contents = r"""\
 from __future__ import absolute_import
 from . import sibling
 from ..uncle.cousin import nephew
@@ -198,16 +207,21 @@
         pkg_dir, mod_fname, mod_name = (
                self._make_pkg(contents, depth))
         try:
-            self._add_relative_modules(pkg_dir, depth)
+            self._add_relative_modules(pkg_dir, contents, depth)
+            pkg_name = mod_name.rpartition('.')[0]
             if verbose: print "Running from source:", mod_name
-            d1 = run_module(mod_name) # Read from source
+            d1 = run_module(mod_name, run_name=run_name) # Read from source
+            self.failUnless("__package__" in d1)
+            self.failUnless(d1["__package__"] == pkg_name)
             self.failUnless("sibling" in d1)
             self.failUnless("nephew" in d1)
             del d1 # Ensure __loader__ entry doesn't keep file open
             __import__(mod_name)
             os.remove(mod_fname)
             if verbose: print "Running from compiled:", mod_name
-            d2 = run_module(mod_name) # Read from bytecode
+            d2 = run_module(mod_name, run_name=run_name) # Read from bytecode
+            self.failUnless("__package__" in d2)
+            self.failUnless(d2["__package__"] == pkg_name)
             self.failUnless("sibling" in d2)
             self.failUnless("nephew" in d2)
             del d2 # Ensure __loader__ entry doesn't keep file open
@@ -225,6 +239,11 @@
             if verbose: print "Testing relative imports at depth:", depth
             self._check_relative_imports(depth)
 
+    def test_main_relative_import(self):
+        for depth in range(2, 5):
+            if verbose: print "Testing main relative imports at depth:", depth
+            self._check_relative_imports(depth, "__main__")
+
 
 def test_main():
     run_unittest(RunModuleCodeTest)

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Mon Dec  3 13:55:17 2007
@@ -12,6 +12,10 @@
 Core and builtins
 -----------------
 
+- PEP 366: Allow explicit relative imports when executing modules
+  inside packages with the -m switch via a new module level
+  __package__ attribute.
+
 - Issue #1534: Added ``PyFloat_GetMax()``, ``PyFloat_GetMin()`` and
   ``PyFloat_GetInfo()`` to the float API.
 

Modified: python/trunk/Objects/moduleobject.c
==============================================================================
--- python/trunk/Objects/moduleobject.c	(original)
+++ python/trunk/Objects/moduleobject.c	Mon Dec  3 13:55:17 2007
@@ -30,6 +30,8 @@
 		goto fail;
 	if (PyDict_SetItemString(m->md_dict, "__doc__", Py_None) != 0)
 		goto fail;
+	if (PyDict_SetItemString(m->md_dict, "__package__", Py_None) != 0)
+		goto fail;
 	Py_DECREF(nameobj);
 	PyObject_GC_Track(m);
 	return (PyObject *)m;

Modified: python/trunk/Python/import.c
==============================================================================
--- python/trunk/Python/import.c	(original)
+++ python/trunk/Python/import.c	Mon Dec  3 13:55:17 2007
@@ -2106,7 +2106,8 @@
 {
 	static PyObject *namestr = NULL;
 	static PyObject *pathstr = NULL;
-	PyObject *modname, *modpath, *modules, *parent;
+	static PyObject *pkgstr = NULL;
+	PyObject *pkgname, *modname, *modpath, *modules, *parent;
 
 	if (globals == NULL || !PyDict_Check(globals) || !level)
 		return Py_None;
@@ -2121,44 +2122,103 @@
 		if (pathstr == NULL)
 			return NULL;
 	}
+	if (pkgstr == NULL) {
+		pkgstr = PyString_InternFromString("__package__");
+		if (pkgstr == NULL)
+			return NULL;
+	}
 
 	*buf = '\0';
 	*p_buflen = 0;
-	modname = PyDict_GetItem(globals, namestr);
-	if (modname == NULL || !PyString_Check(modname))
-		return Py_None;
+	pkgname = PyDict_GetItem(globals, pkgstr);
 
-	modpath = PyDict_GetItem(globals, pathstr);
-	if (modpath != NULL) {
-		Py_ssize_t len = PyString_GET_SIZE(modname);
-		if (len > MAXPATHLEN) {
+	if ((pkgname != NULL) && (pkgname != Py_None)) {
+		/* __package__ is set, so use it */
+		Py_ssize_t len;
+		if (!PyString_Check(pkgname)) {
 			PyErr_SetString(PyExc_ValueError,
-					"Module name too long");
+					"__package__ set to non-string");
 			return NULL;
 		}
-		strcpy(buf, PyString_AS_STRING(modname));
-	}
-	else {
-		char *start = PyString_AS_STRING(modname);
-		char *lastdot = strrchr(start, '.');
-		size_t len;
-		if (lastdot == NULL && level > 0) {
+		len = PyString_GET_SIZE(pkgname);
+		if (len == 0) {
+			if (level > 0) {
+				PyErr_SetString(PyExc_ValueError,
+					"Attempted relative import in non-package");
+				return NULL;
+			}
+			return Py_None;
+		}
+		if (len > MAXPATHLEN) {
 			PyErr_SetString(PyExc_ValueError,
-				"Attempted relative import in non-package");
+					"Package name too long");
 			return NULL;
 		}
-		if (lastdot == NULL)
+		strcpy(buf, PyString_AS_STRING(pkgname));
+	} else {
+		/* __package__ not set, so figure it out and set it */
+		modname = PyDict_GetItem(globals, namestr);
+		if (modname == NULL || !PyString_Check(modname))
 			return Py_None;
-		len = lastdot - start;
-		if (len >= MAXPATHLEN) {
-			PyErr_SetString(PyExc_ValueError,
-					"Module name too long");
-			return NULL;
+	
+		modpath = PyDict_GetItem(globals, pathstr);
+		if (modpath != NULL) {
+			/* __path__ is set, so modname is already the package name */
+			Py_ssize_t len = PyString_GET_SIZE(modname);
+			int error;
+			if (len > MAXPATHLEN) {
+				PyErr_SetString(PyExc_ValueError,
+						"Module name too long");
+				return NULL;
+			}
+			strcpy(buf, PyString_AS_STRING(modname));
+			error = PyDict_SetItem(globals, pkgstr, modname);
+			if (error) {
+				PyErr_SetString(PyExc_ValueError,
+						"Could not set __package__");
+				return NULL;
+			}
+		} else {
+			/* Normal module, so work out the package name if any */
+			char *start = PyString_AS_STRING(modname);
+			char *lastdot = strrchr(start, '.');
+			size_t len;
+			int error;
+			if (lastdot == NULL && level > 0) {
+				PyErr_SetString(PyExc_ValueError,
+					"Attempted relative import in non-package");
+				return NULL;
+			}
+			if (lastdot == NULL) {
+				error = PyDict_SetItem(globals, pkgstr, Py_None);
+				if (error) {
+					PyErr_SetString(PyExc_ValueError,
+						"Could not set __package__");
+					return NULL;
+				}
+				return Py_None;
+			}
+			len = lastdot - start;
+			if (len >= MAXPATHLEN) {
+				PyErr_SetString(PyExc_ValueError,
+						"Module name too long");
+				return NULL;
+			}
+			strncpy(buf, start, len);
+			buf[len] = '\0';
+			pkgname = PyString_FromString(buf);
+			if (pkgname == NULL) {
+				return NULL;
+			}
+			error = PyDict_SetItem(globals, pkgstr, pkgname);
+			Py_DECREF(pkgname);
+			if (error) {
+				PyErr_SetString(PyExc_ValueError,
+						"Could not set __package__");
+				return NULL;
+			}
 		}
-		strncpy(buf, start, len);
-		buf[len] = '\0';
 	}
-
 	while (--level > 0) {
 		char *dot = strrchr(buf, '.');
 		if (dot == NULL) {


More information about the Python-checkins mailing list