Skip to content

Commit 16fc18e

Browse files
authored
Merge pull request #4 from stefanv/better-error
Simplify delayed exception handling and improve reporting
2 parents ab3d7df + 6313b05 commit 16fc18e

File tree

1 file changed

+33
-24
lines changed

1 file changed

+33
-24
lines changed

lazy_loader/__init__.py

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
"""
77
import importlib
88
import importlib.util
9+
import inspect
910
import os
1011
import sys
1112
import types
1213

14+
__all__ = ["attach", "load"]
15+
1316

1417
def attach(package_name, submodules=None, submod_attrs=None):
1518
"""Attach lazily loaded submodules, functions, or other attributes.
@@ -83,6 +86,24 @@ def __dir__():
8386
return __getattr__, __dir__, list(__all__)
8487

8588

89+
class DelayedImportErrorModule(types.ModuleType):
90+
def __init__(self, frame_data, *args, **kwargs):
91+
self.__frame_data = frame_data
92+
super().__init__(*args, **kwargs)
93+
94+
def __getattr__(self, x):
95+
if x in ("__class__", "__file__", "__frame_data"):
96+
super().__getattr__(x)
97+
else:
98+
fd = self.__frame_data
99+
raise ModuleNotFoundError(
100+
f"No module named '{fd['spec']}'\n\n"
101+
"This error is lazily reported, having originally occured in\n"
102+
f' File {fd["filename"]}, line {fd["lineno"]}, in {fd["function"]}\n\n'
103+
f'----> {"".join(fd["code_context"]).strip()}'
104+
)
105+
106+
86107
def load(fullname, error_on_import=False):
87108
"""Return a lazily imported proxy for a module.
88109
@@ -138,13 +159,18 @@ def myfunc():
138159
if error_on_import:
139160
raise ModuleNotFoundError(f"No module named '{fullname}'")
140161
else:
141-
spec = importlib.util.spec_from_loader(fullname, loader=None)
142-
module = importlib.util.module_from_spec(spec)
143-
tmp_loader = importlib.machinery.SourceFileLoader(module, path=None)
144-
loader = DelayedImportErrorLoader(tmp_loader)
145-
loader.exec_module(module)
146-
# dont add to sys.modules. The module wasn't found.
147-
return module
162+
try:
163+
parent = inspect.stack()[1]
164+
frame_data = {
165+
"spec": fullname,
166+
"filename": parent.filename,
167+
"lineno": parent.lineno,
168+
"function": parent.function,
169+
"code_context": parent.code_context,
170+
}
171+
return DelayedImportErrorModule(frame_data, "DelayedImportErrorModule")
172+
finally:
173+
del parent
148174

149175
module = importlib.util.module_from_spec(spec)
150176
sys.modules[fullname] = module
@@ -153,20 +179,3 @@ def myfunc():
153179
loader.exec_module(module)
154180

155181
return module
156-
157-
158-
class DelayedImportErrorLoader(importlib.util.LazyLoader):
159-
def exec_module(self, module):
160-
super().exec_module(module)
161-
module.__class__ = DelayedImportErrorModule
162-
163-
164-
class DelayedImportErrorModule(types.ModuleType):
165-
def __getattribute__(self, attr):
166-
"""Trigger a ModuleNotFoundError upon attribute access"""
167-
spec = super().__getattribute__("__spec__")
168-
# allows isinstance and type functions to work without raising error
169-
if attr in ["__class__"]:
170-
return super().__getattribute__("__class__")
171-
172-
raise ModuleNotFoundError(f"No module named '{spec.name}'")

0 commit comments

Comments
 (0)