omlish 0.0.0.dev470__py3-none-any.whl → 0.0.0.dev472__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.dev470'
2
- __revision__ = '967906fd30a40d061ede9f7aeda9dad0ec608508'
1
+ __version__ = '0.0.0.dev472'
2
+ __revision__ = '78c62ff9afe49d9f0ae12b27ff595b2079e9c082'
3
3
 
4
4
 
5
5
  #
omlish/inject/__init__.py CHANGED
@@ -57,8 +57,12 @@ with _lang.auto_proxy_init(globals()):
57
57
  Element,
58
58
  Elements,
59
59
  Elemental,
60
+
60
61
  as_elements,
61
62
  iter_elements,
63
+
64
+ CollectedElements,
65
+ collect_elements,
62
66
  )
63
67
 
64
68
  from .errors import ( # noqa
omlish/inject/elements.py CHANGED
@@ -6,6 +6,12 @@ from .. import lang
6
6
  from .impl.origins import HasOriginsImpl
7
7
 
8
8
 
9
+ if ta.TYPE_CHECKING:
10
+ from .impl import elements as _elements
11
+ else:
12
+ _elements = lang.proxy_import('.impl.elements', __package__)
13
+
14
+
9
15
  ##
10
16
 
11
17
 
@@ -74,3 +80,14 @@ def iter_elements(*args: Elemental) -> ta.Iterator[Element]:
74
80
  yield from a
75
81
  else:
76
82
  raise TypeError(a)
83
+
84
+
85
+ ##
86
+
87
+
88
+ class CollectedElements(lang.PackageSealed, lang.Abstract):
89
+ pass
90
+
91
+
92
+ def collect_elements(es: Elements | CollectedElements) -> CollectedElements:
93
+ return _elements.collect_elements(es)
@@ -27,6 +27,7 @@ from ... import collections as col
27
27
  from ... import lang
28
28
  from ..bindings import Binding
29
29
  from ..eagers import Eager
30
+ from ..elements import CollectedElements
30
31
  from ..elements import Element
31
32
  from ..elements import Elements
32
33
  from ..errors import ConflictingKeyError
@@ -47,7 +48,7 @@ from .multis import make_multi_provider_impl
47
48
  from .origins import Origins
48
49
  from .origins import set_origins
49
50
  from .providers import ProviderImpl
50
- from .providers2 import make_provider_impl
51
+ from .providersmap import make_provider_impl
51
52
  from .scopes import make_scope_impl
52
53
 
53
54
 
@@ -80,7 +81,7 @@ _NON_BINDING_ELEMENT_TYPES: tuple[type[Element], ...] = (
80
81
  )
81
82
 
82
83
 
83
- class ElementCollection(lang.Final):
84
+ class ElementCollection(CollectedElements, lang.Final):
84
85
  def __init__(self, es: Elements) -> None:
85
86
  super().__init__()
86
87
 
@@ -208,6 +209,10 @@ class ElementCollection(lang.Final):
208
209
 
209
210
  ##
210
211
 
212
+ @lang.cached_function
213
+ def scope_binding_scopes(self) -> ta.Sequence[Scope]:
214
+ return [sb.scope for sb in self.elements_of_type(ScopeBinding)]
215
+
211
216
  @lang.cached_function
212
217
  def eager_keys_by_scope(self) -> ta.Mapping[Scope, ta.Sequence[Key]]:
213
218
  bim = self.binding_impl_map()
@@ -216,3 +221,13 @@ class ElementCollection(lang.Final):
216
221
  bi = bim[e.key]
217
222
  ret.setdefault(bi.scope, []).append(e.key)
218
223
  return ret
224
+
225
+
226
+ ##
227
+
228
+
229
+ def collect_elements(es: Elements | CollectedElements) -> ElementCollection:
230
+ if isinstance(es, CollectedElements):
231
+ return check.isinstance(es, ElementCollection)
232
+ else:
233
+ return ElementCollection(es)
@@ -2,14 +2,12 @@
2
2
  TODO:
3
3
  - ** can currently bind in a child/private scope shadowing an external parent binding **
4
4
  - better source tracking
5
- - cache/export ElementCollections lol
6
5
  - scope bindings, auto in root
7
6
  - injector-internal / blacklisted bindings (Injector itself, default scopes) without rebuilding ElementCollection
8
7
  - config - proxies, impl select, etc
9
8
  - config is probably shared with ElementCollection... but not 'bound', must be shared everywhere
10
9
  - InjectorRoot object?
11
10
  - ** eagers in any scope, on scope init/open
12
- - injection listeners
13
11
  - unions - raise on ambiguous - usecase: sql.AsyncEngineLike
14
12
  - multiple live request scopes on single injector - use private injectors?
15
13
  - more listeners - UnboundKeyListener
@@ -24,7 +22,7 @@ import weakref
24
22
  from ... import check
25
23
  from ... import lang
26
24
  from ...logs import all as logs
27
- from ..elements import Elements
25
+ from ..elements import CollectedElements
28
26
  from ..errors import CyclicDependencyError
29
27
  from ..errors import UnboundKeyError
30
28
  from ..injector import AsyncInjector
@@ -33,7 +31,6 @@ from ..keys import Key
33
31
  from ..keys import as_key
34
32
  from ..listeners import ProvisionListener
35
33
  from ..listeners import ProvisionListenerBinding
36
- from ..scopes import ScopeBinding
37
34
  from ..scopes import Singleton
38
35
  from ..scopes import ThreadScope
39
36
  from ..types import Scope
@@ -61,14 +58,12 @@ DEFAULT_SCOPES: list[Scope] = [
61
58
  class AsyncInjectorImpl(AsyncInjector, lang.Final):
62
59
  def __init__(
63
60
  self,
64
- ec: ElementCollection,
61
+ ec: CollectedElements,
65
62
  p: ta.Optional['AsyncInjectorImpl'] = None,
66
63
  *,
67
64
  internal_consts: dict[Key, ta.Any] | None = None,
68
65
  ) -> None:
69
- super().__init__()
70
-
71
- self._ec = check.isinstance(ec, ElementCollection)
66
+ self._ec = (ec := check.isinstance(ec, ElementCollection))
72
67
  self._p: AsyncInjectorImpl | None = check.isinstance(p, (AsyncInjectorImpl, None))
73
68
 
74
69
  self._internal_consts: dict[Key, ta.Any] = {
@@ -77,28 +72,31 @@ class AsyncInjectorImpl(AsyncInjector, lang.Final):
77
72
  }
78
73
 
79
74
  self._bim = ec.binding_impl_map()
75
+
80
76
  self._ekbs = ec.eager_keys_by_scope()
77
+
81
78
  self._pls: tuple[ProvisionListener, ...] = tuple(
82
79
  b.listener # type: ignore[attr-defined]
83
80
  for b in itertools.chain(
84
81
  ec.elements_of_type(ProvisionListenerBinding),
85
- (p._pls if p is not None else ()), # noqa
82
+ p._pls if p is not None else (), # noqa
86
83
  )
87
84
  )
88
85
 
89
- self._cs: weakref.WeakSet[AsyncInjectorImpl] | None = None # noqa
90
86
  self._root: AsyncInjectorImpl = p._root if p is not None else self # noqa
91
87
 
92
- self.__cur_req: AsyncInjectorImpl._Request | None = None
93
-
94
- ss = [
95
- *DEFAULT_SCOPES,
96
- *[sb.scope for sb in ec.elements_of_type(ScopeBinding)],
97
- ]
98
88
  self._scopes: dict[Scope, ScopeImpl] = {
99
- s: make_scope_impl(s) for s in ss
89
+ s: make_scope_impl(s)
90
+ for s in itertools.chain(
91
+ DEFAULT_SCOPES,
92
+ ec.scope_binding_scopes(),
93
+ )
100
94
  }
101
95
 
96
+ _cs: weakref.WeakSet['AsyncInjectorImpl'] | None = None # noqa
97
+
98
+ __cur_req: ta.Optional['AsyncInjectorImpl._Request'] = None
99
+
102
100
  #
103
101
 
104
102
  _has_run_init: bool = False
@@ -259,7 +257,7 @@ class AsyncInjectorImpl(AsyncInjector, lang.Final):
259
257
  return obj(**kws)
260
258
 
261
259
 
262
- async def create_async_injector(es: Elements) -> AsyncInjector:
263
- i = AsyncInjectorImpl(ElementCollection(es))
260
+ async def create_async_injector(ce: CollectedElements) -> AsyncInjector:
261
+ i = AsyncInjectorImpl(ce)
264
262
  await i._init() # noqa
265
263
  return i
@@ -1,13 +1,12 @@
1
1
  import typing as ta
2
2
 
3
3
  from ... import lang
4
- from ..elements import Elements
4
+ from ..elements import CollectedElements
5
5
  from ..injector import AsyncInjector
6
6
  from ..inspect import KwargsTarget
7
7
  from ..keys import Key
8
8
  from ..maysync import MaysyncInjector
9
9
  from ..sync import Injector
10
- from .elements import ElementCollection
11
10
  from .injector import AsyncInjectorImpl
12
11
 
13
12
 
@@ -30,10 +29,10 @@ class MaysyncInjectorImpl(MaysyncInjector, lang.Final):
30
29
  return lang.run_maysync(self._ai.inject(obj))
31
30
 
32
31
 
33
- def create_maysync_injector(es: Elements) -> MaysyncInjector:
32
+ def create_maysync_injector(ce: CollectedElements) -> MaysyncInjector:
34
33
  si = MaysyncInjectorImpl()
35
34
  ai = AsyncInjectorImpl(
36
- ElementCollection(es),
35
+ ce,
37
36
  internal_consts={
38
37
  Key(MaysyncInjector): si,
39
38
  Key(Injector): si,
@@ -1,12 +1,11 @@
1
1
  import typing as ta
2
2
 
3
3
  from ... import lang
4
- from ..elements import Elements
4
+ from ..elements import CollectedElements
5
5
  from ..injector import AsyncInjector
6
6
  from ..inspect import KwargsTarget
7
7
  from ..keys import Key
8
8
  from ..sync import Injector
9
- from .elements import ElementCollection
10
9
  from .injector import AsyncInjectorImpl
11
10
 
12
11
 
@@ -29,10 +28,10 @@ class InjectorImpl(Injector, lang.Final):
29
28
  return lang.sync_await(self._ai.inject(obj))
30
29
 
31
30
 
32
- def create_injector(es: Elements) -> Injector:
31
+ def create_injector(ce: CollectedElements) -> Injector:
33
32
  si = InjectorImpl()
34
33
  ai = AsyncInjectorImpl(
35
- ElementCollection(es),
34
+ ce,
36
35
  internal_consts={
37
36
  Key(Injector): si,
38
37
  },
omlish/inject/injector.py CHANGED
@@ -1,9 +1,12 @@
1
1
  import abc
2
2
  import typing as ta
3
3
 
4
+ from .. import check
4
5
  from .. import lang
6
+ from .elements import CollectedElements
5
7
  from .elements import Elemental
6
8
  from .elements import as_elements
9
+ from .elements import collect_elements
7
10
  from .inspect import KwargsTarget
8
11
  from .keys import Key
9
12
 
@@ -44,5 +47,31 @@ class AsyncInjector(lang.Abstract):
44
47
  return self.provide(target)
45
48
 
46
49
 
47
- def create_async_injector(*args: Elemental) -> ta.Awaitable[AsyncInjector]:
48
- return _injector.create_async_injector(as_elements(*args))
50
+ ##
51
+
52
+
53
+ @ta.final
54
+ class _InjectorCreator(ta.Generic[T]):
55
+ def __init__(self, fac: ta.Callable[[CollectedElements], T]) -> None:
56
+ self._fac = fac
57
+
58
+ @ta.overload
59
+ def __call__(self, es: CollectedElements, /) -> T: ...
60
+
61
+ @ta.overload
62
+ def __call__(self, *es: Elemental) -> T: ...
63
+
64
+ def __call__(self, arg0, *argv):
65
+ ce: CollectedElements
66
+ if isinstance(arg0, CollectedElements):
67
+ check.arg(not argv)
68
+ ce = arg0
69
+ else:
70
+ ce = collect_elements(as_elements(arg0, *argv))
71
+ return self._fac(ce)
72
+
73
+
74
+ ##
75
+
76
+
77
+ create_async_injector = _InjectorCreator[ta.Awaitable[AsyncInjector]](lambda ce: _injector.create_async_injector(ce))
omlish/inject/maysync.py CHANGED
@@ -1,8 +1,7 @@
1
1
  import typing as ta
2
2
 
3
3
  from .. import lang
4
- from .elements import Elemental
5
- from .elements import as_elements
4
+ from .injector import _InjectorCreator
6
5
  from .sync import Injector
7
6
 
8
7
 
@@ -25,5 +24,4 @@ class MaysyncInjector(Injector, lang.Abstract):
25
24
  ##
26
25
 
27
26
 
28
- def create_maysync_injector(*args: Elemental) -> MaysyncInjector:
29
- return _maysync.create_maysync_injector(as_elements(*args))
27
+ create_maysync_injector = _InjectorCreator[MaysyncInjector](lambda ce: _maysync.create_maysync_injector(ce))
omlish/inject/sync.py CHANGED
@@ -2,8 +2,7 @@ import abc
2
2
  import typing as ta
3
3
 
4
4
  from .. import lang
5
- from .elements import Elemental
6
- from .elements import as_elements
5
+ from .injector import _InjectorCreator
7
6
  from .inspect import KwargsTarget
8
7
  from .keys import Key
9
8
 
@@ -44,5 +43,7 @@ class Injector(lang.Abstract):
44
43
  return self.provide(target)
45
44
 
46
45
 
47
- def create_injector(*args: Elemental) -> Injector:
48
- return _sync.create_injector(as_elements(*args))
46
+ ##
47
+
48
+
49
+ create_injector = _InjectorCreator[Injector](lambda ce: _sync.create_injector(ce))
@@ -342,6 +342,8 @@ def proxy_import(
342
342
  spec: str,
343
343
  package: str | None = None,
344
344
  extras: ta.Iterable[str] | None = None,
345
+ *,
346
+ no_cache: bool = False,
345
347
  ) -> types.ModuleType:
346
348
  """'Legacy' proxy import mechanism."""
347
349
 
@@ -352,12 +354,19 @@ def proxy_import(
352
354
 
353
355
  def __getattr__(att): # noqa
354
356
  nonlocal omod
357
+
355
358
  if omod is None:
356
359
  omod = importlib.import_module(spec, package=package)
357
360
  if extras:
358
361
  for x in extras:
359
362
  importlib.import_module(f'{spec}.{x}', package=package)
360
- return getattr(omod, att)
363
+
364
+ v = getattr(omod, att)
365
+
366
+ if not no_cache:
367
+ setattr(lmod, att, v)
368
+
369
+ return v
361
370
 
362
371
  lmod = types.ModuleType(spec)
363
372
  lmod.__getattr__ = __getattr__ # type: ignore[method-assign]
omlish/lite/abstract.py CHANGED
@@ -3,6 +3,9 @@ import abc
3
3
  import typing as ta
4
4
 
5
5
 
6
+ T = ta.TypeVar('T')
7
+
8
+
6
9
  ##
7
10
 
8
11
 
@@ -14,25 +17,49 @@ def is_abstract_method(obj: ta.Any) -> bool:
14
17
  return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
15
18
 
16
19
 
17
- def update_abstracts(cls, *, force=False):
20
+ def compute_abstract_methods(cls: type) -> ta.FrozenSet[str]:
21
+ # ~> https://github.com/python/cpython/blob/f3476c6507381ca860eec0989f53647b13517423/Modules/_abc.c#L358
22
+
23
+ # Stage 1: direct abstract methods
24
+
25
+ abstracts = {
26
+ a
27
+ # Get items as a list to avoid mutation issues during iteration
28
+ for a, v in list(cls.__dict__.items())
29
+ if is_abstract_method(v)
30
+ }
31
+
32
+ # Stage 2: inherited abstract methods
33
+
34
+ for base in cls.__bases__:
35
+ # Get __abstractmethods__ from base if it exists
36
+ if (base_abstracts := getattr(base, _ABSTRACT_METHODS_ATTR, None)) is None:
37
+ continue
38
+
39
+ # Iterate over abstract methods in base
40
+ for key in base_abstracts:
41
+ # Check if this class has an attribute with this name
42
+ try:
43
+ value = getattr(cls, key)
44
+ except AttributeError:
45
+ # Attribute not found in this class, skip
46
+ continue
47
+
48
+ # Check if it's still abstract
49
+ if is_abstract_method(value):
50
+ abstracts.add(key)
51
+
52
+ return frozenset(abstracts)
53
+
54
+
55
+ def update_abstracts(cls: ta.Type[T], *, force: bool = False) -> ta.Type[T]:
18
56
  if not force and not hasattr(cls, _ABSTRACT_METHODS_ATTR):
19
57
  # Per stdlib: We check for __abstractmethods__ here because cls might by a C implementation or a python
20
58
  # implementation (especially during testing), and we want to handle both cases.
21
59
  return cls
22
60
 
23
- abstracts: ta.Set[str] = set()
24
-
25
- for scls in cls.__bases__:
26
- for name in getattr(scls, _ABSTRACT_METHODS_ATTR, ()):
27
- value = getattr(cls, name, None)
28
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
29
- abstracts.add(name)
30
-
31
- for name, value in cls.__dict__.items():
32
- if getattr(value, _IS_ABSTRACT_METHOD_ATTR, False):
33
- abstracts.add(name)
34
-
35
- setattr(cls, _ABSTRACT_METHODS_ATTR, frozenset(abstracts))
61
+ abstracts = compute_abstract_methods(cls)
62
+ setattr(cls, _ABSTRACT_METHODS_ATTR, abstracts)
36
63
  return cls
37
64
 
38
65
 
@@ -86,23 +113,26 @@ class Abstract:
86
113
  super().__init_subclass__(**kwargs)
87
114
 
88
115
  if not (Abstract in cls.__bases__ or abc.ABC in cls.__bases__):
89
- ams = {a: cls for a, o in cls.__dict__.items() if is_abstract_method(o)}
90
-
91
- seen = set(cls.__dict__)
92
- for b in cls.__bases__:
93
- ams.update({a: b for a in set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen}) # noqa
94
- seen.update(dir(b))
116
+ if ams := compute_abstract_methods(cls):
117
+ amd = {
118
+ a: mcls
119
+ for mcls in cls.__mro__[::-1]
120
+ for a in ams
121
+ if a in mcls.__dict__
122
+ }
95
123
 
96
- if ams:
97
124
  raise AbstractTypeError(
98
125
  f'Cannot subclass abstract class {cls.__name__} with abstract methods: ' +
99
126
  ', '.join(sorted([
100
127
  '.'.join([
101
- *([m] if (m := getattr(c, '__module__')) else []),
102
- getattr(c, '__qualname__', getattr(c, '__name__')),
128
+ *([
129
+ *([m] if (m := getattr(c, '__module__')) else []),
130
+ getattr(c, '__qualname__', getattr(c, '__name__')),
131
+ ] if c is not None else '?'),
103
132
  a,
104
133
  ])
105
- for a, c in ams.items()
134
+ for a in ams
135
+ for c in [amd.get(a)]
106
136
  ])),
107
137
  )
108
138
 
@@ -191,3 +191,17 @@ def dataclass_kw_only_init():
191
191
  return cls
192
192
 
193
193
  return inner
194
+
195
+
196
+ ##
197
+
198
+
199
+ @dc.dataclass()
200
+ class DataclassFieldRequiredError(Exception):
201
+ name: str
202
+
203
+
204
+ def dataclass_field_required(name: str) -> ta.Callable[[], ta.Any]:
205
+ def inner() -> ta.NoReturn:
206
+ raise DataclassFieldRequiredError(name)
207
+ return inner