Trigger the load of the module and return the attribute.
(self, attr)
| 169 | """A subclass of the module type which triggers loading upon attribute access.""" |
| 170 | |
| 171 | def __getattribute__(self, attr): |
| 172 | """Trigger the load of the module and return the attribute.""" |
| 173 | __spec__ = object.__getattribute__(self, '__spec__') |
| 174 | loader_state = __spec__.loader_state |
| 175 | with loader_state['lock']: |
| 176 | # Only the first thread to get the lock should trigger the load |
| 177 | # and reset the module's class. The rest can now getattr(). |
| 178 | if object.__getattribute__(self, '__class__') is _LazyModule: |
| 179 | __class__ = loader_state['__class__'] |
| 180 | |
| 181 | # Reentrant calls from the same thread must be allowed to proceed without |
| 182 | # triggering the load again. |
| 183 | # exec_module() and self-referential imports are the primary ways this can |
| 184 | # happen, but in any case we must return something to avoid deadlock. |
| 185 | if loader_state['is_loading']: |
| 186 | return __class__.__getattribute__(self, attr) |
| 187 | loader_state['is_loading'] = True |
| 188 | |
| 189 | __dict__ = __class__.__getattribute__(self, '__dict__') |
| 190 | |
| 191 | # All module metadata must be gathered from __spec__ in order to avoid |
| 192 | # using mutated values. |
| 193 | # Get the original name to make sure no object substitution occurred |
| 194 | # in sys.modules. |
| 195 | original_name = __spec__.name |
| 196 | # Figure out exactly what attributes were mutated between the creation |
| 197 | # of the module and now. |
| 198 | attrs_then = loader_state['__dict__'] |
| 199 | attrs_now = __dict__ |
| 200 | attrs_updated = {} |
| 201 | for key, value in attrs_now.items(): |
| 202 | # Code that set an attribute may have kept a reference to the |
| 203 | # assigned object, making identity more important than equality. |
| 204 | if key not in attrs_then: |
| 205 | attrs_updated[key] = value |
| 206 | elif id(attrs_now[key]) != id(attrs_then[key]): |
| 207 | attrs_updated[key] = value |
| 208 | __spec__.loader.exec_module(self) |
| 209 | # If exec_module() was used directly there is no guarantee the module |
| 210 | # object was put into sys.modules. |
| 211 | if original_name in sys.modules: |
| 212 | if id(self) != id(sys.modules[original_name]): |
| 213 | raise ValueError(f"module object for {original_name!r} " |
| 214 | "substituted in sys.modules during a lazy " |
| 215 | "load") |
| 216 | # Update after loading since that's what would happen in an eager |
| 217 | # loading situation. |
| 218 | __dict__.update(attrs_updated) |
| 219 | # Finally, stop triggering this method, if the module did not |
| 220 | # already update its own __class__. |
| 221 | if isinstance(self, _LazyModule): |
| 222 | object.__setattr__(self, '__class__', __class__) |
| 223 | |
| 224 | return getattr(self, attr) |
| 225 | |
| 226 | def __delattr__(self, attr): |
| 227 | """Trigger the load and then perform the deletion.""" |
no test coverage detected