omlish 0.0.0.dev398__py3-none-any.whl → 0.0.0.dev399__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 CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev398'
2
- __revision__ = '36ad9ea49db19a697cb584dc80f1fac002db95f3'
1
+ __version__ = '0.0.0.dev399'
2
+ __revision__ = 'a8c0f1b2847879fa35db7e882438c69db3bd3b7c'
3
3
 
4
4
 
5
5
  #
@@ -37,6 +37,6 @@ from .sys import ( # noqa
37
37
  ##
38
38
 
39
39
 
40
- from ..lang.imports import _register_conditional_import # noqa
40
+ from .. import lang as _lang
41
41
 
42
- _register_conditional_import('..marshal', '.marshal', __package__)
42
+ _lang.register_conditional_import('..marshal', '.marshal', __package__)
omlish/lang/__init__.py CHANGED
@@ -219,16 +219,33 @@ from .generators import ( # noqa
219
219
  nextgen,
220
220
  )
221
221
 
222
- from .imports import ( # noqa
223
- LazyGlobals,
224
- can_import,
225
- get_real_module_name,
226
- import_all,
222
+ from .imports.conditional import ( # noqa
223
+ register_conditional_import,
224
+ trigger_conditional_imports,
225
+ )
226
+
227
+ from .imports.lazy import ( # noqa
227
228
  lazy_import,
228
229
  proxy_import,
230
+ )
231
+
232
+ from .imports.proxyinit import ( # noqa
229
233
  proxy_init,
234
+
235
+ AutoProxyInitError,
236
+ AutoProxyInitErrors,
237
+ auto_proxy_init,
238
+ )
239
+
240
+ from .imports.resolution import ( # noqa
241
+ can_import,
242
+ get_real_module_name,
230
243
  resolve_import_name,
231
244
  try_import,
245
+ )
246
+
247
+ from .imports.traversal import ( # noqa
248
+ import_all,
232
249
  yield_import_all,
233
250
  yield_importable,
234
251
  )
@@ -251,6 +268,10 @@ from .iterables import ( # noqa
251
268
  take,
252
269
  )
253
270
 
271
+ from .lazyglobals import ( # noqa
272
+ LazyGlobals,
273
+ )
274
+
254
275
  from .maysyncs import ( # noqa
255
276
  MaysyncP,
256
277
 
File without changes
@@ -0,0 +1,33 @@
1
+ import sys
2
+
3
+ from .resolution import resolve_import_name
4
+
5
+
6
+ ##
7
+
8
+
9
+ # dict[str, None] to preserve insertion order - we don't have OrderedSet here
10
+ _REGISTERED_CONDITIONAL_IMPORTS: dict[str, dict[str, None] | None] = {}
11
+
12
+
13
+ def register_conditional_import(when: str, then: str, package: str | None = None) -> None:
14
+ wn = resolve_import_name(when, package)
15
+ tn = resolve_import_name(then, package)
16
+ if tn in sys.modules:
17
+ return
18
+ if wn in sys.modules:
19
+ __import__(tn)
20
+ else:
21
+ tns = _REGISTERED_CONDITIONAL_IMPORTS.setdefault(wn, {})
22
+ if tns is None:
23
+ raise Exception(f'Conditional import trigger already cleared: {wn=} {tn=}')
24
+ tns[tn] = None
25
+
26
+
27
+ def trigger_conditional_imports(package: str) -> None:
28
+ tns = _REGISTERED_CONDITIONAL_IMPORTS.get(package, {})
29
+ if tns is None:
30
+ raise Exception(f'Conditional import trigger already cleared: {package=}')
31
+ _REGISTERED_CONDITIONAL_IMPORTS[package] = None
32
+ for tn in tns:
33
+ __import__(tn)
@@ -0,0 +1,66 @@
1
+ import importlib.util
2
+ import types
3
+ import typing as ta
4
+
5
+
6
+ ##
7
+
8
+
9
+ def lazy_import(
10
+ name: str,
11
+ package: str | None = None,
12
+ *,
13
+ optional: bool = False,
14
+ cache_failure: bool = False,
15
+ ) -> ta.Callable[[], ta.Any]:
16
+ result = not_set = object()
17
+
18
+ def inner():
19
+ nonlocal result
20
+
21
+ if result is not not_set:
22
+ if isinstance(result, Exception):
23
+ raise result
24
+ return result
25
+
26
+ try:
27
+ mod = importlib.import_module(name, package=package)
28
+
29
+ except Exception as e:
30
+ if optional:
31
+ if cache_failure:
32
+ result = None
33
+ return None
34
+
35
+ if cache_failure:
36
+ result = e
37
+ raise
38
+
39
+ result = mod
40
+ return mod
41
+
42
+ return inner
43
+
44
+
45
+ def proxy_import(
46
+ name: str,
47
+ package: str | None = None,
48
+ extras: ta.Iterable[str] | None = None,
49
+ ) -> types.ModuleType:
50
+ if isinstance(extras, str):
51
+ raise TypeError(extras)
52
+
53
+ omod = None
54
+
55
+ def __getattr__(att): # noqa
56
+ nonlocal omod
57
+ if omod is None:
58
+ omod = importlib.import_module(name, package=package)
59
+ if extras:
60
+ for x in extras:
61
+ importlib.import_module(f'{name}.{x}', package=package)
62
+ return getattr(omod, att)
63
+
64
+ lmod = types.ModuleType(name)
65
+ lmod.__getattr__ = __getattr__ # type: ignore
66
+ return lmod
@@ -0,0 +1,441 @@
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 get(self, attr: str) -> ta.Any:
73
+ try:
74
+ imp = self._imps_by_attr[attr]
75
+ except KeyError:
76
+ raise AttributeError(attr) # noqa
77
+
78
+ val: ta.Any
79
+
80
+ if imp.attr is None:
81
+ val = importlib.import_module(imp.pkg, package=self._name_package.package)
82
+
83
+ else:
84
+ try:
85
+ mod = self._mods_by_pkgs[imp.pkg]
86
+ except KeyError:
87
+ mod = importlib.import_module(imp.pkg, package=self._name_package.package)
88
+
89
+ val = getattr(mod, imp.attr)
90
+
91
+ return val
92
+
93
+
94
+ def proxy_init(
95
+ init_globals: ta.MutableMapping[str, ta.Any],
96
+ package: str,
97
+ attrs: ta.Iterable[str | tuple[str, str]] | None = None,
98
+ ) -> None:
99
+ if isinstance(attrs, str):
100
+ raise TypeError(attrs)
101
+
102
+ init_name_package = NamePackage(
103
+ init_globals['__name__'],
104
+ init_globals['__package__'],
105
+ )
106
+
107
+ pi: _ProxyInit
108
+ try:
109
+ pi = init_globals['__proxy_init__']
110
+
111
+ except KeyError:
112
+ pi = _ProxyInit(
113
+ LazyGlobals.install(init_globals),
114
+ init_name_package,
115
+ )
116
+ init_globals['__proxy_init__'] = pi
117
+
118
+ else:
119
+ if pi.name_package != init_name_package:
120
+ raise Exception(f'Wrong init name: {pi.name_package=} != {init_name_package=}')
121
+
122
+ pi.add(package, attrs)
123
+
124
+
125
+ ##
126
+
127
+
128
+ class AutoProxyInitError(Exception):
129
+ pass
130
+
131
+
132
+ class AutoProxyInitErrors:
133
+ def __new__(cls, *args, **kwargs): # noqa
134
+ raise TypeError
135
+
136
+ class HookError(AutoProxyInitError):
137
+ pass
138
+
139
+ class AttrError(AutoProxyInitError):
140
+ def __init__(self, module: str | None, name: str) -> None:
141
+ super().__init__()
142
+
143
+ self.module = module
144
+ self.name = name
145
+
146
+ def __repr__(self) -> str:
147
+ return f'{self.__class__.__qualname__}(module={self.module!r}, name={self.name!r})'
148
+
149
+ class ImportError(AutoProxyInitError): # noqa
150
+ def __init__(self, module: str, from_list: ta.Sequence[str] | None) -> None:
151
+ super().__init__()
152
+
153
+ self.module = module
154
+ self.from_list = from_list
155
+
156
+ def __repr__(self) -> str:
157
+ return f'{self.__class__.__qualname__}(module={self.module!r}, from_list={self.from_list!r})'
158
+
159
+ class ImportStarForbiddenError(ImportError):
160
+ pass
161
+
162
+ class UnproxiedImportForbiddenError(ImportError):
163
+ pass
164
+
165
+
166
+ class _AutoProxyInitCapture:
167
+ class ModuleSpec(ta.NamedTuple):
168
+ name: str
169
+ level: int
170
+
171
+ def __str__(self) -> str:
172
+ return f'{"." * self.level}{self.name}'
173
+
174
+ def __repr__(self) -> str:
175
+ return repr(str(self))
176
+
177
+ class _ModuleAttr:
178
+ def __init__(self, module: '_AutoProxyInitCapture._Module', name: str) -> None:
179
+ super().__init__()
180
+
181
+ self.__module = module
182
+ self.__name = name
183
+
184
+ def __repr__(self) -> str:
185
+ return f'<{self.__class__.__name__}: {f"{self.__module.spec}:{self.__name}"!r}>'
186
+
187
+ class _Module:
188
+ def __init__(self, spec: '_AutoProxyInitCapture.ModuleSpec') -> None:
189
+ super().__init__()
190
+
191
+ self.spec = spec
192
+
193
+ self.module = types.ModuleType(f'<{self.__class__.__qualname__}: {spec!r}>')
194
+
195
+ self.attrs: dict[str, _AutoProxyInitCapture._ModuleAttr] = {}
196
+ self.imported_whole = False
197
+
198
+ def __repr__(self) -> str:
199
+ return f'{self.__class__.__name__}({self.spec!r})'
200
+
201
+ def __init__(self) -> None:
202
+ super().__init__()
203
+
204
+ self._modules: dict[_AutoProxyInitCapture.ModuleSpec, _AutoProxyInitCapture._Module] = {}
205
+ self._attrs: dict[str, _AutoProxyInitCapture._ModuleAttr | _AutoProxyInitCapture._Module] = {}
206
+
207
+ def _handle_import(
208
+ self,
209
+ module: _Module,
210
+ *,
211
+ from_list: ta.Sequence[str] | None,
212
+ ) -> None:
213
+ if from_list is None:
214
+ if module.spec.level or not module.spec.name:
215
+ raise AutoProxyInitError
216
+
217
+ attr = module.spec.name
218
+
219
+ try:
220
+ xma: ta.Any = self._attrs[attr]
221
+ except KeyError:
222
+ pass
223
+
224
+ else:
225
+ if (
226
+ xma is not self._attrs.get(attr) or
227
+ not module.imported_whole
228
+ ):
229
+ raise AutoProxyInitErrors.AttrError(str(module.spec), attr)
230
+
231
+ return
232
+
233
+ self._attrs[attr] = module
234
+ module.imported_whole = True
235
+
236
+ else:
237
+ for attr in from_list:
238
+ if attr == '*':
239
+ raise AutoProxyInitErrors.ImportStarForbiddenError(str(module.spec), from_list)
240
+
241
+ try:
242
+ xma = getattr(module.module, attr)
243
+ except AttributeError:
244
+ pass
245
+
246
+ else:
247
+ if (
248
+ xma is not module.attrs.get(attr) or
249
+ xma is not self._attrs.get(attr)
250
+ ):
251
+ raise AutoProxyInitErrors.AttrError(str(module.spec), attr)
252
+
253
+ continue
254
+
255
+ if attr in self._attrs:
256
+ raise AutoProxyInitErrors.AttrError(str(module.spec), attr)
257
+
258
+ ma = _AutoProxyInitCapture._ModuleAttr(module, attr)
259
+ self._attrs[attr] = ma
260
+ module.attrs[attr] = ma
261
+ setattr(module.module, attr, ma)
262
+
263
+ _MOD_SELF_ATTR: ta.ClassVar[str] = '__auto_proxy_init_capture__'
264
+
265
+ def _intercept_import(
266
+ self,
267
+ name: str,
268
+ *,
269
+ globals: ta.Mapping[str, ta.Any] | None = None, # noqa
270
+ from_list: ta.Sequence[str] | None = None,
271
+ level: int = 0,
272
+ ) -> types.ModuleType | None:
273
+ if not (
274
+ globals is not None and
275
+ globals.get(self._MOD_SELF_ATTR) is self
276
+ ):
277
+ return None
278
+
279
+ spec = _AutoProxyInitCapture.ModuleSpec(name, level)
280
+ try:
281
+ module = self._modules[spec]
282
+ except KeyError:
283
+ module = self._Module(spec)
284
+ self._modules[spec] = module
285
+
286
+ self._handle_import(
287
+ module,
288
+ from_list=from_list,
289
+ )
290
+
291
+ return module.module
292
+
293
+ @contextlib.contextmanager
294
+ def hook_context(
295
+ self,
296
+ init_globals: ta.MutableMapping[str, ta.Any], # noqa
297
+ *,
298
+ forbid_unproxied_imports: bool = False,
299
+ ) -> ta.Iterator[None]:
300
+ if self._MOD_SELF_ATTR in init_globals:
301
+ raise AutoProxyInitErrors.HookError
302
+
303
+ old_import = builtins.__import__
304
+
305
+ def new_import(
306
+ name,
307
+ globals=None, # noqa
308
+ locals=None, # noqa
309
+ fromlist=None,
310
+ level=0,
311
+ ):
312
+ if (im := self._intercept_import(
313
+ name,
314
+ globals=globals,
315
+ from_list=fromlist,
316
+ level=level,
317
+ )) is not None:
318
+ return im
319
+
320
+ if forbid_unproxied_imports:
321
+ raise AutoProxyInitErrors.UnproxiedImportForbiddenError(
322
+ str(_AutoProxyInitCapture.ModuleSpec(name, level)),
323
+ fromlist,
324
+ )
325
+
326
+ return old_import(
327
+ name,
328
+ globals=globals,
329
+ locals=locals,
330
+ fromlist=fromlist,
331
+ level=level,
332
+ )
333
+
334
+ #
335
+
336
+ init_globals[self._MOD_SELF_ATTR] = self
337
+ builtins.__import__ = new_import
338
+
339
+ try:
340
+ yield
341
+
342
+ finally:
343
+ if not (
344
+ init_globals[self._MOD_SELF_ATTR] is self and
345
+ builtins.__import__ is new_import
346
+ ):
347
+ raise AutoProxyInitErrors.HookError
348
+
349
+ del init_globals[self._MOD_SELF_ATTR]
350
+ builtins.__import__ = old_import
351
+
352
+ def verify_globals(
353
+ self,
354
+ init_globals: ta.MutableMapping[str, ta.Any], # noqa
355
+ ) -> None:
356
+ for attr, obj in self._attrs.items():
357
+ try:
358
+ xo = init_globals[attr]
359
+ except KeyError:
360
+ raise AutoProxyInitErrors.AttrError(None, attr) from None
361
+
362
+ if isinstance(obj, _AutoProxyInitCapture._ModuleAttr):
363
+ if xo is not obj:
364
+ raise AutoProxyInitErrors.AttrError(None, attr) from None
365
+
366
+ elif isinstance(obj, _AutoProxyInitCapture._Module):
367
+ if xo is not obj.module:
368
+ raise AutoProxyInitErrors.AttrError(None, attr) from None
369
+
370
+ else:
371
+ raise TypeError(obj)
372
+
373
+ @property
374
+ def all_attrs(self) -> ta.AbstractSet[str]:
375
+ return self._attrs.keys()
376
+
377
+ class ProxyInit(ta.NamedTuple):
378
+ package: str
379
+ attrs: ta.Sequence[str] | None
380
+
381
+ def build_proxy_inits(self) -> list[ProxyInit]:
382
+ lst: list[_AutoProxyInitCapture.ProxyInit] = []
383
+
384
+ for module in self._modules.values():
385
+ if module.imported_whole:
386
+ lst.append(_AutoProxyInitCapture.ProxyInit(str(module.spec), None))
387
+
388
+ if module.attrs:
389
+ if not module.spec.name:
390
+ for attr in module.attrs:
391
+ if not module.spec.level:
392
+ raise AutoProxyInitError
393
+
394
+ lst.append(_AutoProxyInitCapture.ProxyInit('.' * module.spec.level + attr, None))
395
+
396
+ else:
397
+ lst.append(_AutoProxyInitCapture.ProxyInit(str(module.spec), list(module.attrs)))
398
+
399
+ return lst
400
+
401
+
402
+ @contextlib.contextmanager
403
+ def auto_proxy_init(
404
+ init_globals: ta.MutableMapping[str, ta.Any],
405
+ *,
406
+ disable: bool = False,
407
+ eager: bool = False,
408
+ ) -> ta.Iterator[None]:
409
+ """
410
+ This is a bit extreme - use sparingly. It relies on an interpreter-global import lock, but much of the ecosystem
411
+ implicitly does anyway. It further relies on temporarily patching `__builtins__.__import__`, but could be switched
412
+ to use any number of other import hooks.
413
+ """
414
+
415
+ if disable:
416
+ yield
417
+ return
418
+
419
+ cap = _AutoProxyInitCapture()
420
+
421
+ with cap.hook_context(init_globals):
422
+ yield
423
+
424
+ cap.verify_globals(init_globals)
425
+
426
+ pis = cap.build_proxy_inits()
427
+
428
+ for attr in cap.all_attrs:
429
+ del init_globals[attr]
430
+
431
+ for pi in pis:
432
+ proxy_init(
433
+ init_globals,
434
+ pi.package,
435
+ pi.attrs,
436
+ )
437
+
438
+ if eager:
439
+ lg = LazyGlobals.install(init_globals)
440
+ for attr in cap.all_attrs:
441
+ 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")