Skip to content

Commit 63b8505

Browse files
committed
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.
1 parent 43cfd82 commit 63b8505

File tree

7 files changed

+376
-310
lines changed

7 files changed

+376
-310
lines changed

Doc/reference/import.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,8 @@ the module.
554554
details.
555555

556556
This attribute is used instead of ``__name__`` to calculate explicit
557-
relative imports for main modules, as defined in :pep:`366`.
557+
relative imports for main modules -- as defined in :pep:`366` --
558+
when ``__spec__`` is not defined.
558559

559560
.. attribute:: __spec__
560561

Doc/whatsnew/3.6.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,12 @@ Deprecated features
209209
* The ``pyvenv`` script has been deprecated in favour of ``python3 -m venv``.
210210
This prevents confusion as to what Python interpreter ``pyvenv`` is
211211
connected to and thus what Python interpreter will be used by the virtual
212-
environment. See :issue:`25154`.
212+
environment. (Contributed by Brett Cannon in :issue:`25154`.)
213+
214+
* When performing a relative import, falling back on ``__name__`` and
215+
``__path__`` from the calling module when ``__spec__`` or
216+
``__package__`` are not defined now raises an :exc:`ImportWarning`.
217+
(Contributed by Rose Ames in :issue:`25791`.)
213218

214219

215220
Removed
@@ -251,6 +256,10 @@ Changes in the Python API
251256
:mod:`wave`. This means they will export new symbols when ``import *``
252257
is used. See :issue:`23883`.
253258

259+
* When performing a relative import, ``__spec__.parent`` is used
260+
is ``__spec__`` is defined instead of ``__package__``.
261+
(Contributed by Rose Ames in :issue:`25791`.)
262+
254263

255264
Changes in the C API
256265
--------------------

Lib/importlib/_bootstrap.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,8 +1032,14 @@ def _calc___package__(globals):
10321032
to represent that its proper value is unknown.
10331033
10341034
"""
1035+
spec = globals.get('__spec__')
1036+
if spec is not None:
1037+
return spec.parent
10351038
package = globals.get('__package__')
10361039
if package is None:
1040+
_warnings.warn("can't resolve package from __spec__ or __package__, "
1041+
"falling back on __name__ and __path__",
1042+
ImportWarning, stacklevel=3)
10371043
package = globals['__name__']
10381044
if '__path__' not in globals:
10391045
package = package.rpartition('.')[0]

Lib/test/test_importlib/import_/test___package__.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,31 +33,39 @@ def calc_package(caller_name, has___path__):
3333
3434
"""
3535

36-
def test_using___package__(self):
37-
# [__package__]
36+
def import_module(self, globals_):
3837
with self.mock_modules('pkg.__init__', 'pkg.fake') as importer:
3938
with util.import_state(meta_path=[importer]):
4039
self.__import__('pkg.fake')
4140
module = self.__import__('',
42-
globals={'__package__': 'pkg.fake'},
41+
globals=globals_,
4342
fromlist=['attr'], level=2)
43+
return module
44+
45+
def test_using___package__(self):
46+
# [__package__]
47+
module = self.import_module({'__package__': 'pkg.fake'})
4448
self.assertEqual(module.__name__, 'pkg')
4549

46-
def test_using___name__(self, package_as_None=False):
50+
def test_using___name__(self):
4751
# [__name__]
48-
globals_ = {'__name__': 'pkg.fake', '__path__': []}
49-
if package_as_None:
50-
globals_['__package__'] = None
51-
with self.mock_modules('pkg.__init__', 'pkg.fake') as importer:
52-
with util.import_state(meta_path=[importer]):
53-
self.__import__('pkg.fake')
54-
module = self.__import__('', globals= globals_,
55-
fromlist=['attr'], level=2)
56-
self.assertEqual(module.__name__, 'pkg')
52+
module = self.import_module({'__name__': 'pkg.fake', '__path__': []})
53+
self.assertEqual(module.__name__, 'pkg')
54+
55+
def test_warn_when_using___name__(self):
56+
with self.assertWarns(ImportWarning):
57+
self.import_module({'__name__': 'pkg.fake', '__path__': []})
5758

5859
def test_None_as___package__(self):
5960
# [None]
60-
self.test_using___name__(package_as_None=True)
61+
module = self.import_module({
62+
'__name__': 'pkg.fake', '__path__': [], '__package__': None })
63+
self.assertEqual(module.__name__, 'pkg')
64+
65+
def test_prefers___spec__(self):
66+
globals = {'__spec__': FakeSpec()}
67+
with self.assertRaises(SystemError):
68+
self.__import__('', globals, {}, ['relimport'], 1)
6169

6270
def test_bad__package__(self):
6371
globals = {'__package__': '<not real>'}
@@ -70,6 +78,10 @@ def test_bunk__package__(self):
7078
self.__import__('', globals, {}, ['relimport'], 1)
7179

7280

81+
class FakeSpec:
82+
parent = '<fake>'
83+
84+
7385
class Using__package__PEP302(Using__package__):
7486
mock_modules = util.mock_modules
7587

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Release date: tba
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #25791: Trying to resolve a relative import without __spec__ or
14+
__package__ defined now raises an ImportWarning
15+
1316
- Issue #25961: Disallowed null characters in the type name.
1417

1518
- Issue #25973: Fix segfault when an invalid nonlocal statement binds a name

Python/import.c

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,7 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *given_globals,
13591359
PyObject *final_mod = NULL;
13601360
PyObject *mod = NULL;
13611361
PyObject *package = NULL;
1362+
PyObject *spec = NULL;
13621363
PyObject *globals = NULL;
13631364
PyObject *fromlist = NULL;
13641365
PyInterpreterState *interp = PyThreadState_GET()->interp;
@@ -1414,39 +1415,60 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *given_globals,
14141415
goto error;
14151416
}
14161417
else if (level > 0) {
1417-
package = _PyDict_GetItemId(globals, &PyId___package__);
1418+
spec = _PyDict_GetItemId(globals, &PyId___spec__);
1419+
if (spec != NULL) {
1420+
package = PyObject_GetAttrString(spec, "parent");
1421+
}
14181422
if (package != NULL && package != Py_None) {
1419-
Py_INCREF(package);
14201423
if (!PyUnicode_Check(package)) {
1421-
PyErr_SetString(PyExc_TypeError, "package must be a string");
1424+
PyErr_SetString(PyExc_TypeError,
1425+
"__spec__.parent must be a string");
14221426
goto error;
14231427
}
14241428
}
14251429
else {
1426-
package = _PyDict_GetItemId(globals, &PyId___name__);
1427-
if (package == NULL) {
1428-
PyErr_SetString(PyExc_KeyError, "'__name__' not in globals");
1429-
goto error;
1430-
}
1431-
else if (!PyUnicode_Check(package)) {
1432-
PyErr_SetString(PyExc_TypeError, "__name__ must be a string");
1430+
package = _PyDict_GetItemId(globals, &PyId___package__);
1431+
if (package != NULL && package != Py_None) {
1432+
Py_INCREF(package);
1433+
if (!PyUnicode_Check(package)) {
1434+
PyErr_SetString(PyExc_TypeError, "package must be a string");
1435+
goto error;
1436+
}
14331437
}
1434-
Py_INCREF(package);
1435-
1436-
if (_PyDict_GetItemId(globals, &PyId___path__) == NULL) {
1437-
PyObject *partition = NULL;
1438-
PyObject *borrowed_dot = _PyUnicode_FromId(&single_dot);
1439-
if (borrowed_dot == NULL) {
1438+
else {
1439+
if (PyErr_WarnEx(PyExc_ImportWarning,
1440+
"can't resolve package from __spec__ or __package__, "
1441+
"falling back on __name__ and __path__", 1) < 0) {
14401442
goto error;
14411443
}
1442-
partition = PyUnicode_RPartition(package, borrowed_dot);
1443-
Py_DECREF(package);
1444-
if (partition == NULL) {
1444+
1445+
package = _PyDict_GetItemId(globals, &PyId___name__);
1446+
if (package == NULL) {
1447+
PyErr_SetString(PyExc_KeyError, "'__name__' not in globals");
14451448
goto error;
14461449
}
1447-
package = PyTuple_GET_ITEM(partition, 0);
1450+
14481451
Py_INCREF(package);
1449-
Py_DECREF(partition);
1452+
if (!PyUnicode_Check(package)) {
1453+
PyErr_SetString(PyExc_TypeError, "__name__ must be a string");
1454+
goto error;
1455+
}
1456+
1457+
if (_PyDict_GetItemId(globals, &PyId___path__) == NULL) {
1458+
PyObject *partition = NULL;
1459+
PyObject *borrowed_dot = _PyUnicode_FromId(&single_dot);
1460+
if (borrowed_dot == NULL) {
1461+
goto error;
1462+
}
1463+
partition = PyUnicode_RPartition(package, borrowed_dot);
1464+
Py_DECREF(package);
1465+
if (partition == NULL) {
1466+
goto error;
1467+
}
1468+
package = PyTuple_GET_ITEM(partition, 0);
1469+
Py_INCREF(package);
1470+
Py_DECREF(partition);
1471+
}
14501472
}
14511473
}
14521474

0 commit comments

Comments
 (0)