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
| 1055 | |
| 1056 | |
| 1057 | class NamespacePath: |
| 1058 | """Represents a namespace package's path. |
| 1059 | |
| 1060 | It uses the module *name* to find its parent module, and from there it looks |
| 1061 | up the parent's __path__. When this changes, the module's own path is |
| 1062 | recomputed, using *path_finder*. The initial value is set to *path*. |
| 1063 | |
| 1064 | For top-level modules, the parent module's path is sys.path. |
| 1065 | |
| 1066 | *path_finder* should be a callable with the same signature as |
| 1067 | MetaPathFinder.find_spec((fullname, path, target=None) -> spec). |
| 1068 | """ |
| 1069 | |
| 1070 | # When invalidate_caches() is called, this epoch is incremented |
| 1071 | # https://bugs.python.org/issue45703 |
| 1072 | _epoch = 0 |
| 1073 | |
| 1074 | def __init__(self, name, path, path_finder): |
| 1075 | self._name = name |
| 1076 | self._path = path |
| 1077 | self._last_parent_path = tuple(self._get_parent_path()) |
| 1078 | self._last_epoch = self._epoch |
| 1079 | self._path_finder = path_finder |
| 1080 | |
| 1081 | def _find_parent_path_names(self): |
| 1082 | """Returns a tuple of (parent-module-name, parent-path-attr-name)""" |
| 1083 | parent, dot, me = self._name.rpartition('.') |
| 1084 | if dot == '': |
| 1085 | # This is a top-level module. sys.path contains the parent path. |
| 1086 | return 'sys', 'path' |
| 1087 | # Not a top-level module. parent-module.__path__ contains the |
| 1088 | # parent path. |
| 1089 | return parent, '__path__' |
| 1090 | |
| 1091 | def _get_parent_path(self): |
| 1092 | parent_module_name, path_attr_name = self._find_parent_path_names() |
| 1093 | try: |
| 1094 | module = sys.modules[parent_module_name] |
| 1095 | except KeyError as e: |
| 1096 | raise ModuleNotFoundError( |
| 1097 | f"{parent_module_name!r} must be imported before finding {self._name!r}.", |
| 1098 | name=parent_module_name, |
| 1099 | ) from e |
| 1100 | else: |
| 1101 | return getattr(module, path_attr_name) |
| 1102 | |
| 1103 | def _recalculate(self): |
| 1104 | # If the parent's path has changed, recalculate _path |
| 1105 | parent_path = tuple(self._get_parent_path()) # Make a copy |
| 1106 | if parent_path != self._last_parent_path or self._epoch != self._last_epoch: |
| 1107 | spec = self._path_finder(self._name, parent_path) |
| 1108 | # Note that no changes are made if a loader is returned, but we |
| 1109 | # do remember the new parent path |
| 1110 | if spec is not None and spec.loader is None: |
| 1111 | if spec.submodule_search_locations: |
| 1112 | self._path = spec.submodule_search_locations |
| 1113 | self._last_parent_path = parent_path # Save the copy |
| 1114 | self._last_epoch = self._epoch |