Skip to content

bpo-35673: Add a public alias for namespace package __loader__ attribute #29049

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Doc/library/importlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,24 @@ find and load modules.
.. versionadded:: 3.4


.. class:: NamespaceLoader(name, path, path_finder):

A concrete implementation of :class:`importlib.abc.InspectLoader` for
namespace packages. This is an alias for a private class and is only made
public for introspecting the ``__loader__`` attribute on namespace
packages::

>>> from importlib.machinery import NamespaceLoader
>>> import my_namespace
>>> isinstance(my_namespace.__loader__, NamespaceLoader)
True
>>> import importlib.abc
>>> isinstance(my_namespace.__loader__, importlib.abc.Loader)
True

.. versionadded:: 3.11


.. class:: ModuleSpec(name, loader, *, origin=None, loader_state=None, is_package=None)

A specification for a module's import-system-related state. This is
Expand Down
4 changes: 2 additions & 2 deletions Lib/importlib/_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,9 +507,9 @@ def _init_module_attrs(spec, module, *, override=False):
if spec.submodule_search_locations is not None:
if _bootstrap_external is None:
raise NotImplementedError
_NamespaceLoader = _bootstrap_external._NamespaceLoader
NamespaceLoader = _bootstrap_external.NamespaceLoader

loader = _NamespaceLoader.__new__(_NamespaceLoader)
loader = NamespaceLoader.__new__(NamespaceLoader)
loader._path = spec.submodule_search_locations
spec.loader = loader
# While the docs say that module.__file__ is not set for
Expand Down
12 changes: 9 additions & 3 deletions Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -1279,8 +1279,10 @@ def append(self, item):
self._path.append(item)


# We use this exclusively in module_from_spec() for backward-compatibility.
class _NamespaceLoader:
# This class is actually exposed publicly in a namespace package's __loader__
# attribute, so it should be available through a non-private name.
# https://bugs.python.org/issue35673
class NamespaceLoader:
def __init__(self, name, path, path_finder):
self._path = _NamespacePath(name, path, path_finder)

Expand All @@ -1291,7 +1293,7 @@ def module_repr(module):
The method is deprecated. The import machinery does the job itself.

"""
_warnings.warn("_NamespaceLoader.module_repr() is deprecated and "
_warnings.warn("NamespaceLoader.module_repr() is deprecated and "
"slated for removal in Python 3.12", DeprecationWarning)
return '<module {!r} (namespace)>'.format(module.__name__)

Expand Down Expand Up @@ -1327,6 +1329,10 @@ def get_resource_reader(self, module):
return NamespaceReader(self._path)


# We use this exclusively in module_from_spec() for backward-compatibility.
_NamespaceLoader = NamespaceLoader


# Finders #####################################################################

class PathFinder:
Expand Down
2 changes: 1 addition & 1 deletion Lib/importlib/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def source_to_code(data, path='<string>'):
exec_module = _bootstrap_external._LoaderBasics.exec_module
load_module = _bootstrap_external._LoaderBasics.load_module

_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter)
_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter, machinery.NamespaceLoader)


class ExecutionLoader(InspectLoader):
Expand Down
1 change: 1 addition & 0 deletions Lib/importlib/machinery.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ._bootstrap_external import SourceFileLoader
from ._bootstrap_external import SourcelessFileLoader
from ._bootstrap_external import ExtensionFileLoader
from ._bootstrap_external import NamespaceLoader


def all_suffixes():
Expand Down
7 changes: 7 additions & 0 deletions Lib/test/test_importlib/test_namespace_pkgs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import contextlib
import importlib
import importlib.abc
import importlib.machinery
import os
import sys
import unittest
Expand Down Expand Up @@ -342,6 +344,11 @@ def test_path_indexable(self):
expected_path = os.path.join(self.root, 'portion1', 'foo')
self.assertEqual(foo.__path__[0], expected_path)

def test_loader_abc(self):
import foo
self.assertTrue(isinstance(foo.__loader__, importlib.abc.Loader))
self.assertTrue(isinstance(foo.__loader__, importlib.machinery.NamespaceLoader))


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Improve the introspectability of the ``__loader__`` attribute for namespace
packages. :class:`importlib.machinery.NamespaceLoader` is now public, and
implements the :class:`importlib.abc.InspectLoader` interface. ``_NamespaceLoader``
is kept for backward compatibility.