Skip to content

Commit 077ac12

Browse files
committed
Add Dan Schult'z delayed import error
1 parent eba9efa commit 077ac12

File tree

2 files changed

+40
-2
lines changed

2 files changed

+40
-2
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,9 @@ Use `lazy.load` to lazily import external libraries:
7878
```python
7979
linalg = lazy.load('scipy.linalg') # `linalg` will only be loaded when accessed
8080
```
81+
82+
You can also ask `lazy.load` to raise import errors as soon as it is called:
83+
84+
```
85+
linalg = lazy.load('scipy.linalg', error_on_import=True)
86+
```

lazy_loader/__init__.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import importlib
22
import importlib.util
3+
import types
34
import os
45
import sys
56

@@ -78,7 +79,7 @@ def __dir__():
7879
return __getattr__, __dir__, list(__all__)
7980

8081

81-
def load(fullname):
82+
def load(fullname, error_on_import=False):
8283
"""Return a lazily imported proxy for a module.
8384
8485
We often see the following pattern::
@@ -112,6 +113,9 @@ def myfunc():
112113
113114
sp = lazy.load('scipy') # import scipy as sp
114115
spla = lazy.load('scipy.linalg') # import scipy.linalg as spla
116+
error_on_import : bool
117+
Whether to postpone raising import errors until the module is accessed.
118+
If set to `True`, import errors are raised as soon as `load` is called.
115119
116120
Returns
117121
-------
@@ -127,7 +131,16 @@ def myfunc():
127131

128132
spec = importlib.util.find_spec(fullname)
129133
if spec is None:
130-
raise ModuleNotFoundError(f"No module name '{fullname}'")
134+
if error_on_import:
135+
raise ModuleNotFoundError(f"No module named '{fullname}'")
136+
else:
137+
spec = importlib.util.spec_from_loader(fullname, loader=None)
138+
module = importlib.util.module_from_spec(spec)
139+
tmp_loader = importlib.machinery.SourceFileLoader(module, path=None)
140+
loader = DelayedImportErrorLoader(tmp_loader)
141+
loader.exec_module(module)
142+
# dont add to sys.modules. The module wasn't found.
143+
return module
131144

132145
module = importlib.util.module_from_spec(spec)
133146
sys.modules[fullname] = module
@@ -136,3 +149,22 @@ def myfunc():
136149
loader.exec_module(module)
137150

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

0 commit comments

Comments
 (0)