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.
- omlish/__about__.py +1 -1
- omlish/__init__.py +8 -0
- omlish/asyncs/__init__.py +18 -0
- omlish/asyncs/anyio.py +66 -0
- omlish/asyncs/flavors.py +227 -0
- omlish/asyncs/trio_asyncio.py +47 -0
- omlish/c3.py +1 -1
- omlish/cached.py +1 -2
- omlish/collections/__init__.py +4 -1
- omlish/collections/cache/impl.py +1 -1
- omlish/collections/indexed.py +1 -1
- omlish/collections/utils.py +38 -6
- omlish/configs/__init__.py +5 -0
- omlish/configs/classes.py +53 -0
- omlish/configs/dotenv.py +586 -0
- omlish/configs/props.py +589 -49
- omlish/dataclasses/impl/api.py +1 -1
- omlish/dataclasses/impl/as_.py +1 -1
- omlish/dataclasses/impl/fields.py +1 -0
- omlish/dataclasses/impl/init.py +1 -1
- omlish/dataclasses/impl/main.py +1 -0
- omlish/dataclasses/impl/metaclass.py +6 -1
- omlish/dataclasses/impl/order.py +1 -1
- omlish/dataclasses/impl/reflect.py +15 -2
- omlish/defs.py +1 -1
- omlish/diag/procfs.py +29 -1
- omlish/diag/procstats.py +32 -0
- omlish/diag/replserver/console.py +3 -3
- omlish/diag/replserver/server.py +6 -5
- omlish/diag/threads.py +86 -0
- omlish/docker.py +19 -0
- omlish/dynamic.py +2 -2
- omlish/fnpairs.py +121 -24
- omlish/graphs/dags.py +113 -0
- omlish/graphs/domination.py +268 -0
- omlish/graphs/trees.py +2 -2
- omlish/http/__init__.py +25 -0
- omlish/http/asgi.py +131 -0
- omlish/http/consts.py +31 -4
- omlish/http/cookies.py +194 -0
- omlish/http/dates.py +70 -0
- omlish/http/encodings.py +6 -0
- omlish/http/json.py +273 -0
- omlish/http/sessions.py +197 -0
- omlish/inject/__init__.py +8 -2
- omlish/inject/bindings.py +3 -3
- omlish/inject/exceptions.py +3 -3
- omlish/inject/impl/elements.py +46 -25
- omlish/inject/impl/injector.py +8 -5
- omlish/inject/impl/multis.py +74 -0
- omlish/inject/impl/providers.py +19 -39
- omlish/inject/{proxy.py → impl/proxy.py} +2 -2
- omlish/inject/impl/scopes.py +4 -2
- omlish/inject/injector.py +1 -0
- omlish/inject/keys.py +3 -9
- omlish/inject/multis.py +70 -0
- omlish/inject/providers.py +23 -23
- omlish/inject/scopes.py +7 -3
- omlish/inject/types.py +0 -8
- omlish/iterators.py +13 -0
- omlish/json.py +138 -1
- omlish/lang/__init__.py +8 -0
- omlish/lang/classes/restrict.py +1 -1
- omlish/lang/classes/virtual.py +2 -2
- omlish/lang/contextmanagers.py +64 -0
- omlish/lang/datetimes.py +6 -5
- omlish/lang/functions.py +10 -0
- omlish/lang/imports.py +11 -2
- omlish/lang/sys.py +7 -0
- omlish/lang/typing.py +1 -0
- omlish/logs/utils.py +1 -1
- omlish/marshal/datetimes.py +1 -1
- omlish/reflect.py +8 -2
- omlish/sql/__init__.py +9 -0
- omlish/sql/asyncs.py +148 -0
- omlish/sync.py +70 -0
- omlish/term.py +6 -1
- omlish/testing/pydevd.py +2 -0
- omlish/testing/pytest/__init__.py +5 -0
- omlish/testing/pytest/helpers.py +0 -24
- omlish/testing/pytest/inject/harness.py +1 -1
- omlish/testing/pytest/marks.py +48 -0
- omlish/testing/pytest/plugins/__init__.py +2 -0
- omlish/testing/pytest/plugins/managermarks.py +60 -0
- omlish/testing/testing.py +10 -0
- omlish/text/delimit.py +4 -0
- {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/METADATA +4 -1
- {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/RECORD +91 -70
- {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/WHEEL +1 -1
- {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev3.dist-info → omlish-0.0.0.dev5.dist-info}/top_level.txt +0 -0
omlish/http/sessions.py
ADDED
|
@@ -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.
|
|
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
|
-
|
|
45
|
-
return Binding(Key(
|
|
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:
|
omlish/inject/exceptions.py
CHANGED
|
@@ -17,17 +17,17 @@ class BaseKeyError(Exception):
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
@dc.dataclass()
|
|
20
|
-
class UnboundKeyError(
|
|
20
|
+
class UnboundKeyError(BaseKeyError):
|
|
21
21
|
pass
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
@dc.dataclass()
|
|
25
|
-
class DuplicateKeyError(
|
|
25
|
+
class DuplicateKeyError(BaseKeyError):
|
|
26
26
|
pass
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@dc.dataclass()
|
|
30
|
-
class CyclicDependencyError(
|
|
30
|
+
class CyclicDependencyError(BaseKeyError):
|
|
31
31
|
pass
|
|
32
32
|
|
|
33
33
|
|
omlish/inject/impl/elements.py
CHANGED
|
@@ -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
|
-
-
|
|
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 .
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
omlish/inject/impl/injector.py
CHANGED
|
@@ -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
|
|
75
|
-
self.provide(
|
|
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)
|
omlish/inject/impl/providers.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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,
|
omlish/inject/impl/scopes.py
CHANGED
|
@@ -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
|
-
|
|
145
|
-
self._ssi = check.isinstance(
|
|
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
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
|
-
|
|
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
|
-
|
|
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)
|