omlish 0.0.0.dev431__py3-none-any.whl → 0.0.0.dev433__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.dev431'
2
- __revision__ = '2294dfec3fc11ecdc80e86c2f6912e3c650d5c9c'
1
+ __version__ = '0.0.0.dev433'
2
+ __revision__ = '8b1520428b44d4d75317513a61d17c5e6f0c7711'
3
3
 
4
4
 
5
5
  #
@@ -41,7 +41,7 @@ class Project(ProjectBase):
41
41
 
42
42
  'greenlet ~= 3.2',
43
43
 
44
- 'trio ~= 0.30',
44
+ 'trio ~= 0.31',
45
45
  'trio-asyncio ~= 0.15',
46
46
  ],
47
47
 
omlish/lang/__init__.py CHANGED
@@ -260,6 +260,12 @@ with _auto_proxy_init(
260
260
  nextgen,
261
261
  )
262
262
 
263
+ from .imports.capture import ( # noqa
264
+ ImportCaptureError,
265
+ ImportCaptureErrors,
266
+ ImportCapture,
267
+ )
268
+
263
269
  from .imports.conditional import ( # noqa
264
270
  register_conditional_import,
265
271
  trigger_conditional_imports,
@@ -267,15 +273,17 @@ with _auto_proxy_init(
267
273
 
268
274
  from .imports.lazy import ( # noqa
269
275
  lazy_import,
276
+ )
277
+
278
+ from .imports.proxy import ( # noqa
270
279
  proxy_import,
280
+
281
+ auto_proxy_import,
271
282
  )
272
283
 
273
284
  from .imports.proxyinit import ( # noqa
274
285
  proxy_init,
275
286
 
276
- AutoProxyInitError,
277
- AutoProxyInitErrors,
278
- AutoProxyInit,
279
287
  auto_proxy_init,
280
288
  )
281
289
 
@@ -0,0 +1,491 @@
1
+ import builtins
2
+ import contextlib
3
+ import functools
4
+ import types
5
+ import typing as ta
6
+
7
+
8
+ ##
9
+
10
+
11
+ class ImportCaptureError(Exception):
12
+ pass
13
+
14
+
15
+ class ImportCaptureErrors:
16
+ def __new__(cls, *args, **kwargs): # noqa
17
+ raise TypeError
18
+
19
+ class HookError(ImportCaptureError):
20
+ pass
21
+
22
+ class AttrError(ImportCaptureError):
23
+ def __init__(self, module: str | None, name: str) -> None:
24
+ super().__init__()
25
+
26
+ self.module = module
27
+ self.name = name
28
+
29
+ def __repr__(self) -> str:
30
+ return f'{self.__class__.__qualname__}(module={self.module!r}, name={self.name!r})'
31
+
32
+ class ImportError(ImportCaptureError): # noqa
33
+ def __init__(self, module: str, from_list: ta.Sequence[str] | None) -> None:
34
+ super().__init__()
35
+
36
+ self.module = module
37
+ self.from_list = from_list
38
+
39
+ def __repr__(self) -> str:
40
+ return f'{self.__class__.__qualname__}(module={self.module!r}, from_list={self.from_list!r})'
41
+
42
+ class ImportStarForbiddenError(ImportError):
43
+ pass
44
+
45
+ class UncapturedImportForbiddenError(ImportError):
46
+ pass
47
+
48
+ class UnreferencedImportsError(ImportCaptureError):
49
+ def __init__(self, unreferenced: ta.Mapping[str, ta.Sequence[str | None]]) -> None:
50
+ super().__init__()
51
+
52
+ self.unreferenced = unreferenced
53
+
54
+ def __repr__(self) -> str:
55
+ return f'{self.__class__.__qualname__}(unreferenced={self.unreferenced!r})'
56
+
57
+ class CaptureInProgressError(ImportCaptureError):
58
+ pass
59
+
60
+
61
+ class _ImportCaptureImpl:
62
+ class ModuleSpec(ta.NamedTuple):
63
+ name: str
64
+ level: int
65
+
66
+ def __str__(self) -> str:
67
+ return f'{"." * self.level}{self.name}'
68
+
69
+ def __repr__(self) -> str:
70
+ return repr(str(self))
71
+
72
+ def __init__(self) -> None:
73
+ super().__init__()
74
+
75
+ self._modules_by_spec: dict[_ImportCaptureImpl.ModuleSpec, _ImportCaptureImpl._Module] = {}
76
+ self._modules_by_module_obj: dict[types.ModuleType, _ImportCaptureImpl._Module] = {}
77
+
78
+ self._attrs: dict[_ImportCaptureImpl._ModuleAttr, tuple[_ImportCaptureImpl._Module, str]] = {}
79
+
80
+ #
81
+
82
+ class _ModuleAttr:
83
+ def __init__(
84
+ self,
85
+ module: '_ImportCaptureImpl._Module',
86
+ name: str,
87
+ ) -> None:
88
+ super().__init__()
89
+
90
+ self.__module = module
91
+ self.__name = name
92
+
93
+ def __repr__(self) -> str:
94
+ return f'<{self.__class__.__name__}: {f"{self.__module.spec}:{self.__name}"!r}>'
95
+
96
+ class _Module:
97
+ def __init__(
98
+ self,
99
+ spec: '_ImportCaptureImpl.ModuleSpec',
100
+ *,
101
+ getattr_handler: ta.Callable[['_ImportCaptureImpl._Module', str], ta.Any] | None = None,
102
+ ) -> None:
103
+ super().__init__()
104
+
105
+ self.spec = spec
106
+
107
+ self.module_obj = types.ModuleType(f'<{self.__class__.__qualname__}: {spec!r}>')
108
+ if getattr_handler is not None:
109
+ self.module_obj.__getattr__ = functools.partial(getattr_handler, self) # type: ignore[method-assign] # noqa
110
+ self.initial_module_dict = dict(self.module_obj.__dict__)
111
+
112
+ self.contents: dict[str, _ImportCaptureImpl._ModuleAttr | types.ModuleType] = {}
113
+ self.imported_whole = False
114
+
115
+ def __repr__(self) -> str:
116
+ return f'{self.__class__.__name__}({self.spec!r})'
117
+
118
+ def _get_or_make_module(self, spec: ModuleSpec) -> _Module:
119
+ try:
120
+ return self._modules_by_spec[spec]
121
+ except KeyError:
122
+ pass
123
+
124
+ module = self._Module(
125
+ spec,
126
+ getattr_handler=self._handle_module_getattr,
127
+ )
128
+ self._modules_by_spec[spec] = module
129
+ self._modules_by_module_obj[module.module_obj] = module
130
+ return module
131
+
132
+ def _handle_module_getattr(self, module: _Module, attr: str) -> ta.Any:
133
+ if attr in module.contents:
134
+ raise ImportCaptureErrors.AttrError(str(module.spec), attr)
135
+
136
+ v: _ImportCaptureImpl._ModuleAttr | types.ModuleType
137
+ if not module.spec.name:
138
+ if not module.spec.level:
139
+ raise ImportCaptureError
140
+ cs = _ImportCaptureImpl.ModuleSpec(attr, module.spec.level)
141
+ cm = self._get_or_make_module(cs)
142
+ cm.imported_whole = True
143
+ v = cm.module_obj
144
+
145
+ else:
146
+ ma = _ImportCaptureImpl._ModuleAttr(module, attr)
147
+ self._attrs[ma] = (module, attr)
148
+ v = ma
149
+
150
+ module.contents[attr] = v
151
+ setattr(module.module_obj, attr, v)
152
+ return v
153
+
154
+ def _handle_import(
155
+ self,
156
+ module: _Module,
157
+ *,
158
+ from_list: ta.Sequence[str] | None,
159
+ ) -> None:
160
+ if from_list is None:
161
+ if module.spec.level or not module.spec.name:
162
+ raise ImportCaptureError
163
+
164
+ module.imported_whole = True
165
+
166
+ else:
167
+ for attr in from_list:
168
+ if attr == '*':
169
+ raise ImportCaptureErrors.ImportStarForbiddenError(str(module.spec), from_list)
170
+
171
+ x = getattr(module.module_obj, attr)
172
+
173
+ bad = False
174
+ if x is not module.contents.get(attr):
175
+ bad = True
176
+ if isinstance(x, _ImportCaptureImpl._ModuleAttr):
177
+ if self._attrs[x] != (module, attr):
178
+ bad = True
179
+ elif isinstance(x, types.ModuleType):
180
+ if x not in self._modules_by_module_obj:
181
+ bad = True
182
+ else:
183
+ bad = True
184
+ if bad:
185
+ raise ImportCaptureErrors.AttrError(str(module.spec), attr)
186
+
187
+ #
188
+
189
+ _MOD_SELF_ATTR: ta.ClassVar[str] = '__import_capture__'
190
+
191
+ def _intercept_import(
192
+ self,
193
+ name: str,
194
+ *,
195
+ globals: ta.Mapping[str, ta.Any] | None = None, # noqa
196
+ from_list: ta.Sequence[str] | None = None,
197
+ level: int = 0,
198
+ ) -> types.ModuleType | None:
199
+ if not (
200
+ globals is not None and
201
+ globals.get(self._MOD_SELF_ATTR) is self
202
+ ):
203
+ return None
204
+
205
+ spec = _ImportCaptureImpl.ModuleSpec(name, level)
206
+ module = self._get_or_make_module(spec)
207
+
208
+ self._handle_import(
209
+ module,
210
+ from_list=from_list,
211
+ )
212
+
213
+ return module.module_obj
214
+
215
+ @contextlib.contextmanager
216
+ def hook_context(
217
+ self,
218
+ mod_globals: ta.MutableMapping[str, ta.Any], # noqa
219
+ *,
220
+ forbid_uncaptured_imports: bool = False,
221
+ ) -> ta.Iterator[None]:
222
+ if self._MOD_SELF_ATTR in mod_globals:
223
+ raise ImportCaptureErrors.HookError
224
+
225
+ old_import = builtins.__import__
226
+
227
+ def new_import(
228
+ name,
229
+ globals=None, # noqa
230
+ locals=None, # noqa
231
+ fromlist=None,
232
+ level=0,
233
+ ):
234
+ if (im := self._intercept_import(
235
+ name,
236
+ globals=globals,
237
+ from_list=fromlist,
238
+ level=level,
239
+ )) is not None:
240
+ return im
241
+
242
+ if forbid_uncaptured_imports:
243
+ raise ImportCaptureErrors.UncapturedImportForbiddenError(
244
+ str(_ImportCaptureImpl.ModuleSpec(name, level)),
245
+ fromlist,
246
+ )
247
+
248
+ return old_import(
249
+ name,
250
+ globals=globals,
251
+ locals=locals,
252
+ fromlist=fromlist,
253
+ level=level,
254
+ )
255
+
256
+ #
257
+
258
+ mod_globals[self._MOD_SELF_ATTR] = self
259
+ builtins.__import__ = new_import
260
+
261
+ try:
262
+ yield
263
+
264
+ finally:
265
+ if not (
266
+ mod_globals[self._MOD_SELF_ATTR] is self and
267
+ builtins.__import__ is new_import
268
+ ):
269
+ raise ImportCaptureErrors.HookError
270
+
271
+ del mod_globals[self._MOD_SELF_ATTR]
272
+ builtins.__import__ = old_import
273
+
274
+ #
275
+
276
+ def verify_state(
277
+ self,
278
+ mod_globals: ta.MutableMapping[str, ta.Any], # noqa
279
+ ) -> None:
280
+ for m in self._modules_by_spec.values():
281
+ for a, o in m.module_obj.__dict__.items():
282
+ try:
283
+ i = m.initial_module_dict[a]
284
+
285
+ except KeyError:
286
+ if o is not m.contents[a]:
287
+ raise ImportCaptureErrors.AttrError(str(m.spec), a) from None
288
+
289
+ else:
290
+ if o != i:
291
+ raise ImportCaptureErrors.AttrError(str(m.spec), a)
292
+
293
+ #
294
+
295
+ def build_captured(
296
+ self,
297
+ mod_globals: ta.MutableMapping[str, ta.Any], # noqa
298
+ *,
299
+ collect_unreferenced: bool = False,
300
+ ) -> 'ImportCapture.Captured':
301
+ dct: dict[_ImportCaptureImpl._Module, list[tuple[str | None, str]]] = {}
302
+
303
+ rem_whole_mods: set[_ImportCaptureImpl._Module] = set()
304
+ rem_mod_attrs: set[_ImportCaptureImpl._ModuleAttr] = set()
305
+ if collect_unreferenced:
306
+ rem_whole_mods.update([m for m in self._modules_by_spec.values() if m.imported_whole])
307
+ rem_mod_attrs.update(self._attrs)
308
+
309
+ for attr, obj in mod_globals.items():
310
+ if isinstance(obj, _ImportCaptureImpl._ModuleAttr):
311
+ try:
312
+ m, a = self._attrs[obj]
313
+ except KeyError:
314
+ raise ImportCaptureErrors.AttrError(None, attr) from None
315
+ dct.setdefault(m, []).append((a, attr))
316
+ rem_mod_attrs.discard(obj)
317
+
318
+ elif isinstance(obj, _ImportCaptureImpl._Module):
319
+ raise ImportCaptureErrors.AttrError(None, attr) from None
320
+
321
+ elif isinstance(obj, types.ModuleType):
322
+ try:
323
+ m = self._modules_by_module_obj[obj]
324
+ except KeyError:
325
+ continue
326
+ if not m.imported_whole:
327
+ raise RuntimeError(f'ImportCapture module {m.spec!r} not imported_whole')
328
+ dct.setdefault(m, []).append((None, attr))
329
+ rem_whole_mods.discard(m)
330
+
331
+ lst: list[ImportCapture.Import] = []
332
+ for m, ts in dct.items():
333
+ if not m.spec.name:
334
+ if not m.spec.level:
335
+ raise ImportCaptureError
336
+ for imp_attr, as_attr in ts:
337
+ if not imp_attr:
338
+ raise RuntimeError
339
+ lst.append(ImportCapture.Import(
340
+ '.' * m.spec.level + imp_attr,
341
+ [(None, as_attr)],
342
+ ))
343
+
344
+ else:
345
+ lst.append(ImportCapture.Import(
346
+ str(m.spec),
347
+ ts,
348
+ ))
349
+
350
+ unreferenced: dict[str, list[str | None]] | None = None
351
+ if collect_unreferenced and (rem_whole_mods or rem_mod_attrs):
352
+ unreferenced = {}
353
+ for m in rem_whole_mods:
354
+ unreferenced.setdefault(str(m.spec), []).append(None)
355
+ for ma in rem_mod_attrs:
356
+ m, a = self._attrs[ma]
357
+ unreferenced.setdefault(str(m.spec), []).append(a)
358
+
359
+ return ImportCapture.Captured(
360
+ lst,
361
+ unreferenced,
362
+ )
363
+
364
+
365
+ class ImportCapture:
366
+ """
367
+ This is a bit extreme, but worth it. For simplicity, it currently relies on temporarily patching
368
+ `__builtins__.__import__` for the duration of its context manager, but it can be switched to use any number of other
369
+ import hooks (like `sys.meta_path`). It does not rely on any permanent modification to import machinery, only for
370
+ the duration of its capture.
371
+ """
372
+
373
+ class Import(ta.NamedTuple):
374
+ spec: str
375
+ attrs: ta.Sequence[tuple[str | None, str]]
376
+
377
+ class Captured(ta.NamedTuple):
378
+ imports: ta.Sequence['ImportCapture.Import']
379
+ unreferenced: ta.Mapping[str, ta.Sequence[str | None]] | None
380
+
381
+ @property
382
+ def attrs(self) -> ta.Iterator[str]:
383
+ for pi in self.imports:
384
+ for _, a in pi.attrs:
385
+ yield a
386
+
387
+ #
388
+
389
+ def __init__(
390
+ self,
391
+ mod_globals: ta.MutableMapping[str, ta.Any],
392
+ *,
393
+ disable: bool = False,
394
+ ) -> None:
395
+ super().__init__()
396
+
397
+ self._mod_globals = mod_globals
398
+
399
+ self._disabled = disable
400
+
401
+ @property
402
+ def disabled(self) -> bool:
403
+ return self._disabled
404
+
405
+ #
406
+
407
+ class _Result(ta.NamedTuple):
408
+ captured: 'ImportCapture.Captured'
409
+
410
+ _result_: _Result | None = None
411
+
412
+ @property
413
+ def _result(self) -> _Result:
414
+ if (rs := self._result_) is None:
415
+ raise ImportCaptureErrors.CaptureInProgressError
416
+ return rs
417
+
418
+ @property
419
+ def is_complete(self) -> bool:
420
+ return self._result_ is not None
421
+
422
+ @property
423
+ def captured(self) -> Captured:
424
+ return self._result.captured
425
+
426
+ #
427
+
428
+ @contextlib.contextmanager
429
+ def capture(
430
+ self,
431
+ *,
432
+ unreferenced_callback: ta.Callable[[ta.Mapping[str, ta.Sequence[str | None]]], None] | None = None,
433
+ raise_unreferenced: bool = False,
434
+ ) -> ta.Iterator[ta.Self]:
435
+ if self._result_ is not None:
436
+ raise ImportCaptureError('capture already complete')
437
+
438
+ if self._disabled:
439
+ self._result_ = ImportCapture._Result(
440
+ ImportCapture.Captured(
441
+ [],
442
+ None,
443
+ ),
444
+ )
445
+ yield self
446
+ return
447
+
448
+ cap = _ImportCaptureImpl()
449
+
450
+ with cap.hook_context(self._mod_globals):
451
+ yield self
452
+
453
+ cap.verify_state(self._mod_globals)
454
+
455
+ blt = cap.build_captured(
456
+ self._mod_globals,
457
+ collect_unreferenced=unreferenced_callback is not None or raise_unreferenced,
458
+ )
459
+
460
+ if blt.unreferenced:
461
+ if unreferenced_callback:
462
+ unreferenced_callback(blt.unreferenced)
463
+ if raise_unreferenced:
464
+ raise ImportCaptureErrors.UnreferencedImportsError(blt.unreferenced)
465
+
466
+ for pi in blt.imports:
467
+ for _, a in pi.attrs:
468
+ del self._mod_globals[a]
469
+
470
+ self._result_ = ImportCapture._Result(
471
+ blt,
472
+ )
473
+
474
+ #
475
+
476
+ def update_exports(self) -> None:
477
+ cap = self._result.captured
478
+
479
+ try:
480
+ al: ta.Any = self._mod_globals['__all__']
481
+ except KeyError:
482
+ al = self._mod_globals['__all__'] = [k for k in self._mod_globals if not k.startswith('_')]
483
+ else:
484
+ if not isinstance(al, ta.MutableSequence):
485
+ al = self._mod_globals['__all__'] = list(al)
486
+
487
+ al_s = set(al)
488
+ for a in cap.attrs:
489
+ if a not in al_s:
490
+ al.append(a)
491
+ al_s.add(a)
@@ -1,5 +1,4 @@
1
1
  import importlib.util
2
- import types
3
2
  import typing as ta
4
3
 
5
4
 
@@ -40,27 +39,3 @@ def lazy_import(
40
39
  return mod
41
40
 
42
41
  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[method-assign]
66
- return lmod
@@ -0,0 +1,62 @@
1
+ import contextlib
2
+ import importlib.util
3
+ import types
4
+ import typing as ta
5
+
6
+ from .capture import ImportCapture
7
+
8
+
9
+ ##
10
+
11
+
12
+ def proxy_import(
13
+ spec: str,
14
+ package: str | None = None,
15
+ extras: ta.Iterable[str] | None = None,
16
+ ) -> types.ModuleType:
17
+ if isinstance(extras, str):
18
+ raise TypeError(extras)
19
+
20
+ omod = None
21
+
22
+ def __getattr__(att): # noqa
23
+ nonlocal omod
24
+ if omod is None:
25
+ omod = importlib.import_module(spec, package=package)
26
+ if extras:
27
+ for x in extras:
28
+ importlib.import_module(f'{spec}.{x}', package=package)
29
+ return getattr(omod, att)
30
+
31
+ lmod = types.ModuleType(spec)
32
+ lmod.__getattr__ = __getattr__ # type: ignore[method-assign]
33
+ return lmod
34
+
35
+
36
+ ##
37
+
38
+
39
+ @contextlib.contextmanager
40
+ def auto_proxy_import(
41
+ mod_globals: ta.MutableMapping[str, ta.Any],
42
+ *,
43
+ disable: bool = False,
44
+
45
+ unreferenced_callback: ta.Callable[[ta.Mapping[str, ta.Sequence[str | None]]], None] | None = None,
46
+ raise_unreferenced: bool = False,
47
+ ) -> ta.Iterator[ImportCapture]:
48
+ inst = ImportCapture(
49
+ mod_globals,
50
+ disable=disable,
51
+ )
52
+
53
+ with inst.capture(
54
+ unreferenced_callback=unreferenced_callback,
55
+ raise_unreferenced=raise_unreferenced,
56
+ ):
57
+ yield inst
58
+
59
+ pkg = mod_globals.get('__package__')
60
+ for pi in inst.captured.imports:
61
+ for sa, ma in pi.attrs:
62
+ mod_globals[ma] = proxy_import(pi.spec + (('.' + sa) if sa is not None else ''), pkg)