20
20
from .utils import singleton
21
21
22
22
ALL_EXTS = tuple (all_suffixes ())
23
- NAMESPACE_PKG_TAG = "__melobot_namespace__"
23
+ EMPTY_PKG_TAG = "__melobot_namespace_pkg__"
24
+ ZIP_MODULE_TAG = "__melobot_zip_module__"
24
25
25
26
26
27
class _NestedQuickExit (BaseException ): ...
27
28
28
29
29
30
@singleton
30
31
class SpecFinder (MetaPathFinder ):
31
-
32
32
def find_spec (
33
33
self ,
34
34
fullname : str ,
35
35
paths : Sequence [str ] | None ,
36
- target : ModuleType | None = None , # pylint: disable=unused-argument
36
+ target : ModuleType | None = None ,
37
37
sys_cache : bool = True ,
38
38
load_cache : bool = True ,
39
39
pre_sys_len : int = - 1 ,
@@ -48,43 +48,50 @@ def find_spec(
48
48
49
49
mod_path : Path | None = None
50
50
submod_locs : list [str ] | None = None
51
- # The spec finding according PEP420: https://peps.python.org/pep-0420/#specification
51
+
52
+ # 模块查找的优先级,遵循 PEP420: https://peps.python.org/pep-0420/#specification
52
53
try :
53
54
for entry in paths :
54
55
entry_path = Path (entry )
55
- zip_file_path = entry_path .joinpath (f"{ name } .zip" )
56
56
dir_path = entry_path .joinpath (name )
57
57
pkg_init_path = dir_path .joinpath ("__init__.py" )
58
58
59
+ # 带有 __init__.py 的包优先
59
60
if pkg_init_path .exists ():
60
61
mod_path = pkg_init_path
61
62
submod_locs = [str (dir_path .resolve ())]
62
63
raise _NestedQuickExit
63
64
65
+ # 其次是各种可加载的文件
64
66
for ext in ALL_EXTS :
65
67
_mod_path = entry_path .joinpath (f"{ name } { ext } " )
66
68
if _mod_path .exists ():
67
69
mod_path = _mod_path
68
70
submod_locs = None
69
71
raise _NestedQuickExit
70
72
71
- if zip_file_path .exists ():
72
- spec = zipimport .zipimporter (str (zip_file_path )).find_spec (
73
- fullname , target
74
- )
75
- assert spec is not None
76
- assert spec .loader is not None
77
- spec .loader = ModuleLoader (
78
- fullname ,
79
- zip_file_path ,
80
- sys_cache ,
81
- load_cache ,
82
- pre_sys_len ,
83
- pre_cache_len ,
84
- spec .loader ,
73
+ # 再次是 zip 文件导入
74
+ if entry_path .suffix == ".zip" and entry_path .exists ():
75
+ zip_importer = zipimport .zipimporter ( # pylint: disable=no-member
76
+ str (entry_path )
85
77
)
86
- return spec
78
+ spec = zip_importer .find_spec (fullname , target )
79
+ if spec is not None :
80
+ assert spec .origin is not None and spec .origin != ""
81
+ assert spec .loader is not None
82
+ spec .loader = ModuleLoader (
83
+ fullname ,
84
+ Path (spec .origin ).resolve (),
85
+ sys_cache ,
86
+ load_cache ,
87
+ pre_sys_len ,
88
+ pre_cache_len ,
89
+ spec .loader ,
90
+ )
91
+ setattr (spec , ZIP_MODULE_TAG , True )
92
+ return spec
87
93
94
+ # 没有 __init__.py 的包最后查找,spec 设置为与内置导入兼容的命名空间包格式
88
95
if dir_path .exists () and dir_path .is_dir ():
89
96
dir_path_str = str (dir_path .resolve ())
90
97
submod_locs = _NamespacePath (
@@ -109,7 +116,7 @@ def find_spec(
109
116
assert spec is not None
110
117
spec .has_location = False
111
118
spec .origin = None
112
- setattr (spec , NAMESPACE_PKG_TAG , True )
119
+ setattr (spec , EMPTY_PKG_TAG , True )
113
120
return spec
114
121
115
122
except _NestedQuickExit :
@@ -155,7 +162,7 @@ def set_cache(self, name: str, mod: ModuleType) -> None:
155
162
):
156
163
return
157
164
158
- # __file__ 存在且不为空,可能包或任意可被加载的文件,包对应 __init__.py,应该转换为不包含 __init__.py 后缀的形式
165
+ # __file__ 存在且不为空,可能包或任意可被加载的文件
159
166
if mod .__file__ is not None :
160
167
fp = Path (mod .__file__ )
161
168
if fp .parts [- 1 ] == "__init__.py" :
@@ -192,61 +199,66 @@ def __init__(
192
199
inner_loader : Loader | None = None ,
193
200
) -> None :
194
201
super ().__init__ ()
195
- self .cacher = ModuleCacher ()
196
- self .fullname = fullname
197
- self .fp = fp
198
- self .use_sys_cache = sys_cache
199
- self .use_load_cache = load_cache
200
- self .pre_sys_len = pre_sys_len
201
- self .pre_cache_len = pre_cache_len
202
-
203
- self .inner_loader : Loader | None = inner_loader
202
+ # 避免在 self.__getattr__() 运行反射时重名
203
+ self .melobot_cacher = ModuleCacher ()
204
+ self .melobot_fullname = fullname
205
+ self .melobot_fp = fp
206
+ self .melobot_sys_cache = sys_cache
207
+ self .melobot_load_cache = load_cache
208
+ self .melobot_pre_sys_len = pre_sys_len
209
+ self .melobot_pre_cache_len = pre_cache_len
210
+
211
+ self .melobot_inner_loader : Loader | None = inner_loader
204
212
if inner_loader is not None :
205
213
return
206
214
207
215
for loader_class , suffixes in _get_supported_file_loaders ():
208
216
if str (fp ).endswith (tuple (suffixes )):
209
217
loader = loader_class (fullname , str (fp )) # pylint: disable=not-callable
210
- self .inner_loader = loader
218
+ self .melobot_inner_loader = loader
211
219
break
212
220
213
221
def create_module (self , spec : ModuleSpec ) -> ModuleType | None :
214
222
mod = None
215
- if self .use_load_cache :
216
- mod = self .cacher .get_cache (self .fp )
217
- if mod is None and self .inner_loader is not None :
218
- mod = self .inner_loader .create_module (spec )
223
+ if self .melobot_load_cache :
224
+ mod = self .melobot_cacher .get_cache (self .melobot_fp )
225
+ if mod is None and self .melobot_inner_loader is not None :
226
+ mod = self .melobot_inner_loader .create_module (spec )
219
227
return mod
220
228
221
229
def exec_module (self , mod : ModuleType ) -> None :
222
- if self .cacher .has_cache (mod ):
223
- pass
224
- else :
225
- if self .inner_loader is not None :
226
- if self .use_sys_cache :
227
- sys .modules [self .fullname ] = mod
230
+ if (
231
+ not self .melobot_cacher .has_cache (mod )
232
+ and self .melobot_inner_loader is not None
233
+ ):
234
+ # 遵循先记录原则,防止 exec_module 发起的某些递归导入出现错误
235
+ if self .melobot_sys_cache :
236
+ sys .modules [self .melobot_fullname ] = mod
228
237
238
+ try :
239
+ self .melobot_inner_loader .exec_module (mod )
240
+ # 设置为与内置导入机制兼容的模式
241
+ if hasattr (mod .__spec__ , EMPTY_PKG_TAG ):
242
+ mod .__file__ = None
243
+ except BaseException :
229
244
try :
230
- self .inner_loader .exec_module (mod )
231
- if hasattr (mod .__spec__ , NAMESPACE_PKG_TAG ):
232
- mod .__file__ = None
233
- except BaseException :
234
- try :
235
- del sys .modules [self .fullname ]
236
- except KeyError :
237
- pass
238
- raise
239
-
240
- if self .use_load_cache :
241
- self .cacher .set_cache (self .fullname , mod )
242
-
243
- if not self .use_load_cache :
244
- diff = self .cacher .get_len () - self .pre_cache_len
245
+ del sys .modules [self .melobot_fullname ]
246
+ except KeyError :
247
+ pass
248
+ raise
249
+ # 若 inner_loader 为空,则是纯粹的命名空间包(没有 __init__.py 的包模块)
250
+ # 也就不需要任何实质性的 exec_module 过程
251
+
252
+ if self .melobot_load_cache :
253
+ self .melobot_cacher .set_cache (self .melobot_fullname , mod )
254
+
255
+ if not self .melobot_load_cache :
256
+ diff = self .melobot_cacher .get_len () - self .melobot_pre_cache_len
245
257
if diff > 0 :
246
- self .cacher .rm_lastn (diff )
258
+ self .melobot_cacher .rm_lastn (diff )
247
259
248
- if not self .use_sys_cache :
249
- diff = len (sys .modules ) - self .pre_sys_len
260
+ if not self .melobot_sys_cache :
261
+ diff = len (sys .modules ) - self .melobot_pre_sys_len
250
262
if diff > 0 :
251
263
iter = reversed (sys .modules .keys ())
252
264
rm_names : list [str ] = []
@@ -256,8 +268,10 @@ def exec_module(self, mod: ModuleType) -> None:
256
268
sys .modules .pop (name )
257
269
258
270
def __getattr__ (self , name : str ) -> Any :
259
- if self .inner_loader is not None :
260
- return getattr (self .inner_loader , name )
271
+ # 直接使用反射,而不是更复杂的继承方案
272
+ # inner_loader 实现了必要的 内省接口、importlib.resources 接口
273
+ if self .melobot_inner_loader is not None :
274
+ return getattr (self .melobot_inner_loader , name )
261
275
raise AttributeError (
262
276
f"'{ self .__class__ .__name__ } ' object has no attribute '{ name } '"
263
277
)
@@ -271,13 +285,13 @@ def import_mod(
271
285
sys_cache : bool = True ,
272
286
load_cache : bool = True ,
273
287
) -> ModuleType :
288
+ # 必须先获取,后续可能运行的递归将会影响序列长度
274
289
pre_sys_len = len (sys .modules )
275
290
pre_cache_len = ModuleCacher ().get_len ()
276
291
277
292
if path is not None :
278
- if load_cache :
279
- if (mod_cache := Importer .get_cache (Path (path ))) is not None :
280
- return mod_cache
293
+ if load_cache and (mod_cache := Importer .get_cache (Path (path ))) is not None :
294
+ return mod_cache
281
295
282
296
try :
283
297
sep = name .rindex ("." )
@@ -302,6 +316,7 @@ def import_mod(
302
316
name = name ,
303
317
path = str (path ),
304
318
)
319
+
305
320
mod = module_from_spec (spec )
306
321
assert spec .loader is not None
307
322
spec .loader .exec_module (mod )
@@ -316,7 +331,13 @@ def get_cache(path: Path) -> ModuleType | None:
316
331
return ModuleCacher ().get_cache (path )
317
332
318
333
334
+ def _union_provider (mod : Any ) -> pkg_resources .NullProvider :
335
+ if hasattr (mod .__spec__ , ZIP_MODULE_TAG ):
336
+ return pkg_resources .ZipProvider (mod )
337
+ return pkg_resources .DefaultProvider (mod )
338
+
339
+
319
340
sys .meta_path .insert (0 , SpecFinder ())
320
- pkg_resources . register_finder ( SpecFinder , pkg_resources . find_on_path )
321
- pkg_resources .register_loader_type (ModuleLoader , pkg_resources . DefaultProvider )
341
+ # 兼容 pkg_resources 的资源获取操作
342
+ pkg_resources .register_loader_type (ModuleLoader , cast ( type , _union_provider ) )
322
343
pkg_resources .register_namespace_handler (SpecFinder , pkg_resources .file_ns_handler )
0 commit comments