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>
This commit is contained in:
@@ -1013,6 +1013,36 @@ find and load modules.
|
||||
:exc:`ImportError` is raised.
|
||||
|
||||
|
||||
.. class:: NamespacePath(name, path, path_finder)
|
||||
|
||||
Represents a :term:`namespace package`'s path (:attr:`module.__path__`).
|
||||
|
||||
When its ``__path__`` value is accessed it will be recomputed if necessary.
|
||||
This keeps it in-sync with the global state (:attr:`sys.modules`).
|
||||
|
||||
The *name* argument is the name of the namespace module.
|
||||
|
||||
The *path* argument is the initial path value.
|
||||
|
||||
The *path_finder* argument is the callable used to recompute the path value.
|
||||
The callable has the same signature as :meth:`importlib.abc.MetaPathFinder.find_spec`.
|
||||
|
||||
When the parent's :attr:`module.__path__` attribute is updated, the path
|
||||
value is recomputed.
|
||||
|
||||
If the parent module is missing from :data:`sys.modules`, then
|
||||
:exc:`ModuleNotFoundError` will be raised.
|
||||
|
||||
For top-level modules, the parent module's path is :data:`sys.path`.
|
||||
|
||||
.. note::
|
||||
|
||||
:meth:`PathFinder.invalidate_caches` invalidates :class:`NamespacePath`,
|
||||
forcing the path value to be recomputed next time it is accessed.
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
|
||||
.. class:: SourceFileLoader(fullname, path)
|
||||
|
||||
A concrete implementation of :class:`importlib.abc.SourceLoader` by
|
||||
|
||||
@@ -1086,12 +1086,18 @@ class ExtensionFileLoader(FileLoader, _LoaderBasics):
|
||||
return self.path
|
||||
|
||||
|
||||
class _NamespacePath:
|
||||
"""Represents a namespace package's path. It uses the module name
|
||||
to find its parent module, and from there it looks up the parent's
|
||||
__path__. When this changes, the module's own path is recomputed,
|
||||
using path_finder. For top-level modules, the parent module's path
|
||||
is sys.path."""
|
||||
class NamespacePath:
|
||||
"""Represents a namespace package's path.
|
||||
|
||||
It uses the module *name* to find its parent module, and from there it looks
|
||||
up the parent's __path__. When this changes, the module's own path is
|
||||
recomputed, using *path_finder*. The initial value is set to *path*.
|
||||
|
||||
For top-level modules, the parent module's path is sys.path.
|
||||
|
||||
*path_finder* should be a callable with the same signature as
|
||||
MetaPathFinder.find_spec((fullname, path, target=None) -> spec).
|
||||
"""
|
||||
|
||||
# When invalidate_caches() is called, this epoch is incremented
|
||||
# https://bugs.python.org/issue45703
|
||||
@@ -1153,7 +1159,7 @@ class _NamespacePath:
|
||||
return len(self._recalculate())
|
||||
|
||||
def __repr__(self):
|
||||
return f'_NamespacePath({self._path!r})'
|
||||
return f'NamespacePath({self._path!r})'
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self._recalculate()
|
||||
@@ -1162,12 +1168,16 @@ class _NamespacePath:
|
||||
self._path.append(item)
|
||||
|
||||
|
||||
# For backwards-compatibility for anyone desperate enough to get at the class back in the day.
|
||||
_NamespacePath = NamespacePath
|
||||
|
||||
|
||||
# This class is actually exposed publicly in a namespace package's __loader__
|
||||
# attribute, so it should be available through a non-private name.
|
||||
# https://github.com/python/cpython/issues/92054
|
||||
class NamespaceLoader:
|
||||
def __init__(self, name, path, path_finder):
|
||||
self._path = _NamespacePath(name, path, path_finder)
|
||||
self._path = NamespacePath(name, path, path_finder)
|
||||
|
||||
def is_package(self, fullname):
|
||||
return True
|
||||
@@ -1222,9 +1232,9 @@ class PathFinder:
|
||||
del sys.path_importer_cache[name]
|
||||
elif hasattr(finder, 'invalidate_caches'):
|
||||
finder.invalidate_caches()
|
||||
# Also invalidate the caches of _NamespacePaths
|
||||
# Also invalidate the caches of NamespacePaths
|
||||
# https://bugs.python.org/issue45703
|
||||
_NamespacePath._epoch += 1
|
||||
NamespacePath._epoch += 1
|
||||
|
||||
from importlib.metadata import MetadataPathFinder
|
||||
MetadataPathFinder.invalidate_caches()
|
||||
@@ -1310,7 +1320,7 @@ class PathFinder:
|
||||
# We found at least one namespace path. Return a spec which
|
||||
# can create the namespace package.
|
||||
spec.origin = None
|
||||
spec.submodule_search_locations = _NamespacePath(fullname, namespace_path, cls._get_spec)
|
||||
spec.submodule_search_locations = NamespacePath(fullname, namespace_path, cls._get_spec)
|
||||
return spec
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -16,6 +16,7 @@ from ._bootstrap_external import SourcelessFileLoader
|
||||
from ._bootstrap_external import ExtensionFileLoader
|
||||
from ._bootstrap_external import AppleFrameworkLoader
|
||||
from ._bootstrap_external import NamespaceLoader
|
||||
from ._bootstrap_external import NamespacePath
|
||||
|
||||
|
||||
def all_suffixes():
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Publicly expose and document :class:`importlib.machinery.NamespacePath`.
|
||||
Reference in New Issue
Block a user