]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-119668: expose importlib.machinery.NamespacePath (#119669)
authorFilipe Laíns <lains@riseup.net>
Sat, 1 Nov 2025 00:39:48 +0000 (00:39 +0000)
committerGitHub <noreply@github.com>
Sat, 1 Nov 2025 00:39:48 +0000 (00:39 +0000)
* 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>
Doc/library/importlib.rst
Lib/importlib/_bootstrap_external.py
Lib/importlib/machinery.py
Misc/NEWS.d/next/Library/2024-05-28-17-14-30.gh-issue-119668.RrIGpn.rst [new file with mode: 0644]

index 19296eb247ae293dbaf53dcc35ed576e6e0abe63..602a7100a123508023910b58f0548741955aa9d1 100644 (file)
@@ -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
index a1319c0f6a91aa2e2fe2bc739ec9decf9c9ac46a..035ae0fcae14e853c7cc2beb8f28796480c2454c 100644 (file)
@@ -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
index 63d726445c3d96d8dda46aed39897a10b8d05db1..023f77d750fd2bc1a3ac73c04fbf5a310d73b51a 100644 (file)
@@ -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 (file)
index 0000000..87cdf8d
--- /dev/null
@@ -0,0 +1 @@
+Publicly expose and document :class:`importlib.machinery.NamespacePath`.