diff options
author | Stefan van der Walt <stefanv@berkeley.edu> | 2022-03-03 12:55:12 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-03 12:55:12 -0800 |
commit | 8feb4a645c93598bb187bf1707beb673fa27915e (patch) | |
tree | 859978a7f07974fd7b404974007ced0810e3cc73 | |
parent | 2b01a30d6967cc94a0f8caca2252bce7817b2b1c (diff) | |
download | networkx-8feb4a645c93598bb187bf1707beb673fa27915e.tar.gz |
Simplify DelayedImportErrorModule (#5371)
* Simplify DelayedImportErrorModule
* Only allow very restrictive dunder access on DelayedImportErrorModule
* Improve lazy loading exception
-rw-r--r-- | networkx/lazy_imports.py | 60 |
1 files changed, 31 insertions, 29 deletions
diff --git a/networkx/lazy_imports.py b/networkx/lazy_imports.py index 248f3b43..9a88ed6d 100644 --- a/networkx/lazy_imports.py +++ b/networkx/lazy_imports.py @@ -1,5 +1,6 @@ import importlib import importlib.util +import inspect import types import os import sys @@ -81,6 +82,24 @@ def attach(module_name, submodules=None, submod_attrs=None): return __getattr__, __dir__, list(__all__) +class DelayedImportErrorModule(types.ModuleType): + def __init__(self, frame_data, *args, **kwargs): + self.__frame_data = frame_data + super().__init__(*args, **kwargs) + + def __getattr__(self, x): + if x in ("__class__", "__file__", "__frame_data"): + super().__getattr__(x) + else: + fd = self.__frame_data + raise ModuleNotFoundError( + f"No module named '{fd['spec']}'\n\n" + "This error is lazily reported, having originally occured in\n" + f' File {fd["filename"]}, line {fd["lineno"]}, in {fd["function"]}\n\n' + f'----> {"".join(fd["code_context"]).strip()}' + ) + + def lazy_import(fullname): """Return a lazily imported proxy for a module or library. @@ -132,14 +151,18 @@ def lazy_import(fullname): spec = importlib.util.find_spec(fullname) if spec is None: - # module not found - construct a DelayedImportErrorModule - spec = importlib.util.spec_from_loader(fullname, loader=None) - module = importlib.util.module_from_spec(spec) - tmp_loader = importlib.machinery.SourceFileLoader(module, path=None) - loader = DelayedImportErrorLoader(tmp_loader) - loader.exec_module(module) - # dont add to sys.modules. The module wasn't found. - return module + try: + parent = inspect.stack()[1] + frame_data = { + "spec": fullname, + "filename": parent.filename, + "lineno": parent.lineno, + "function": parent.function, + "code_context": parent.code_context, + } + return DelayedImportErrorModule(frame_data, "DelayedImportErrorModule") + finally: + del parent module = importlib.util.module_from_spec(spec) sys.modules[fullname] = module @@ -148,24 +171,3 @@ def lazy_import(fullname): loader.exec_module(module) return module - - -class DelayedImportErrorLoader(importlib.util.LazyLoader): - def exec_module(self, module): - super().exec_module(module) - module.__class__ = DelayedImportErrorModule - - -class DelayedImportErrorModule(types.ModuleType): - def __getattribute__(self, attr): - """Trigger a ModuleNotFoundError upon attribute access""" - spec = super().__getattribute__("__spec__") - # allows isinstance and type functions to work without raising error - if attr in ["__class__"]: - return super().__getattribute__("__class__") - - raise ModuleNotFoundError( - f"Delayed Report: module named '{spec.name}' not found.\n" - "Reporting was Lazy -- delayed until module attributes accessed.\n" - f"Most likely, {spec.name} is not installed" - ) |