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.
- omlish/__about__.py +1 -1
- omlish/__init__.py +1 -1
- omlish/asyncs/__init__.py +10 -4
- omlish/asyncs/anyio.py +142 -12
- omlish/asyncs/asyncio.py +23 -0
- omlish/asyncs/asyncs.py +9 -6
- omlish/asyncs/bridge.py +316 -0
- omlish/asyncs/flavors.py +27 -1
- omlish/asyncs/trio_asyncio.py +28 -18
- omlish/c3.py +1 -1
- omlish/cached.py +1 -2
- omlish/collections/__init__.py +5 -1
- omlish/collections/cache/impl.py +1 -1
- omlish/collections/identity.py +7 -0
- 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/strings.py +94 -0
- omlish/dataclasses/__init__.py +9 -0
- omlish/dataclasses/impl/api.py +1 -1
- omlish/dataclasses/impl/as_.py +1 -1
- omlish/dataclasses/impl/copy.py +30 -0
- omlish/dataclasses/impl/exceptions.py +6 -0
- omlish/dataclasses/impl/fields.py +25 -25
- omlish/dataclasses/impl/init.py +5 -3
- omlish/dataclasses/impl/main.py +3 -0
- omlish/dataclasses/impl/metaclass.py +6 -1
- omlish/dataclasses/impl/order.py +1 -1
- omlish/dataclasses/impl/reflect.py +15 -2
- omlish/dataclasses/utils.py +44 -0
- omlish/defs.py +1 -1
- omlish/diag/__init__.py +4 -0
- omlish/diag/procfs.py +31 -3
- omlish/diag/procstats.py +32 -0
- omlish/{testing → diag}/pydevd.py +35 -0
- omlish/diag/replserver/console.py +3 -3
- omlish/diag/replserver/server.py +6 -5
- omlish/diag/threads.py +86 -0
- omlish/dispatch/_dispatch2.py +65 -0
- omlish/dispatch/_dispatch3.py +104 -0
- omlish/docker.py +20 -1
- omlish/fnpairs.py +37 -18
- 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 +132 -0
- omlish/http/collections.py +15 -0
- omlish/http/consts.py +47 -5
- 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 +204 -0
- omlish/inject/__init__.py +51 -17
- omlish/inject/binder.py +185 -5
- omlish/inject/bindings.py +3 -36
- omlish/inject/eagers.py +2 -8
- omlish/inject/elements.py +30 -9
- omlish/inject/exceptions.py +3 -3
- omlish/inject/impl/elements.py +65 -31
- omlish/inject/impl/injector.py +20 -2
- omlish/inject/impl/inspect.py +33 -5
- omlish/inject/impl/multis.py +74 -0
- omlish/inject/impl/origins.py +75 -0
- omlish/inject/impl/{private.py → privates.py} +2 -2
- omlish/inject/impl/providers.py +19 -39
- omlish/inject/{proxy.py → impl/proxy.py} +2 -2
- omlish/inject/impl/scopes.py +7 -2
- omlish/inject/injector.py +9 -4
- omlish/inject/inspect.py +18 -0
- omlish/inject/keys.py +11 -23
- omlish/inject/listeners.py +26 -0
- omlish/inject/managed.py +76 -10
- omlish/inject/multis.py +120 -0
- omlish/inject/origins.py +27 -0
- omlish/inject/overrides.py +5 -4
- omlish/inject/{private.py → privates.py} +6 -10
- omlish/inject/providers.py +12 -85
- omlish/inject/scopes.py +20 -9
- omlish/inject/types.py +2 -8
- omlish/iterators.py +13 -0
- omlish/lang/__init__.py +12 -2
- omlish/lang/cached.py +2 -2
- omlish/lang/classes/restrict.py +3 -2
- omlish/lang/classes/simple.py +18 -8
- omlish/lang/classes/virtual.py +2 -2
- omlish/lang/contextmanagers.py +75 -2
- omlish/lang/datetimes.py +6 -5
- omlish/lang/descriptors.py +131 -0
- omlish/lang/functions.py +18 -28
- omlish/lang/imports.py +11 -2
- omlish/lang/iterables.py +20 -1
- omlish/lang/typing.py +6 -0
- omlish/lifecycles/__init__.py +34 -0
- omlish/lifecycles/abstract.py +43 -0
- omlish/lifecycles/base.py +51 -0
- omlish/lifecycles/contextmanagers.py +74 -0
- omlish/lifecycles/controller.py +116 -0
- omlish/lifecycles/manager.py +161 -0
- omlish/lifecycles/states.py +43 -0
- omlish/lifecycles/transitions.py +64 -0
- omlish/logs/formatters.py +1 -1
- omlish/logs/utils.py +1 -1
- omlish/marshal/__init__.py +4 -0
- omlish/marshal/datetimes.py +1 -1
- omlish/marshal/naming.py +4 -0
- omlish/marshal/objects.py +1 -0
- omlish/marshal/polymorphism.py +4 -4
- omlish/reflect.py +139 -18
- omlish/secrets/__init__.py +7 -0
- omlish/secrets/marshal.py +41 -0
- omlish/secrets/passwords.py +120 -0
- omlish/secrets/secrets.py +47 -0
- omlish/serde/__init__.py +0 -0
- omlish/serde/dotenv.py +574 -0
- omlish/{json.py → serde/json.py} +4 -2
- omlish/serde/props.py +604 -0
- omlish/serde/yaml.py +223 -0
- omlish/sql/dbs.py +1 -1
- omlish/sql/duckdb.py +136 -0
- omlish/sql/sqlean.py +17 -0
- omlish/sync.py +70 -0
- omlish/term.py +7 -2
- omlish/testing/pytest/__init__.py +8 -2
- omlish/testing/pytest/helpers.py +0 -24
- omlish/testing/pytest/inject/harness.py +4 -4
- omlish/testing/pytest/marks.py +45 -0
- omlish/testing/pytest/plugins/__init__.py +3 -0
- omlish/testing/pytest/plugins/asyncs.py +136 -0
- omlish/testing/pytest/plugins/managermarks.py +60 -0
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/testing.py +10 -0
- omlish/text/delimit.py +4 -0
- omlish/text/glyphsplit.py +92 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/METADATA +1 -1
- omlish-0.0.0.dev6.dist-info/RECORD +240 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/WHEEL +1 -1
- omlish/configs/props.py +0 -64
- omlish-0.0.0.dev4.dist-info/RECORD +0 -195
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev6.dist-info}/top_level.txt +0 -0
omlish/http/sessions.py
ADDED
|
@@ -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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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 .
|
|
56
|
-
|
|
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 .
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
184
|
+
if eager:
|
|
185
|
+
elements.append(Eager(key))
|
|
186
|
+
if expose:
|
|
187
|
+
elements.append(Expose(key))
|
|
11
188
|
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
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
|
|
25
|
+
class ConflictingKeyError(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
|
|