omlish 0.0.0.dev398__py3-none-any.whl → 0.0.0.dev400__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omlish/__about__.py +2 -2
- omlish/bootstrap/__init__.py +2 -2
- omlish/collections/__init__.py +135 -149
- omlish/configs/formats.py +6 -2
- omlish/diag/lsof.py +4 -3
- omlish/funcs/builders.py +2 -1
- omlish/lang/__init__.py +26 -5
- omlish/lang/imports/__init__.py +0 -0
- omlish/lang/imports/conditional.py +33 -0
- omlish/lang/imports/lazy.py +66 -0
- omlish/lang/imports/proxyinit.py +445 -0
- omlish/lang/imports/resolution.py +86 -0
- omlish/lang/imports/traversal.py +104 -0
- omlish/lang/lazyglobals.py +59 -0
- omlish/lang/resources.py +1 -1
- omlish/lite/imports.py +1 -1
- omlish/lite/reprs.py +2 -1
- omlish/manifests/base.py +2 -1
- omlish/manifests/loading.py +1 -1
- omlish/marshal/__init__.py +2 -2
- omlish/os/pidfiles/pinning.py +2 -1
- omlish/secrets/all.py +2 -2
- omlish/secrets/secrets.py +1 -1
- omlish/specs/jsonrpc/__init__.py +2 -2
- omlish/specs/jsonschema/__init__.py +2 -2
- omlish/specs/openapi/__init__.py +2 -2
- omlish/sql/api/__init__.py +2 -2
- omlish/sql/queries/__init__.py +3 -9
- omlish/sql/tabledefs/__init__.py +2 -2
- omlish/typedvalues/__init__.py +2 -2
- {omlish-0.0.0.dev398.dist-info → omlish-0.0.0.dev400.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev398.dist-info → omlish-0.0.0.dev400.dist-info}/RECORD +36 -30
- omlish/lang/imports.py +0 -418
- {omlish-0.0.0.dev398.dist-info → omlish-0.0.0.dev400.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev398.dist-info → omlish-0.0.0.dev400.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev398.dist-info → omlish-0.0.0.dev400.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev398.dist-info → omlish-0.0.0.dev400.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,445 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- auto_proxy_init can capture `import as` by scanning globals for sentinels
|
4
|
+
- replaces _AutoProxyInitCapture._attrs dict outright
|
5
|
+
- should raise on unbound or shadowed import - was probably imported for side-effects but will never get
|
6
|
+
proxy imported
|
7
|
+
"""
|
8
|
+
import builtins
|
9
|
+
import contextlib
|
10
|
+
import functools
|
11
|
+
import importlib.util
|
12
|
+
import types
|
13
|
+
import typing as ta
|
14
|
+
|
15
|
+
from ..lazyglobals import LazyGlobals
|
16
|
+
|
17
|
+
|
18
|
+
##
|
19
|
+
|
20
|
+
|
21
|
+
class NamePackage(ta.NamedTuple):
|
22
|
+
name: str
|
23
|
+
package: str
|
24
|
+
|
25
|
+
|
26
|
+
class _ProxyInit:
|
27
|
+
class _Import(ta.NamedTuple):
|
28
|
+
pkg: str
|
29
|
+
attr: str | None
|
30
|
+
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
lazy_globals: LazyGlobals,
|
34
|
+
name_package: NamePackage,
|
35
|
+
) -> None:
|
36
|
+
super().__init__()
|
37
|
+
|
38
|
+
self._lazy_globals = lazy_globals
|
39
|
+
self._name_package = name_package
|
40
|
+
|
41
|
+
self._imps_by_attr: dict[str, _ProxyInit._Import] = {}
|
42
|
+
self._mods_by_pkgs: dict[str, ta.Any] = {}
|
43
|
+
|
44
|
+
@property
|
45
|
+
def name_package(self) -> NamePackage:
|
46
|
+
return self._name_package
|
47
|
+
|
48
|
+
def add(
|
49
|
+
self,
|
50
|
+
package: str,
|
51
|
+
attrs: ta.Iterable[str | tuple[str, str]] | None = None,
|
52
|
+
) -> None:
|
53
|
+
if isinstance(attrs, str):
|
54
|
+
raise TypeError(attrs)
|
55
|
+
|
56
|
+
if attrs is None:
|
57
|
+
whole_attr = package.split('.')[-1]
|
58
|
+
|
59
|
+
self._imps_by_attr[whole_attr] = self._Import(package, None)
|
60
|
+
self._lazy_globals.set_fn(whole_attr, functools.partial(self.get, whole_attr))
|
61
|
+
|
62
|
+
else:
|
63
|
+
for attr in attrs:
|
64
|
+
if isinstance(attr, tuple):
|
65
|
+
imp_attr, attr = attr
|
66
|
+
else:
|
67
|
+
imp_attr = attr
|
68
|
+
|
69
|
+
self._imps_by_attr[attr] = self._Import(package, imp_attr)
|
70
|
+
self._lazy_globals.set_fn(attr, functools.partial(self.get, attr))
|
71
|
+
|
72
|
+
def _import_module(self, name: str) -> ta.Any:
|
73
|
+
return importlib.import_module(name, package=self._name_package.package)
|
74
|
+
|
75
|
+
def get(self, attr: str) -> ta.Any:
|
76
|
+
try:
|
77
|
+
imp = self._imps_by_attr[attr]
|
78
|
+
except KeyError:
|
79
|
+
raise AttributeError(attr) # noqa
|
80
|
+
|
81
|
+
val: ta.Any
|
82
|
+
|
83
|
+
if imp.attr is None:
|
84
|
+
val = self._import_module(imp.pkg)
|
85
|
+
|
86
|
+
else:
|
87
|
+
try:
|
88
|
+
mod = self._mods_by_pkgs[imp.pkg]
|
89
|
+
except KeyError:
|
90
|
+
mod = self._import_module(imp.pkg)
|
91
|
+
self._mods_by_pkgs[imp.pkg] = mod
|
92
|
+
|
93
|
+
val = getattr(mod, imp.attr)
|
94
|
+
|
95
|
+
return val
|
96
|
+
|
97
|
+
|
98
|
+
def proxy_init(
|
99
|
+
init_globals: ta.MutableMapping[str, ta.Any],
|
100
|
+
package: str,
|
101
|
+
attrs: ta.Iterable[str | tuple[str, str]] | None = None,
|
102
|
+
) -> None:
|
103
|
+
if isinstance(attrs, str):
|
104
|
+
raise TypeError(attrs)
|
105
|
+
|
106
|
+
init_name_package = NamePackage(
|
107
|
+
init_globals['__name__'],
|
108
|
+
init_globals['__package__'],
|
109
|
+
)
|
110
|
+
|
111
|
+
pi: _ProxyInit
|
112
|
+
try:
|
113
|
+
pi = init_globals['__proxy_init__']
|
114
|
+
|
115
|
+
except KeyError:
|
116
|
+
pi = _ProxyInit(
|
117
|
+
LazyGlobals.install(init_globals),
|
118
|
+
init_name_package,
|
119
|
+
)
|
120
|
+
init_globals['__proxy_init__'] = pi
|
121
|
+
|
122
|
+
else:
|
123
|
+
if pi.name_package != init_name_package:
|
124
|
+
raise Exception(f'Wrong init name: {pi.name_package=} != {init_name_package=}')
|
125
|
+
|
126
|
+
pi.add(package, attrs)
|
127
|
+
|
128
|
+
|
129
|
+
##
|
130
|
+
|
131
|
+
|
132
|
+
class AutoProxyInitError(Exception):
|
133
|
+
pass
|
134
|
+
|
135
|
+
|
136
|
+
class AutoProxyInitErrors:
|
137
|
+
def __new__(cls, *args, **kwargs): # noqa
|
138
|
+
raise TypeError
|
139
|
+
|
140
|
+
class HookError(AutoProxyInitError):
|
141
|
+
pass
|
142
|
+
|
143
|
+
class AttrError(AutoProxyInitError):
|
144
|
+
def __init__(self, module: str | None, name: str) -> None:
|
145
|
+
super().__init__()
|
146
|
+
|
147
|
+
self.module = module
|
148
|
+
self.name = name
|
149
|
+
|
150
|
+
def __repr__(self) -> str:
|
151
|
+
return f'{self.__class__.__qualname__}(module={self.module!r}, name={self.name!r})'
|
152
|
+
|
153
|
+
class ImportError(AutoProxyInitError): # noqa
|
154
|
+
def __init__(self, module: str, from_list: ta.Sequence[str] | None) -> None:
|
155
|
+
super().__init__()
|
156
|
+
|
157
|
+
self.module = module
|
158
|
+
self.from_list = from_list
|
159
|
+
|
160
|
+
def __repr__(self) -> str:
|
161
|
+
return f'{self.__class__.__qualname__}(module={self.module!r}, from_list={self.from_list!r})'
|
162
|
+
|
163
|
+
class ImportStarForbiddenError(ImportError):
|
164
|
+
pass
|
165
|
+
|
166
|
+
class UnproxiedImportForbiddenError(ImportError):
|
167
|
+
pass
|
168
|
+
|
169
|
+
|
170
|
+
class _AutoProxyInitCapture:
|
171
|
+
class ModuleSpec(ta.NamedTuple):
|
172
|
+
name: str
|
173
|
+
level: int
|
174
|
+
|
175
|
+
def __str__(self) -> str:
|
176
|
+
return f'{"." * self.level}{self.name}'
|
177
|
+
|
178
|
+
def __repr__(self) -> str:
|
179
|
+
return repr(str(self))
|
180
|
+
|
181
|
+
class _ModuleAttr:
|
182
|
+
def __init__(self, module: '_AutoProxyInitCapture._Module', name: str) -> None:
|
183
|
+
super().__init__()
|
184
|
+
|
185
|
+
self.__module = module
|
186
|
+
self.__name = name
|
187
|
+
|
188
|
+
def __repr__(self) -> str:
|
189
|
+
return f'<{self.__class__.__name__}: {f"{self.__module.spec}:{self.__name}"!r}>'
|
190
|
+
|
191
|
+
class _Module:
|
192
|
+
def __init__(self, spec: '_AutoProxyInitCapture.ModuleSpec') -> None:
|
193
|
+
super().__init__()
|
194
|
+
|
195
|
+
self.spec = spec
|
196
|
+
|
197
|
+
self.module = types.ModuleType(f'<{self.__class__.__qualname__}: {spec!r}>')
|
198
|
+
|
199
|
+
self.attrs: dict[str, _AutoProxyInitCapture._ModuleAttr] = {}
|
200
|
+
self.imported_whole = False
|
201
|
+
|
202
|
+
def __repr__(self) -> str:
|
203
|
+
return f'{self.__class__.__name__}({self.spec!r})'
|
204
|
+
|
205
|
+
def __init__(self) -> None:
|
206
|
+
super().__init__()
|
207
|
+
|
208
|
+
self._modules: dict[_AutoProxyInitCapture.ModuleSpec, _AutoProxyInitCapture._Module] = {}
|
209
|
+
self._attrs: dict[str, _AutoProxyInitCapture._ModuleAttr | _AutoProxyInitCapture._Module] = {}
|
210
|
+
|
211
|
+
def _handle_import(
|
212
|
+
self,
|
213
|
+
module: _Module,
|
214
|
+
*,
|
215
|
+
from_list: ta.Sequence[str] | None,
|
216
|
+
) -> None:
|
217
|
+
if from_list is None:
|
218
|
+
if module.spec.level or not module.spec.name:
|
219
|
+
raise AutoProxyInitError
|
220
|
+
|
221
|
+
attr = module.spec.name
|
222
|
+
|
223
|
+
try:
|
224
|
+
xma: ta.Any = self._attrs[attr]
|
225
|
+
except KeyError:
|
226
|
+
pass
|
227
|
+
|
228
|
+
else:
|
229
|
+
if (
|
230
|
+
xma is not self._attrs.get(attr) or
|
231
|
+
not module.imported_whole
|
232
|
+
):
|
233
|
+
raise AutoProxyInitErrors.AttrError(str(module.spec), attr)
|
234
|
+
|
235
|
+
return
|
236
|
+
|
237
|
+
self._attrs[attr] = module
|
238
|
+
module.imported_whole = True
|
239
|
+
|
240
|
+
else:
|
241
|
+
for attr in from_list:
|
242
|
+
if attr == '*':
|
243
|
+
raise AutoProxyInitErrors.ImportStarForbiddenError(str(module.spec), from_list)
|
244
|
+
|
245
|
+
try:
|
246
|
+
xma = getattr(module.module, attr)
|
247
|
+
except AttributeError:
|
248
|
+
pass
|
249
|
+
|
250
|
+
else:
|
251
|
+
if (
|
252
|
+
xma is not module.attrs.get(attr) or
|
253
|
+
xma is not self._attrs.get(attr)
|
254
|
+
):
|
255
|
+
raise AutoProxyInitErrors.AttrError(str(module.spec), attr)
|
256
|
+
|
257
|
+
continue
|
258
|
+
|
259
|
+
if attr in self._attrs:
|
260
|
+
raise AutoProxyInitErrors.AttrError(str(module.spec), attr)
|
261
|
+
|
262
|
+
ma = _AutoProxyInitCapture._ModuleAttr(module, attr)
|
263
|
+
self._attrs[attr] = ma
|
264
|
+
module.attrs[attr] = ma
|
265
|
+
setattr(module.module, attr, ma)
|
266
|
+
|
267
|
+
_MOD_SELF_ATTR: ta.ClassVar[str] = '__auto_proxy_init_capture__'
|
268
|
+
|
269
|
+
def _intercept_import(
|
270
|
+
self,
|
271
|
+
name: str,
|
272
|
+
*,
|
273
|
+
globals: ta.Mapping[str, ta.Any] | None = None, # noqa
|
274
|
+
from_list: ta.Sequence[str] | None = None,
|
275
|
+
level: int = 0,
|
276
|
+
) -> types.ModuleType | None:
|
277
|
+
if not (
|
278
|
+
globals is not None and
|
279
|
+
globals.get(self._MOD_SELF_ATTR) is self
|
280
|
+
):
|
281
|
+
return None
|
282
|
+
|
283
|
+
spec = _AutoProxyInitCapture.ModuleSpec(name, level)
|
284
|
+
try:
|
285
|
+
module = self._modules[spec]
|
286
|
+
except KeyError:
|
287
|
+
module = self._Module(spec)
|
288
|
+
self._modules[spec] = module
|
289
|
+
|
290
|
+
self._handle_import(
|
291
|
+
module,
|
292
|
+
from_list=from_list,
|
293
|
+
)
|
294
|
+
|
295
|
+
return module.module
|
296
|
+
|
297
|
+
@contextlib.contextmanager
|
298
|
+
def hook_context(
|
299
|
+
self,
|
300
|
+
init_globals: ta.MutableMapping[str, ta.Any], # noqa
|
301
|
+
*,
|
302
|
+
forbid_unproxied_imports: bool = False,
|
303
|
+
) -> ta.Iterator[None]:
|
304
|
+
if self._MOD_SELF_ATTR in init_globals:
|
305
|
+
raise AutoProxyInitErrors.HookError
|
306
|
+
|
307
|
+
old_import = builtins.__import__
|
308
|
+
|
309
|
+
def new_import(
|
310
|
+
name,
|
311
|
+
globals=None, # noqa
|
312
|
+
locals=None, # noqa
|
313
|
+
fromlist=None,
|
314
|
+
level=0,
|
315
|
+
):
|
316
|
+
if (im := self._intercept_import(
|
317
|
+
name,
|
318
|
+
globals=globals,
|
319
|
+
from_list=fromlist,
|
320
|
+
level=level,
|
321
|
+
)) is not None:
|
322
|
+
return im
|
323
|
+
|
324
|
+
if forbid_unproxied_imports:
|
325
|
+
raise AutoProxyInitErrors.UnproxiedImportForbiddenError(
|
326
|
+
str(_AutoProxyInitCapture.ModuleSpec(name, level)),
|
327
|
+
fromlist,
|
328
|
+
)
|
329
|
+
|
330
|
+
return old_import(
|
331
|
+
name,
|
332
|
+
globals=globals,
|
333
|
+
locals=locals,
|
334
|
+
fromlist=fromlist,
|
335
|
+
level=level,
|
336
|
+
)
|
337
|
+
|
338
|
+
#
|
339
|
+
|
340
|
+
init_globals[self._MOD_SELF_ATTR] = self
|
341
|
+
builtins.__import__ = new_import
|
342
|
+
|
343
|
+
try:
|
344
|
+
yield
|
345
|
+
|
346
|
+
finally:
|
347
|
+
if not (
|
348
|
+
init_globals[self._MOD_SELF_ATTR] is self and
|
349
|
+
builtins.__import__ is new_import
|
350
|
+
):
|
351
|
+
raise AutoProxyInitErrors.HookError
|
352
|
+
|
353
|
+
del init_globals[self._MOD_SELF_ATTR]
|
354
|
+
builtins.__import__ = old_import
|
355
|
+
|
356
|
+
def verify_globals(
|
357
|
+
self,
|
358
|
+
init_globals: ta.MutableMapping[str, ta.Any], # noqa
|
359
|
+
) -> None:
|
360
|
+
for attr, obj in self._attrs.items():
|
361
|
+
try:
|
362
|
+
xo = init_globals[attr]
|
363
|
+
except KeyError:
|
364
|
+
raise AutoProxyInitErrors.AttrError(None, attr) from None
|
365
|
+
|
366
|
+
if isinstance(obj, _AutoProxyInitCapture._ModuleAttr):
|
367
|
+
if xo is not obj:
|
368
|
+
raise AutoProxyInitErrors.AttrError(None, attr) from None
|
369
|
+
|
370
|
+
elif isinstance(obj, _AutoProxyInitCapture._Module):
|
371
|
+
if xo is not obj.module:
|
372
|
+
raise AutoProxyInitErrors.AttrError(None, attr) from None
|
373
|
+
|
374
|
+
else:
|
375
|
+
raise TypeError(obj)
|
376
|
+
|
377
|
+
@property
|
378
|
+
def all_attrs(self) -> ta.AbstractSet[str]:
|
379
|
+
return self._attrs.keys()
|
380
|
+
|
381
|
+
class ProxyInit(ta.NamedTuple):
|
382
|
+
package: str
|
383
|
+
attrs: ta.Sequence[str] | None
|
384
|
+
|
385
|
+
def build_proxy_inits(self) -> list[ProxyInit]:
|
386
|
+
lst: list[_AutoProxyInitCapture.ProxyInit] = []
|
387
|
+
|
388
|
+
for module in self._modules.values():
|
389
|
+
if module.imported_whole:
|
390
|
+
lst.append(_AutoProxyInitCapture.ProxyInit(str(module.spec), None))
|
391
|
+
|
392
|
+
if module.attrs:
|
393
|
+
if not module.spec.name:
|
394
|
+
for attr in module.attrs:
|
395
|
+
if not module.spec.level:
|
396
|
+
raise AutoProxyInitError
|
397
|
+
|
398
|
+
lst.append(_AutoProxyInitCapture.ProxyInit('.' * module.spec.level + attr, None))
|
399
|
+
|
400
|
+
else:
|
401
|
+
lst.append(_AutoProxyInitCapture.ProxyInit(str(module.spec), list(module.attrs)))
|
402
|
+
|
403
|
+
return lst
|
404
|
+
|
405
|
+
|
406
|
+
@contextlib.contextmanager
|
407
|
+
def auto_proxy_init(
|
408
|
+
init_globals: ta.MutableMapping[str, ta.Any],
|
409
|
+
*,
|
410
|
+
disable: bool = False,
|
411
|
+
eager: bool = False,
|
412
|
+
) -> ta.Iterator[None]:
|
413
|
+
"""
|
414
|
+
This is a bit extreme - use sparingly. It relies on an interpreter-global import lock, but much of the ecosystem
|
415
|
+
implicitly does anyway. It further relies on temporarily patching `__builtins__.__import__`, but could be switched
|
416
|
+
to use any number of other import hooks.
|
417
|
+
"""
|
418
|
+
|
419
|
+
if disable:
|
420
|
+
yield
|
421
|
+
return
|
422
|
+
|
423
|
+
cap = _AutoProxyInitCapture()
|
424
|
+
|
425
|
+
with cap.hook_context(init_globals):
|
426
|
+
yield
|
427
|
+
|
428
|
+
cap.verify_globals(init_globals)
|
429
|
+
|
430
|
+
pis = cap.build_proxy_inits()
|
431
|
+
|
432
|
+
for attr in cap.all_attrs:
|
433
|
+
del init_globals[attr]
|
434
|
+
|
435
|
+
for pi in pis:
|
436
|
+
proxy_init(
|
437
|
+
init_globals,
|
438
|
+
pi.package,
|
439
|
+
pi.attrs,
|
440
|
+
)
|
441
|
+
|
442
|
+
if eager:
|
443
|
+
lg = LazyGlobals.install(init_globals)
|
444
|
+
for attr in cap.all_attrs:
|
445
|
+
lg.get(attr)
|
@@ -0,0 +1,86 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- use importlib.util.resolve_name
|
4
|
+
"""
|
5
|
+
import importlib.util
|
6
|
+
import sys
|
7
|
+
import types
|
8
|
+
import typing as ta
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
|
13
|
+
|
14
|
+
def can_import(name: str, package: str | None = None) -> bool:
|
15
|
+
try:
|
16
|
+
spec = importlib.util.find_spec(name, package)
|
17
|
+
except ImportError:
|
18
|
+
return False
|
19
|
+
else:
|
20
|
+
return spec is not None
|
21
|
+
|
22
|
+
|
23
|
+
##
|
24
|
+
|
25
|
+
|
26
|
+
def try_import(spec: str) -> types.ModuleType | None:
|
27
|
+
s = spec.lstrip('.')
|
28
|
+
l = len(spec) - len(s)
|
29
|
+
try:
|
30
|
+
return __import__(s, globals(), level=l)
|
31
|
+
except ImportError:
|
32
|
+
return None
|
33
|
+
|
34
|
+
|
35
|
+
##
|
36
|
+
|
37
|
+
|
38
|
+
def resolve_import_name(name: str, package: str | None = None) -> str:
|
39
|
+
# FIXME: importlib.util.resolve_name
|
40
|
+
level = 0
|
41
|
+
|
42
|
+
if name.startswith('.'):
|
43
|
+
if not package:
|
44
|
+
raise TypeError("the 'package' argument is required to perform a relative import for {name!r}")
|
45
|
+
for character in name:
|
46
|
+
if character != '.':
|
47
|
+
break
|
48
|
+
level += 1
|
49
|
+
|
50
|
+
name = name[level:]
|
51
|
+
|
52
|
+
if not isinstance(name, str):
|
53
|
+
raise TypeError(f'module name must be str, not {type(name)}')
|
54
|
+
if level < 0:
|
55
|
+
raise ValueError('level must be >= 0')
|
56
|
+
if level > 0:
|
57
|
+
if not isinstance(package, str):
|
58
|
+
raise TypeError('__package__ not set to a string')
|
59
|
+
elif not package:
|
60
|
+
raise ImportError('attempted relative import with no known parent package')
|
61
|
+
if not name and level == 0:
|
62
|
+
raise ValueError('Empty module name')
|
63
|
+
|
64
|
+
if level > 0:
|
65
|
+
bits = package.rsplit('.', level - 1) # type: ignore
|
66
|
+
if len(bits) < level:
|
67
|
+
raise ImportError('attempted relative import beyond top-level package')
|
68
|
+
base = bits[0]
|
69
|
+
name = f'{base}.{name}' if name else base
|
70
|
+
|
71
|
+
return name
|
72
|
+
|
73
|
+
|
74
|
+
##
|
75
|
+
|
76
|
+
|
77
|
+
def get_real_module_name(globals: ta.Mapping[str, ta.Any]) -> str: # noqa
|
78
|
+
module = sys.modules[globals['__name__']]
|
79
|
+
|
80
|
+
if module.__spec__ and module.__spec__.name:
|
81
|
+
return module.__spec__.name
|
82
|
+
|
83
|
+
if module.__package__:
|
84
|
+
return module.__package__
|
85
|
+
|
86
|
+
raise RuntimeError("Can't determine real module name")
|
@@ -0,0 +1,104 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- overhaul this - use pkgutil.walk_packages unless called needs non-importing (which this currently doesn't do anyway),
|
4
|
+
and support namespace packages if they do.
|
5
|
+
"""
|
6
|
+
import contextlib
|
7
|
+
import sys
|
8
|
+
import typing as ta
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
|
13
|
+
|
14
|
+
SPECIAL_IMPORTABLE: ta.AbstractSet[str] = frozenset([
|
15
|
+
'__init__.py',
|
16
|
+
'__main__.py',
|
17
|
+
])
|
18
|
+
|
19
|
+
|
20
|
+
def yield_importable(
|
21
|
+
package_root: str,
|
22
|
+
*,
|
23
|
+
recursive: bool = False,
|
24
|
+
filter: ta.Callable[[str], bool] | None = None, # noqa
|
25
|
+
include_special: bool = False,
|
26
|
+
raise_on_failure: bool = False,
|
27
|
+
) -> ta.Iterator[str]:
|
28
|
+
import importlib.resources
|
29
|
+
|
30
|
+
def rec(cur):
|
31
|
+
if cur.split('.')[-1] == '__pycache__':
|
32
|
+
return
|
33
|
+
|
34
|
+
try:
|
35
|
+
module = sys.modules[cur]
|
36
|
+
except KeyError:
|
37
|
+
module = importlib.import_module(cur)
|
38
|
+
|
39
|
+
# FIXME: pyox
|
40
|
+
if getattr(module, '__file__', None) is None:
|
41
|
+
return
|
42
|
+
|
43
|
+
for file in importlib.resources.files(cur).iterdir():
|
44
|
+
if file.is_file() and file.name.endswith('.py'):
|
45
|
+
if not (include_special or file.name not in SPECIAL_IMPORTABLE):
|
46
|
+
continue
|
47
|
+
|
48
|
+
name = cur + '.' + file.name[:-3]
|
49
|
+
if filter is not None and not filter(name):
|
50
|
+
continue
|
51
|
+
|
52
|
+
yield name
|
53
|
+
|
54
|
+
elif recursive and file.is_dir():
|
55
|
+
name = cur + '.' + file.name
|
56
|
+
|
57
|
+
if filter is not None and not filter(name):
|
58
|
+
continue
|
59
|
+
|
60
|
+
if raise_on_failure:
|
61
|
+
yield from rec(name)
|
62
|
+
|
63
|
+
else:
|
64
|
+
with contextlib.suppress(ImportError, NotImplementedError):
|
65
|
+
yield from rec(name)
|
66
|
+
|
67
|
+
yield from rec(package_root)
|
68
|
+
|
69
|
+
|
70
|
+
def yield_import_all(
|
71
|
+
package_root: str,
|
72
|
+
*,
|
73
|
+
globals: dict[str, ta.Any] | None = None, # noqa
|
74
|
+
locals: dict[str, ta.Any] | None = None, # noqa
|
75
|
+
recursive: bool = False,
|
76
|
+
filter: ta.Callable[[str], bool] | None = None, # noqa
|
77
|
+
include_special: bool = False,
|
78
|
+
raise_on_failure: bool = False,
|
79
|
+
) -> ta.Iterator[str]:
|
80
|
+
for import_path in yield_importable(
|
81
|
+
package_root,
|
82
|
+
recursive=recursive,
|
83
|
+
filter=filter,
|
84
|
+
include_special=include_special,
|
85
|
+
raise_on_failure=raise_on_failure,
|
86
|
+
):
|
87
|
+
__import__(import_path, globals=globals, locals=locals)
|
88
|
+
yield import_path
|
89
|
+
|
90
|
+
|
91
|
+
def import_all(
|
92
|
+
package_root: str,
|
93
|
+
*,
|
94
|
+
recursive: bool = False,
|
95
|
+
filter: ta.Callable[[str], bool] | None = None, # noqa
|
96
|
+
include_special: bool = False,
|
97
|
+
) -> None:
|
98
|
+
for _ in yield_import_all(
|
99
|
+
package_root,
|
100
|
+
recursive=recursive,
|
101
|
+
filter=filter,
|
102
|
+
include_special=include_special,
|
103
|
+
):
|
104
|
+
pass
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
|
4
|
+
##
|
5
|
+
|
6
|
+
|
7
|
+
class LazyGlobals:
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
*,
|
11
|
+
globals: ta.MutableMapping[str, ta.Any] | None = None, # noqa
|
12
|
+
update_globals: bool = False,
|
13
|
+
) -> None:
|
14
|
+
super().__init__()
|
15
|
+
|
16
|
+
self._globals = globals
|
17
|
+
self._update_globals = update_globals
|
18
|
+
|
19
|
+
self._attr_fns: dict[str, ta.Callable[[], ta.Any]] = {}
|
20
|
+
|
21
|
+
@classmethod
|
22
|
+
def install(cls, globals: ta.MutableMapping[str, ta.Any]) -> 'LazyGlobals': # noqa
|
23
|
+
try:
|
24
|
+
xga = globals['__getattr__']
|
25
|
+
except KeyError:
|
26
|
+
pass
|
27
|
+
else:
|
28
|
+
if not isinstance(xga, cls):
|
29
|
+
raise RuntimeError(f'Module already has __getattr__ hook: {xga}') # noqa
|
30
|
+
return xga
|
31
|
+
|
32
|
+
lm = cls(
|
33
|
+
globals=globals,
|
34
|
+
update_globals=True,
|
35
|
+
)
|
36
|
+
|
37
|
+
globals['__getattr__'] = lm
|
38
|
+
|
39
|
+
return lm
|
40
|
+
|
41
|
+
def set_fn(self, attr: str, fn: ta.Callable[[], ta.Any]) -> 'LazyGlobals':
|
42
|
+
self._attr_fns[attr] = fn
|
43
|
+
return self
|
44
|
+
|
45
|
+
def get(self, attr: str) -> ta.Any:
|
46
|
+
try:
|
47
|
+
fn = self._attr_fns[attr]
|
48
|
+
except KeyError:
|
49
|
+
raise AttributeError(attr) from None
|
50
|
+
|
51
|
+
val = fn()
|
52
|
+
|
53
|
+
if self._update_globals and self._globals is not None:
|
54
|
+
self._globals[attr] = val
|
55
|
+
|
56
|
+
return val
|
57
|
+
|
58
|
+
def __call__(self, attr: str) -> ta.Any:
|
59
|
+
return self.get(attr)
|
omlish/lang/resources.py
CHANGED
omlish/lite/imports.py
CHANGED