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.
| 1083 | |
| 1084 | |
| 1085 | class _NamespacePath: |
| 1086 | """Represents a namespace package's path. It uses the module name |
| 1087 | to find its parent module, and from there it looks up the parent's |
| 1088 | __path__. When this changes, the module's own path is recomputed, |
| 1089 | using path_finder. For top-level modules, the parent module's path |
| 1090 | is sys.path.""" |
| 1091 | |
| 1092 | # When invalidate_caches() is called, this epoch is incremented |
| 1093 | # https://bugs.python.org/issue45703 |
| 1094 | _epoch = 0 |
| 1095 | |
| 1096 | def __init__(self, name, path, path_finder): |
| 1097 | self._name = name |
| 1098 | self._path = path |
| 1099 | self._last_parent_path = tuple(self._get_parent_path()) |
| 1100 | self._last_epoch = self._epoch |
| 1101 | self._path_finder = path_finder |
| 1102 | |
| 1103 | def _find_parent_path_names(self): |
| 1104 | """Returns a tuple of (parent-module-name, parent-path-attr-name)""" |
| 1105 | parent, dot, me = self._name.rpartition('.') |
| 1106 | if dot == '': |
| 1107 | # This is a top-level module. sys.path contains the parent path. |
| 1108 | return 'sys', 'path' |
| 1109 | # Not a top-level module. parent-module.__path__ contains the |
| 1110 | # parent path. |
| 1111 | return parent, '__path__' |
| 1112 | |
| 1113 | def _get_parent_path(self): |
| 1114 | parent_module_name, path_attr_name = self._find_parent_path_names() |
| 1115 | return getattr(sys.modules[parent_module_name], path_attr_name) |
| 1116 | |
| 1117 | def _recalculate(self): |
| 1118 | # If the parent's path has changed, recalculate _path |
| 1119 | parent_path = tuple(self._get_parent_path()) # Make a copy |
| 1120 | if parent_path != self._last_parent_path or self._epoch != self._last_epoch: |
| 1121 | spec = self._path_finder(self._name, parent_path) |
| 1122 | # Note that no changes are made if a loader is returned, but we |
| 1123 | # do remember the new parent path |
| 1124 | if spec is not None and spec.loader is None: |
| 1125 | if spec.submodule_search_locations: |
| 1126 | self._path = spec.submodule_search_locations |
| 1127 | self._last_parent_path = parent_path # Save the copy |
| 1128 | self._last_epoch = self._epoch |
| 1129 | return self._path |
| 1130 | |
| 1131 | def __iter__(self): |
| 1132 | return iter(self._recalculate()) |
| 1133 | |
| 1134 | def __getitem__(self, index): |
| 1135 | return self._recalculate()[index] |
| 1136 | |
| 1137 | def __setitem__(self, index, path): |
| 1138 | self._path[index] = path |
| 1139 | |
| 1140 | def __len__(self): |
| 1141 | return len(self._recalculate()) |
| 1142 |