Skip to content

Commit c206e53

Browse files
gh-65961: Raise DeprecationWarning when __package__ differs from __spec__.parent (#97879)
Also remove `importlib.util.set_package()` which was already slated for removal. Co-authored-by: Eric Snow <[email protected]>
1 parent 2016bc5 commit c206e53

File tree

9 files changed

+45
-102
lines changed

9 files changed

+45
-102
lines changed

Doc/library/importlib.rst

-9
Original file line numberDiff line numberDiff line change
@@ -1378,15 +1378,6 @@ an :term:`importer`.
13781378
.. deprecated:: 3.4
13791379
The import machinery takes care of this automatically.
13801380

1381-
.. decorator:: set_package
1382-
1383-
A :term:`decorator` for :meth:`importlib.abc.Loader.load_module` to set the
1384-
:attr:`__package__` attribute on the returned module. If :attr:`__package__`
1385-
is set and has a value other than ``None`` it will not be changed.
1386-
1387-
.. deprecated:: 3.4
1388-
The import machinery takes care of this automatically.
1389-
13901381
.. function:: spec_from_loader(name, loader, *, origin=None, is_package=None)
13911382

13921383
A factory function for creating a :class:`~importlib.machinery.ModuleSpec`

Doc/reference/import.rst

+24-6
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,6 @@ of what happens during the loading portion of import::
358358
sys.modules[spec.name] = module
359359
elif not hasattr(spec.loader, 'exec_module'):
360360
module = spec.loader.load_module(spec.name)
361-
# Set __loader__ and __package__ if missing.
362361
else:
363362
sys.modules[spec.name] = module
364363
try:
@@ -539,6 +538,10 @@ The import machinery fills in these attributes on each module object
539538
during loading, based on the module's spec, before the loader executes
540539
the module.
541540

541+
It is **strongly** recommended that you rely on :attr:`__spec__` and
542+
its attributes instead of any of the other individual attributes
543+
listed below.
544+
542545
.. attribute:: __name__
543546

544547
The ``__name__`` attribute must be set to the fully qualified name of
@@ -552,24 +555,36 @@ the module.
552555
for introspection, but can be used for additional loader-specific
553556
functionality, for example getting data associated with a loader.
554557

558+
It is **strongly** recommended that you rely on :attr:`__spec__`
559+
instead instead of this attribute.
560+
555561
.. attribute:: __package__
556562

557-
The module's ``__package__`` attribute must be set. Its value must
563+
The module's ``__package__`` attribute may be set. Its value must
558564
be a string, but it can be the same value as its ``__name__``. When
559565
the module is a package, its ``__package__`` value should be set to
560566
its ``__name__``. When the module is not a package, ``__package__``
561567
should be set to the empty string for top-level modules, or for
562568
submodules, to the parent package's name. See :pep:`366` for further
563569
details.
564570

565-
This attribute is used instead of ``__name__`` to calculate explicit
566-
relative imports for main modules, as defined in :pep:`366`. It is
567-
expected to have the same value as ``__spec__.parent``.
571+
It is **strongly** recommended that you rely on :attr:`__spec__`
572+
instead instead of this attribute.
568573

569574
.. versionchanged:: 3.6
570575
The value of ``__package__`` is expected to be the same as
571576
``__spec__.parent``.
572577

578+
.. versionchanged:: 3.10
579+
:exc:`ImportWarning` is raised if import falls back to
580+
``__package__`` instead of
581+
:attr:`~importlib.machinery.ModuleSpec.parent`.
582+
583+
.. versionchanged:: 3.12
584+
Raise :exc:`DeprecationWarning` instead of :exc:`ImportWarning`
585+
when falling back to ``__package__``.
586+
587+
573588
.. attribute:: __spec__
574589

575590
The ``__spec__`` attribute must be set to the module spec that was
@@ -578,7 +593,7 @@ the module.
578593
interpreter startup <programs>`. The one exception is ``__main__``,
579594
where ``__spec__`` is :ref:`set to None in some cases <main_spec>`.
580595

581-
When ``__package__`` is not defined, ``__spec__.parent`` is used as
596+
When ``__spec__.parent`` is not set, ``__package__`` is used as
582597
a fallback.
583598

584599
.. versionadded:: 3.4
@@ -623,6 +638,9 @@ the module.
623638
if a loader can load from a cached module but otherwise does not load
624639
from a file, that atypical scenario may be appropriate.
625640

641+
It is **strongly** recommended that you rely on :attr:`__spec__`
642+
instead instead of ``__cached__``.
643+
626644
.. _package-path-rules:
627645

628646
module.__path__

Doc/whatsnew/3.12.rst

+12
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ Deprecated
215215
may be removed in a future version of Python. Use the single-arg versions
216216
of these functions instead. (Contributed by Ofey Chan in :gh:`89874`.)
217217

218+
* :exc:`DeprecationWarning` is now raised when ``__package__`` on a
219+
module differs from ``__spec__.parent`` (previously it was
220+
:exc:`ImportWarning`).
221+
(Contributed by Brett Cannon in :gh:`65961`.)
222+
218223

219224
Pending Removal in Python 3.13
220225
------------------------------
@@ -275,6 +280,9 @@ Pending Removal in Python 3.14
275280
* Creating :c:data:`immutable types <Py_TPFLAGS_IMMUTABLETYPE>` with mutable
276281
bases using the C API.
277282

283+
* ``__package__`` will cease to be set or taken into consideration by
284+
the import system (:gh:`97879`).
285+
278286

279287
Pending Removal in Future Versions
280288
----------------------------------
@@ -432,6 +440,10 @@ Removed
432440
* References to, and support for ``module_repr()`` has been eradicated.
433441

434442

443+
* ``importlib.util.set_package`` has been removed.
444+
(Contributed by Brett Cannon in :gh:`65961`.)
445+
446+
435447
Porting to Python 3.12
436448
======================
437449

Lib/importlib/_bootstrap.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1228,7 +1228,7 @@ def _calc___package__(globals):
12281228
if spec is not None and package != spec.parent:
12291229
_warnings.warn("__package__ != __spec__.parent "
12301230
f"({package!r} != {spec.parent!r})",
1231-
ImportWarning, stacklevel=3)
1231+
DeprecationWarning, stacklevel=3)
12321232
return package
12331233
elif spec is not None:
12341234
return spec.parent

Lib/importlib/util.py

-20
Original file line numberDiff line numberDiff line change
@@ -141,26 +141,6 @@ def _module_to_load(name):
141141
module.__initializing__ = False
142142

143143

144-
def set_package(fxn):
145-
"""Set __package__ on the returned module.
146-
147-
This function is deprecated.
148-
149-
"""
150-
@functools.wraps(fxn)
151-
def set_package_wrapper(*args, **kwargs):
152-
warnings.warn('The import system now takes care of this automatically; '
153-
'this decorator is slated for removal in Python 3.12',
154-
DeprecationWarning, stacklevel=2)
155-
module = fxn(*args, **kwargs)
156-
if getattr(module, '__package__', None) is None:
157-
module.__package__ = module.__name__
158-
if not hasattr(module, '__path__'):
159-
module.__package__ = module.__package__.rpartition('.')[0]
160-
return module
161-
return set_package_wrapper
162-
163-
164144
def set_loader(fxn):
165145
"""Set __loader__ on the returned module.
166146

Lib/test/test_importlib/import_/test___package__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ def test_spec_fallback(self):
7474
self.assertEqual(module.__name__, 'pkg')
7575

7676
def test_warn_when_package_and_spec_disagree(self):
77-
# Raise an ImportWarning if __package__ != __spec__.parent.
78-
with self.assertWarns(ImportWarning):
77+
# Raise a DeprecationWarning if __package__ != __spec__.parent.
78+
with self.assertWarns(DeprecationWarning):
7979
self.import_module({'__package__': 'pkg.fake',
8080
'__spec__': FakeSpec('pkg.fakefake')})
8181

Lib/test/test_importlib/test_util.py

-63
Original file line numberDiff line numberDiff line change
@@ -252,69 +252,6 @@ def load_module(self, module):
252252
) = util.test_both(ModuleForLoaderTests, util=importlib_util)
253253

254254

255-
class SetPackageTests:
256-
257-
"""Tests for importlib.util.set_package."""
258-
259-
def verify(self, module, expect):
260-
"""Verify the module has the expected value for __package__ after
261-
passing through set_package."""
262-
fxn = lambda: module
263-
wrapped = self.util.set_package(fxn)
264-
with warnings.catch_warnings():
265-
warnings.simplefilter('ignore', DeprecationWarning)
266-
wrapped()
267-
self.assertTrue(hasattr(module, '__package__'))
268-
self.assertEqual(expect, module.__package__)
269-
270-
def test_top_level(self):
271-
# __package__ should be set to the empty string if a top-level module.
272-
# Implicitly tests when package is set to None.
273-
module = types.ModuleType('module')
274-
module.__package__ = None
275-
self.verify(module, '')
276-
277-
def test_package(self):
278-
# Test setting __package__ for a package.
279-
module = types.ModuleType('pkg')
280-
module.__path__ = ['<path>']
281-
module.__package__ = None
282-
self.verify(module, 'pkg')
283-
284-
def test_submodule(self):
285-
# Test __package__ for a module in a package.
286-
module = types.ModuleType('pkg.mod')
287-
module.__package__ = None
288-
self.verify(module, 'pkg')
289-
290-
def test_setting_if_missing(self):
291-
# __package__ should be set if it is missing.
292-
module = types.ModuleType('mod')
293-
if hasattr(module, '__package__'):
294-
delattr(module, '__package__')
295-
self.verify(module, '')
296-
297-
def test_leaving_alone(self):
298-
# If __package__ is set and not None then leave it alone.
299-
for value in (True, False):
300-
module = types.ModuleType('mod')
301-
module.__package__ = value
302-
self.verify(module, value)
303-
304-
def test_decorator_attrs(self):
305-
def fxn(module): pass
306-
with warnings.catch_warnings():
307-
warnings.simplefilter('ignore', DeprecationWarning)
308-
wrapped = self.util.set_package(fxn)
309-
self.assertEqual(wrapped.__name__, fxn.__name__)
310-
self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
311-
312-
313-
(Frozen_SetPackageTests,
314-
Source_SetPackageTests
315-
) = util.test_both(SetPackageTests, util=importlib_util)
316-
317-
318255
class SetLoaderTests:
319256

320257
"""Tests importlib.util.set_loader()."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
When ``__package__`` is different than ``__spec__.parent``, raise a
2+
``DeprecationWarning`` instead of ``ImportWarning``.
3+
4+
Also remove ``importlib.util.set_package()`` which was scheduled for
5+
removal.

Python/import.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1573,7 +1573,7 @@ resolve_name(PyThreadState *tstate, PyObject *name, PyObject *globals, int level
15731573
goto error;
15741574
}
15751575
else if (equal == 0) {
1576-
if (PyErr_WarnEx(PyExc_ImportWarning,
1576+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
15771577
"__package__ != __spec__.parent", 1) < 0) {
15781578
goto error;
15791579
}

0 commit comments

Comments
 (0)