omlish 0.0.0.dev484__py3-none-any.whl → 0.0.0.dev506__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.

Files changed (93) hide show
  1. omlish/CODESTYLE.md +345 -0
  2. omlish/README.md +199 -0
  3. omlish/__about__.py +12 -5
  4. omlish/_check.cc +209 -0
  5. omlish/check.py +11 -0
  6. omlish/dataclasses/__init__.py +4 -0
  7. omlish/dataclasses/impl/concerns/frozen.py +4 -1
  8. omlish/dataclasses/impl/generation/plans.py +2 -17
  9. omlish/dataclasses/impl/generation/processor.py +2 -2
  10. omlish/dataclasses/impl/processing/driving.py +13 -1
  11. omlish/dataclasses/tools/replace.py +27 -0
  12. omlish/diag/_pycharm/runhack.py +1 -1
  13. omlish/dispatch/functions.py +1 -1
  14. omlish/formats/json/stream/lexing.py +13 -5
  15. omlish/formats/json/stream/parsing.py +1 -1
  16. omlish/inject/README.md +430 -0
  17. omlish/inject/__init__.py +20 -11
  18. omlish/inject/_dataclasses.py +1545 -1383
  19. omlish/inject/binder.py +7 -4
  20. omlish/inject/eagers.py +2 -4
  21. omlish/inject/elements.py +4 -0
  22. omlish/inject/helpers/late.py +76 -0
  23. omlish/inject/{managed.py → helpers/managed.py} +37 -34
  24. omlish/inject/impl/elements.py +7 -4
  25. omlish/inject/impl/injector.py +14 -26
  26. omlish/inject/impl/inspect.py +0 -8
  27. omlish/inject/impl/origins.py +1 -0
  28. omlish/inject/impl/privates.py +2 -6
  29. omlish/inject/impl/providers.py +0 -4
  30. omlish/inject/impl/scopes.py +14 -18
  31. omlish/inject/inspect.py +10 -1
  32. omlish/inject/multis.py +0 -3
  33. omlish/inject/scopes.py +7 -5
  34. omlish/io/buffers.py +35 -8
  35. omlish/lang/__init__.py +10 -0
  36. omlish/lang/classes/simple.py +2 -1
  37. omlish/lang/iterables.py +6 -0
  38. omlish/lang/objects.py +13 -0
  39. omlish/lang/outcomes.py +1 -1
  40. omlish/lang/recursion.py +1 -1
  41. omlish/lang/sequences.py +33 -0
  42. omlish/lifecycles/README.md +30 -0
  43. omlish/lifecycles/__init__.py +87 -13
  44. omlish/lifecycles/_dataclasses.py +1388 -0
  45. omlish/lifecycles/base.py +178 -64
  46. omlish/lifecycles/contextmanagers.py +113 -4
  47. omlish/lifecycles/controller.py +150 -87
  48. omlish/lifecycles/injection.py +143 -0
  49. omlish/lifecycles/listeners.py +56 -0
  50. omlish/lifecycles/managed.py +142 -0
  51. omlish/lifecycles/manager.py +218 -93
  52. omlish/lifecycles/states.py +2 -0
  53. omlish/lifecycles/transitions.py +3 -0
  54. omlish/lifecycles/unwrap.py +57 -0
  55. omlish/lite/maybes.py +7 -0
  56. omlish/lite/typing.py +33 -0
  57. omlish/logs/_amalg.py +1 -1
  58. omlish/logs/all.py +36 -11
  59. omlish/logs/asyncs.py +73 -0
  60. omlish/logs/base.py +101 -12
  61. omlish/logs/bisync.py +99 -0
  62. omlish/logs/contexts.py +4 -1
  63. omlish/logs/lists.py +125 -0
  64. omlish/logs/modules.py +19 -1
  65. omlish/logs/std/loggers.py +6 -1
  66. omlish/logs/std/noisy.py +11 -9
  67. omlish/logs/{standard.py → std/standard.py} +3 -4
  68. omlish/logs/utils.py +16 -1
  69. omlish/marshal/_dataclasses.py +813 -813
  70. omlish/reflect/__init__.py +43 -26
  71. omlish/reflect/ops.py +10 -1
  72. omlish/specs/jmespath/_dataclasses.py +597 -597
  73. omlish/specs/jsonschema/keywords/_dataclasses.py +244 -244
  74. omlish/sql/__init__.py +24 -5
  75. omlish/sql/api/dbapi.py +1 -1
  76. omlish/sql/dbapi/__init__.py +15 -0
  77. omlish/sql/{dbapi.py → dbapi/drivers.py} +2 -2
  78. omlish/sql/queries/__init__.py +3 -0
  79. omlish/testing/pytest/plugins/asyncs/plugin.py +2 -0
  80. omlish/text/docwrap/cli.py +5 -0
  81. omlish/typedvalues/_collection.cc +500 -0
  82. omlish/typedvalues/collection.py +159 -62
  83. omlish/typedvalues/generic.py +5 -4
  84. omlish/typedvalues/values.py +6 -0
  85. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/METADATA +14 -9
  86. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/RECORD +92 -77
  87. omlish/lifecycles/abstract.py +0 -86
  88. /omlish/inject/{impl → helpers}/proxy.py +0 -0
  89. /omlish/sql/{abc.py → dbapi/abc.py} +0 -0
  90. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/WHEEL +0 -0
  91. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/entry_points.txt +0 -0
  92. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/licenses/LICENSE +0 -0
  93. {omlish-0.0.0.dev484.dist-info → omlish-0.0.0.dev506.dist-info}/top_level.txt +0 -0
omlish/inject/binder.py CHANGED
@@ -88,7 +88,7 @@ def bind(
88
88
  in_: Scope | None = None,
89
89
  singleton: bool = False,
90
90
 
91
- eager: bool = False,
91
+ eager: bool | int = False,
92
92
  expose: bool = False,
93
93
  ) -> Element | Elements:
94
94
  if obj is None or obj is inspect.Parameter.empty:
@@ -117,7 +117,10 @@ def bind(
117
117
  elif _is_fn(obj) and not has_to:
118
118
  sig = _inspect.signature(obj)
119
119
  ty = rfl.type_(sig.return_annotation)
120
- to_fn = obj
120
+ if inspect.iscoroutinefunction(obj):
121
+ to_async_fn = obj
122
+ else:
123
+ to_fn = obj
121
124
  key = Key(ty)
122
125
  else:
123
126
  if to_const is not None:
@@ -180,8 +183,8 @@ def bind(
180
183
 
181
184
  elements: list[Element] = [binding]
182
185
 
183
- if eager:
184
- elements.append(Eager(key))
186
+ if eager is not False:
187
+ elements.append(Eager(key, priority=eager if isinstance(eager, int) else 0))
185
188
  if expose:
186
189
  elements.append(Expose(key))
187
190
 
omlish/inject/eagers.py CHANGED
@@ -1,7 +1,3 @@
1
- """
2
- TODO:
3
- - SCOPED - eagers for EACH SCOPE
4
- """
5
1
  from .. import check
6
2
  from .. import dataclasses as dc
7
3
  from .. import lang
@@ -16,3 +12,5 @@ from .keys import Key
16
12
  @dc.extra_class_params(cache_hash=True)
17
13
  class Eager(Element, lang.Final):
18
14
  key: Key = dc.xfield(coerce=check.of_isinstance(Key))
15
+
16
+ priority: int = dc.xfield(0, kw_only=True)
omlish/inject/elements.py CHANGED
@@ -37,6 +37,10 @@ class Elements(lang.Final):
37
37
  for c in self.cs:
38
38
  yield from c
39
39
 
40
+ @property
41
+ def debug(self) -> ta.Sequence[Element]:
42
+ return list(self)
43
+
40
44
 
41
45
  Elemental: ta.TypeAlias = ta.Union[ # noqa
42
46
  Element,
@@ -0,0 +1,76 @@
1
+ """
2
+ A minor tidiness for hiding refs to [Async]Injector in circ-dep workarounds. Unfortunately unsuitable outside of
3
+ injection modules due to it itself being in here (as user code shouldn't reference injector guts), but at least it's
4
+ something.
5
+ """
6
+ import abc
7
+ import functools
8
+ import typing as ta
9
+
10
+ from ... import lang
11
+ from ... import reflect as rfl
12
+ from ..binder import bind
13
+ from ..elements import Elements
14
+ from ..elements import as_elements
15
+ from ..injector import AsyncInjector
16
+ from ..inspect import KwargsTarget
17
+ from ..keys import Key
18
+ from ..keys import as_key
19
+ from ..sync import Injector
20
+
21
+
22
+ T = ta.TypeVar('T')
23
+
24
+
25
+ ##
26
+
27
+
28
+ class Late(lang.Abstract, ta.Generic[T]):
29
+ @abc.abstractmethod
30
+ def __call__(self) -> T:
31
+ raise NotImplementedError
32
+
33
+
34
+ class AsyncLate(lang.Abstract, ta.Generic[T]):
35
+ @abc.abstractmethod
36
+ def __call__(self) -> ta.Awaitable[T]:
37
+ raise NotImplementedError
38
+
39
+
40
+ #
41
+
42
+
43
+ def _bind_late(
44
+ injector_cls: type,
45
+ late_cls: ta.Any,
46
+ inner: ta.Any,
47
+ outer: ta.Any | None = None,
48
+ ) -> Elements:
49
+ inner_key = as_key(inner)
50
+
51
+ outer_key: Key
52
+ outer_fac: ta.Callable[[ta.Any], ta.Any]
53
+ if outer is None:
54
+ inner_ann = rfl.to_annotation(inner_key.ty)
55
+ outer_ann = late_cls[inner_ann]
56
+ outer_key = Key(rfl.type_(outer_ann), tag=inner_key.tag)
57
+ outer_fac = lang.identity
58
+ else:
59
+ outer_key = as_key(outer)
60
+ outer_cls = rfl.get_concrete_type(outer_key.ty)
61
+ outer_fac = outer_cls # type: ignore[assignment]
62
+
63
+ return as_elements(
64
+ bind(outer_key, to_fn=KwargsTarget.of( # noqa
65
+ lambda i: outer_fac(functools.partial(i.provide, inner_key)),
66
+ i=injector_cls,
67
+ )),
68
+ )
69
+
70
+
71
+ def bind_late(inner: ta.Any, outer: ta.Any | None = None) -> Elements:
72
+ return _bind_late(Injector, Late, inner, outer)
73
+
74
+
75
+ def bind_async_late(inner: ta.Any, outer: ta.Any | None = None) -> Elements:
76
+ return _bind_late(AsyncInjector, AsyncLate, inner, outer)
@@ -5,20 +5,20 @@ TODO:
5
5
  import contextlib
6
6
  import typing as ta
7
7
 
8
- from .. import lang
9
- from .binder import bind
10
- from .elements import Elemental
11
- from .impl.inspect import build_kwargs_target
8
+ from ... import lang
9
+ from ..binder import bind
10
+ from ..elements import Elemental
11
+ from ..impl.inspect import build_kwargs_target
12
12
 
13
13
 
14
14
  if ta.TYPE_CHECKING:
15
- from . import injector as _injector
16
- from . import maysync as _maysync
17
- from . import sync as _sync
15
+ from .. import injector as _injector
16
+ from .. import maysync as _maysync
17
+ from .. import sync as _sync
18
18
  else:
19
- _injector = lang.proxy_import('.injector', __package__)
20
- _maysync = lang.proxy_import('.maysync', __package__)
21
- _sync = lang.proxy_import('.sync', __package__)
19
+ _injector = lang.proxy_import('..injector', __package__)
20
+ _maysync = lang.proxy_import('..maysync', __package__)
21
+ _sync = lang.proxy_import('..sync', __package__)
22
22
 
23
23
 
24
24
  T = ta.TypeVar('T')
@@ -27,14 +27,15 @@ T = ta.TypeVar('T')
27
27
  ##
28
28
 
29
29
 
30
- @contextlib.asynccontextmanager
31
- async def create_async_managed_injector(*args: Elemental) -> ta.AsyncGenerator['_injector.AsyncInjector']:
32
- ai = await _injector.create_async_injector(
33
- bind(contextlib.AsyncExitStack, singleton=True, eager=True),
34
- *args,
35
- )
36
- async with (await ai[contextlib.AsyncExitStack]):
37
- yield ai
30
+ def create_async_managed_injector(*args: Elemental) -> ta.AsyncContextManager['_injector.AsyncInjector']:
31
+ @contextlib.asynccontextmanager
32
+ async def inner():
33
+ async with contextlib.AsyncExitStack() as aes:
34
+ yield await _injector.create_async_injector(
35
+ bind(contextlib.AsyncExitStack, to_const=aes),
36
+ *args,
37
+ )
38
+ return inner()
38
39
 
39
40
 
40
41
  def make_async_managed_provider(
@@ -61,14 +62,15 @@ def make_async_managed_provider(
61
62
  ##
62
63
 
63
64
 
64
- @contextlib.contextmanager
65
- def create_managed_injector(*args: Elemental) -> ta.Generator['_sync.Injector']:
66
- i = _sync.create_injector(
67
- bind(contextlib.ExitStack, singleton=True, eager=True),
68
- *args,
69
- )
70
- with i[contextlib.ExitStack]:
71
- yield i
65
+ def create_managed_injector(*args: Elemental) -> ta.ContextManager['_sync.Injector']:
66
+ @contextlib.contextmanager
67
+ def inner():
68
+ with contextlib.ExitStack() as es:
69
+ yield _sync.create_injector(
70
+ bind(contextlib.ExitStack, to_const=es),
71
+ *args,
72
+ )
73
+ return inner()
72
74
 
73
75
 
74
76
  def make_managed_provider(
@@ -95,14 +97,15 @@ def make_managed_provider(
95
97
  ##
96
98
 
97
99
 
98
- @contextlib.contextmanager
99
- def create_maysync_managed_injector(*args: Elemental) -> ta.Generator['_maysync.MaysyncInjector']:
100
- i = _maysync.create_maysync_injector(
101
- bind(contextlib.ExitStack, singleton=True, eager=True),
102
- *args,
103
- )
104
- with i[contextlib.ExitStack]:
105
- yield i
100
+ def create_maysync_managed_injector(*args: Elemental) -> ta.ContextManager['_maysync.MaysyncInjector']:
101
+ @contextlib.contextmanager
102
+ def inner():
103
+ with contextlib.ExitStack() as es:
104
+ yield _maysync.create_maysync_injector(
105
+ bind(contextlib.ExitStack, to_const=es),
106
+ *args,
107
+ )
108
+ return inner()
106
109
 
107
110
 
108
111
  def make_maysync_managed_provider(
@@ -214,13 +214,16 @@ class ElementCollection(CollectedElements, lang.Final):
214
214
  return [sb.scope for sb in self.elements_of_type(ScopeBinding)]
215
215
 
216
216
  @lang.cached_function
217
- def eager_keys_by_scope(self) -> ta.Mapping[Scope, ta.Sequence[Key]]:
217
+ def sorted_eager_keys_by_scope(self) -> ta.Mapping[Scope, ta.Sequence[Key]]:
218
218
  bim = self.binding_impl_map()
219
- ret: dict[Scope, list[Key]] = {}
219
+ dct: dict[Scope, list[Eager]] = {}
220
220
  for e in self.elements_of_type(Eager):
221
221
  bi = bim[e.key]
222
- ret.setdefault(bi.scope, []).append(e.key)
223
- return ret
222
+ dct.setdefault(bi.scope, []).append(e)
223
+ return {
224
+ sc: tuple(eg.key for eg in sorted(egs, key=lambda eg: eg.priority))
225
+ for sc, egs in dct.items()
226
+ }
224
227
 
225
228
 
226
229
  ##
@@ -1,18 +1,3 @@
1
- """
2
- TODO:
3
- - ** can currently bind in a child/private scope shadowing an external parent binding **
4
- - better source tracking
5
- - scope bindings, auto in root
6
- - injector-internal / blacklisted bindings (Injector itself, default scopes) without rebuilding ElementCollection
7
- - config - proxies, impl select, etc
8
- - config is probably shared with ElementCollection... but not 'bound', must be shared everywhere
9
- - InjectorRoot object?
10
- - ** eagers in any scope, on scope init/open
11
- - unions - raise on ambiguous - usecase: sql.AsyncEngineLike
12
- - multiple live request scopes on single injector - use private injectors?
13
- - more listeners - UnboundKeyListener
14
- - lazy parent listener chain cache thing
15
- """
16
1
  import contextlib
17
2
  import functools
18
3
  import itertools
@@ -73,14 +58,14 @@ class AsyncInjectorImpl(AsyncInjector, lang.Final):
73
58
 
74
59
  self._bim = ec.binding_impl_map()
75
60
 
76
- self._ekbs = ec.eager_keys_by_scope()
61
+ self._ekbs = ec.sorted_eager_keys_by_scope()
77
62
 
78
- self._pls: tuple[ProvisionListener, ...] = tuple(
79
- b.listener # type: ignore[attr-defined]
80
- for b in itertools.chain(
81
- ec.elements_of_type(ProvisionListenerBinding),
82
- p._pls if p is not None else (), # noqa
83
- )
63
+ self._pls: tuple[ProvisionListener, ...] = (
64
+ *(
65
+ b.listener
66
+ for b in ec.elements_of_type(ProvisionListenerBinding)
67
+ ),
68
+ *(p._pls if p is not None else []), # noqa
84
69
  )
85
70
 
86
71
  self._root: AsyncInjectorImpl = p._root if p is not None else self # noqa
@@ -93,6 +78,9 @@ class AsyncInjectorImpl(AsyncInjector, lang.Final):
93
78
  )
94
79
  }
95
80
 
81
+ if self._p is not None:
82
+ self._p._add_child(self) # noqa
83
+
96
84
  _cs: weakref.WeakSet['AsyncInjectorImpl'] | None = None # noqa
97
85
 
98
86
  __cur_req: ta.Optional['AsyncInjectorImpl._Request'] = None
@@ -123,8 +111,8 @@ class AsyncInjectorImpl(AsyncInjector, lang.Final):
123
111
  def get_scope_impl(self, sc: Scope) -> ScopeImpl:
124
112
  return self._scopes[sc]
125
113
 
126
- def create_child(self, ec: ElementCollection) -> AsyncInjector:
127
- c = AsyncInjectorImpl(ec, self)
114
+ def _add_child(self, c: 'AsyncInjectorImpl') -> AsyncInjector:
115
+ check.isinstance(c, AsyncInjectorImpl)
128
116
  if self._cs is None:
129
117
  self._cs = weakref.WeakSet()
130
118
  self._cs.add(c)
@@ -257,7 +245,7 @@ class AsyncInjectorImpl(AsyncInjector, lang.Final):
257
245
  return obj(**kws)
258
246
 
259
247
 
260
- async def create_async_injector(ce: CollectedElements) -> AsyncInjector:
261
- i = AsyncInjectorImpl(ce)
248
+ async def create_async_injector(ce: CollectedElements, p: AsyncInjector | None = None) -> AsyncInjector:
249
+ i = AsyncInjectorImpl(ce, check.isinstance(p, (AsyncInjectorImpl, None)))
262
250
  await i._init() # noqa
263
251
  return i
@@ -1,11 +1,3 @@
1
- """
2
- TODO:
3
- - cache kwarg_keys
4
- - tag annotations? x: ta.Annotated[int, inj.Tag('foo')]
5
- - tag decorator - @inj.tag(x='foo')
6
- - *unpack optional here*
7
- - use ...metadata
8
- """
9
1
  import dataclasses as dc
10
2
  import inspect
11
3
  import typing as ta
@@ -21,6 +21,7 @@ ORIGIN_BASE_OFS = 2
21
21
  ORIGIN_IGNORED_PACKAGES = frozenset([
22
22
  __package__,
23
23
  __package__.rpartition('.')[0],
24
+ __package__.rpartition('.')[0] + '.helpers',
24
25
 
25
26
  lang.__name__,
26
27
  lang.functions.__name__,
@@ -1,12 +1,7 @@
1
- """
2
- TODO:
3
- - add origin to Id
4
- """
5
1
  import itertools
6
2
  import typing as ta
7
3
 
8
4
  from ... import cached
9
- from ... import check
10
5
  from ... import dataclasses as dc
11
6
  from ... import lang
12
7
  from ..bindings import Binding
@@ -20,6 +15,7 @@ from ..providers import Provider
20
15
  from ..scopes import Singleton
21
16
  from .elements import ElementCollection
22
17
  from .injector import AsyncInjectorImpl
18
+ from .injector import create_async_injector
23
19
  from .providers import InternalProvider
24
20
  from .providers import ProviderImpl
25
21
 
@@ -48,7 +44,7 @@ class PrivateInjectorProviderImpl(ProviderImpl, lang.Final):
48
44
  return ()
49
45
 
50
46
  async def provide(self, injector: AsyncInjector) -> ta.Any:
51
- return check.isinstance(injector, AsyncInjectorImpl).create_child(self.ec)
47
+ return await create_async_injector(self.ec, injector)
52
48
 
53
49
 
54
50
  ##
@@ -1,7 +1,3 @@
1
- """
2
- TODO:
3
- - required_keys
4
- """
5
1
  import abc
6
2
  import typing as ta
7
3
 
@@ -1,9 +1,3 @@
1
- """
2
- TODO:
3
- - ContextVar ('context')
4
- - greenlet?
5
- - dynamic? https://github.com/wrmsr/iceworm/blob/2f6b4d5e9d237ef9665f7d57cfa6ce328efa0757/iceworm/utils/inject.py#L44
6
- """
7
1
  import abc
8
2
  import contextlib
9
3
  import threading
@@ -154,18 +148,20 @@ class SeededScopeImpl(ScopeImpl):
154
148
  self._ii = check.isinstance(i, _injector.AsyncInjectorImpl)
155
149
  self._ssi = check.isinstance(self._ii.get_scope_impl(self._ss), SeededScopeImpl)
156
150
 
157
- @contextlib.asynccontextmanager
158
- async def __call__(self, seeds: ta.Mapping[Key, ta.Any]) -> ta.AsyncGenerator[None]:
159
- try:
160
- if self._ssi._st is not None: # noqa
161
- raise ScopeAlreadyOpenError(self._ss)
162
- self._ssi._st = SeededScopeImpl.State(dict(seeds)) # noqa
163
- await self._ii._instantiate_eagers(self._ss) # noqa
164
- yield
165
- finally:
166
- if self._ssi._st is None: # noqa
167
- raise ScopeNotOpenError(self._ss)
168
- self._ssi._st = None # noqa
151
+ def __call__(self, seeds: ta.Mapping[Key, ta.Any]) -> ta.AsyncContextManager[None]:
152
+ @contextlib.asynccontextmanager
153
+ async def inner():
154
+ try:
155
+ if self._ssi._st is not None: # noqa
156
+ raise ScopeAlreadyOpenError(self._ss)
157
+ self._ssi._st = SeededScopeImpl.State(dict(seeds)) # noqa
158
+ await self._ii._instantiate_eagers(self._ss) # noqa
159
+ yield
160
+ finally:
161
+ if self._ssi._st is None: # noqa
162
+ raise ScopeNotOpenError(self._ss)
163
+ self._ssi._st = None # noqa
164
+ return inner()
169
165
 
170
166
  def auto_elements(self) -> Elements:
171
167
  return as_elements(
omlish/inject/inspect.py CHANGED
@@ -58,7 +58,7 @@ class KwargsTarget(ta.NamedTuple):
58
58
 
59
59
  return cls(
60
60
  obj,
61
- [*kws, *kw_kwargs],
61
+ (*kws, *kw_kwargs),
62
62
  )
63
63
 
64
64
 
@@ -68,3 +68,12 @@ def tag(obj: T, **kwargs: ta.Any) -> T:
68
68
 
69
69
  def build_kwargs_target(obj: ta.Any, **kwargs: ta.Any) -> KwargsTarget:
70
70
  return _inspect.build_kwargs_target(obj, **kwargs)
71
+
72
+
73
+ ##
74
+
75
+
76
+ def target(**kwargs: ta.Any) -> ta.Callable[[ta.Any], KwargsTarget]:
77
+ def inner(obj: ta.Any) -> KwargsTarget:
78
+ return KwargsTarget.of(obj, **kwargs)
79
+ return inner
omlish/inject/multis.py CHANGED
@@ -1,8 +1,5 @@
1
1
  """
2
2
  TODO:
3
- - DynamicSetBinding / DynamicMapBinding ? provider of set[T] / map[K, V] ?
4
- - doable not guicey - too much dynamism
5
- - scopes
6
3
  """
7
4
  import collections.abc
8
5
  import typing as ta
omlish/inject/scopes.py CHANGED
@@ -89,14 +89,16 @@ def bind_scope_seed(k: ta.Any, ss: SeededScope) -> Element:
89
89
  ##
90
90
 
91
91
 
92
- @contextlib.asynccontextmanager
93
- async def async_enter_seeded_scope(
92
+ def async_enter_seeded_scope(
94
93
  i: '_injector.AsyncInjector',
95
94
  ss: SeededScope,
96
95
  keys: ta.Mapping[Key, ta.Any],
97
- ) -> ta.AsyncGenerator[None]:
98
- async with (await i.provide(Key(SeededScope.Manager, tag=ss)))(keys):
99
- yield
96
+ ) -> ta.AsyncContextManager[None]:
97
+ @contextlib.asynccontextmanager
98
+ async def inner():
99
+ async with (await i.provide(Key(SeededScope.Manager, tag=ss)))(keys):
100
+ yield
101
+ return inner()
100
102
 
101
103
 
102
104
  def enter_seeded_scope(
omlish/io/buffers.py CHANGED
@@ -190,16 +190,18 @@ class ReadableListBuffer:
190
190
  super().__init__()
191
191
 
192
192
  self._lst: list[bytes] = []
193
+ self._len = 0
193
194
 
194
195
  def __bool__(self) -> ta.NoReturn:
195
196
  raise TypeError("Use 'buf is not None' or 'len(buf)'.")
196
197
 
197
198
  def __len__(self) -> int:
198
- return sum(map(len, self._lst))
199
+ return self._len
199
200
 
200
201
  def feed(self, d: bytes) -> None:
201
202
  if d:
202
203
  self._lst.append(d)
204
+ self._len += len(d)
203
205
 
204
206
  def _chop(self, i: int, e: int) -> bytes:
205
207
  lst = self._lst
@@ -215,6 +217,8 @@ class ReadableListBuffer:
215
217
  *lst[i + 1:],
216
218
  ]
217
219
 
220
+ self._len -= len(o)
221
+
218
222
  return o
219
223
 
220
224
  def read(self, n: ta.Optional[int] = None) -> ta.Optional[bytes]:
@@ -224,6 +228,7 @@ class ReadableListBuffer:
224
228
 
225
229
  o = b''.join(self._lst)
226
230
  self._lst = []
231
+ self._len = 0
227
232
  return o
228
233
 
229
234
  if not (lst := self._lst):
@@ -293,7 +298,12 @@ class ReadableListBuffer:
293
298
  if not (b := self._raw.read1(n)):
294
299
  break
295
300
  self._buf.feed(b)
296
- return self._buf.read(n) or b''
301
+
302
+ if len(self._buf) >= n:
303
+ return self._buf.read(n) or b''
304
+
305
+ # EOF with a partial buffer: return what we have.
306
+ return self._buf.read() or b''
297
307
 
298
308
  def readall(self) -> bytes:
299
309
  buf = io.BytesIO()
@@ -343,7 +353,12 @@ class ReadableListBuffer:
343
353
  if not (b := await self._raw.read1(n)):
344
354
  break
345
355
  self._buf.feed(b)
346
- return self._buf.read(n) or b''
356
+
357
+ if len(self._buf) >= n:
358
+ return self._buf.read(n) or b''
359
+
360
+ # EOF with a partial buffer: return what we have.
361
+ return self._buf.read() or b''
347
362
 
348
363
  async def readall(self) -> bytes:
349
364
  buf = io.BytesIO()
@@ -396,16 +411,28 @@ class IncrementalWriteBuffer:
396
411
 
397
412
  t = 0
398
413
  for i, d in enumerate(lst): # noqa
399
- n = fn(check.not_empty(d))
414
+ d = check.not_empty(d)
415
+ n = fn(d)
400
416
  if not n:
401
417
  break
418
+
419
+ if n > len(d):
420
+ raise ValueError(n)
421
+
402
422
  t += n
403
423
 
424
+ if n < len(d):
425
+ # Short write - keep the remainder of this chunk and stop.
426
+ self._lst = [
427
+ d[n:],
428
+ *lst[i + 1:],
429
+ ]
430
+ self._pos += t
431
+ return t
432
+
404
433
  if t:
405
- self._lst = [
406
- *([d[n:]] if n < len(d) else []),
407
- *lst[i + 1:],
408
- ]
434
+ # Only fully-written chunks were consumed.
435
+ self._lst = lst[i + 1:]
409
436
  self._pos += t
410
437
 
411
438
  return t
omlish/lang/__init__.py CHANGED
@@ -341,6 +341,7 @@ with _auto_proxy_init(globals(), update_exports=True):
341
341
  ilen,
342
342
  take,
343
343
  consume,
344
+ opt_list,
344
345
  peek,
345
346
  chunk,
346
347
  interleave,
@@ -381,6 +382,8 @@ with _auto_proxy_init(globals(), update_exports=True):
381
382
  from .objects import ( # noqa
382
383
  arg_repr,
383
384
  opt_repr,
385
+ just_repr,
386
+ opt_or_just_repr,
384
387
 
385
388
  can_weakref,
386
389
 
@@ -466,6 +469,10 @@ with _auto_proxy_init(globals(), update_exports=True):
466
469
  iterslice,
467
470
  iterrange,
468
471
 
472
+ seqs_all,
473
+ seqs_equal,
474
+ seqs_identical,
475
+
469
476
  SeqView,
470
477
  )
471
478
 
@@ -621,6 +628,9 @@ with _auto_proxy_init(globals(), update_exports=True):
621
628
  Func2,
622
629
  Func3,
623
630
 
631
+ CachedFunc0,
632
+ AsyncCachedFunc0,
633
+
624
634
  typing_annotations_attr,
625
635
  )
626
636
 
@@ -3,6 +3,7 @@ import functools
3
3
  import threading
4
4
  import typing as ta
5
5
 
6
+ from ...lite.abstract import Abstract
6
7
  from .restrict import Final
7
8
  from .restrict import NotInstantiable
8
9
 
@@ -40,7 +41,7 @@ class _MarkerMeta(abc.ABCMeta):
40
41
  else:
41
42
  if set(namespace) - _MARKER_NAMESPACE_KEYS:
42
43
  raise TypeError('Markers must not include contents. Did you mean to use Namespace?')
43
- if Final not in bases:
44
+ if Final not in bases and Abstract not in bases:
44
45
  bases += (Final,)
45
46
 
46
47
  return super().__new__(mcls, name, bases, namespace)
omlish/lang/iterables.py CHANGED
@@ -26,6 +26,12 @@ def consume(it: ta.Iterable[ta.Any]) -> None:
26
26
  collections.deque(it, maxlen=0)
27
27
 
28
28
 
29
+ def opt_list(it: ta.Iterable[T] | None) -> list[T] | None:
30
+ if it is None:
31
+ return None
32
+ return list(it)
33
+
34
+
29
35
  def peek(vs: ta.Iterable[T]) -> tuple[T, ta.Iterator[T]]:
30
36
  it = iter(vs)
31
37
  v = next(it)