omlish 0.0.0.dev3__py3-none-any.whl → 0.0.0.dev5__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 (91) hide show
  1. omlish/__about__.py +1 -1
  2. omlish/__init__.py +8 -0
  3. omlish/asyncs/__init__.py +18 -0
  4. omlish/asyncs/anyio.py +66 -0
  5. omlish/asyncs/flavors.py +227 -0
  6. omlish/asyncs/trio_asyncio.py +47 -0
  7. omlish/c3.py +1 -1
  8. omlish/cached.py +1 -2
  9. omlish/collections/__init__.py +4 -1
  10. omlish/collections/cache/impl.py +1 -1
  11. omlish/collections/indexed.py +1 -1
  12. omlish/collections/utils.py +38 -6
  13. omlish/configs/__init__.py +5 -0
  14. omlish/configs/classes.py +53 -0
  15. omlish/configs/dotenv.py +586 -0
  16. omlish/configs/props.py +589 -49
  17. omlish/dataclasses/impl/api.py +1 -1
  18. omlish/dataclasses/impl/as_.py +1 -1
  19. omlish/dataclasses/impl/fields.py +1 -0
  20. omlish/dataclasses/impl/init.py +1 -1
  21. omlish/dataclasses/impl/main.py +1 -0
  22. omlish/dataclasses/impl/metaclass.py +6 -1
  23. omlish/dataclasses/impl/order.py +1 -1
  24. omlish/dataclasses/impl/reflect.py +15 -2
  25. omlish/defs.py +1 -1
  26. omlish/diag/procfs.py +29 -1
  27. omlish/diag/procstats.py +32 -0
  28. omlish/diag/replserver/console.py +3 -3
  29. omlish/diag/replserver/server.py +6 -5
  30. omlish/diag/threads.py +86 -0
  31. omlish/docker.py +19 -0
  32. omlish/dynamic.py +2 -2
  33. omlish/fnpairs.py +121 -24
  34. omlish/graphs/dags.py +113 -0
  35. omlish/graphs/domination.py +268 -0
  36. omlish/graphs/trees.py +2 -2
  37. omlish/http/__init__.py +25 -0
  38. omlish/http/asgi.py +131 -0
  39. omlish/http/consts.py +31 -4
  40. omlish/http/cookies.py +194 -0
  41. omlish/http/dates.py +70 -0
  42. omlish/http/encodings.py +6 -0
  43. omlish/http/json.py +273 -0
  44. omlish/http/sessions.py +197 -0
  45. omlish/inject/__init__.py +8 -2
  46. omlish/inject/bindings.py +3 -3
  47. omlish/inject/exceptions.py +3 -3
  48. omlish/inject/impl/elements.py +46 -25
  49. omlish/inject/impl/injector.py +8 -5
  50. omlish/inject/impl/multis.py +74 -0
  51. omlish/inject/impl/providers.py +19 -39
  52. omlish/inject/{proxy.py → impl/proxy.py} +2 -2
  53. omlish/inject/impl/scopes.py +4 -2
  54. omlish/inject/injector.py +1 -0
  55. omlish/inject/keys.py +3 -9
  56. omlish/inject/multis.py +70 -0
  57. omlish/inject/providers.py +23 -23
  58. omlish/inject/scopes.py +7 -3
  59. omlish/inject/types.py +0 -8
  60. omlish/iterators.py +13 -0
  61. omlish/json.py +138 -1
  62. omlish/lang/__init__.py +8 -0
  63. omlish/lang/classes/restrict.py +1 -1
  64. omlish/lang/classes/virtual.py +2 -2
  65. omlish/lang/contextmanagers.py +64 -0
  66. omlish/lang/datetimes.py +6 -5
  67. omlish/lang/functions.py +10 -0
  68. omlish/lang/imports.py +11 -2
  69. omlish/lang/sys.py +7 -0
  70. omlish/lang/typing.py +1 -0
  71. omlish/logs/utils.py +1 -1
  72. omlish/marshal/datetimes.py +1 -1
  73. omlish/reflect.py +8 -2
  74. omlish/sql/__init__.py +9 -0
  75. omlish/sql/asyncs.py +148 -0
  76. omlish/sync.py +70 -0
  77. omlish/term.py +6 -1
  78. omlish/testing/pydevd.py +2 -0
  79. omlish/testing/pytest/__init__.py +5 -0
  80. omlish/testing/pytest/helpers.py +0 -24
  81. omlish/testing/pytest/inject/harness.py +1 -1
  82. omlish/testing/pytest/marks.py +48 -0
  83. omlish/testing/pytest/plugins/__init__.py +2 -0
  84. omlish/testing/pytest/plugins/managermarks.py +60 -0
  85. omlish/testing/testing.py +10 -0
  86. omlish/text/delimit.py +4 -0
  87. {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/METADATA +4 -1
  88. {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/RECORD +91 -70
  89. {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/WHEEL +1 -1
  90. {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/LICENSE +0 -0
  91. {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,197 @@
1
+ import base64
2
+ import dataclasses as dc
3
+ import datetime
4
+ import hashlib
5
+ import hmac
6
+ import struct
7
+ import time
8
+ import typing as ta
9
+ import zlib
10
+
11
+ from .. import fnpairs as fps
12
+ from .. import lang
13
+ from .cookies import dump_cookie
14
+ from .cookies import parse_cookie
15
+ from .json import JSON_TAGGER
16
+
17
+
18
+ Session: ta.TypeAlias = dict[str, ta.Any]
19
+
20
+
21
+ ##
22
+
23
+
24
+ def base64_encode(b: bytes) -> bytes:
25
+ return base64.urlsafe_b64encode(b).rstrip(b'=')
26
+
27
+
28
+ def base64_decode(b: bytes) -> bytes:
29
+ b += b'=' * (-len(b) % 4)
30
+ return base64.urlsafe_b64decode(b)
31
+
32
+
33
+ def int_to_bytes(num: int) -> bytes:
34
+ return struct.pack('>Q', num).lstrip(b'\0')
35
+
36
+
37
+ def bytes_to_int(bytestr: bytes) -> int:
38
+ return struct.unpack('>Q', bytestr.rjust(8, b'\0'))[0]
39
+
40
+
41
+ ##
42
+
43
+
44
+ class Signer:
45
+ @dc.dataclass(frozen=True)
46
+ class Config:
47
+ secret_key: str
48
+ salt: str = 'cookie-session'
49
+
50
+ def __init__(self, config: Config) -> None:
51
+ super().__init__()
52
+
53
+ self._config = config
54
+
55
+ @lang.cached_function
56
+ def digest(self) -> ta.Any:
57
+ return hashlib.sha1
58
+
59
+ @lang.cached_function
60
+ def derive_key(self) -> bytes:
61
+ mac = hmac.new(self._config.secret_key.encode(), digestmod=self.digest())
62
+ mac.update(self._config.salt.encode())
63
+ return mac.digest()
64
+
65
+ def get_signature(self, value: bytes) -> bytes:
66
+ mac = hmac.new(self.derive_key(), msg=value, digestmod=self.digest())
67
+ return mac.digest()
68
+
69
+ def verify_signature(self, value: bytes, sig: bytes) -> bool:
70
+ return hmac.compare_digest(sig, self.get_signature(value))
71
+
72
+
73
+ ##
74
+
75
+
76
+ class SessionExpiredError(Exception):
77
+ pass
78
+
79
+
80
+ class SessionVerificationError(Exception):
81
+ pass
82
+
83
+
84
+ class SessionMarshal:
85
+ def __init__(
86
+ self,
87
+ signer: Signer,
88
+ serializer: fps.ObjectStr = fps.of(JSON_TAGGER.dumps, JSON_TAGGER.loads),
89
+ ) -> None:
90
+ super().__init__()
91
+
92
+ self._signer = signer
93
+ self._serializer = serializer
94
+
95
+ SEP = b'.'
96
+
97
+ def load(self, bs: bytes) -> ta.Any:
98
+ value, sig = bs.rsplit(self.SEP, 1)
99
+
100
+ sig_b = base64_decode(sig)
101
+
102
+ if not self._signer.verify_signature(value, sig_b):
103
+ raise SessionVerificationError
104
+
105
+ value, ts_bytes = value.rsplit(self.SEP, 1)
106
+ ts_int = bytes_to_int(base64_decode(ts_bytes))
107
+
108
+ max_age = 31 * 24 * 60 * 60
109
+ age = int(time.time()) - ts_int
110
+
111
+ if age > max_age:
112
+ raise SessionExpiredError
113
+ if age < 0:
114
+ raise SessionExpiredError
115
+
116
+ payload = value
117
+
118
+ decompress = False
119
+ if payload.startswith(b'.'):
120
+ payload = payload[1:]
121
+ decompress = True
122
+
123
+ jb = base64_decode(payload)
124
+
125
+ if decompress:
126
+ jb = zlib.decompress(jb)
127
+
128
+ jbs = jb.decode()
129
+
130
+ obj = self._serializer.backward(jbs)
131
+
132
+ return obj
133
+
134
+ def dump(self, obj: ta.Any) -> bytes:
135
+ jbs = self._serializer.forward(obj)
136
+
137
+ jb = jbs.encode()
138
+
139
+ is_compressed = False
140
+ compressed = zlib.compress(jb)
141
+
142
+ if len(compressed) < (len(jb) - 1):
143
+ jb = compressed
144
+ is_compressed = True
145
+
146
+ base64d = base64_encode(jb)
147
+
148
+ if is_compressed:
149
+ base64d = b'.' + base64d
150
+
151
+ payload = base64d
152
+
153
+ timestamp = base64_encode(int_to_bytes(int(time.time())))
154
+
155
+ value = payload + self.SEP + timestamp
156
+ return value + self.SEP + base64_encode(self._signer.get_signature(value))
157
+
158
+
159
+ ##
160
+
161
+
162
+ class CookieSessionStore:
163
+ @dc.dataclass(frozen=True)
164
+ class Config:
165
+ key: str = 'session'
166
+ max_age: datetime.timedelta | int | None = None
167
+
168
+ def __init__(self, marshal: SessionMarshal, config: Config = Config()) -> None:
169
+ super().__init__()
170
+
171
+ self._marshal = marshal
172
+ self._config = config
173
+
174
+ def extract(self, scope) -> Session:
175
+ for k, v in scope['headers']:
176
+ if k == b'cookie':
177
+ cks = parse_cookie(v.decode('latin-1', 'strict'))
178
+ sk = cks.get(self._config.key)
179
+ if sk:
180
+ return self._marshal.load(sk[0].encode('latin-1', 'strict'))
181
+
182
+ return {}
183
+
184
+ def build_headers(self, session: Session) -> list[tuple[bytes, bytes]]:
185
+ d = self._marshal.dump(session)
186
+
187
+ c = dump_cookie(
188
+ self._config.key,
189
+ d.decode('latin-1', 'strict'),
190
+ max_age=self._config.max_age,
191
+ httponly=True,
192
+ )
193
+
194
+ return [
195
+ (b'Vary', b'Cookie'),
196
+ (b'Set-Cookie', c.encode('latin-1', 'strict')),
197
+ ]
omlish/inject/__init__.py CHANGED
@@ -40,7 +40,6 @@ from .inspect import ( # noqa
40
40
  from .keys import ( # noqa
41
41
  Key,
42
42
  as_key,
43
- multi,
44
43
  tag,
45
44
  )
46
45
 
@@ -48,6 +47,14 @@ from .managed import ( # noqa
48
47
  create_managed_injector,
49
48
  )
50
49
 
50
+ from .multis import ( # noqa
51
+ MapBinding,
52
+ SetBinding,
53
+ bind_map_provider,
54
+ bind_set_provider,
55
+ )
56
+
57
+
51
58
  from .overrides import ( # noqa
52
59
  override,
53
60
  )
@@ -79,7 +86,6 @@ from .scopes import ( # noqa
79
86
  )
80
87
 
81
88
  from .types import ( # noqa
82
- Cls,
83
89
  Scope,
84
90
  Unscoped,
85
91
  )
omlish/inject/bindings.py CHANGED
@@ -36,13 +36,13 @@ def as_binding(o: ta.Any) -> Binding:
36
36
  return o
37
37
  check.not_isinstance(o, (Element, Elements))
38
38
  if isinstance(o, Provider):
39
- return Binding(Key(check.not_none(o.provided_cls())), o) # type: ignore # noqa
39
+ return Binding(Key(check.not_none(o.provided_ty())), o)
40
40
  if isinstance(o, type):
41
41
  return as_binding(ctor(o))
42
42
  if callable(o):
43
43
  return as_binding(fn(o))
44
- cls = type(o)
45
- return Binding(Key(cls), const(o, cls))
44
+ ty = type(o)
45
+ return Binding(Key(ty), const(o, ty))
46
46
 
47
47
 
48
48
  def as_(k: ta.Any, p: ta.Any) -> Binding:
@@ -17,17 +17,17 @@ class BaseKeyError(Exception):
17
17
 
18
18
 
19
19
  @dc.dataclass()
20
- class UnboundKeyError(KeyError):
20
+ class UnboundKeyError(BaseKeyError):
21
21
  pass
22
22
 
23
23
 
24
24
  @dc.dataclass()
25
- class DuplicateKeyError(KeyError):
25
+ class DuplicateKeyError(BaseKeyError):
26
26
  pass
27
27
 
28
28
 
29
29
  @dc.dataclass()
30
- class CyclicDependencyError(KeyError):
30
+ class CyclicDependencyError(BaseKeyError):
31
31
  pass
32
32
 
33
33
 
@@ -10,11 +10,13 @@ Multi's + Scopes:
10
10
 
11
11
  Element Types:
12
12
  - Binding
13
+ - SetBinding
14
+ - MapBinding
13
15
  - Eager
14
16
  - Overrides
15
17
  - Expose
16
18
  - Private
17
- - ScopeSeed
19
+ - ScopeBinding
18
20
  """
19
21
  import typing as ta
20
22
 
@@ -27,15 +29,22 @@ from ..elements import Element
27
29
  from ..elements import Elements
28
30
  from ..exceptions import DuplicateKeyError
29
31
  from ..keys import Key
32
+ from ..multis import MapBinding
33
+ from ..multis import MapProvider
34
+ from ..multis import SetBinding
35
+ from ..multis import SetProvider
30
36
  from ..overrides import Overrides
31
37
  from ..private import Expose
32
38
  from ..private import Private
33
39
  from ..scopes import ScopeBinding
40
+ from ..types import Scope
34
41
  from .bindings import BindingImpl
35
- from .providers import MultiProviderImpl
42
+ from .multis import make_multi_provider_impl
43
+ from .providers import ProviderImpl
36
44
  from .providers import make_provider_impl
37
45
  from .scopes import make_scope_impl
38
46
 
47
+
39
48
  if ta.TYPE_CHECKING:
40
49
  from . import private as private_
41
50
  else:
@@ -91,10 +100,13 @@ class ElementCollection(lang.Final):
91
100
  pi = self._get_private_info(e)
92
101
  self._build_raw_element_multimap(pi.owner_elements(), out)
93
102
 
103
+ elif isinstance(e, (SetBinding, MapBinding)):
104
+ add(e.multi_key, e)
105
+
94
106
  elif isinstance(e, Overrides):
95
107
  ovr = self._build_raw_element_multimap(e.ovr)
96
108
  src = self._build_raw_element_multimap(e.src)
97
- for k, b in src.items():
109
+ for k, b in src.items(): # FIXME: merge None keys?
98
110
  try:
99
111
  bs = ovr[k]
100
112
  except KeyError:
@@ -116,39 +128,48 @@ class ElementCollection(lang.Final):
116
128
 
117
129
  ##
118
130
 
119
- def _make_binding_impls(self, e: Element) -> ta.Iterable[BindingImpl]:
120
- if isinstance(e, Binding):
121
- p = make_provider_impl(e.provider)
122
- return (BindingImpl(e.key, p, e.scope, e),)
123
-
124
- elif isinstance(e, (Eager, Expose)):
125
- return ()
126
-
127
- else:
128
- raise TypeError(e)
129
-
130
131
  def _build_binding_impl_map(self, em: ta.Mapping[Key | None, ta.Sequence[Element]]) -> dict[Key, BindingImpl]:
131
132
  pm: dict[Key, BindingImpl] = {}
132
- mm: dict[Key, list[BindingImpl]] = {}
133
133
  for k, es in em.items():
134
134
  if k is None:
135
135
  continue
136
136
 
137
- bis = [bi for e in es for bi in self._make_binding_impls(e)]
138
- if k.multi:
139
- mm.setdefault(k, []).extend(bis)
140
- else:
141
- if len(bis) > 1:
137
+ es_by_ty = col.multi_map_by(type, es)
138
+
139
+ es_by_ty.pop(Eager, None)
140
+ es_by_ty.pop(Expose, None)
141
+
142
+ if (bs := es_by_ty.pop(Binding, None)):
143
+ if len(bs) > 1:
142
144
  raise DuplicateKeyError(k)
143
- [pm[k]] = bis
144
145
 
145
- if mm:
146
- for k, aps in mm.items():
147
- mp = MultiProviderImpl([ap.provider for ap in aps])
148
- pm[k] = BindingImpl(k, mp) # FIXME: SCOPING
146
+ b: Binding = check.isinstance(check.single(bs), Binding)
147
+ p: ProviderImpl
148
+
149
+ if isinstance(b.provider, (SetProvider, MapProvider)):
150
+ p = make_multi_provider_impl(b.provider, es_by_ty)
151
+
152
+ else:
153
+ p = make_provider_impl(b.provider)
154
+
155
+ pm[k] = BindingImpl(b.key, p, b.scope, b)
156
+
157
+ if es_by_ty:
158
+ raise TypeError(set(es_by_ty))
149
159
 
150
160
  return pm
151
161
 
152
162
  @lang.cached_function
153
163
  def binding_impl_map(self) -> ta.Mapping[Key, BindingImpl]:
154
164
  return self._build_binding_impl_map(self.element_multimap())
165
+
166
+ ##
167
+
168
+ @lang.cached_function
169
+ def eager_keys_by_scope(self) -> ta.Mapping[Scope, ta.Sequence[Key]]:
170
+ bim = self.binding_impl_map()
171
+ ret: dict[Scope, list[Key]] = {}
172
+ for e in self.elements_of_type(Eager):
173
+ bi = bim[e.key]
174
+ ret.setdefault(bi.scope, []).append(e.key)
175
+ return ret
@@ -7,6 +7,9 @@ TODO:
7
7
  - config is probably shared with ElementCollection... but not 'bound', must be shared everywhere
8
8
  - InjectorRoot object?
9
9
  - ** eagers in any scope, on scope init/open
10
+ - injection listeners
11
+ - unions - raise on ambiguous - usecase: sql.AsyncEngineLike
12
+ - multiple live request scopes on single injector - use private injectors?
10
13
  """
11
14
  import contextlib
12
15
  import typing as ta
@@ -14,7 +17,6 @@ import weakref
14
17
 
15
18
  from ... import check
16
19
  from ... import lang
17
- from ..eagers import Eager
18
20
  from ..elements import Elements
19
21
  from ..exceptions import CyclicDependencyError
20
22
  from ..exceptions import UnboundKeyError
@@ -52,6 +54,7 @@ class InjectorImpl(Injector, lang.Final):
52
54
  }
53
55
 
54
56
  self._bim = ec.binding_impl_map()
57
+ self._ekbs = ec.eager_keys_by_scope()
55
58
 
56
59
  self._cs: weakref.WeakSet[InjectorImpl] | None = None
57
60
  self._root: InjectorImpl = p._root if p is not None else self # noqa
@@ -66,13 +69,13 @@ class InjectorImpl(Injector, lang.Final):
66
69
  s: make_scope_impl(s) for s in ss
67
70
  }
68
71
 
69
- self._instantiate_eagers()
72
+ self._instantiate_eagers(Unscoped())
70
73
 
71
74
  _root: 'InjectorImpl'
72
75
 
73
- def _instantiate_eagers(self) -> None:
74
- for e in self._ec.elements_of_type(Eager):
75
- self.provide(e.key)
76
+ def _instantiate_eagers(self, sc: Scope) -> None:
77
+ for k in self._ekbs.get(sc, ()):
78
+ self.provide(k)
76
79
 
77
80
  def get_scope_impl(self, sc: Scope) -> ScopeImpl:
78
81
  return self._scopes[sc]
@@ -0,0 +1,74 @@
1
+ import typing as ta
2
+
3
+ from ... import dataclasses as dc
4
+ from ... import lang
5
+ from ..elements import Element
6
+ from ..injector import Injector
7
+ from ..multis import MapBinding
8
+ from ..multis import MapProvider
9
+ from ..multis import SetBinding
10
+ from ..multis import SetProvider
11
+ from ..providers import LinkProvider
12
+ from ..providers import Provider
13
+ from .providers import LinkProviderImpl
14
+ from .providers import ProviderImpl
15
+
16
+
17
+ @dc.dataclass(frozen=True, eq=False)
18
+ class SetProviderImpl(ProviderImpl, lang.Final):
19
+ ps: ta.Sequence[ProviderImpl]
20
+
21
+ @property
22
+ def providers(self) -> ta.Iterable[Provider]:
23
+ for p in self.ps:
24
+ yield from p.providers
25
+
26
+ def provide(self, injector: Injector) -> ta.Any:
27
+ rv = set()
28
+ for ep in self.ps:
29
+ o = ep.provide(injector)
30
+ rv.add(o)
31
+ return rv
32
+
33
+
34
+ @dc.dataclass(frozen=True, eq=False)
35
+ class MapProviderImpl(ProviderImpl, lang.Final):
36
+ class Entry(ta.NamedTuple):
37
+ k: ta.Any
38
+ v: ProviderImpl
39
+
40
+ es: ta.Sequence[Entry]
41
+
42
+ @property
43
+ def providers(self) -> ta.Iterable[Provider]:
44
+ for e in self.es:
45
+ yield from e.v.providers
46
+
47
+ def provide(self, injector: Injector) -> ta.Any:
48
+ rv = {}
49
+ for e in self.es:
50
+ o = e.v.provide(injector)
51
+ rv[e.k] = o
52
+ return rv
53
+
54
+
55
+ def make_multi_provider_impl(p: Provider, es_by_ty: ta.MutableMapping[type, list[Element]]) -> ProviderImpl:
56
+ if isinstance(p, SetProvider):
57
+ sbs: ta.Iterable[SetBinding] = es_by_ty.pop(SetBinding, ()) # type: ignore
58
+ return SetProviderImpl([
59
+ LinkProviderImpl(LinkProvider(sb.dst))
60
+ for sb in sbs
61
+ ])
62
+
63
+ elif isinstance(p, MapProvider):
64
+ mbs: ta.Iterable[MapBinding] = es_by_ty.pop(MapBinding, ()) # type: ignore
65
+ return MapProviderImpl([
66
+ MapProviderImpl.Entry(
67
+ mb.map_key,
68
+ LinkProviderImpl(LinkProvider(mb.dst)),
69
+ )
70
+ for mb in mbs
71
+ ])
72
+
73
+ else:
74
+ raise TypeError(p)
@@ -5,10 +5,9 @@ TODO:
5
5
  import abc
6
6
  import typing as ta
7
7
 
8
- from .. import Cls
9
- from ... import check
10
8
  from ... import dataclasses as dc
11
9
  from ... import lang
10
+ from ... import reflect as rfl
12
11
  from ..injector import Injector
13
12
  from ..inspect import KwargsTarget
14
13
  from ..providers import ConstProvider
@@ -19,6 +18,9 @@ from ..providers import Provider
19
18
  from .inspect import build_kwargs_target
20
19
 
21
20
 
21
+ ##
22
+
23
+
22
24
  class ProviderImpl(lang.Abstract):
23
25
  @property
24
26
  @abc.abstractmethod
@@ -30,14 +32,20 @@ class ProviderImpl(lang.Abstract):
30
32
  raise NotImplementedError
31
33
 
32
34
 
35
+ ##
36
+
37
+
33
38
  @dc.dataclass(frozen=True, eq=False)
34
39
  class InternalProvider(Provider):
35
40
  impl: ProviderImpl
36
41
 
37
- def provided_cls(self) -> Cls | None:
42
+ def provided_ty(self) -> rfl.Type | None:
38
43
  raise TypeError
39
44
 
40
45
 
46
+ ##
47
+
48
+
41
49
  @dc.dataclass(frozen=True, eq=False)
42
50
  class CallableProviderImpl(ProviderImpl, lang.Final):
43
51
  p: FnProvider | CtorProvider
@@ -51,6 +59,9 @@ class CallableProviderImpl(ProviderImpl, lang.Final):
51
59
  return injector.inject(self.kt)
52
60
 
53
61
 
62
+ ##
63
+
64
+
54
65
  @dc.dataclass(frozen=True, eq=False)
55
66
  class ConstProviderImpl(ProviderImpl, lang.Final):
56
67
  p: ConstProvider
@@ -63,6 +74,9 @@ class ConstProviderImpl(ProviderImpl, lang.Final):
63
74
  return self.p.v
64
75
 
65
76
 
77
+ ##
78
+
79
+
66
80
  @dc.dataclass(frozen=True, eq=False)
67
81
  class LinkProviderImpl(ProviderImpl, lang.Final):
68
82
  p: LinkProvider
@@ -75,46 +89,12 @@ class LinkProviderImpl(ProviderImpl, lang.Final):
75
89
  return injector.provide(self.p.k)
76
90
 
77
91
 
78
- _ILLEGAL_MULTI_TYPES = (str, bytes, bytearray)
79
-
80
-
81
- def _unnest_multi_providers(ps: ta.Iterable[ProviderImpl]) -> ta.Sequence[ProviderImpl]:
82
- lst = []
83
-
84
- def rec(o):
85
- if isinstance(o, MultiProviderImpl):
86
- for c in o.ps:
87
- rec(c)
88
- else:
89
- lst.append(check.isinstance(o, ProviderImpl))
90
-
91
- for o in ps:
92
- rec(o)
93
- return tuple(lst)
94
-
95
-
96
- @dc.dataclass(frozen=True, eq=False)
97
- class MultiProviderImpl(ProviderImpl, lang.Final):
98
- ps: ta.Sequence[ProviderImpl] = dc.xfield(coerce=_unnest_multi_providers)
99
-
100
- @property
101
- def providers(self) -> ta.Iterable[Provider]:
102
- for p in self.ps:
103
- yield from p.providers
104
-
105
- def provide(self, injector: Injector) -> ta.Any:
106
- rv = []
107
- for ep in self.ps:
108
- o = ep.provide(injector)
109
- if isinstance(o, _ILLEGAL_MULTI_TYPES):
110
- raise TypeError(o)
111
- rv.extend(o)
112
- return rv
92
+ ##
113
93
 
114
94
 
115
95
  PROVIDER_IMPLS_BY_PROVIDER: dict[type[Provider], ta.Callable[..., ProviderImpl]] = {
116
96
  FnProvider: lambda p: CallableProviderImpl(p, build_kwargs_target(p.fn)),
117
- CtorProvider: lambda p: CallableProviderImpl(p, build_kwargs_target(p.cls)),
97
+ CtorProvider: lambda p: CallableProviderImpl(p, build_kwargs_target(p.ty)),
118
98
  ConstProvider: ConstProviderImpl,
119
99
  LinkProvider: LinkProviderImpl,
120
100
  InternalProvider: lambda p: p.impl,
@@ -1,7 +1,7 @@
1
1
  import typing as ta
2
2
 
3
- from .. import lang
4
- from .. import check
3
+ from ... import check
4
+ from ... import lang
5
5
 
6
6
 
7
7
  @lang.cached_function
@@ -25,6 +25,7 @@ from .bindings import BindingImpl
25
25
  from .providers import PROVIDER_IMPLS_BY_PROVIDER
26
26
  from .providers import ProviderImpl
27
27
 
28
+
28
29
  if ta.TYPE_CHECKING:
29
30
  from . import injector as injector_
30
31
  else:
@@ -141,8 +142,8 @@ class SeededScopeImpl(ScopeImpl):
141
142
  def __init__(self, ss: SeededScope, i: Injector) -> None:
142
143
  super().__init__()
143
144
  self._ss = check.isinstance(ss, SeededScope)
144
- ii = check.isinstance(i, injector_.InjectorImpl)
145
- self._ssi = check.isinstance(ii.get_scope_impl(self._ss), SeededScopeImpl)
145
+ self._ii = check.isinstance(i, injector_.InjectorImpl)
146
+ self._ssi = check.isinstance(self._ii.get_scope_impl(self._ss), SeededScopeImpl)
146
147
 
147
148
  @contextlib.contextmanager
148
149
  def __call__(self, seeds: ta.Mapping[Key, ta.Any]) -> ta.Generator[None, None, None]:
@@ -150,6 +151,7 @@ class SeededScopeImpl(ScopeImpl):
150
151
  if self._ssi._st is not None: # noqa
151
152
  raise ScopeAlreadyOpenError(self._ss)
152
153
  self._ssi._st = SeededScopeImpl.State(dict(seeds)) # noqa
154
+ self._ii._instantiate_eagers(self._ss) # noqa
153
155
  yield
154
156
  finally:
155
157
  if self._ssi._st is None: # noqa
omlish/inject/injector.py CHANGED
@@ -6,6 +6,7 @@ from .elements import Elements
6
6
  from .inspect import KwargsTarget
7
7
  from .keys import Key
8
8
 
9
+
9
10
  _impl = lang.proxy_import('.impl.injector', __package__)
10
11
 
11
12
 
omlish/inject/keys.py CHANGED
@@ -3,6 +3,7 @@ import typing as ta
3
3
 
4
4
  from .. import dataclasses as dc
5
5
  from .. import lang
6
+ from .. import reflect as rfl
6
7
 
7
8
 
8
9
  T = ta.TypeVar('T')
@@ -14,9 +15,8 @@ T = ta.TypeVar('T')
14
15
  @dc.dataclass(frozen=True)
15
16
  @dc.extra_params(cache_hash=True)
16
17
  class Key(lang.Final, ta.Generic[T]):
17
- cls: type[T] | ta.NewType
18
+ ty: rfl.Type = dc.xfield(coerce=rfl.type_)
18
19
  tag: ta.Any = dc.field(default=None, kw_only=True)
19
- multi: bool = dc.field(default=False, kw_only=True)
20
20
 
21
21
 
22
22
  ##
@@ -27,17 +27,11 @@ def as_key(o: ta.Any) -> Key:
27
27
  raise TypeError(o)
28
28
  if isinstance(o, Key):
29
29
  return o
30
- if isinstance(o, (type, ta.NewType)): # noqa
31
- return Key(o)
32
- raise TypeError(o)
30
+ return Key(rfl.type_(o))
33
31
 
34
32
 
35
33
  ##
36
34
 
37
35
 
38
- def multi(o: ta.Any) -> Key:
39
- return dc.replace(as_key(o), multi=True)
40
-
41
-
42
36
  def tag(o: ta.Any, t: ta.Any) -> Key:
43
37
  return dc.replace(as_key(o), tag=t)