From: Filipe Laíns Date: Sat, 1 Nov 2025 00:39:48 +0000 (+0000) Subject: GH-119668: expose importlib.machinery.NamespacePath (#119669) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ede5693be1cefb859522b246897b6835c87ed6d9;p=thirdparty%2FPython%2Fcpython.git GH-119668: expose importlib.machinery.NamespacePath (#119669) * GH-119668: expose importlib.NamespacePath Signed-off-by: Filipe Laíns * add news Signed-off-by: Filipe Laíns * add to docs Signed-off-by: Filipe Laíns * 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 * Link to module.__path__ in NamespacePath docs Signed-off-by: Filipe Laíns * Mention the path argument in the documentation Signed-off-by: Filipe Laíns * Simplify docs text Signed-off-by: Filipe Laíns * Highlight argument names in docs text Signed-off-by: Filipe Laíns * Update Lib/importlib/_bootstrap_external.py Co-authored-by: Brett Cannon * Rewrite NamespacePath's doc Signed-off-by: Filipe Laíns * Specify path_finder's type in the NamespacePath docstring Signed-off-by: Filipe Laíns * Fix doc tests Signed-off-by: Filipe Laíns * Apply suggestions from code review Co-authored-by: Barry Warsaw * Fix doc lint Signed-off-by: Filipe Laíns * Update Doc/library/importlib.rst Co-authored-by: Brett Cannon --------- Signed-off-by: Filipe Laíns Co-authored-by: Brett Cannon Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Barry Warsaw --- diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index 19296eb247ae..602a7100a123 100644 --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -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 diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index a1319c0f6a91..035ae0fcae14 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -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 diff --git a/Lib/importlib/machinery.py b/Lib/importlib/machinery.py index 63d726445c3d..023f77d750fd 100644 --- a/Lib/importlib/machinery.py +++ b/Lib/importlib/machinery.py @@ -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(): diff --git a/Misc/NEWS.d/next/Library/2024-05-28-17-14-30.gh-issue-119668.RrIGpn.rst b/Misc/NEWS.d/next/Library/2024-05-28-17-14-30.gh-issue-119668.RrIGpn.rst new file mode 100644 index 000000000000..87cdf8d89d52 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-05-28-17-14-30.gh-issue-119668.RrIGpn.rst @@ -0,0 +1 @@ +Publicly expose and document :class:`importlib.machinery.NamespacePath`.