: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
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
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()
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
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()
# 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