6
6
"""
7
7
import importlib
8
8
import importlib .util
9
+ import inspect
9
10
import os
10
11
import sys
11
12
import types
12
13
14
+ __all__ = ["attach" , "load" ]
15
+
13
16
14
17
def attach (package_name , submodules = None , submod_attrs = None ):
15
18
"""Attach lazily loaded submodules, functions, or other attributes.
@@ -83,6 +86,24 @@ def __dir__():
83
86
return __getattr__ , __dir__ , list (__all__ )
84
87
85
88
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
+
86
107
def load (fullname , error_on_import = False ):
87
108
"""Return a lazily imported proxy for a module.
88
109
@@ -138,13 +159,18 @@ def myfunc():
138
159
if error_on_import :
139
160
raise ModuleNotFoundError (f"No module named '{ fullname } '" )
140
161
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
148
174
149
175
module = importlib .util .module_from_spec (spec )
150
176
sys .modules [fullname ] = module
@@ -153,20 +179,3 @@ def myfunc():
153
179
loader .exec_module (module )
154
180
155
181
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