omlish 0.0.0.dev459__py3-none-any.whl → 0.0.0.dev460__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.dev459'
2
- __revision__ = 'a12178d42c88b1d69bca74aaf2cd0f1fd8106a47'
1
+ __version__ = '0.0.0.dev460'
2
+ __revision__ = 'e37de6bf186be6d30e6b4de285fa5b4350e2f84e'
3
3
 
4
4
 
5
5
  #
@@ -120,30 +120,27 @@ class _ImportCaptureHook:
120
120
 
121
121
  if name.startswith('.'):
122
122
  raise ImportCaptureError
123
+
123
124
  self.name = name
124
- self.base_name = name.rpartition('.')[2]
125
125
  self.parent = parent
126
126
 
127
+ self.base_name = name.rpartition('.')[2]
128
+ self.root: _ImportCaptureHook._Module = parent.root if parent is not None else self # noqa
129
+
130
+ self.children: dict[str, _ImportCaptureHook._Module] = {}
131
+ self.descendants: set[_ImportCaptureHook._Module] = set()
132
+
127
133
  self.module_obj = types.ModuleType(f'<{self.__class__.__qualname__}: {name}>')
128
134
  self.module_obj.__file__ = None
129
135
  self.module_obj.__getattr__ = functools.partial(getattr_handler, self) # type: ignore[method-assign] # noqa
130
136
  self.initial_module_dict = dict(self.module_obj.__dict__)
131
137
 
132
- self.children: dict[str, _ImportCaptureHook._Module] = {}
133
- self.descendants: set[_ImportCaptureHook._Module] = set()
134
138
  self.explicit = False
135
139
  self.immediate = False
136
140
 
137
141
  def __repr__(self) -> str:
138
142
  return f'{self.__class__.__name__}<{self.name}{"!" if self.immediate else "+" if self.explicit else ""}>'
139
143
 
140
- @property
141
- def root(self) -> '_ImportCaptureHook._Module':
142
- out = self
143
- while out.parent is not None:
144
- out = out.parent
145
- return out
146
-
147
144
  def set_explicit(self) -> None:
148
145
  cur: _ImportCaptureHook._Module | None = self
149
146
  while cur is not None and not cur.explicit:
@@ -157,35 +154,32 @@ class _ImportCaptureHook:
157
154
  return sorted(self._modules_by_name.values(), key=lambda m: m.name)
158
155
 
159
156
  def _get_or_make_module(self, name: str) -> _Module:
160
- def rec(name: str) -> _ImportCaptureHook._Module: # noqa
161
- try:
162
- return self._modules_by_name[name]
163
- except KeyError:
164
- pass
165
-
166
- parent: _ImportCaptureHook._Module | None = None
167
- if '.' in name:
168
- rest, _, attr = name.rpartition('.')
169
- parent = rec(rest)
170
- if attr in parent.children:
171
- raise ImportCaptureErrors.AttrError(rest, attr)
157
+ try:
158
+ return self._modules_by_name[name]
159
+ except KeyError:
160
+ pass
172
161
 
173
- module = self._Module(
174
- name,
175
- self._handle_module_getattr,
176
- parent=parent,
177
- )
178
- self._modules_by_name[name] = module
179
- self._modules_by_module_obj[module.module_obj] = module
162
+ parent: _ImportCaptureHook._Module | None = None
163
+ if '.' in name:
164
+ rest, _, attr = name.rpartition('.')
165
+ parent = self._get_or_make_module(rest)
166
+ if attr in parent.children:
167
+ raise ImportCaptureErrors.AttrError(rest, attr)
180
168
 
181
- if parent is not None:
182
- parent.children[attr] = module # noqa
183
- setattr(parent.module_obj, attr, module.module_obj)
184
- parent.root.descendants.add(module)
169
+ module = _ImportCaptureHook._Module(
170
+ name,
171
+ self._handle_module_getattr,
172
+ parent=parent,
173
+ )
174
+ self._modules_by_name[name] = module
175
+ self._modules_by_module_obj[module.module_obj] = module
185
176
 
186
- return module
177
+ if parent is not None:
178
+ parent.children[module.base_name] = module
179
+ setattr(parent.module_obj, module.base_name, module.module_obj)
180
+ parent.root.descendants.add(module)
187
181
 
188
- return rec(name)
182
+ return module
189
183
 
190
184
  def _make_child_module(self, module: _Module, attr: str) -> _Module:
191
185
  if attr in module.children:
@@ -322,7 +316,11 @@ class _ImportCaptureHook:
322
316
  ) -> 'ImportCapture.Captured':
323
317
  rem_explicit_mods: set[_ImportCaptureHook._Module] = set()
324
318
  if collect_unreferenced:
325
- rem_explicit_mods.update([m for m in self._modules_by_name.values() if m.immediate])
319
+ rem_explicit_mods.update(
320
+ m for m in self._modules_by_name.values()
321
+ if m.immediate
322
+ and m.parent is not None # No good way to tell if user did `import a.b.c` or `import a.b.c as c`
323
+ )
326
324
 
327
325
  #
328
326
 
@@ -395,8 +393,8 @@ class _ImportCaptureHook:
395
393
  for m, ts in sorted(dct.items(), key=lambda t: t[0].name):
396
394
  imps.append(ImportCapture.Import(
397
395
  mods[m.name],
398
- [r for l, r in ts if l is None],
399
- [(l, r) for l, r in ts if l is not None],
396
+ [r for l, r in ts if l is None] or None,
397
+ [(l, r) for l, r in ts if l is not None] or None,
400
398
  ))
401
399
 
402
400
  #
@@ -419,6 +417,14 @@ class _ImportCaptureHook:
419
417
 
420
418
 
421
419
  class _AbstractBuiltinsImportCaptureHook(_ImportCaptureHook):
420
+ def __init__(
421
+ self,
422
+ *,
423
+ _frame: types.FrameType | None = None,
424
+ **kwargs: ta.Any,
425
+ ) -> None:
426
+ super().__init__(**kwargs)
427
+
422
428
  def _new_import(
423
429
  self,
424
430
  old_import,
@@ -575,11 +581,24 @@ class _SomewhatThreadSafeGlobalBuiltinsImportCaptureHook(_AbstractBuiltinsImport
575
581
  #
576
582
 
577
583
 
578
- _capture: ta.Any = None
579
- try:
580
- from . import _capture # type: ignore
581
- except ImportError:
582
- pass
584
+ _cext_: ta.Any
585
+
586
+
587
+ def _cext() -> ta.Any:
588
+ global _cext_
589
+ try:
590
+ return _cext_
591
+ except NameError:
592
+ pass
593
+
594
+ cext: ta.Any
595
+ try:
596
+ from . import _capture as cext # type: ignore
597
+ except ImportError:
598
+ cext = None
599
+
600
+ _cext_ = cext
601
+ return cext
583
602
 
584
603
 
585
604
  class _FrameBuiltinsImportCaptureHook(_AbstractBuiltinsImportCaptureHook):
@@ -599,7 +618,7 @@ class _FrameBuiltinsImportCaptureHook(_AbstractBuiltinsImportCaptureHook):
599
618
  frame: types.FrameType,
600
619
  new_builtins: dict[str, ta.Any],
601
620
  ) -> bool:
602
- return _capture._set_frame_builtins(frame, frame.f_builtins, new_builtins) # noqa
621
+ return _cext()._set_frame_builtins(frame, frame.f_builtins, new_builtins) # noqa
603
622
 
604
623
  @contextlib.contextmanager
605
624
  def _hook_context(
@@ -629,22 +648,37 @@ class _FrameBuiltinsImportCaptureHook(_AbstractBuiltinsImportCaptureHook):
629
648
  #
630
649
 
631
650
 
651
+ _CAPTURE_IMPLS: ta.Mapping[str, type[_AbstractBuiltinsImportCaptureHook]] = {
652
+ 'cext': _FrameBuiltinsImportCaptureHook,
653
+ 'somewhat_safe': _SomewhatThreadSafeGlobalBuiltinsImportCaptureHook,
654
+ 'unsafe': _UnsafeGlobalBuiltinsImportCaptureHook,
655
+ }
656
+
657
+
632
658
  def _new_import_capture_hook(
633
659
  mod_globals: ta.MutableMapping[str, ta.Any], # noqa
634
660
  *,
635
661
  stack_offset: int = 0,
662
+ capture_impl: str | None = None,
636
663
  **kwargs: ta.Any,
637
664
  ) -> '_ImportCaptureHook':
638
- frame: types.FrameType | None = sys._getframe(1 + stack_offset) # noqa
639
- if frame is None or frame.f_globals is not mod_globals:
640
- raise ImportCaptureError("Can't find importing frame")
665
+ if '_frame' not in kwargs:
666
+ frame: types.FrameType | None = sys._getframe(1 + stack_offset) # noqa
667
+ if frame is None or frame.f_globals is not mod_globals:
668
+ raise ImportCaptureError("Can't find importing frame")
669
+ kwargs['_frame'] = frame
641
670
 
642
671
  kwargs.setdefault('package', mod_globals.get('__package__'))
643
672
 
644
- if _capture is not None:
645
- return _FrameBuiltinsImportCaptureHook(_frame=frame, **kwargs)
673
+ cls: type[_AbstractBuiltinsImportCaptureHook]
674
+ if capture_impl is not None:
675
+ cls = _CAPTURE_IMPLS[capture_impl]
676
+ elif _cext() is not None:
677
+ cls = _FrameBuiltinsImportCaptureHook
678
+ else:
679
+ cls = _SomewhatThreadSafeGlobalBuiltinsImportCaptureHook
646
680
 
647
- return _SomewhatThreadSafeGlobalBuiltinsImportCaptureHook(**kwargs)
681
+ return cls(**kwargs)
648
682
 
649
683
 
650
684
  ##
@@ -693,20 +727,28 @@ class ImportCapture:
693
727
  ')',
694
728
  ])
695
729
 
730
+ _root: 'ImportCapture.Module'
731
+
696
732
  @property
697
733
  def root(self) -> 'ImportCapture.Module':
698
- out = self
699
- while out.parent is not None:
700
- out = out.parent
701
- return out
734
+ try:
735
+ return self._root
736
+ except AttributeError:
737
+ pass
738
+
739
+ root = self
740
+ while root.parent is not None:
741
+ root = root.parent
742
+ self._root = root
743
+ return root
702
744
 
703
745
  @ta.final
704
746
  class Import:
705
747
  def __init__(
706
748
  self,
707
749
  module: 'ImportCapture.Module',
708
- as_: ta.Sequence[str],
709
- attrs: ta.Sequence[tuple[str, str]], # ('foo', 'bar') -> `import foo as bar` - explicitly not a dict
750
+ as_: ta.Sequence[str] | None,
751
+ attrs: ta.Sequence[tuple[str, str]] | None, # ('foo', 'bar') -> `import foo as bar` - explicitly not a dict # noqa
710
752
  ) -> None:
711
753
  self.module = module
712
754
  self.as_ = as_
@@ -743,9 +785,11 @@ class ImportCapture:
743
785
  @property
744
786
  def attrs(self) -> ta.Iterator[str]:
745
787
  for pi in self.imports.values():
746
- yield from pi.as_
747
- for _, a in pi.attrs:
748
- yield a
788
+ if pi.as_:
789
+ yield from pi.as_
790
+ if pi.attrs:
791
+ for _, a in pi.attrs:
792
+ yield a
749
793
 
750
794
  EMPTY_CAPTURED: ta.ClassVar[Captured] = Captured(
751
795
  {},
@@ -1,6 +1,27 @@
1
- import contextlib
1
+ """
2
+ TODO:
3
+ - _ProxyImports
4
+ - if already imported just return?
5
+ - no, need sub-imports..
6
+ - seal on first use? or just per module? can't seal roots and still be usable
7
+ - only if not hasattr?
8
+ - audit for deadlock risk - does importlib._bootstrap do it for us? do we need a global _ProxyImporter lock? would only
9
+ happen on reification
10
+ - ProxyImportError
11
+
12
+ See:
13
+ - https://peps.python.org/pep-0810/
14
+ - https://github.com/LazyImportsCabal/cpython/tree/lazy
15
+ - https://developers.facebook.com/blog/post/2022/06/15/python-lazy-imports-with-cinder/
16
+ - https://engineering.fb.com/2024/01/18/developer-tools/lazy-imports-cinder-machine-learning-meta/
17
+ - https://www.hudsonrivertrading.com/hrtbeat/inside-hrts-python-fork/
18
+ - https://bugreports.qt.io/browse/PYSIDE-2404
19
+ - https://scientific-python.org/specs/spec-0001/
20
+ - https://github.com/scientific-python/lazy-loader
21
+ """
2
22
  import functools
3
23
  import importlib.util
24
+ import threading
4
25
  import types
5
26
  import typing as ta
6
27
 
@@ -9,179 +30,340 @@ from .capture import ImportCapture
9
30
  from .capture import _new_import_capture_hook
10
31
 
11
32
 
12
- ##
33
+ _ProxyImporterModuleAttr: ta.TypeAlias = ta.Literal[
34
+ 'child', # 'outranks' proxy_attr - all child attrs must be proxy_attrs but not vice versa
35
+ 'proxy_attr',
36
+ 'pending_child',
37
+ 'pending_attr',
38
+ ]
13
39
 
14
40
 
15
- def _translate_old_style_import_capture(
16
- cap: ImportCapture.Captured,
17
- ) -> ta.Mapping[str, ta.Sequence[tuple[str | None, str]]]:
18
- dct: dict[str, list[tuple[str | None, str]]] = {}
41
+ ##
19
42
 
20
- for ci in cap.imports.values():
21
- if ci.module.kind == 'leaf':
22
- if (p := ci.module.parent) is None:
23
- raise NotImplementedError
24
43
 
25
- if ci.attrs:
26
- raise NotImplementedError
44
+ class _ProxyImporter:
45
+ def __init__(
46
+ self,
47
+ *,
48
+ owner_globals: ta.MutableMapping[str, ta.Any] | None = None,
49
+ ) -> None:
50
+ super().__init__()
27
51
 
28
- for a in ci.as_:
29
- dct.setdefault(p.name, []).append(
30
- (ci.module.base_name, a),
31
- )
52
+ self._owner_globals = owner_globals
53
+
54
+ self._owner_name: str | None = owner_globals.get('__name__') if owner_globals else None
55
+
56
+ # NOTE: Import machinery may be reentrant for things like gc ops and signal handling:
57
+ # TODO: audit for reentrancy this lol
58
+ # https://github.com/python/cpython/blob/72f25a8d9a5673d39c107cf522465a566b979ed5/Lib/importlib/_bootstrap.py#L233-L237 # noqa
59
+ self._lock = threading.RLock()
60
+
61
+ self._modules_by_name: dict[str, _ProxyImporter._Module] = {}
62
+ self._modules_by_proxy_obj: dict[types.ModuleType, _ProxyImporter._Module] = {}
63
+
64
+ class _Module:
65
+ def __init__(
66
+ self,
67
+ name: str,
68
+ getattr_handler: ta.Callable[['_ProxyImporter._Module', str], ta.Any],
69
+ *,
70
+ parent: ta.Optional['_ProxyImporter._Module'] = None,
71
+ ) -> None:
72
+ super().__init__()
73
+
74
+ self.name = name
75
+ self.parent = parent
76
+
77
+ self.base_name = name.rpartition('.')[2]
78
+ self.root: _ProxyImporter._Module = parent.root if parent is not None else self # noqa
79
+
80
+ self.children: dict[str, _ProxyImporter._Module] = {}
81
+ self.descendants: set[_ProxyImporter._Module] = set()
82
+
83
+ self.proxy_obj = types.ModuleType(f'<{self.__class__.__qualname__}: {name}>')
84
+ self.proxy_obj.__file__ = None
85
+ self.proxy_obj.__getattr__ = functools.partial(getattr_handler, self) # type: ignore[method-assign]
86
+
87
+ self.pending_children: set[str] = set()
88
+ self.pending_attrs: set[str] = set()
89
+
90
+ real_obj: types.ModuleType | None = None
91
+
92
+ def __repr__(self) -> str:
93
+ return f'{self.__class__.__name__}<{self.name}{"!" if self.real_obj is not None else ""}>'
94
+
95
+ def find_attr(self, attr: str) -> _ProxyImporterModuleAttr | None:
96
+ is_child = attr in self.children
97
+ is_proxy_attr = attr in self.proxy_obj.__dict__
98
+ is_pending_child = attr in self.pending_children
99
+ is_pending_attr = attr in self.pending_attrs
100
+
101
+ if is_child:
102
+ if (
103
+ not is_proxy_attr or
104
+ is_pending_child or
105
+ is_pending_attr
106
+ ):
107
+ raise RuntimeError
108
+ return 'child'
109
+
110
+ elif is_proxy_attr:
111
+ if (
112
+ is_pending_child or
113
+ is_pending_attr
114
+ ):
115
+ raise RuntimeError
116
+ return 'proxy_attr'
117
+
118
+ elif is_pending_child:
119
+ if (
120
+ is_child or
121
+ is_proxy_attr or
122
+ is_pending_attr
123
+ ):
124
+ raise RuntimeError
125
+ return 'pending_child'
126
+
127
+ elif is_pending_attr:
128
+ if (
129
+ is_child or
130
+ is_proxy_attr or
131
+ is_pending_child
132
+ ):
133
+ raise RuntimeError
134
+ return 'pending_attr'
32
135
 
33
- elif ci.module.kind == 'terminal':
34
- if ci.module.children:
35
- raise NotImplementedError
136
+ else:
137
+ return None
36
138
 
37
- for a in ci.as_:
38
- dct.setdefault(ci.module.name, []).append(
39
- (None, a),
40
- )
139
+ #
41
140
 
42
- for sa, da in ci.attrs:
43
- dct.setdefault(ci.module.name, []).append(
44
- (sa, da),
45
- )
141
+ def _get_or_make_module_locked(self, name: str) -> _Module:
142
+ try:
143
+ return self._modules_by_name[name]
144
+ except KeyError:
145
+ pass
46
146
 
47
- else:
48
- raise NotImplementedError
147
+ parent: _ProxyImporter._Module | None = None
148
+ if '.' in name:
149
+ rest, _, attr = name.rpartition('.')
150
+ parent = self._get_or_make_module_locked(rest)
49
151
 
50
- return dct
152
+ fa = parent.find_attr(attr)
153
+ if not (fa == 'pending_child' or fa is None):
154
+ raise RuntimeError
51
155
 
156
+ if (ro := parent.real_obj) is not None and attr not in ro.__dict__:
157
+ raise NotImplementedError
52
158
 
53
- ##
159
+ module = self._modules_by_name[name] = _ProxyImporter._Module(
160
+ name,
161
+ self._handle_module_getattr,
162
+ )
54
163
 
164
+ self._modules_by_name[name] = module
165
+ self._modules_by_proxy_obj[module.proxy_obj] = module
55
166
 
56
- def proxy_import(
57
- spec: str,
58
- package: str | None = None,
59
- extras: ta.Iterable[str] | None = None,
60
- ) -> types.ModuleType:
61
- if isinstance(extras, str):
62
- raise TypeError(extras)
167
+ if parent is not None:
168
+ parent.pending_children.discard(module.base_name)
169
+ parent.children[module.base_name] = module
170
+ setattr(parent.proxy_obj, module.base_name, module.proxy_obj)
171
+ parent.root.descendants.add(module)
63
172
 
64
- omod = None
173
+ return module
65
174
 
66
- def __getattr__(att): # noqa
67
- nonlocal omod
68
- if omod is None:
69
- omod = importlib.import_module(spec, package=package)
70
- if extras:
71
- for x in extras:
72
- importlib.import_module(f'{spec}.{x}', package=package)
73
- return getattr(omod, att)
175
+ def _extend_module_locked(
176
+ self,
177
+ module: _Module,
178
+ *,
179
+ children: ta.Iterable[str] | None = None,
180
+ attrs: ta.Iterable[str] | None = None,
181
+ ) -> None:
182
+ for l in (children, attrs):
183
+ for n in l or ():
184
+ if n in module.proxy_obj.__dict__:
185
+ raise NotImplementedError
74
186
 
75
- lmod = types.ModuleType(spec)
76
- lmod.__getattr__ = __getattr__ # type: ignore[method-assign]
77
- return lmod
187
+ if (ro := module.real_obj) is not None and n in ro.__dict__:
188
+ raise NotImplementedError
78
189
 
190
+ for n in children or ():
191
+ fa = module.find_attr(n)
192
+ if not (fa == 'pending_child' or fa is None):
193
+ raise RuntimeError
79
194
 
80
- #
195
+ for n in attrs or ():
196
+ fa = module.find_attr(n)
197
+ if not (fa == 'pending_attr' or fa is None):
198
+ raise RuntimeError
81
199
 
200
+ #
82
201
 
83
- def auto_proxy_import(
84
- mod_globals: ta.MutableMapping[str, ta.Any],
85
- *,
86
- disable: bool = False,
202
+ if children:
203
+ module.pending_children.update(n for n in children if n not in module.children)
204
+ if attrs:
205
+ module.pending_attrs.update(attrs)
87
206
 
88
- unreferenced_callback: ta.Callable[[ta.Sequence[str]], None] | None = None,
89
- raise_unreferenced: bool = False,
207
+ def _retrieve_from_module_locked(self, module: _Module, attr: str) -> ta.Any:
208
+ fa = module.find_attr(attr)
90
209
 
91
- _stack_offset: int = 0,
92
- ) -> ta.ContextManager[ImportCapture]:
93
- inst = ImportCapture(
94
- mod_globals,
95
- _hook=_new_import_capture_hook(
96
- mod_globals,
97
- stack_offset=_stack_offset + 1,
98
- ),
99
- disable=disable,
100
- )
210
+ if fa == 'child' or fa == 'proxy_attr':
211
+ return module.proxy_obj.__dict__[attr]
101
212
 
102
- @contextlib.contextmanager
103
- def inner() -> ta.Iterator[ImportCapture]:
104
- with inst.capture(
105
- unreferenced_callback=unreferenced_callback,
106
- raise_unreferenced=raise_unreferenced,
107
- ):
108
- yield inst
213
+ val: ta.Any
109
214
 
110
- for spec, attrs in _translate_old_style_import_capture(inst.captured).items():
111
- for sa, ma in attrs:
112
- mod_globals[ma] = proxy_import(spec + (('.' + sa) if sa is not None else ''))
215
+ if fa == 'pending_child':
216
+ if module.name == self._owner_name:
217
+ val = importlib.import_module(f'{module.name}.{attr}')
113
218
 
114
- return inner()
219
+ else:
220
+ mod = __import__(
221
+ module.name,
222
+ self._owner_globals or {},
223
+ {},
224
+ [attr],
225
+ 0,
226
+ )
115
227
 
228
+ val = getattr(mod, attr)
116
229
 
117
- ##
230
+ module.pending_children.remove(attr)
118
231
 
232
+ elif fa == 'pending_attr' or fa is None:
233
+ if module.name == self._owner_name:
234
+ raise NotImplementedError
119
235
 
120
- class _ProxyInit:
121
- class NamePackage(ta.NamedTuple):
122
- name: str
123
- package: str
236
+ if (ro := module.real_obj) is None:
237
+ ro = module.real_obj = importlib.import_module(module.name)
124
238
 
125
- class _Import(ta.NamedTuple):
126
- name: str
127
- attr: str | None
239
+ val = getattr(ro, attr)
128
240
 
129
- def __init__(
130
- self,
131
- lazy_globals: LazyGlobals,
132
- name_package: NamePackage,
133
- ) -> None:
134
- super().__init__()
241
+ if fa == 'pending_attr':
242
+ module.pending_attrs.remove(attr)
135
243
 
136
- self._lazy_globals = lazy_globals
137
- self._name_package = name_package
244
+ else:
245
+ raise TypeError(fa)
246
+
247
+ setattr(module.proxy_obj, attr, val)
248
+ return val
138
249
 
139
- self._imps_by_attr: dict[str, _ProxyInit._Import] = {}
250
+ #
140
251
 
141
- @property
142
- def name_package(self) -> NamePackage:
143
- return self._name_package
252
+ def _handle_module_getattr(self, module: _Module, attr: str) -> ta.Any:
253
+ with self._lock:
254
+ return self._retrieve_from_module_locked(module, attr)
144
255
 
145
256
  def add(
146
257
  self,
147
- name: str,
148
- attrs: ta.Iterable[tuple[str | None, str]],
149
- ) -> None:
150
- for imp_attr, as_attr in attrs:
151
- if imp_attr is None:
152
- self._imps_by_attr[as_attr] = self._Import(name, None)
153
- self._lazy_globals.set_fn(as_attr, functools.partial(self.get, as_attr))
258
+ module: str,
259
+ *,
260
+ children: ta.Iterable[str] | None = None,
261
+ attrs: ta.Iterable[str] | None = None,
262
+ ) -> types.ModuleType:
263
+ if isinstance(children, str):
264
+ raise TypeError(children)
265
+
266
+ # Leaf modules get collapsed into parents' pending_children
267
+ if not children and not attrs and '.' in module:
268
+ module, _, child = module.rpartition('.')
269
+ children = [child]
270
+
271
+ with self._lock:
272
+ m = self._get_or_make_module_locked(module)
273
+
274
+ if children or attrs:
275
+ self._extend_module_locked(
276
+ m,
277
+ children=children,
278
+ attrs=attrs,
279
+ )
154
280
 
155
- else:
156
- self._imps_by_attr[as_attr] = self._Import(name, imp_attr)
157
- self._lazy_globals.set_fn(as_attr, functools.partial(self.get, as_attr))
281
+ return m.proxy_obj
158
282
 
159
- def get(self, attr: str) -> ta.Any:
283
+ def get_module(self, name: str) -> types.ModuleType:
160
284
  try:
161
- imp = self._imps_by_attr[attr]
285
+ return self._modules_by_name[name].proxy_obj
162
286
  except KeyError:
163
- raise AttributeError(attr) # noqa
164
-
165
- val: ta.Any
287
+ pass
166
288
 
167
- if imp.attr is None:
168
- val = importlib.import_module(imp.name)
289
+ with self._lock:
290
+ return self._get_or_make_module_locked(name).proxy_obj
169
291
 
170
- elif imp.name == self._name_package.name:
171
- val = importlib.import_module(f'{imp.name}.{imp.attr}')
292
+ def lookup(self, spec: str) -> ta.Any:
293
+ if '.' not in spec:
294
+ return self._modules_by_name[spec].proxy_obj
172
295
 
296
+ try:
297
+ module = self._modules_by_name[spec]
298
+ except KeyError:
299
+ pass
173
300
  else:
174
- mod = __import__(
175
- imp.name,
176
- self._lazy_globals._globals, # noqa
177
- {},
178
- [imp.attr],
179
- 0,
180
- )
301
+ return module.proxy_obj
181
302
 
182
- val = getattr(mod, imp.attr)
303
+ rest, _, attr = spec.rpartition('.')
304
+ module = self._modules_by_name[rest]
305
+ return getattr(module.proxy_obj, attr)
183
306
 
184
- return val
307
+
308
+ #
309
+
310
+
311
+ _MODULE_PROXY_IMPORTER_GLOBAL_NAME = '__proxy_importer__'
312
+
313
+
314
+ def _get_module_proxy_importer(mod_globals: ta.MutableMapping[str, ta.Any]) -> _ProxyImporter:
315
+ """Assumed to only be called in a module body - no locking is done."""
316
+
317
+ pi: _ProxyImporter
318
+ try:
319
+ pi = mod_globals[_MODULE_PROXY_IMPORTER_GLOBAL_NAME]
320
+
321
+ except KeyError:
322
+ pi = _ProxyImporter(
323
+ owner_globals=mod_globals,
324
+ )
325
+ mod_globals[_MODULE_PROXY_IMPORTER_GLOBAL_NAME] = pi
326
+
327
+ else:
328
+ if pi.__class__ is not _ProxyImporter:
329
+ raise TypeError(pi)
330
+
331
+ if pi._owner_globals is not mod_globals: # noqa
332
+ raise RuntimeError
333
+
334
+ return pi
335
+
336
+
337
+ ##
338
+
339
+
340
+ def proxy_import(
341
+ spec: str,
342
+ package: str | None = None,
343
+ extras: ta.Iterable[str] | None = None,
344
+ ) -> types.ModuleType:
345
+ """'Legacy' proxy import mechanism."""
346
+
347
+ if isinstance(extras, str):
348
+ raise TypeError(extras)
349
+
350
+ omod = None
351
+
352
+ def __getattr__(att): # noqa
353
+ nonlocal omod
354
+ if omod is None:
355
+ omod = importlib.import_module(spec, package=package)
356
+ if extras:
357
+ for x in extras:
358
+ importlib.import_module(f'{spec}.{x}', package=package)
359
+ return getattr(omod, att)
360
+
361
+ lmod = types.ModuleType(spec)
362
+ lmod.__getattr__ = __getattr__ # type: ignore[method-assign]
363
+ return lmod
364
+
365
+
366
+ #
185
367
 
186
368
 
187
369
  def proxy_init(
@@ -225,76 +407,143 @@ def proxy_init(
225
407
 
226
408
  #
227
409
 
228
- init_name_package = _ProxyInit.NamePackage(
229
- init_globals['__name__'],
230
- init_globals['__package__'],
410
+ pi = _get_module_proxy_importer(init_globals)
411
+ lg = LazyGlobals.install(init_globals)
412
+
413
+ pi.add(
414
+ name,
415
+ children=[r for l, r in al if r is not None],
231
416
  )
232
417
 
233
- pi: _ProxyInit
234
- try:
235
- pi = init_globals['__proxy_init__']
418
+ for imp_attr, as_attr in al:
419
+ lg.set_fn(as_attr, functools.partial(pi.lookup, name if imp_attr is None else f'{name}.{imp_attr}'))
236
420
 
237
- except KeyError:
238
- pi = _ProxyInit(
239
- LazyGlobals.install(init_globals),
240
- init_name_package,
421
+
422
+ ##
423
+
424
+
425
+ class _AutoProxy:
426
+ def __init__(
427
+ self,
428
+ mod_globals: ta.MutableMapping[str, ta.Any],
429
+ *,
430
+ disable: bool = False,
431
+ eager: bool = False,
432
+
433
+ unreferenced_callback: ta.Callable[[ta.Sequence[str]], None] | None = None,
434
+ raise_unreferenced: bool = False,
435
+
436
+ update_exports: bool = False,
437
+
438
+ _stack_offset: int = 0,
439
+ _capture_impl: str | None = None,
440
+ ) -> None:
441
+ super().__init__()
442
+
443
+ self._mod_globals = mod_globals
444
+
445
+ self._disabled = disable
446
+ self._eager = eager
447
+
448
+ self._unreferenced_callback = unreferenced_callback
449
+ self._raise_unreferenced = raise_unreferenced
450
+
451
+ self._update_exports = update_exports
452
+
453
+ self._ic = ImportCapture(
454
+ mod_globals,
455
+ _hook=_new_import_capture_hook(
456
+ mod_globals,
457
+ stack_offset=_stack_offset + 1,
458
+ capture_impl=_capture_impl,
459
+ ),
460
+ disable=disable,
241
461
  )
242
- init_globals['__proxy_init__'] = pi
462
+ self._icc: ta.Any = None
243
463
 
244
- else:
245
- if pi.name_package != init_name_package:
246
- raise Exception(f'Wrong init name: {pi.name_package=} != {init_name_package=}')
464
+ def __enter__(self) -> ImportCapture:
465
+ if self._icc is not None:
466
+ raise RuntimeError
247
467
 
248
- pi.add(name, al)
468
+ self._icc = self._ic.capture(
469
+ unreferenced_callback=self._unreferenced_callback,
470
+ raise_unreferenced=self._raise_unreferenced,
471
+ )
249
472
 
473
+ return self._icc.__enter__() # noqa
250
474
 
251
- #
475
+ def __exit__(self, exc_type, exc_val, exc_tb):
476
+ if self._icc is None:
477
+ raise RuntimeError
252
478
 
479
+ self._icc.__exit__(exc_type, exc_val, exc_tb)
253
480
 
254
- def auto_proxy_init(
255
- init_globals: ta.MutableMapping[str, ta.Any],
256
- *,
257
- disable: bool = False,
258
- eager: bool = False,
259
-
260
- unreferenced_callback: ta.Callable[[ta.Sequence[str]], None] | None = None,
261
- raise_unreferenced: bool = False,
262
-
263
- update_exports: bool = False,
264
-
265
- _stack_offset: int = 0,
266
- ) -> ta.ContextManager[ImportCapture]:
267
- inst = ImportCapture(
268
- init_globals,
269
- _hook=_new_import_capture_hook(
270
- init_globals,
271
- stack_offset=_stack_offset + 1,
272
- ),
273
- disable=disable,
274
- )
481
+ if not self._disabled and exc_type is None:
482
+ self._install()
483
+
484
+ # @abc.abstractmethod
485
+ def _install(self) -> None:
486
+ raise NotImplementedError
487
+
488
+
489
+ @ta.final
490
+ class _AutoProxyImport(_AutoProxy):
491
+ def _install(self) -> None:
492
+ cap = self._ic.captured
493
+
494
+ for cm in cap.modules.values():
495
+ if cm.attrs:
496
+ raise RuntimeError
497
+
498
+ pi = _get_module_proxy_importer(self._mod_globals)
275
499
 
276
- @contextlib.contextmanager
277
- def inner() -> ta.Iterator[ImportCapture]:
278
- with inst.capture(
279
- unreferenced_callback=unreferenced_callback,
280
- raise_unreferenced=raise_unreferenced,
281
- ):
282
- yield inst
283
-
284
- for spec, attrs in _translate_old_style_import_capture(inst.captured).items():
285
- proxy_init(
286
- init_globals,
287
- spec,
288
- attrs,
500
+ for cm in cap.modules.values():
501
+ pi.add(
502
+ cm.name,
503
+ children=cm.children,
289
504
  )
290
505
 
291
- if eager:
292
- lg = LazyGlobals.install(init_globals)
506
+ for ci in cap.imports.values():
507
+ pm = pi.get_module(ci.module.name)
508
+ for a in ci.as_ or ():
509
+ self._mod_globals[a] = pm
293
510
 
294
- for a in inst.captured.attrs:
511
+ if self._eager:
512
+ for ci in cap.imports.values():
513
+ pi.lookup(ci.module.name)
514
+
515
+ if self._update_exports:
516
+ self._ic.update_exports()
517
+
518
+
519
+ @ta.final
520
+ class _AutoProxyInit(_AutoProxy):
521
+ def _install(self) -> None:
522
+ cap = self._ic.captured
523
+
524
+ pi = _get_module_proxy_importer(self._mod_globals)
525
+ lg = LazyGlobals.install(self._mod_globals)
526
+
527
+ for cm in cap.modules.values():
528
+ pi.add(
529
+ cm.name,
530
+ children=cm.children,
531
+ attrs=cm.attrs,
532
+ )
533
+
534
+ for ci in cap.imports.values():
535
+ for a in ci.as_ or ():
536
+ lg.set_fn(a, functools.partial(pi.lookup, ci.module.name))
537
+ for sa, da in ci.attrs or ():
538
+ lg.set_fn(da, functools.partial(pi.lookup, f'{ci.module.name}.{sa}'))
539
+
540
+ if self._eager:
541
+ for a in cap.attrs:
295
542
  lg.get(a)
296
543
 
297
- if update_exports:
298
- inst.update_exports()
544
+ if self._update_exports:
545
+ self._ic.update_exports()
546
+
299
547
 
300
- return inner()
548
+ auto_proxy_import = _AutoProxyImport
549
+ auto_proxy_init = _AutoProxyInit
@@ -1,3 +1,4 @@
1
+ import threading
1
2
  import typing as ta
2
3
 
3
4
 
@@ -15,6 +16,10 @@ class AmbiguousLazyGlobalsFallbackError(Exception):
15
16
  return f'{self.__class__.__name__}({self.attr!r}, {self.fallbacks!r})'
16
17
 
17
18
 
19
+ _LAZY_GLOBALS_LOCK = threading.RLock()
20
+
21
+
22
+ @ta.final
18
23
  class LazyGlobals:
19
24
  def __init__(
20
25
  self,
@@ -22,8 +27,6 @@ class LazyGlobals:
22
27
  globals: ta.MutableMapping[str, ta.Any] | None = None, # noqa
23
28
  update_globals: bool = False,
24
29
  ) -> None:
25
- super().__init__()
26
-
27
30
  self._globals = globals
28
31
  self._update_globals = update_globals
29
32
 
@@ -37,18 +40,28 @@ class LazyGlobals:
37
40
  except KeyError:
38
41
  pass
39
42
  else:
40
- if not isinstance(xga, cls):
43
+ if xga.__class__ is not cls:
41
44
  raise RuntimeError(f'Module already has __getattr__ hook: {xga}') # noqa
42
45
  return xga
43
46
 
44
- lm = cls(
45
- globals=globals,
46
- update_globals=True,
47
- )
47
+ with _LAZY_GLOBALS_LOCK:
48
+ try:
49
+ xga = globals['__getattr__']
50
+ except KeyError:
51
+ pass
52
+ else:
53
+ if xga.__class__ is not cls:
54
+ raise RuntimeError(f'Module already has __getattr__ hook: {xga}') # noqa
55
+ return xga
56
+
57
+ lm = cls(
58
+ globals=globals,
59
+ update_globals=True,
60
+ )
48
61
 
49
- globals['__getattr__'] = lm
62
+ globals['__getattr__'] = lm
50
63
 
51
- return lm
64
+ return lm
52
65
 
53
66
  def set_fn(self, attr: str, fn: ta.Callable[[], ta.Any]) -> 'LazyGlobals':
54
67
  self._attr_fns[attr] = fn
@@ -5,9 +5,6 @@ import typing as ta
5
5
  from ...lite.check import check
6
6
 
7
7
 
8
- T = ta.TypeVar('T')
9
-
10
-
11
8
  ##
12
9
 
13
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev459
3
+ Version: 0.0.0.dev460
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License-Expression: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.omlish-manifests.json,sha256=FLw7xkPiSXuImZgqSP8BwrEib2R1doSzUPLUkc-QUIA,8410
2
- omlish/__about__.py,sha256=xtL6Mzn2wu2_IspeSZ_MxiJmXyGvptjs7xRjcNyq79E,3613
2
+ omlish/__about__.py,sha256=Lh3QcQsg1H85i-Syg0Z1HsN6E9om5sxdQHnAEl79_ng,3613
3
3
  omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
4
4
  omlish/c3.py,sha256=ZNIMl1kwg3qdei4DiUrJPQe5M81S1e76N-GuNSwLBAE,8683
5
5
  omlish/cached.py,sha256=MLap_p0rdGoDIMVhXVHm1tsbcWobJF0OanoodV03Ju8,542
@@ -425,7 +425,7 @@ omlish/lang/errors.py,sha256=shcS-NCnEUudF8qC_SmO2TQyjivKlS4TDjaz_faqQ0c,44
425
425
  omlish/lang/functions.py,sha256=tZL7Yi5Oy34lvzP6HhWmV5q1eg5-mk3FrWEjsmhKRhY,5707
426
426
  omlish/lang/generators.py,sha256=nJiSmDpsfPiypGzJ8qlOO7-BUnCsrAeDow9mhtGgBio,5196
427
427
  omlish/lang/iterables.py,sha256=o_s8ouaJrdUqEVl2_bzJk5CVdabmrywXY0gPn7xss3w,3371
428
- omlish/lang/lazyglobals.py,sha256=YfPtWgNEa0ULtbIiQIQ12pbafUwd6INQRw_PFcAabjo,2282
428
+ omlish/lang/lazyglobals.py,sha256=5v4S0YSUrxdZFC6znrWolfeheArMa_DEJEcz46XCQN4,2676
429
429
  omlish/lang/maybes.py,sha256=8GUpqJvyx9y5PQBQBBx6yTSE5WKsMbXMHPt4_vojKUw,209
430
430
  omlish/lang/maysync.py,sha256=S2Q_rGC4AxRa1UsGJdSzZsYpgOcX9Y8ZmYGA9v8OWn8,1635
431
431
  omlish/lang/objects.py,sha256=JMOZU5I6sK4rJm04898dOJkvef-lDIh5O3k6tlLbBJw,4616
@@ -452,10 +452,10 @@ omlish/lang/classes/simple.py,sha256=3AJSs-plVg2flq4SC6I39LxP0nBaB241puv3D5YCP5I
452
452
  omlish/lang/classes/virtual.py,sha256=J4y-uiv1RaP2rfFeptXqQ1a4MRek0TMlAFFraO_lzhs,3397
453
453
  omlish/lang/imports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
454
454
  omlish/lang/imports/_capture.cc,sha256=cp-L5yFGjftfBa12sKNz0Q5Yhh54a88DH7VEN1oa5hU,2313
455
- omlish/lang/imports/capture.py,sha256=z91F2CVcBDX4trhd2oRB5YL_d_Nu9KBTjzhy98N9YDQ,26170
455
+ omlish/lang/imports/capture.py,sha256=pyON5qUw9IuzzOcIHh4QNjlzAEjIGb9DwI7Z8yLDUfw,27265
456
456
  omlish/lang/imports/conditional.py,sha256=R-E47QD95mMonPImWlrde3rnJrFKCCkYz71c94W05sc,1006
457
457
  omlish/lang/imports/lazy.py,sha256=Eefs9hkj5surMdwgxX_Q3BOqPcox10v0sKT5rKIQknc,808
458
- omlish/lang/imports/proxy.py,sha256=tIIChKvl5TCZp_vg6D7WR7NxnN0J-L-bqjp0DYvgUuA,7562
458
+ omlish/lang/imports/proxy.py,sha256=ZhSETt41gjb4STGz9byZ5fpTi8GVdoSmPL-tbr0BEgQ,15897
459
459
  omlish/lang/imports/resolving.py,sha256=DeRarn35Fryg5JhVhy8wbiC9lvr58AnllI9B_reswUE,2085
460
460
  omlish/lang/imports/traversal.py,sha256=pbFQIa880NGjSfcLsno2vE_G41_CLwDHb-7gWg2J3BI,2855
461
461
  omlish/lifecycles/__init__.py,sha256=zOuvV4pErPwxcKUSgshmME2Duw9GrjwckpNmW3FPKng,810
@@ -763,7 +763,7 @@ omlish/term/progressbar.py,sha256=nCnTX1dUNCGle9FzzeTBuoxQaellErOQkyBsKVrGG1I,36
763
763
  omlish/term/vt100/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
764
764
  omlish/term/vt100/c.py,sha256=93HARU6Dd1rVF-n8cAyXqkEqleYLcofuLgSUNd6-GbU,3537
765
765
  omlish/term/vt100/states.py,sha256=OxPUxfFTcfz56MhtDgIigEApChOtN6XO1g6R2H08mu4,8303
766
- omlish/term/vt100/terminal.py,sha256=KUlg331ele7P6SHsBKdbpdQFDKsxSply1Ds27NkppTs,9359
766
+ omlish/term/vt100/terminal.py,sha256=VHbZs5mHZPBizxseWKcRH77QAjE4bWDvuHi3NM6PvbA,9337
767
767
  omlish/testing/__init__.py,sha256=h8GMv0PhVyMECsQoYuqoBzHjYO6UJyXBZvnozzUg9M8,171
768
768
  omlish/testing/testing.py,sha256=tlBeqVmzGhjRbWygJt3mZljq662qR4nAaGNE2VoAey4,3617
769
769
  omlish/testing/pytest/__init__.py,sha256=i4ti6Q2rVYJ-XBk9UYDfUUagCrEDTC5jOeSykBjYYZQ,234
@@ -826,9 +826,9 @@ omlish/typedvalues/marshal.py,sha256=2xqX6JllhtGpmeYkU7C-qzgU__0x-vd6CzYbAsocQlc
826
826
  omlish/typedvalues/of_.py,sha256=UXkxSj504WI2UrFlqdZJbu2hyDwBhL7XVrc2qdR02GQ,1309
827
827
  omlish/typedvalues/reflect.py,sha256=PAvKW6T4cW7u--iX80w3HWwZUS3SmIZ2_lQjT65uAyk,1026
828
828
  omlish/typedvalues/values.py,sha256=ym46I-q2QJ_6l4UlERqv3yj87R-kp8nCKMRph0xQ3UA,1307
829
- omlish-0.0.0.dev459.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
830
- omlish-0.0.0.dev459.dist-info/METADATA,sha256=vA7Ker_93at2wRMI8zaoBhaoAnE8BmZqvBEDDGy9gEg,19003
831
- omlish-0.0.0.dev459.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
832
- omlish-0.0.0.dev459.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
833
- omlish-0.0.0.dev459.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
834
- omlish-0.0.0.dev459.dist-info/RECORD,,
829
+ omlish-0.0.0.dev460.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
830
+ omlish-0.0.0.dev460.dist-info/METADATA,sha256=RXnH7VCjc6A71590hroJ9LF3IFEaonZi1aHkHevvASI,19003
831
+ omlish-0.0.0.dev460.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
832
+ omlish-0.0.0.dev460.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
833
+ omlish-0.0.0.dev460.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
834
+ omlish-0.0.0.dev460.dist-info/RECORD,,