omlish 0.0.0.dev443__py3-none-any.whl → 0.0.0.dev445__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.

Potentially problematic release.


This version of omlish might be problematic. Click here for more details.

@@ -1,6 +1,20 @@
1
+ """
2
+ Insufficient alt impls:
3
+ - `sys.meta_path`:
4
+ - need access to the `fromlist` and `level` arguments passed to `__import__`
5
+ - need to return fake modules to the import operation which are not added to `sys.modules`
6
+ - `sys.addaudithook`: cannot prevent import or inject result
7
+ - `sys.settrace` / bytecode tracing: same
8
+ - jit bytecode rewriting: slower than just importing everything
9
+
10
+ Possible alt impls:
11
+ - aot static analysis, codegen, compare, if valid skip ctxmgr body and inject proxies, otherwise warn and run
12
+ """
1
13
  import builtins
2
14
  import contextlib
3
15
  import functools
16
+ import sys
17
+ import threading
4
18
  import types
5
19
  import typing as ta
6
20
 
@@ -58,7 +72,10 @@ class ImportCaptureErrors:
58
72
  pass
59
73
 
60
74
 
61
- class _ImportCaptureImpl:
75
+ ##
76
+
77
+
78
+ class _ImportCaptureHook:
62
79
  class ModuleSpec(ta.NamedTuple):
63
80
  name: str
64
81
  level: int
@@ -69,20 +86,26 @@ class _ImportCaptureImpl:
69
86
  def __repr__(self) -> str:
70
87
  return repr(str(self))
71
88
 
72
- def __init__(self) -> None:
89
+ def __init__(
90
+ self,
91
+ *,
92
+ forbid_uncaptured_imports: bool = False,
93
+ ) -> None:
73
94
  super().__init__()
74
95
 
75
- self._modules_by_spec: dict[_ImportCaptureImpl.ModuleSpec, _ImportCaptureImpl._Module] = {}
76
- self._modules_by_module_obj: dict[types.ModuleType, _ImportCaptureImpl._Module] = {}
96
+ self._forbid_uncaptured_imports = forbid_uncaptured_imports
97
+
98
+ self._modules_by_spec: dict[_ImportCaptureHook.ModuleSpec, _ImportCaptureHook._Module] = {}
99
+ self._modules_by_module_obj: dict[types.ModuleType, _ImportCaptureHook._Module] = {}
77
100
 
78
- self._attrs: dict[_ImportCaptureImpl._ModuleAttr, tuple[_ImportCaptureImpl._Module, str]] = {}
101
+ self._attrs: dict[_ImportCaptureHook._ModuleAttr, tuple[_ImportCaptureHook._Module, str]] = {}
79
102
 
80
103
  #
81
104
 
82
105
  class _ModuleAttr:
83
106
  def __init__(
84
107
  self,
85
- module: '_ImportCaptureImpl._Module',
108
+ module: '_ImportCaptureHook._Module',
86
109
  name: str,
87
110
  ) -> None:
88
111
  super().__init__()
@@ -96,9 +119,9 @@ class _ImportCaptureImpl:
96
119
  class _Module:
97
120
  def __init__(
98
121
  self,
99
- spec: '_ImportCaptureImpl.ModuleSpec',
122
+ spec: '_ImportCaptureHook.ModuleSpec',
100
123
  *,
101
- getattr_handler: ta.Callable[['_ImportCaptureImpl._Module', str], ta.Any] | None = None,
124
+ getattr_handler: ta.Callable[['_ImportCaptureHook._Module', str], ta.Any] | None = None,
102
125
  ) -> None:
103
126
  super().__init__()
104
127
 
@@ -109,7 +132,7 @@ class _ImportCaptureImpl:
109
132
  self.module_obj.__getattr__ = functools.partial(getattr_handler, self) # type: ignore[method-assign] # noqa
110
133
  self.initial_module_dict = dict(self.module_obj.__dict__)
111
134
 
112
- self.contents: dict[str, _ImportCaptureImpl._ModuleAttr | types.ModuleType] = {}
135
+ self.contents: dict[str, _ImportCaptureHook._ModuleAttr | types.ModuleType] = {}
113
136
  self.imported_whole = False
114
137
 
115
138
  def __repr__(self) -> str:
@@ -133,17 +156,17 @@ class _ImportCaptureImpl:
133
156
  if attr in module.contents:
134
157
  raise ImportCaptureErrors.AttrError(str(module.spec), attr)
135
158
 
136
- v: _ImportCaptureImpl._ModuleAttr | types.ModuleType
159
+ v: _ImportCaptureHook._ModuleAttr | types.ModuleType
137
160
  if not module.spec.name:
138
161
  if not module.spec.level:
139
162
  raise ImportCaptureError
140
- cs = _ImportCaptureImpl.ModuleSpec(attr, module.spec.level)
163
+ cs = _ImportCaptureHook.ModuleSpec(attr, module.spec.level)
141
164
  cm = self._get_or_make_module(cs)
142
165
  cm.imported_whole = True
143
166
  v = cm.module_obj
144
167
 
145
168
  else:
146
- ma = _ImportCaptureImpl._ModuleAttr(module, attr)
169
+ ma = _ImportCaptureHook._ModuleAttr(module, attr)
147
170
  self._attrs[ma] = (module, attr)
148
171
  v = ma
149
172
 
@@ -173,7 +196,7 @@ class _ImportCaptureImpl:
173
196
  bad = False
174
197
  if x is not module.contents.get(attr):
175
198
  bad = True
176
- if isinstance(x, _ImportCaptureImpl._ModuleAttr):
199
+ if isinstance(x, _ImportCaptureHook._ModuleAttr):
177
200
  if self._attrs[x] != (module, attr):
178
201
  bad = True
179
202
  elif isinstance(x, types.ModuleType):
@@ -202,7 +225,7 @@ class _ImportCaptureImpl:
202
225
  ):
203
226
  return None
204
227
 
205
- spec = _ImportCaptureImpl.ModuleSpec(name, level)
228
+ spec = _ImportCaptureHook.ModuleSpec(name, level)
206
229
  module = self._get_or_make_module(spec)
207
230
 
208
231
  self._handle_import(
@@ -212,64 +235,33 @@ class _ImportCaptureImpl:
212
235
 
213
236
  return module.module_obj
214
237
 
238
+ @ta.final
215
239
  @contextlib.contextmanager
216
240
  def hook_context(
217
241
  self,
218
242
  mod_globals: ta.MutableMapping[str, ta.Any], # noqa
219
- *,
220
- forbid_uncaptured_imports: bool = False,
221
243
  ) -> ta.Iterator[None]:
222
244
  if self._MOD_SELF_ATTR in mod_globals:
223
245
  raise ImportCaptureErrors.HookError
224
246
 
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
247
  mod_globals[self._MOD_SELF_ATTR] = self
259
- builtins.__import__ = new_import
260
248
 
261
249
  try:
262
- yield
250
+ with self._hook_context(mod_globals):
251
+ yield
263
252
 
264
253
  finally:
265
- if not (
266
- mod_globals[self._MOD_SELF_ATTR] is self and
267
- builtins.__import__ is new_import
268
- ):
254
+ if mod_globals[self._MOD_SELF_ATTR] is not self:
269
255
  raise ImportCaptureErrors.HookError
270
256
 
271
257
  del mod_globals[self._MOD_SELF_ATTR]
272
- builtins.__import__ = old_import
258
+
259
+ # @abc.abstractmethod
260
+ def _hook_context(
261
+ self,
262
+ mod_globals: ta.MutableMapping[str, ta.Any], # noqa
263
+ ) -> ta.ContextManager[None]:
264
+ raise NotImplementedError
273
265
 
274
266
  #
275
267
 
@@ -298,16 +290,16 @@ class _ImportCaptureImpl:
298
290
  *,
299
291
  collect_unreferenced: bool = False,
300
292
  ) -> 'ImportCapture.Captured':
301
- dct: dict[_ImportCaptureImpl._Module, list[tuple[str | None, str]]] = {}
293
+ dct: dict[_ImportCaptureHook._Module, list[tuple[str | None, str]]] = {}
302
294
 
303
- rem_whole_mods: set[_ImportCaptureImpl._Module] = set()
304
- rem_mod_attrs: set[_ImportCaptureImpl._ModuleAttr] = set()
295
+ rem_whole_mods: set[_ImportCaptureHook._Module] = set()
296
+ rem_mod_attrs: set[_ImportCaptureHook._ModuleAttr] = set()
305
297
  if collect_unreferenced:
306
298
  rem_whole_mods.update([m for m in self._modules_by_spec.values() if m.imported_whole])
307
299
  rem_mod_attrs.update(self._attrs)
308
300
 
309
301
  for attr, obj in mod_globals.items():
310
- if isinstance(obj, _ImportCaptureImpl._ModuleAttr):
302
+ if isinstance(obj, _ImportCaptureHook._ModuleAttr):
311
303
  try:
312
304
  m, a = self._attrs[obj]
313
305
  except KeyError:
@@ -315,7 +307,7 @@ class _ImportCaptureImpl:
315
307
  dct.setdefault(m, []).append((a, attr))
316
308
  rem_mod_attrs.discard(obj)
317
309
 
318
- elif isinstance(obj, _ImportCaptureImpl._Module):
310
+ elif isinstance(obj, _ImportCaptureHook._Module):
319
311
  raise ImportCaptureErrors.AttrError(None, attr) from None
320
312
 
321
313
  elif isinstance(obj, types.ModuleType):
@@ -362,14 +354,236 @@ class _ImportCaptureImpl:
362
354
  )
363
355
 
364
356
 
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
- """
357
+ #
358
+
359
+
360
+ class _AbstractBuiltinsImportCaptureHook(_ImportCaptureHook):
361
+ def _new_import(
362
+ self,
363
+ old_import,
364
+ name,
365
+ globals=None, # noqa
366
+ locals=None, # noqa
367
+ fromlist=None,
368
+ level=0,
369
+ ):
370
+ if (im := self._intercept_import(
371
+ name,
372
+ globals=globals,
373
+ from_list=fromlist,
374
+ level=level,
375
+ )) is not None:
376
+ return im
377
+
378
+ if self._forbid_uncaptured_imports:
379
+ raise ImportCaptureErrors.UncapturedImportForbiddenError(
380
+ str(_ImportCaptureHook.ModuleSpec(name, level)),
381
+ fromlist,
382
+ )
383
+
384
+ return old_import(
385
+ name,
386
+ globals=globals,
387
+ locals=locals,
388
+ fromlist=fromlist,
389
+ level=level,
390
+ )
391
+
392
+
393
+ class _UnsafeGlobalBuiltinsImportCaptureHook(_AbstractBuiltinsImportCaptureHook):
394
+ @contextlib.contextmanager
395
+ def _hook_context(
396
+ self,
397
+ mod_globals: ta.MutableMapping[str, ta.Any], # noqa
398
+ ) -> ta.Iterator[None]:
399
+ old_import = builtins.__import__
400
+ new_import = functools.partial(self._new_import, old_import)
401
+
402
+ builtins.__import__ = new_import
403
+
404
+ try:
405
+ yield
406
+
407
+ finally:
408
+ if builtins.__import__ is not new_import:
409
+ raise ImportCaptureErrors.HookError
410
+
411
+ builtins.__import__ = old_import
412
+
413
+
414
+ class _SomewhatThreadSafeGlobalBuiltinsImportCaptureHook(_AbstractBuiltinsImportCaptureHook):
415
+ class _AlreadyPatchedError(Exception):
416
+ pass
417
+
418
+ @ta.final
419
+ class _Patch:
420
+ __lock: ta.ClassVar[threading.Lock] = threading.Lock()
421
+
422
+ def __init__(self, old_import):
423
+ self.__old_import = old_import
424
+ self.__hooks = {}
425
+ self.__uninstalled = False
426
+
427
+ @classmethod
428
+ def _add_hook(cls, mod_globals, new_import) -> '_SomewhatThreadSafeGlobalBuiltinsImportCaptureHook._Patch':
429
+ gi = id(mod_globals)
430
+ for _ in range(1_000):
431
+ try:
432
+ with cls.__lock:
433
+ x: ta.Any = builtins.__import__
434
+ p: _SomewhatThreadSafeGlobalBuiltinsImportCaptureHook._Patch
435
+ if x.__class__ is cls:
436
+ p = x
437
+ if p.__uninstalled: # noqa
438
+ raise _SomewhatThreadSafeGlobalBuiltinsImportCaptureHook._AlreadyPatchedError # noqa
439
+ else:
440
+ p = cls(x)
441
+ builtins.__import__ = p
442
+ p.__hooks[gi] = (mod_globals, new_import)
443
+ return p
444
+ except _SomewhatThreadSafeGlobalBuiltinsImportCaptureHook._AlreadyPatchedError:
445
+ pass
446
+ raise ImportCaptureErrors.HookError('Failed to install builtins hook')
447
+
448
+ def _remove_hook(self, mod_globals, *, no_raise=False):
449
+ gi = id(mod_globals)
450
+ with self.__lock:
451
+ tg, _ = self.__hooks[gi]
452
+ del self.__hooks[gi]
453
+ if not self.__uninstalled and not self.__hooks:
454
+ self.__uninstalled = True
455
+ if builtins.__import__ is not self:
456
+ if not no_raise:
457
+ # TODO: warn?
458
+ raise ImportCaptureErrors.HookError('Unexpected builtins hook')
459
+ else:
460
+ builtins.__import__ = self.__old_import
461
+ if tg is not mod_globals:
462
+ if not no_raise:
463
+ # TODO: warn?
464
+ raise ImportCaptureErrors.HookError('Mismatched globals')
465
+
466
+ def __call__(
467
+ self,
468
+ name,
469
+ globals=None, # noqa
470
+ locals=None, # noqa
471
+ fromlist=None,
472
+ level=0,
473
+ ):
474
+ if globals is not None and (tup := self.__hooks.get(id(globals))) is not None:
475
+ tg, tf = tup
476
+ if tg is globals:
477
+ return tf(
478
+ self.__old_import,
479
+ name,
480
+ globals=globals,
481
+ locals=locals,
482
+ fromlist=fromlist,
483
+ level=level,
484
+ )
485
+ else:
486
+ self._remove_hook(tg, no_raise=True)
487
+
488
+ return self.__old_import(
489
+ name,
490
+ globals=globals,
491
+ locals=locals,
492
+ fromlist=fromlist,
493
+ level=level,
494
+ )
495
+
496
+ @contextlib.contextmanager
497
+ def _hook_context(
498
+ self,
499
+ mod_globals: ta.MutableMapping[str, ta.Any], # noqa
500
+ ) -> ta.Iterator[None]:
501
+ patch = _SomewhatThreadSafeGlobalBuiltinsImportCaptureHook._Patch._add_hook(mod_globals, self._new_import) # noqa
502
+
503
+ try:
504
+ yield
505
+
506
+ finally:
507
+ patch._remove_hook(mod_globals) # noqa
372
508
 
509
+
510
+ #
511
+
512
+
513
+ _capture: ta.Any = None
514
+ try:
515
+ from . import _capture # type: ignore
516
+ except ImportError:
517
+ pass
518
+
519
+
520
+ class _FrameBuiltinsImportCaptureHook(_AbstractBuiltinsImportCaptureHook):
521
+ def __init__(
522
+ self,
523
+ *,
524
+ _frame: types.FrameType,
525
+ **kwargs: ta.Any,
526
+ ) -> None:
527
+ super().__init__(**kwargs)
528
+
529
+ self._frame = _frame
530
+
531
+ @classmethod
532
+ def _set_frame_builtins(
533
+ cls,
534
+ frame: types.FrameType,
535
+ new_builtins: dict[str, ta.Any],
536
+ ) -> bool:
537
+ return _capture._set_frame_builtins(frame, frame.f_builtins, new_builtins) # noqa
538
+
539
+ @contextlib.contextmanager
540
+ def _hook_context(
541
+ self,
542
+ mod_globals: ta.MutableMapping[str, ta.Any], # noqa
543
+ ) -> ta.Iterator[None]:
544
+ old_builtins = self._frame.f_builtins
545
+ old_import = old_builtins['__import__']
546
+ new_import = functools.partial(self._new_import, old_import)
547
+
548
+ new_builtins = dict(old_builtins)
549
+ new_builtins['__import__'] = new_import
550
+ if not self._set_frame_builtins(self._frame, new_builtins):
551
+ raise ImportCaptureErrors.HookError
552
+
553
+ try:
554
+ yield
555
+
556
+ finally:
557
+ if self._frame.f_builtins is not new_builtins:
558
+ raise ImportCaptureErrors.HookError
559
+
560
+ if not self._set_frame_builtins(self._frame, old_builtins):
561
+ raise ImportCaptureErrors.HookError
562
+
563
+
564
+ #
565
+
566
+
567
+ def _new_import_capture_hook(
568
+ mod_globals: ta.MutableMapping[str, ta.Any], # noqa
569
+ *,
570
+ stack_offset: int = 0,
571
+ **kwargs: ta.Any,
572
+ ) -> '_ImportCaptureHook':
573
+ frame: types.FrameType | None = sys._getframe(1 + stack_offset) # noqa
574
+ if frame is None or frame.f_globals is not mod_globals:
575
+ raise ImportCaptureError("Can't find importing frame")
576
+
577
+ if _capture is not None:
578
+ return _FrameBuiltinsImportCaptureHook(_frame=frame, **kwargs)
579
+
580
+ return _SomewhatThreadSafeGlobalBuiltinsImportCaptureHook(**kwargs)
581
+
582
+
583
+ ##
584
+
585
+
586
+ class ImportCapture:
373
587
  class Import(ta.NamedTuple):
374
588
  spec: str
375
589
  attrs: ta.Sequence[tuple[str | None, str]]
@@ -390,11 +604,14 @@ class ImportCapture:
390
604
  self,
391
605
  mod_globals: ta.MutableMapping[str, ta.Any],
392
606
  *,
607
+ _hook: _ImportCaptureHook,
608
+
393
609
  disable: bool = False,
394
610
  ) -> None:
395
611
  super().__init__()
396
612
 
397
613
  self._mod_globals = mod_globals
614
+ self._hook = _hook
398
615
 
399
616
  self._disabled = disable
400
617
 
@@ -445,14 +662,12 @@ class ImportCapture:
445
662
  yield self
446
663
  return
447
664
 
448
- cap = _ImportCaptureImpl()
449
-
450
- with cap.hook_context(self._mod_globals):
665
+ with self._hook.hook_context(self._mod_globals):
451
666
  yield self
452
667
 
453
- cap.verify_state(self._mod_globals)
668
+ self._hook.verify_state(self._mod_globals)
454
669
 
455
- blt = cap.build_captured(
670
+ blt = self._hook.build_captured(
456
671
  self._mod_globals,
457
672
  collect_unreferenced=unreferenced_callback is not None or raise_unreferenced,
458
673
  )