[Python-checkins] cpython: Issue #25791: Raise an ImportWarning when __spec__ or __package__ are

brett.cannon python-checkins at python.org
Fri Jan 15 16:33:10 EST 2016


https://hg.python.org/cpython/rev/6908b2c9a404
changeset:   99910:6908b2c9a404
user:        Brett Cannon <brett at python.org>
date:        Fri Jan 15 13:33:03 2016 -0800
summary:
  Issue #25791: Raise an ImportWarning when __spec__ or __package__ are
not defined for a relative import.

This is the start of work to try and clean up import semantics to rely
more on a module's spec than on the myriad attributes that get set on
a module. Thanks to Rose Ames for the patch.

files:
  Doc/reference/import.rst                            |    3 +-
  Doc/whatsnew/3.6.rst                                |   11 +-
  Lib/importlib/_bootstrap.py                         |    6 +
  Lib/test/test_importlib/import_/test___package__.py |   40 +-
  Misc/NEWS                                           |    3 +
  Python/import.c                                     |   66 +-
  Python/importlib.h                                  |  559 +++++----
  7 files changed, 377 insertions(+), 311 deletions(-)


diff --git a/Doc/reference/import.rst b/Doc/reference/import.rst
--- a/Doc/reference/import.rst
+++ b/Doc/reference/import.rst
@@ -554,7 +554,8 @@
    details.
 
    This attribute is used instead of ``__name__`` to calculate explicit
-   relative imports for main modules, as defined in :pep:`366`.
+   relative imports for main modules -- as defined in :pep:`366` --
+   when ``__spec__`` is not defined.
 
 .. attribute:: __spec__
 
diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst
--- a/Doc/whatsnew/3.6.rst
+++ b/Doc/whatsnew/3.6.rst
@@ -209,7 +209,12 @@
 * The ``pyvenv`` script has been deprecated in favour of ``python3 -m venv``.
   This prevents confusion as to what Python interpreter ``pyvenv`` is
   connected to and thus what Python interpreter will be used by the virtual
-  environment. See :issue:`25154`.
+  environment. (Contributed by Brett Cannon in :issue:`25154`.)
+
+* When performing a relative import, falling back on ``__name__`` and
+  ``__path__`` from the calling module when ``__spec__`` or
+  ``__package__`` are not defined now raises an :exc:`ImportWarning`.
+  (Contributed by Rose Ames in :issue:`25791`.)
 
 
 Removed
@@ -251,6 +256,10 @@
   :mod:`wave`.  This means they will export new symbols when ``import *``
   is used.  See :issue:`23883`.
 
+* When performing a relative import, ``__spec__.parent`` is used
+  is ``__spec__`` is defined instead of ``__package__``.
+  (Contributed by Rose Ames in :issue:`25791`.)
+
 
 Changes in the C API
 --------------------
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -1032,8 +1032,14 @@
     to represent that its proper value is unknown.
 
     """
+    spec = globals.get('__spec__')
+    if spec is not None:
+        return spec.parent
     package = globals.get('__package__')
     if package is None:
+        _warnings.warn("can't resolve package from __spec__ or __package__, "
+                       "falling back on __name__ and __path__",
+                       ImportWarning, stacklevel=3)
         package = globals['__name__']
         if '__path__' not in globals:
             package = package.rpartition('.')[0]
diff --git a/Lib/test/test_importlib/import_/test___package__.py b/Lib/test/test_importlib/import_/test___package__.py
--- a/Lib/test/test_importlib/import_/test___package__.py
+++ b/Lib/test/test_importlib/import_/test___package__.py
@@ -33,31 +33,39 @@
 
     """
 
-    def test_using___package__(self):
-        # [__package__]
+    def import_module(self, globals_):
         with self.mock_modules('pkg.__init__', 'pkg.fake') as importer:
             with util.import_state(meta_path=[importer]):
                 self.__import__('pkg.fake')
                 module = self.__import__('',
-                                            globals={'__package__': 'pkg.fake'},
+                                            globals=globals_,
                                             fromlist=['attr'], level=2)
+        return module
+
+    def test_using___package__(self):
+        # [__package__]
+        module = self.import_module({'__package__': 'pkg.fake'})
         self.assertEqual(module.__name__, 'pkg')
 
-    def test_using___name__(self, package_as_None=False):
+    def test_using___name__(self):
         # [__name__]
-        globals_ = {'__name__': 'pkg.fake', '__path__': []}
-        if package_as_None:
-            globals_['__package__'] = None
-        with self.mock_modules('pkg.__init__', 'pkg.fake') as importer:
-            with util.import_state(meta_path=[importer]):
-                self.__import__('pkg.fake')
-                module = self.__import__('', globals= globals_,
-                                                fromlist=['attr'], level=2)
-            self.assertEqual(module.__name__, 'pkg')
+        module = self.import_module({'__name__': 'pkg.fake', '__path__': []})
+        self.assertEqual(module.__name__, 'pkg')
+
+    def test_warn_when_using___name__(self):
+        with self.assertWarns(ImportWarning):
+            self.import_module({'__name__': 'pkg.fake', '__path__': []})
 
     def test_None_as___package__(self):
         # [None]
-        self.test_using___name__(package_as_None=True)
+        module = self.import_module({
+            '__name__': 'pkg.fake', '__path__': [], '__package__': None })
+        self.assertEqual(module.__name__, 'pkg')
+
+    def test_prefers___spec__(self):
+        globals = {'__spec__': FakeSpec()}
+        with self.assertRaises(SystemError):
+            self.__import__('', globals, {}, ['relimport'], 1)
 
     def test_bad__package__(self):
         globals = {'__package__': '<not real>'}
@@ -70,6 +78,10 @@
             self.__import__('', globals, {}, ['relimport'], 1)
 
 
+class FakeSpec:
+    parent = '<fake>'
+
+
 class Using__package__PEP302(Using__package__):
     mock_modules = util.mock_modules
 
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@
 Core and Builtins
 -----------------
 
+- Issue #25791: Trying to resolve a relative import without __spec__ or
+  __package__ defined now raises an ImportWarning
+
 - Issue #25961: Disallowed null characters in the type name.
 
 - Issue #25973: Fix segfault when an invalid nonlocal statement binds a name
diff --git a/Python/import.c b/Python/import.c
--- a/Python/import.c
+++ b/Python/import.c
@@ -1359,6 +1359,7 @@
     PyObject *final_mod = NULL;
     PyObject *mod = NULL;
     PyObject *package = NULL;
+    PyObject *spec = NULL;
     PyObject *globals = NULL;
     PyObject *fromlist = NULL;
     PyInterpreterState *interp = PyThreadState_GET()->interp;
@@ -1414,39 +1415,60 @@
         goto error;
     }
     else if (level > 0) {
-        package = _PyDict_GetItemId(globals, &PyId___package__);
+        spec = _PyDict_GetItemId(globals, &PyId___spec__);
+        if (spec != NULL) {
+            package = PyObject_GetAttrString(spec, "parent");
+        }
         if (package != NULL && package != Py_None) {
-            Py_INCREF(package);
             if (!PyUnicode_Check(package)) {
-                PyErr_SetString(PyExc_TypeError, "package must be a string");
+                PyErr_SetString(PyExc_TypeError,
+                        "__spec__.parent must be a string");
                 goto error;
             }
         }
         else {
-            package = _PyDict_GetItemId(globals, &PyId___name__);
-            if (package == NULL) {
-                PyErr_SetString(PyExc_KeyError, "'__name__' not in globals");
-                goto error;
-            }
-            else if (!PyUnicode_Check(package)) {
-                PyErr_SetString(PyExc_TypeError, "__name__ must be a string");
-            }
-            Py_INCREF(package);
-
-            if (_PyDict_GetItemId(globals, &PyId___path__) == NULL) {
-                PyObject *partition = NULL;
-                PyObject *borrowed_dot = _PyUnicode_FromId(&single_dot);
-                if (borrowed_dot == NULL) {
+            package = _PyDict_GetItemId(globals, &PyId___package__);
+            if (package != NULL && package != Py_None) {
+                Py_INCREF(package);
+                if (!PyUnicode_Check(package)) {
+                    PyErr_SetString(PyExc_TypeError, "package must be a string");
                     goto error;
                 }
-                partition = PyUnicode_RPartition(package, borrowed_dot);
-                Py_DECREF(package);
-                if (partition == NULL) {
+            }
+            else {
+                if (PyErr_WarnEx(PyExc_ImportWarning,
+                            "can't resolve package from __spec__ or __package__, "
+                            "falling back on __name__ and __path__", 1) < 0) {
                     goto error;
                 }
-                package = PyTuple_GET_ITEM(partition, 0);
+
+                package = _PyDict_GetItemId(globals, &PyId___name__);
+                if (package == NULL) {
+                    PyErr_SetString(PyExc_KeyError, "'__name__' not in globals");
+                    goto error;
+                }
+
                 Py_INCREF(package);
-                Py_DECREF(partition);
+                if (!PyUnicode_Check(package)) {
+                    PyErr_SetString(PyExc_TypeError, "__name__ must be a string");
+                    goto error;
+                }
+
+                if (_PyDict_GetItemId(globals, &PyId___path__) == NULL) {
+                    PyObject *partition = NULL;
+                    PyObject *borrowed_dot = _PyUnicode_FromId(&single_dot);
+                    if (borrowed_dot == NULL) {
+                        goto error;
+                    }
+                    partition = PyUnicode_RPartition(package, borrowed_dot);
+                    Py_DECREF(package);
+                    if (partition == NULL) {
+                        goto error;
+                    }
+                    package = PyTuple_GET_ITEM(partition, 0);
+                    Py_INCREF(package);
+                    Py_DECREF(partition);
+                }
             }
         }
 
diff --git a/Python/importlib.h b/Python/importlib.h
--- a/Python/importlib.h
+++ b/Python/importlib.h
[stripped]

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list