https://github.com/python/cpython/commit/ede5693be1cefb859522b246897b6835c87ed6d9
commit: ede5693be1cefb859522b246897b6835c87ed6d9
branch: main
author: Filipe Laíns <[email protected]>
committer: FFY00 <[email protected]>
date: 2025-11-01T00:39:48Z
summary:

GH-119668: expose importlib.machinery.NamespacePath (#119669)

* GH-119668: expose importlib.NamespacePath

Signed-off-by: Filipe Laíns <[email protected]>

* add news

Signed-off-by: Filipe Laíns <[email protected]>

* add to docs

Signed-off-by: Filipe Laíns <[email protected]>

* Apply suggestions from code review

Co-authored-by: Bénédikt Tran <[email protected]>

* Fix news (importlib.NamespacePath > importlib.machinery.NamespacePath)

Signed-off-by: Filipe Laíns <[email protected]>

* Link to module.__path__ in NamespacePath docs

Signed-off-by: Filipe Laíns <[email protected]>

* Mention the path argument in the documentation

Signed-off-by: Filipe Laíns <[email protected]>

* Simplify docs text

Signed-off-by: Filipe Laíns <[email protected]>

* Highlight argument names in docs text

Signed-off-by: Filipe Laíns <[email protected]>

* Update Lib/importlib/_bootstrap_external.py

Co-authored-by: Brett Cannon <[email protected]>

* Rewrite NamespacePath's doc

Signed-off-by: Filipe Laíns <[email protected]>

* Specify path_finder's type in the NamespacePath docstring

Signed-off-by: Filipe Laíns <[email protected]>

* Fix doc tests

Signed-off-by: Filipe Laíns <[email protected]>

* Apply suggestions from code review

Co-authored-by: Barry Warsaw <[email protected]>

* Fix doc lint

Signed-off-by: Filipe Laíns <[email protected]>

* Update Doc/library/importlib.rst

Co-authored-by: Brett Cannon <[email protected]>

---------

Signed-off-by: Filipe Laíns <[email protected]>
Co-authored-by: Brett Cannon <[email protected]>
Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Barry Warsaw <[email protected]>

files:
A Misc/NEWS.d/next/Library/2024-05-28-17-14-30.gh-issue-119668.RrIGpn.rst
M Doc/library/importlib.rst
M Lib/importlib/_bootstrap_external.py
M Lib/importlib/machinery.py

diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst
index 19296eb247ae29..602a7100a12350 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 a1319c0f6a91aa..035ae0fcae14e8 100644
--- a/Lib/importlib/_bootstrap_external.py
+++ b/Lib/importlib/_bootstrap_external.py
@@ -1086,12 +1086,18 @@ def get_filename(self, fullname):
         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 @@ def __len__(self):
         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 @@ def append(self, item):
         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 @@ def invalidate_caches():
                 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 @@ def find_spec(cls, fullname, path=None, target=None):
                 # 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 63d726445c3d96..023f77d750fd2b 100644
--- a/Lib/importlib/machinery.py
+++ b/Lib/importlib/machinery.py
@@ -16,6 +16,7 @@
 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 00000000000000..87cdf8d89d5202
--- /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`.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to