omlish 0.0.0.dev4__py3-none-any.whl → 0.0.0.dev6__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 (143) hide show
  1. omlish/__about__.py +1 -1
  2. omlish/__init__.py +1 -1
  3. omlish/asyncs/__init__.py +10 -4
  4. omlish/asyncs/anyio.py +142 -12
  5. omlish/asyncs/asyncio.py +23 -0
  6. omlish/asyncs/asyncs.py +9 -6
  7. omlish/asyncs/bridge.py +316 -0
  8. omlish/asyncs/flavors.py +27 -1
  9. omlish/asyncs/trio_asyncio.py +28 -18
  10. omlish/c3.py +1 -1
  11. omlish/cached.py +1 -2
  12. omlish/collections/__init__.py +5 -1
  13. omlish/collections/cache/impl.py +1 -1
  14. omlish/collections/identity.py +7 -0
  15. omlish/collections/indexed.py +1 -1
  16. omlish/collections/utils.py +38 -6
  17. omlish/configs/__init__.py +5 -0
  18. omlish/configs/classes.py +53 -0
  19. omlish/configs/strings.py +94 -0
  20. omlish/dataclasses/__init__.py +9 -0
  21. omlish/dataclasses/impl/api.py +1 -1
  22. omlish/dataclasses/impl/as_.py +1 -1
  23. omlish/dataclasses/impl/copy.py +30 -0
  24. omlish/dataclasses/impl/exceptions.py +6 -0
  25. omlish/dataclasses/impl/fields.py +25 -25
  26. omlish/dataclasses/impl/init.py +5 -3
  27. omlish/dataclasses/impl/main.py +3 -0
  28. omlish/dataclasses/impl/metaclass.py +6 -1
  29. omlish/dataclasses/impl/order.py +1 -1
  30. omlish/dataclasses/impl/reflect.py +15 -2
  31. omlish/dataclasses/utils.py +44 -0
  32. omlish/defs.py +1 -1
  33. omlish/diag/__init__.py +4 -0
  34. omlish/diag/procfs.py +31 -3
  35. omlish/diag/procstats.py +32 -0
  36. omlish/{testing → diag}/pydevd.py +35 -0
  37. omlish/diag/replserver/console.py +3 -3
  38. omlish/diag/replserver/server.py +6 -5
  39. omlish/diag/threads.py +86 -0
  40. omlish/dispatch/_dispatch2.py +65 -0
  41. omlish/dispatch/_dispatch3.py +104 -0
  42. omlish/docker.py +20 -1
  43. omlish/fnpairs.py +37 -18
  44. omlish/graphs/dags.py +113 -0
  45. omlish/graphs/domination.py +268 -0
  46. omlish/graphs/trees.py +2 -2
  47. omlish/http/__init__.py +25 -0
  48. omlish/http/asgi.py +132 -0
  49. omlish/http/collections.py +15 -0
  50. omlish/http/consts.py +47 -5
  51. omlish/http/cookies.py +194 -0
  52. omlish/http/dates.py +70 -0
  53. omlish/http/encodings.py +6 -0
  54. omlish/http/json.py +273 -0
  55. omlish/http/sessions.py +204 -0
  56. omlish/inject/__init__.py +51 -17
  57. omlish/inject/binder.py +185 -5
  58. omlish/inject/bindings.py +3 -36
  59. omlish/inject/eagers.py +2 -8
  60. omlish/inject/elements.py +30 -9
  61. omlish/inject/exceptions.py +3 -3
  62. omlish/inject/impl/elements.py +65 -31
  63. omlish/inject/impl/injector.py +20 -2
  64. omlish/inject/impl/inspect.py +33 -5
  65. omlish/inject/impl/multis.py +74 -0
  66. omlish/inject/impl/origins.py +75 -0
  67. omlish/inject/impl/{private.py → privates.py} +2 -2
  68. omlish/inject/impl/providers.py +19 -39
  69. omlish/inject/{proxy.py → impl/proxy.py} +2 -2
  70. omlish/inject/impl/scopes.py +7 -2
  71. omlish/inject/injector.py +9 -4
  72. omlish/inject/inspect.py +18 -0
  73. omlish/inject/keys.py +11 -23
  74. omlish/inject/listeners.py +26 -0
  75. omlish/inject/managed.py +76 -10
  76. omlish/inject/multis.py +120 -0
  77. omlish/inject/origins.py +27 -0
  78. omlish/inject/overrides.py +5 -4
  79. omlish/inject/{private.py → privates.py} +6 -10
  80. omlish/inject/providers.py +12 -85
  81. omlish/inject/scopes.py +20 -9
  82. omlish/inject/types.py +2 -8
  83. omlish/iterators.py +13 -0
  84. omlish/lang/__init__.py +12 -2
  85. omlish/lang/cached.py +2 -2
  86. omlish/lang/classes/restrict.py +3 -2
  87. omlish/lang/classes/simple.py +18 -8
  88. omlish/lang/classes/virtual.py +2 -2
  89. omlish/lang/contextmanagers.py +75 -2
  90. omlish/lang/datetimes.py +6 -5
  91. omlish/lang/descriptors.py +131 -0
  92. omlish/lang/functions.py +18 -28
  93. omlish/lang/imports.py +11 -2
  94. omlish/lang/iterables.py +20 -1
  95. omlish/lang/typing.py +6 -0
  96. omlish/lifecycles/__init__.py +34 -0
  97. omlish/lifecycles/abstract.py +43 -0
  98. omlish/lifecycles/base.py +51 -0
  99. omlish/lifecycles/contextmanagers.py +74 -0
  100. omlish/lifecycles/controller.py +116 -0
  101. omlish/lifecycles/manager.py +161 -0
  102. omlish/lifecycles/states.py +43 -0
  103. omlish/lifecycles/transitions.py +64 -0
  104. omlish/logs/formatters.py +1 -1
  105. omlish/logs/utils.py +1 -1
  106. omlish/marshal/__init__.py +4 -0
  107. omlish/marshal/datetimes.py +1 -1
  108. omlish/marshal/naming.py +4 -0
  109. omlish/marshal/objects.py +1 -0
  110. omlish/marshal/polymorphism.py +4 -4
  111. omlish/reflect.py +139 -18
  112. omlish/secrets/__init__.py +7 -0
  113. omlish/secrets/marshal.py +41 -0
  114. omlish/secrets/passwords.py +120 -0
  115. omlish/secrets/secrets.py +47 -0
  116. omlish/serde/__init__.py +0 -0
  117. omlish/serde/dotenv.py +574 -0
  118. omlish/{json.py → serde/json.py} +4 -2
  119. omlish/serde/props.py +604 -0
  120. omlish/serde/yaml.py +223 -0
  121. omlish/sql/dbs.py +1 -1
  122. omlish/sql/duckdb.py +136 -0
  123. omlish/sql/sqlean.py +17 -0
  124. omlish/sync.py +70 -0
  125. omlish/term.py +7 -2
  126. omlish/testing/pytest/__init__.py +8 -2
  127. omlish/testing/pytest/helpers.py +0 -24
  128. omlish/testing/pytest/inject/harness.py +4 -4
  129. omlish/testing/pytest/marks.py +45 -0
  130. omlish/testing/pytest/plugins/__init__.py +3 -0
  131. omlish/testing/pytest/plugins/asyncs.py +136 -0
  132. omlish/testing/pytest/plugins/managermarks.py +60 -0
  133. omlish/testing/pytest/plugins/pydevd.py +1 -1
  134. omlish/testing/testing.py +10 -0
  135. omlish/text/delimit.py +4 -0
  136. omlish/text/glyphsplit.py +92 -0
  137. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
  138. omlish-0.0.0.dev6.dist-info/RECORD +240 -0
  139. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +1 -1
  140. omlish/configs/props.py +0 -64
  141. omlish-0.0.0.dev4.dist-info/RECORD +0 -195
  142. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
  143. {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,204 @@
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 .. import secrets as sec
14
+ from .cookies import dump_cookie
15
+ from .cookies import parse_cookie
16
+ from .json import JSON_TAGGER
17
+
18
+
19
+ Session: ta.TypeAlias = dict[str, ta.Any]
20
+
21
+
22
+ ##
23
+
24
+
25
+ def base64_encode(b: bytes) -> bytes:
26
+ return base64.urlsafe_b64encode(b).rstrip(b'=')
27
+
28
+
29
+ def base64_decode(b: bytes) -> bytes:
30
+ b += b'=' * (-len(b) % 4)
31
+ return base64.urlsafe_b64decode(b)
32
+
33
+
34
+ def int_to_bytes(num: int) -> bytes:
35
+ return struct.pack('>Q', num).lstrip(b'\0')
36
+
37
+
38
+ def bytes_to_int(bytestr: bytes) -> int:
39
+ return struct.unpack('>Q', bytestr.rjust(8, b'\0'))[0]
40
+
41
+
42
+ ##
43
+
44
+
45
+ class Signer:
46
+ @dc.dataclass(frozen=True)
47
+ class Config:
48
+ secret_key: str | sec.Secret = dc.field()
49
+ salt: str = 'cookie-session'
50
+
51
+ def __init__(
52
+ self,
53
+ config: Config,
54
+ *,
55
+ secrets: sec.Secrets = sec.EMPTY_SECRETS,
56
+ ) -> None:
57
+ super().__init__()
58
+
59
+ self._config = config
60
+ self._secrets = secrets
61
+
62
+ @lang.cached_function
63
+ def digest(self) -> ta.Any:
64
+ return hashlib.sha1
65
+
66
+ @lang.cached_function
67
+ def derive_key(self) -> bytes:
68
+ mac = hmac.new(self._secrets.fix(self._config.secret_key).encode(), digestmod=self.digest())
69
+ mac.update(self._config.salt.encode())
70
+ return mac.digest()
71
+
72
+ def get_signature(self, value: bytes) -> bytes:
73
+ mac = hmac.new(self.derive_key(), msg=value, digestmod=self.digest())
74
+ return mac.digest()
75
+
76
+ def verify_signature(self, value: bytes, sig: bytes) -> bool:
77
+ return hmac.compare_digest(sig, self.get_signature(value))
78
+
79
+
80
+ ##
81
+
82
+
83
+ class SessionExpiredError(Exception):
84
+ pass
85
+
86
+
87
+ class SessionVerificationError(Exception):
88
+ pass
89
+
90
+
91
+ class SessionMarshal:
92
+ def __init__(
93
+ self,
94
+ signer: Signer,
95
+ serializer: fps.ObjectStr = fps.of(JSON_TAGGER.dumps, JSON_TAGGER.loads),
96
+ ) -> None:
97
+ super().__init__()
98
+
99
+ self._signer = signer
100
+ self._serializer = serializer
101
+
102
+ SEP = b'.'
103
+
104
+ def load(self, bs: bytes) -> ta.Any:
105
+ value, sig = bs.rsplit(self.SEP, 1)
106
+
107
+ sig_b = base64_decode(sig)
108
+
109
+ if not self._signer.verify_signature(value, sig_b):
110
+ raise SessionVerificationError
111
+
112
+ value, ts_bytes = value.rsplit(self.SEP, 1)
113
+ ts_int = bytes_to_int(base64_decode(ts_bytes))
114
+
115
+ max_age = 31 * 24 * 60 * 60
116
+ age = int(time.time()) - ts_int
117
+
118
+ if age > max_age:
119
+ raise SessionExpiredError
120
+ if age < 0:
121
+ raise SessionExpiredError
122
+
123
+ payload = value
124
+
125
+ decompress = False
126
+ if payload.startswith(b'.'):
127
+ payload = payload[1:]
128
+ decompress = True
129
+
130
+ jb = base64_decode(payload)
131
+
132
+ if decompress:
133
+ jb = zlib.decompress(jb)
134
+
135
+ jbs = jb.decode()
136
+
137
+ obj = self._serializer.backward(jbs)
138
+
139
+ return obj
140
+
141
+ def dump(self, obj: ta.Any) -> bytes:
142
+ jbs = self._serializer.forward(obj)
143
+
144
+ jb = jbs.encode()
145
+
146
+ is_compressed = False
147
+ compressed = zlib.compress(jb)
148
+
149
+ if len(compressed) < (len(jb) - 1):
150
+ jb = compressed
151
+ is_compressed = True
152
+
153
+ base64d = base64_encode(jb)
154
+
155
+ if is_compressed:
156
+ base64d = b'.' + base64d
157
+
158
+ payload = base64d
159
+
160
+ timestamp = base64_encode(int_to_bytes(int(time.time())))
161
+
162
+ value = payload + self.SEP + timestamp
163
+ return value + self.SEP + base64_encode(self._signer.get_signature(value))
164
+
165
+
166
+ ##
167
+
168
+
169
+ class CookieSessionStore:
170
+ @dc.dataclass(frozen=True)
171
+ class Config:
172
+ key: str = 'session'
173
+ max_age: datetime.timedelta | int | None = None
174
+
175
+ def __init__(self, marshal: SessionMarshal, config: Config = Config()) -> None:
176
+ super().__init__()
177
+
178
+ self._marshal = marshal
179
+ self._config = config
180
+
181
+ def extract(self, scope) -> Session:
182
+ for k, v in scope['headers']:
183
+ if k == b'cookie':
184
+ cks = parse_cookie(v.decode('latin-1', 'strict'))
185
+ sk = cks.get(self._config.key)
186
+ if sk:
187
+ return self._marshal.load(sk[0].encode('latin-1', 'strict'))
188
+
189
+ return {}
190
+
191
+ def build_headers(self, session: Session) -> list[tuple[bytes, bytes]]:
192
+ d = self._marshal.dump(session)
193
+
194
+ c = dump_cookie(
195
+ self._config.key,
196
+ d.decode('latin-1', 'strict'),
197
+ max_age=self._config.max_age,
198
+ httponly=True,
199
+ )
200
+
201
+ return [
202
+ (b'Vary', b'Cookie'),
203
+ (b'Set-Cookie', c.encode('latin-1', 'strict')),
204
+ ]
omlish/inject/__init__.py CHANGED
@@ -1,26 +1,30 @@
1
1
  """
2
2
  ~> https://github.com/google/guice/commit/70248eafa90cd70a68b293763e53f6aec656e73c
3
3
  """
4
+ from .binder import ( # noqa
5
+ bind,
6
+ bind_as_fn,
7
+ )
8
+
4
9
  from .bindings import ( # noqa
5
10
  Binding,
6
- as_,
7
- as_binding,
8
11
  )
9
12
 
10
13
  from .eagers import ( # noqa
11
- eager,
14
+ Eager,
12
15
  )
13
16
 
14
17
  from .elements import ( # noqa
15
- as_elements,
16
18
  Element,
19
+ Elemental,
17
20
  Elements,
21
+ as_elements,
18
22
  )
19
23
 
20
24
  from .exceptions import ( # noqa
21
25
  BaseKeyError,
26
+ ConflictingKeyError,
22
27
  CyclicDependencyError,
23
- DuplicateKeyError,
24
28
  ScopeAlreadyOpenError,
25
29
  ScopeError,
26
30
  ScopeNotOpenError,
@@ -35,51 +39,81 @@ from .injector import ( # noqa
35
39
  from .inspect import ( # noqa
36
40
  Kwarg,
37
41
  KwargsTarget,
42
+ build_kwargs_target,
43
+ tag,
38
44
  )
39
45
 
40
46
  from .keys import ( # noqa
41
47
  Key,
42
48
  as_key,
43
- multi,
44
- tag,
49
+ )
50
+
51
+ from .listeners import ( # noqa
52
+ ProvisionListener,
53
+ ProvisionListenerBinding,
54
+ bind_provision_listener,
45
55
  )
46
56
 
47
57
  from .managed import ( # noqa
58
+ create_async_managed_injector,
48
59
  create_managed_injector,
60
+ make_async_managed_provider,
61
+ make_managed_provider,
49
62
  )
50
63
 
64
+ from .multis import ( # noqa
65
+ MapBinding,
66
+ MapProvider,
67
+ SetBinding,
68
+ SetProvider,
69
+ MapBinder,
70
+ SetBinder,
71
+
72
+ MapBinder as map_binder, # noqa
73
+ SetBinder as set_binder, # noqa
74
+ )
75
+
76
+
51
77
  from .overrides import ( # noqa
78
+ Overrides,
52
79
  override,
53
80
  )
54
81
 
55
- from .private import ( # noqa
56
- expose,
82
+ from .origins import ( # noqa
83
+ HasOrigins,
84
+ Origin,
85
+ Origins,
86
+ )
87
+
88
+ from .privates import ( # noqa
89
+ Expose,
90
+ Private,
57
91
  private,
92
+
93
+ Expose as expose, # noqa
58
94
  )
59
95
 
60
96
  from .providers import ( # noqa
97
+ ConstProvider,
98
+ CtorProvider,
99
+ FnProvider,
100
+ LinkProvider,
61
101
  Provider,
62
- as_provider,
63
- const,
64
- ctor,
65
- fn,
66
- link,
67
102
  )
68
103
 
69
104
  from .scopes import ( # noqa
70
105
  ScopeBinding,
106
+ ScopeSeededProvider,
71
107
  SeededScope,
72
108
  Singleton,
73
109
  Thread,
74
110
  bind_scope,
75
111
  bind_scope_seed,
76
112
  enter_seeded_scope,
77
- in_,
78
- singleton,
79
113
  )
80
114
 
81
115
  from .types import ( # noqa
82
- Cls,
83
116
  Scope,
117
+ Tag,
84
118
  Unscoped,
85
119
  )
omlish/inject/binder.py CHANGED
@@ -1,12 +1,192 @@
1
+ """
2
+ TODO:
3
+ - multis?
4
+
5
+ class SetBinding(Element, lang.Final):
6
+ class SetProvider(Provider):
7
+
8
+ class MapBinding(Element, lang.Final):
9
+ class MapProvider(Provider):
10
+ """
11
+ import functools
12
+ import inspect
13
+ import types
1
14
  import typing as ta
2
15
 
16
+ from .. import check
17
+ from .. import dataclasses as dc
18
+ from .. import lang
19
+ from .. import reflect as rfl
20
+ from .bindings import Binding
21
+ from .eagers import Eager
3
22
  from .elements import Element
4
23
  from .elements import Elements
5
- from .elements import as_elements
24
+ from .keys import Key
25
+ from .keys import as_key
26
+ from .privates import Expose
27
+ from .providers import ConstProvider
28
+ from .providers import CtorProvider
29
+ from .providers import FnProvider
30
+ from .providers import LinkProvider
31
+ from .providers import Provider
32
+ from .scopes import SCOPE_ALIASES
33
+ from .scopes import Singleton
34
+ from .types import Scope
35
+ from .types import Unscoped
36
+
37
+
38
+ if ta.TYPE_CHECKING:
39
+ from .impl.inspect import inspect as _inspect
40
+ else:
41
+ _inspect = lang.proxy_import('.impl.inspect', __package__)
42
+
43
+
44
+ T = ta.TypeVar('T')
45
+
46
+
47
+ ##
48
+
49
+
50
+ _FN_TYPES: tuple[type, ...] = (
51
+ types.FunctionType,
52
+ types.MethodType,
53
+
54
+ classmethod,
55
+ staticmethod,
56
+
57
+ functools.partial,
58
+ functools.partialmethod,
59
+ )
60
+
61
+
62
+ def _is_fn(obj: ta.Any) -> bool:
63
+ return isinstance(obj, _FN_TYPES)
64
+
65
+
66
+ def bind_as_fn(cls: type[T]) -> type[T]:
67
+ check.isinstance(cls, type)
68
+ global _FN_TYPES
69
+ if cls not in _FN_TYPES:
70
+ _FN_TYPES = (*_FN_TYPES, cls)
71
+ return cls
72
+
73
+
74
+ ##
75
+
76
+
77
+ _BANNED_BIND_TYPES = (
78
+ Element,
79
+ Provider,
80
+ Elements,
81
+ Scope,
82
+ )
83
+
84
+
85
+ def bind(
86
+ obj: ta.Any,
87
+ *,
88
+ tag: ta.Any = None,
89
+
90
+ in_: Scope | None = None,
91
+ singleton: bool = False,
92
+
93
+ to_fn: ta.Any = None,
94
+ to_ctor: ta.Any = None,
95
+ to_const: ta.Any = None,
96
+ to_key: ta.Any = None,
97
+
98
+ eager: bool = False,
99
+ expose: bool = False,
100
+ ) -> Element | Elements:
101
+ if obj is None or obj is inspect.Parameter.empty:
102
+ raise TypeError(obj)
103
+ if isinstance(obj, _BANNED_BIND_TYPES):
104
+ raise TypeError(obj)
105
+
106
+ ##
107
+
108
+ has_to = (
109
+ to_fn is not None or
110
+ to_ctor is not None or
111
+ to_const is not None or
112
+ to_key is not None
113
+ )
114
+ if isinstance(obj, Key):
115
+ key = obj
116
+ elif isinstance(obj, type):
117
+ if not has_to:
118
+ to_ctor = obj
119
+ key = Key(obj)
120
+ elif isinstance(obj, rfl.TYPES) or rfl.is_type(obj):
121
+ key = Key(obj)
122
+ elif _is_fn(obj) and not has_to:
123
+ sig = _inspect.signature(obj)
124
+ ty = rfl.type_(sig.return_annotation)
125
+ to_fn = obj
126
+ key = Key(ty)
127
+ else:
128
+ if to_const is not None:
129
+ raise TypeError('Cannot bind instance with to_const')
130
+ to_const = obj
131
+ key = Key(type(obj))
132
+ del has_to
133
+
134
+ ##
135
+
136
+ if tag is not None:
137
+ if key.tag is not None:
138
+ raise TypeError('Tag already set')
139
+ key = dc.replace(key, tag=tag)
140
+
141
+ ##
142
+
143
+ providers: list[Provider] = []
144
+ if to_fn is not None:
145
+ providers.append(FnProvider(to_fn))
146
+ if to_ctor is not None:
147
+ providers.append(CtorProvider(to_ctor))
148
+ if to_const is not None:
149
+ providers.append(ConstProvider(to_const))
150
+ if to_key is not None:
151
+ providers.append(LinkProvider(as_key(to_key)))
152
+ if not providers:
153
+ raise TypeError('Must specify provider')
154
+ if len(providers) > 1:
155
+ raise TypeError('May not specify multiple providers')
156
+ provider, = providers
157
+
158
+ ##
159
+
160
+ scopes: list[Scope] = []
161
+ if in_ is not None:
162
+ if isinstance(in_, str):
163
+ scopes.append(SCOPE_ALIASES[in_])
164
+ else:
165
+ scopes.append(check.isinstance(in_, Scope))
166
+ if singleton:
167
+ scopes.append(Singleton())
168
+ if len(scopes) > 1:
169
+ raise TypeError('May not specify multiple scopes')
170
+ scope: Scope
171
+ if not scopes:
172
+ scope = Unscoped()
173
+ else:
174
+ scope, = scopes
175
+
176
+ ##
177
+
178
+ binding = Binding(key, provider, scope)
179
+
180
+ ##
6
181
 
182
+ elements: list[Element] = [binding]
7
183
 
8
- def bind(*args: ta.Any) -> Elements:
9
- if all(isinstance(a, (Element, Elements)) for a in args):
10
- return as_elements(*args)
184
+ if eager:
185
+ elements.append(Eager(key))
186
+ if expose:
187
+ elements.append(Expose(key))
11
188
 
12
- raise TypeError(args)
189
+ if len(elements) == 1:
190
+ return elements[0]
191
+ else:
192
+ return Elements(frozenset(elements))
omlish/inject/bindings.py CHANGED
@@ -1,49 +1,16 @@
1
- import typing as ta
2
-
3
1
  from .. import check
4
2
  from .. import dataclasses as dc
5
3
  from .. import lang
6
4
  from .elements import Element
7
- from .elements import Elements
8
5
  from .keys import Key
9
- from .keys import as_key
10
6
  from .providers import Provider
11
- from .providers import as_provider
12
- from .providers import const
13
- from .providers import ctor
14
- from .providers import fn
15
7
  from .types import Scope
16
8
  from .types import Unscoped
17
9
 
18
10
 
19
- ##
20
-
21
-
22
11
  @dc.dataclass(frozen=True)
23
12
  @dc.extra_params(cache_hash=True)
24
13
  class Binding(Element, lang.Final):
25
- key: Key
26
- provider: Provider
27
- scope: Scope = Unscoped()
28
-
29
-
30
- ##
31
-
32
-
33
- def as_binding(o: ta.Any) -> Binding:
34
- check.not_none(o)
35
- if isinstance(o, Binding):
36
- return o
37
- check.not_isinstance(o, (Element, Elements))
38
- if isinstance(o, Provider):
39
- return Binding(Key(check.not_none(o.provided_cls())), o) # type: ignore # noqa
40
- if isinstance(o, type):
41
- return as_binding(ctor(o))
42
- if callable(o):
43
- return as_binding(fn(o))
44
- cls = type(o)
45
- return Binding(Key(cls), const(o, cls))
46
-
47
-
48
- def as_(k: ta.Any, p: ta.Any) -> Binding:
49
- return Binding(as_key(k), as_provider(p))
14
+ key: Key = dc.xfield(coerce=check.of_isinstance(Key))
15
+ provider: Provider = dc.xfield(coerce=check.of_isinstance(Provider))
16
+ scope: Scope = dc.xfield(default=Unscoped(), coerce=check.of_isinstance(Scope))
omlish/inject/eagers.py CHANGED
@@ -2,20 +2,14 @@
2
2
  TODO:
3
3
  - SCOPED - eagers for EACH SCOPE
4
4
  """
5
- import typing as ta
6
-
5
+ from .. import check
7
6
  from .. import dataclasses as dc
8
7
  from .. import lang
9
8
  from .elements import Element
10
9
  from .keys import Key
11
- from .keys import as_key
12
10
 
13
11
 
14
12
  @dc.dataclass(frozen=True)
15
13
  @dc.extra_params(cache_hash=True)
16
14
  class Eager(Element, lang.Final):
17
- key: Key
18
-
19
-
20
- def eager(k: ta.Any) -> Element:
21
- return Eager(as_key(k))
15
+ key: Key = dc.xfield(coerce=check.of_isinstance(Key))
omlish/inject/elements.py CHANGED
@@ -1,21 +1,26 @@
1
- """
2
- TODO:
3
- - as_element[s] - universal
4
- """
1
+ import abc
5
2
  import typing as ta
6
3
 
4
+ from .. import check
7
5
  from .. import dataclasses as dc
8
6
  from .. import lang
7
+ from .impl.origins import HasOriginsImpl
9
8
 
10
9
 
11
- class Element(lang.Abstract):
10
+ class Element(HasOriginsImpl, lang.Abstract, lang.PackageSealed):
12
11
  pass
13
12
 
14
13
 
14
+ class ElementGenerator(lang.Abstract, lang.PackageSealed):
15
+ @abc.abstractmethod
16
+ def __iter__(self) -> ta.Iterator[Element]:
17
+ raise NotImplementedError
18
+
19
+
15
20
  @dc.dataclass(frozen=True)
16
21
  class Elements(lang.Final):
17
- es: frozenset[Element] | None = None
18
- cs: frozenset['Elements'] | None = None
22
+ es: frozenset[Element] | None = dc.xfield(None, coerce=check.of_isinstance((frozenset, None)))
23
+ cs: frozenset['Elements'] | None = dc.xfield(None, coerce=check.of_isinstance((frozenset, None)))
19
24
 
20
25
  def __iter__(self) -> ta.Generator[Element, None, None]:
21
26
  if self.es:
@@ -25,18 +30,34 @@ class Elements(lang.Final):
25
30
  yield from c
26
31
 
27
32
 
28
- def as_elements(*args: Element | Elements) -> Elements:
33
+ Elemental = ta.Union[ # noqa
34
+ Element,
35
+ Elements,
36
+ ElementGenerator,
37
+ ]
38
+
39
+
40
+ def as_elements(*args: Elemental) -> Elements:
29
41
  es: set[Element] = set()
30
42
  cs: set[Elements] = set()
31
- for a in args:
43
+
44
+ def rec(a):
32
45
  if isinstance(a, Element):
33
46
  es.add(a)
34
47
  elif isinstance(a, Elements):
35
48
  cs.add(a)
49
+ elif isinstance(a, ElementGenerator):
50
+ for n in a:
51
+ rec(n)
36
52
  else:
37
53
  raise TypeError(a)
54
+
55
+ for a in args:
56
+ rec(a)
57
+
38
58
  if not es and len(cs) == 1:
39
59
  return next(iter(cs))
60
+
40
61
  return Elements(
41
62
  frozenset(es) if es else None,
42
63
  frozenset(cs) if cs else None,
@@ -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 ConflictingKeyError(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