Skip to content

Commit ede5693

Browse files
FFY00brettcannonpicnixzwarsaw
authored
GH-119668: expose importlib.machinery.NamespacePath (#119669)
* GH-119668: expose importlib.NamespacePath Signed-off-by: Filipe Laíns <lains@riseup.net> * add news Signed-off-by: Filipe Laíns <lains@riseup.net> * add to docs Signed-off-by: Filipe Laíns <lains@riseup.net> * Apply suggestions from code review Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> * Fix news (importlib.NamespacePath > importlib.machinery.NamespacePath) Signed-off-by: Filipe Laíns <lains@riseup.net> * Link to module.__path__ in NamespacePath docs Signed-off-by: Filipe Laíns <lains@riseup.net> * Mention the path argument in the documentation Signed-off-by: Filipe Laíns <lains@riseup.net> * Simplify docs text Signed-off-by: Filipe Laíns <lains@riseup.net> * Highlight argument names in docs text Signed-off-by: Filipe Laíns <lains@riseup.net> * Update Lib/importlib/_bootstrap_external.py Co-authored-by: Brett Cannon <brett@python.org> * Rewrite NamespacePath's doc Signed-off-by: Filipe Laíns <lains@riseup.net> * Specify path_finder's type in the NamespacePath docstring Signed-off-by: Filipe Laíns <lains@riseup.net> * Fix doc tests Signed-off-by: Filipe Laíns <lains@riseup.net> * Apply suggestions from code review Co-authored-by: Barry Warsaw <barry@python.org> * Fix doc lint Signed-off-by: Filipe Laíns <lains@riseup.net> * Update Doc/library/importlib.rst Co-authored-by: Brett Cannon <brett@python.org> --------- Signed-off-by: Filipe Laíns <lains@riseup.net> Co-authored-by: Brett Cannon <brett@python.org> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Barry Warsaw <barry@python.org>
1 parent a17c57e commit ede5693

File tree

4 files changed

+53
-11
lines changed

4 files changed

+53
-11
lines changed

Doc/library/importlib.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,36 @@ find and load modules.
10131013
:exc:`ImportError` is raised.
10141014

10151015

1016+
.. class:: NamespacePath(name, path, path_finder)
1017+
1018+
Represents a :term:`namespace package`'s path (:attr:`module.__path__`).
1019+
1020+
When its ``__path__`` value is accessed it will be recomputed if necessary.
1021+
This keeps it in-sync with the global state (:attr:`sys.modules`).
1022+
1023+
The *name* argument is the name of the namespace module.
1024+
1025+
The *path* argument is the initial path value.
1026+
1027+
The *path_finder* argument is the callable used to recompute the path value.
1028+
The callable has the same signature as :meth:`importlib.abc.MetaPathFinder.find_spec`.
1029+
1030+
When the parent's :attr:`module.__path__` attribute is updated, the path
1031+
value is recomputed.
1032+
1033+
If the parent module is missing from :data:`sys.modules`, then
1034+
:exc:`ModuleNotFoundError` will be raised.
1035+
1036+
For top-level modules, the parent module's path is :data:`sys.path`.
1037+
1038+
.. note::
1039+
1040+
:meth:`PathFinder.invalidate_caches` invalidates :class:`NamespacePath`,
1041+
forcing the path value to be recomputed next time it is accessed.
1042+
1043+
.. versionadded:: next
1044+
1045+
10161046
.. class:: SourceFileLoader(fullname, path)
10171047

10181048
A concrete implementation of :class:`importlib.abc.SourceLoader` by

Lib/importlib/_bootstrap_external.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,12 +1086,18 @@ def get_filename(self, fullname):
10861086
return self.path
10871087

10881088

1089-
class _NamespacePath:
1090-
"""Represents a namespace package's path. It uses the module name
1091-
to find its parent module, and from there it looks up the parent's
1092-
__path__. When this changes, the module's own path is recomputed,
1093-
using path_finder. For top-level modules, the parent module's path
1094-
is sys.path."""
1089+
class NamespacePath:
1090+
"""Represents a namespace package's path.
1091+
1092+
It uses the module *name* to find its parent module, and from there it looks
1093+
up the parent's __path__. When this changes, the module's own path is
1094+
recomputed, using *path_finder*. The initial value is set to *path*.
1095+
1096+
For top-level modules, the parent module's path is sys.path.
1097+
1098+
*path_finder* should be a callable with the same signature as
1099+
MetaPathFinder.find_spec((fullname, path, target=None) -> spec).
1100+
"""
10951101

10961102
# When invalidate_caches() is called, this epoch is incremented
10971103
# https://bugs.python.org/issue45703
@@ -1153,7 +1159,7 @@ def __len__(self):
11531159
return len(self._recalculate())
11541160

11551161
def __repr__(self):
1156-
return f'_NamespacePath({self._path!r})'
1162+
return f'NamespacePath({self._path!r})'
11571163

11581164
def __contains__(self, item):
11591165
return item in self._recalculate()
@@ -1162,12 +1168,16 @@ def append(self, item):
11621168
self._path.append(item)
11631169

11641170

1171+
# For backwards-compatibility for anyone desperate enough to get at the class back in the day.
1172+
_NamespacePath = NamespacePath
1173+
1174+
11651175
# This class is actually exposed publicly in a namespace package's __loader__
11661176
# attribute, so it should be available through a non-private name.
11671177
# https://github.com/python/cpython/issues/92054
11681178
class NamespaceLoader:
11691179
def __init__(self, name, path, path_finder):
1170-
self._path = _NamespacePath(name, path, path_finder)
1180+
self._path = NamespacePath(name, path, path_finder)
11711181

11721182
def is_package(self, fullname):
11731183
return True
@@ -1222,9 +1232,9 @@ def invalidate_caches():
12221232
del sys.path_importer_cache[name]
12231233
elif hasattr(finder, 'invalidate_caches'):
12241234
finder.invalidate_caches()
1225-
# Also invalidate the caches of _NamespacePaths
1235+
# Also invalidate the caches of NamespacePaths
12261236
# https://bugs.python.org/issue45703
1227-
_NamespacePath._epoch += 1
1237+
NamespacePath._epoch += 1
12281238

12291239
from importlib.metadata import MetadataPathFinder
12301240
MetadataPathFinder.invalidate_caches()
@@ -1310,7 +1320,7 @@ def find_spec(cls, fullname, path=None, target=None):
13101320
# We found at least one namespace path. Return a spec which
13111321
# can create the namespace package.
13121322
spec.origin = None
1313-
spec.submodule_search_locations = _NamespacePath(fullname, namespace_path, cls._get_spec)
1323+
spec.submodule_search_locations = NamespacePath(fullname, namespace_path, cls._get_spec)
13141324
return spec
13151325
else:
13161326
return None

Lib/importlib/machinery.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from ._bootstrap_external import ExtensionFileLoader
1717
from ._bootstrap_external import AppleFrameworkLoader
1818
from ._bootstrap_external import NamespaceLoader
19+
from ._bootstrap_external import NamespacePath
1920

2021

2122
def all_suffixes():
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Publicly expose and document :class:`importlib.machinery.NamespacePath`.

0 commit comments

Comments
 (0)