omlish 0.0.0.dev339__py3-none-any.whl → 0.0.0.dev341__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.
- omlish/__about__.py +2 -2
- omlish/asyncs/bluelet/LICENSE +1 -1
- omlish/asyncs/bluelet/api.py +1 -1
- omlish/asyncs/bluelet/core.py +1 -1
- omlish/asyncs/bluelet/events.py +1 -1
- omlish/asyncs/bluelet/files.py +1 -1
- omlish/asyncs/bluelet/runner.py +1 -1
- omlish/asyncs/bluelet/sockets.py +1 -1
- omlish/check.py +197 -30
- omlish/collections/__init__.py +19 -0
- omlish/collections/multimaps.py +151 -0
- omlish/formats/json/__init__.py +13 -13
- omlish/formats/json/backends/__init__.py +2 -2
- omlish/formats/json/backends/default.py +56 -12
- omlish/formats/json/backends/orjson.py +6 -5
- omlish/formats/json/backends/std.py +4 -1
- omlish/formats/json/backends/ujson.py +6 -5
- omlish/formats/json/codecs.py +4 -4
- omlish/graphs/dags.py +112 -48
- omlish/graphs/domination.py +5 -1
- omlish/graphs/dot/items.py +3 -0
- omlish/graphs/dot/make.py +3 -0
- omlish/graphs/dot/rendering.py +3 -0
- omlish/graphs/dot/utils.py +3 -0
- omlish/graphs/trees.py +5 -4
- omlish/lang/classes/bindable.py +2 -0
- omlish/lite/check.py +6 -6
- omlish/math/__init__.py +15 -15
- omlish/math/fixed.py +25 -15
- omlish/os/forkhooks.py +4 -4
- omlish/typedvalues/__init__.py +4 -0
- omlish/typedvalues/collection.py +3 -0
- omlish/typedvalues/consumer.py +12 -0
- {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/RECORD +39 -39
- omlish/formats/json/json.py +0 -17
- {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
omlish/asyncs/bluelet/LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Based on bluelet ( https://github.com/sampsyo/bluelet ) by Adrian Sampson, original license:
|
2
2
|
|
3
|
-
THE SOFTWARE IS PROVIDED
|
3
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
4
4
|
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
5
5
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
6
6
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
omlish/asyncs/bluelet/api.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
3
|
# Based on bluelet ( https://github.com/sampsyo/bluelet ) by Adrian Sampson, original license:
|
4
|
-
# THE SOFTWARE IS PROVIDED
|
4
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
5
5
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
6
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
7
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
omlish/asyncs/bluelet/core.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
3
|
# Based on bluelet ( https://github.com/sampsyo/bluelet ) by Adrian Sampson, original license:
|
4
|
-
# THE SOFTWARE IS PROVIDED
|
4
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
5
5
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
6
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
7
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
omlish/asyncs/bluelet/events.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# ruff: noqa: UP007
|
2
2
|
# @omlish-lite
|
3
3
|
# Based on bluelet ( https://github.com/sampsyo/bluelet ) by Adrian Sampson, original license:
|
4
|
-
# THE SOFTWARE IS PROVIDED
|
4
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
5
5
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
6
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
7
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
omlish/asyncs/bluelet/files.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
3
|
# Based on bluelet ( https://github.com/sampsyo/bluelet ) by Adrian Sampson, original license:
|
4
|
-
# THE SOFTWARE IS PROVIDED
|
4
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
5
5
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
6
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
7
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
omlish/asyncs/bluelet/runner.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
3
|
# Based on bluelet ( https://github.com/sampsyo/bluelet ) by Adrian Sampson, original license:
|
4
|
-
# THE SOFTWARE IS PROVIDED
|
4
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
5
5
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
6
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
7
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
omlish/asyncs/bluelet/sockets.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
3
|
# Based on bluelet ( https://github.com/sampsyo/bluelet ) by Adrian Sampson, original license:
|
4
|
-
# THE SOFTWARE IS PROVIDED
|
4
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
5
5
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
6
6
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
7
7
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
omlish/check.py
CHANGED
@@ -14,6 +14,10 @@ from .lite.check import Checks
|
|
14
14
|
from .lite.check import check
|
15
15
|
|
16
16
|
|
17
|
+
T = ta.TypeVar('T')
|
18
|
+
SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
|
19
|
+
|
20
|
+
|
17
21
|
_isinstance = isinstance
|
18
22
|
_issubclass = issubclass
|
19
23
|
_callable = callable
|
@@ -22,8 +26,12 @@ _callable = callable
|
|
22
26
|
##
|
23
27
|
|
24
28
|
|
25
|
-
register_on_raise
|
26
|
-
|
29
|
+
def register_on_raise(fn: OnRaiseFn) -> None:
|
30
|
+
check.register_on_raise(fn)
|
31
|
+
|
32
|
+
|
33
|
+
def unregister_on_raise(fn: OnRaiseFn) -> None:
|
34
|
+
check.unregister_on_raise(fn)
|
27
35
|
|
28
36
|
|
29
37
|
##
|
@@ -57,48 +65,207 @@ check.register_late_configure(_try_enable_args_rendering)
|
|
57
65
|
|
58
66
|
|
59
67
|
##
|
68
|
+
# The following code manually proxies to the lite code, as opposed to simply assigning global names to instance methods
|
69
|
+
# of `check`, to assist type analysis in tools - pycharm for example has a hard time deducing the return types in such
|
70
|
+
# cases. The functions are however dynamically overwritten below with direct references to the method to remove one
|
71
|
+
# layer of call indirection at runtime.
|
72
|
+
|
73
|
+
|
74
|
+
_CHECK_PROXY_FUNCTIONS: dict[str, ta.Any] = {}
|
75
|
+
|
76
|
+
|
77
|
+
def _check_proxy_function(fn: T) -> T:
|
78
|
+
name = fn.__name__ # type: ignore[attr-defined]
|
79
|
+
if name in _CHECK_PROXY_FUNCTIONS:
|
80
|
+
raise NameError(fn)
|
81
|
+
_CHECK_PROXY_FUNCTIONS[name] = fn
|
82
|
+
return fn
|
83
|
+
|
84
|
+
|
85
|
+
##
|
86
|
+
|
87
|
+
|
88
|
+
@ta.overload
|
89
|
+
def isinstance(v: ta.Any, spec: type[T], msg: Message = None) -> T: # noqa
|
90
|
+
...
|
91
|
+
|
92
|
+
|
93
|
+
@ta.overload
|
94
|
+
def isinstance(v: ta.Any, spec: ta.Any, msg: Message = None) -> ta.Any: # noqa
|
95
|
+
...
|
96
|
+
|
97
|
+
|
98
|
+
@_check_proxy_function
|
99
|
+
def isinstance(v, spec, msg=None): # noqa
|
100
|
+
return check.isinstance(v, spec, msg=msg)
|
101
|
+
|
102
|
+
|
103
|
+
@ta.overload
|
104
|
+
def of_isinstance(spec: type[T], msg: Message = None) -> ta.Callable[[ta.Any], T]:
|
105
|
+
...
|
106
|
+
|
60
107
|
|
108
|
+
@ta.overload
|
109
|
+
def of_isinstance(spec: ta.Any, msg: Message = None) -> ta.Callable[[ta.Any], ta.Any]:
|
110
|
+
...
|
111
|
+
|
112
|
+
|
113
|
+
@_check_proxy_function
|
114
|
+
def of_isinstance(spec, msg=None):
|
115
|
+
return check.of_isinstance(spec, msg=msg)
|
116
|
+
|
117
|
+
|
118
|
+
@_check_proxy_function
|
119
|
+
def cast(v: ta.Any, cls: type[T], msg: Message = None) -> T:
|
120
|
+
return check.cast(v, cls, msg=msg)
|
121
|
+
|
122
|
+
|
123
|
+
@_check_proxy_function
|
124
|
+
def of_cast(cls: type[T], msg: Message = None) -> ta.Callable[[T], T]:
|
125
|
+
return check.of_cast(cls, msg=msg)
|
126
|
+
|
127
|
+
|
128
|
+
@_check_proxy_function
|
129
|
+
def not_isinstance(v: T, spec: ta.Any, msg: Message = None) -> T:
|
130
|
+
return check.not_isinstance(v, spec, msg=msg)
|
131
|
+
|
132
|
+
|
133
|
+
@_check_proxy_function
|
134
|
+
def of_not_isinstance(spec: ta.Any, msg: Message = None) -> ta.Callable[[T], T]:
|
135
|
+
return check.of_not_isinstance(spec, msg=msg)
|
61
136
|
|
62
|
-
isinstance = check.isinstance # noqa
|
63
|
-
of_isinstance = check.of_isinstance
|
64
|
-
cast = check.cast
|
65
|
-
of_cast = check.of_cast
|
66
|
-
not_isinstance = check.not_isinstance
|
67
|
-
of_not_isinstance = check.of_not_isinstance
|
68
137
|
|
69
138
|
#
|
70
139
|
|
71
|
-
|
72
|
-
|
140
|
+
|
141
|
+
@_check_proxy_function
|
142
|
+
def issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: # noqa
|
143
|
+
return check.issubclass(v, spec, msg=msg)
|
144
|
+
|
145
|
+
|
146
|
+
@_check_proxy_function
|
147
|
+
def not_issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]:
|
148
|
+
return check.not_issubclass(v, spec, msg=msg)
|
149
|
+
|
73
150
|
|
74
151
|
#
|
75
152
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
153
|
+
|
154
|
+
@_check_proxy_function
|
155
|
+
def in_(v: T, c: ta.Container[T], msg: Message = None) -> T:
|
156
|
+
return check.in_(v, c, msg=msg)
|
157
|
+
|
158
|
+
|
159
|
+
@_check_proxy_function
|
160
|
+
def not_in(v: T, c: ta.Container[T], msg: Message = None) -> T:
|
161
|
+
return check.not_in(v, c, msg=msg)
|
162
|
+
|
163
|
+
|
164
|
+
@_check_proxy_function
|
165
|
+
def empty(v: SizedT, msg: Message = None) -> SizedT:
|
166
|
+
return check.empty(v, msg=msg)
|
167
|
+
|
168
|
+
|
169
|
+
@_check_proxy_function
|
170
|
+
def iterempty(v: ta.Iterable[T], msg: Message = None) -> ta.Iterable[T]:
|
171
|
+
return check.iterempty(v, msg=msg)
|
172
|
+
|
173
|
+
|
174
|
+
@_check_proxy_function
|
175
|
+
def not_empty(v: SizedT, msg: Message = None) -> SizedT:
|
176
|
+
return check.not_empty(v, msg=msg)
|
177
|
+
|
178
|
+
|
179
|
+
@_check_proxy_function
|
180
|
+
def unique(it: ta.Iterable[T], msg: Message = None) -> ta.Iterable[T]:
|
181
|
+
return check.unique(it, msg=msg)
|
182
|
+
|
183
|
+
|
184
|
+
@_check_proxy_function
|
185
|
+
def single(obj: ta.Iterable[T], msg: Message = None) -> T:
|
186
|
+
return check.single(obj, msg=msg)
|
187
|
+
|
188
|
+
|
189
|
+
@_check_proxy_function
|
190
|
+
def opt_single(obj: ta.Iterable[T], msg: Message = None) -> T | None:
|
191
|
+
return check.opt_single(obj, msg=msg)
|
192
|
+
|
84
193
|
|
85
194
|
#
|
86
195
|
|
87
|
-
|
88
|
-
|
196
|
+
|
197
|
+
@_check_proxy_function
|
198
|
+
def none(v: ta.Any, msg: Message = None) -> None:
|
199
|
+
return check.none(v, msg=msg)
|
200
|
+
|
201
|
+
|
202
|
+
@_check_proxy_function
|
203
|
+
def not_none(v: T | None, msg: Message = None) -> T:
|
204
|
+
return check.not_none(v, msg=msg)
|
205
|
+
|
89
206
|
|
90
207
|
#
|
91
208
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
209
|
+
|
210
|
+
@_check_proxy_function
|
211
|
+
def equal(v: T, o: ta.Any, msg: Message = None) -> T:
|
212
|
+
return check.equal(v, o, msg=msg)
|
213
|
+
|
214
|
+
|
215
|
+
@_check_proxy_function
|
216
|
+
def not_equal(v: T, o: ta.Any, msg: Message = None) -> T:
|
217
|
+
return check.not_equal(v, o, msg=msg)
|
218
|
+
|
219
|
+
|
220
|
+
@_check_proxy_function
|
221
|
+
def is_(v: T, o: ta.Any, msg: Message = None) -> T:
|
222
|
+
return check.is_(v, o, msg=msg)
|
223
|
+
|
224
|
+
|
225
|
+
@_check_proxy_function
|
226
|
+
def is_not(v: T, o: ta.Any, msg: Message = None) -> T:
|
227
|
+
return check.is_not(v, o, msg=msg)
|
228
|
+
|
229
|
+
|
230
|
+
@_check_proxy_function
|
231
|
+
def callable(v: T, msg: Message = None) -> T: # noqa
|
232
|
+
return check.callable(v, msg=msg)
|
233
|
+
|
234
|
+
|
235
|
+
@_check_proxy_function
|
236
|
+
def non_empty_str(v: str | None, msg: Message = None) -> str:
|
237
|
+
return check.non_empty_str(v, msg=msg)
|
238
|
+
|
239
|
+
|
240
|
+
@_check_proxy_function
|
241
|
+
def replacing(expected: ta.Any, old: ta.Any, new: T, msg: Message = None) -> T:
|
242
|
+
return check.replacing(expected, old, new, msg=msg)
|
243
|
+
|
244
|
+
|
245
|
+
@_check_proxy_function
|
246
|
+
def replacing_none(old: ta.Any, new: T, msg: Message = None) -> T:
|
247
|
+
return check.replacing_none(old, new, msg=msg)
|
248
|
+
|
100
249
|
|
101
250
|
#
|
102
251
|
|
103
|
-
|
104
|
-
|
252
|
+
|
253
|
+
@_check_proxy_function
|
254
|
+
def arg(v: bool, msg: Message = None) -> None:
|
255
|
+
return check.arg(v, msg=msg)
|
256
|
+
|
257
|
+
|
258
|
+
@_check_proxy_function
|
259
|
+
def state(v: bool, msg: Message = None) -> None:
|
260
|
+
return check.state(v, msg=msg)
|
261
|
+
|
262
|
+
|
263
|
+
##
|
264
|
+
|
265
|
+
|
266
|
+
def _install_direct_check_proxy_functions() -> None:
|
267
|
+
for n in _CHECK_PROXY_FUNCTIONS:
|
268
|
+
globals()[n] = getattr(check, n)
|
269
|
+
|
270
|
+
|
271
|
+
_install_direct_check_proxy_functions()
|
omlish/collections/__init__.py
CHANGED
@@ -76,6 +76,25 @@ from .mappings import ( # noqa
|
|
76
76
|
multikey_dict,
|
77
77
|
)
|
78
78
|
|
79
|
+
from .multimaps import ( # noqa
|
80
|
+
MultiMap,
|
81
|
+
|
82
|
+
SequenceMultiMap,
|
83
|
+
AbstractSetMultiMap,
|
84
|
+
|
85
|
+
BiMultiMap,
|
86
|
+
InverseBiMultiMap,
|
87
|
+
|
88
|
+
SequenceBiMultiMap,
|
89
|
+
AbstractSetBiMultiMap,
|
90
|
+
|
91
|
+
TupleBiMultiMap,
|
92
|
+
seq_bi_multi_map,
|
93
|
+
|
94
|
+
FrozensetBiMultiMap,
|
95
|
+
abs_set_bi_multi_map,
|
96
|
+
)
|
97
|
+
|
79
98
|
from .ordered import ( # noqa
|
80
99
|
OrderedFrozenSet,
|
81
100
|
OrderedSet,
|
@@ -0,0 +1,151 @@
|
|
1
|
+
import abc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from .. import lang
|
5
|
+
|
6
|
+
|
7
|
+
K = ta.TypeVar('K')
|
8
|
+
V = ta.TypeVar('V')
|
9
|
+
MV = ta.TypeVar('MV', bound=ta.Iterable)
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
|
14
|
+
|
15
|
+
class MultiMap(ta.Mapping[K, MV], abc.ABC, ta.Generic[K, V, MV]):
|
16
|
+
pass
|
17
|
+
|
18
|
+
|
19
|
+
SequenceMultiMap: ta.TypeAlias = MultiMap[K, V, ta.Sequence[V]]
|
20
|
+
AbstractSetMultiMap: ta.TypeAlias = MultiMap[K, V, ta.AbstractSet[V]]
|
21
|
+
|
22
|
+
|
23
|
+
##
|
24
|
+
|
25
|
+
|
26
|
+
class BiMultiMap(MultiMap[K, V, MV], abc.ABC, ta.Generic[K, V, MV]):
|
27
|
+
@abc.abstractmethod
|
28
|
+
def inverse(self) -> 'InverseBiMultiMap[K, V, MV]':
|
29
|
+
raise NotImplementedError
|
30
|
+
|
31
|
+
|
32
|
+
class InverseBiMultiMap(ta.Mapping[V, K], abc.ABC, ta.Generic[K, V, MV]):
|
33
|
+
@abc.abstractmethod
|
34
|
+
def inverse(self) -> BiMultiMap[K, V, MV]:
|
35
|
+
raise NotImplementedError
|
36
|
+
|
37
|
+
|
38
|
+
SequenceBiMultiMap: ta.TypeAlias = BiMultiMap[K, V, ta.Sequence[V]]
|
39
|
+
AbstractSetBiMultiMap: ta.TypeAlias = BiMultiMap[K, V, ta.AbstractSet[V]]
|
40
|
+
|
41
|
+
|
42
|
+
##
|
43
|
+
|
44
|
+
|
45
|
+
class InverseBiMultiMapImpl(InverseBiMultiMap[K, V, MV], ta.Generic[K, V, MV]):
|
46
|
+
def __init__(self, m: BiMultiMap[K, V, MV], dct: ta.Mapping[V, K]) -> None:
|
47
|
+
super().__init__()
|
48
|
+
|
49
|
+
self._m = m
|
50
|
+
self._dct = dct
|
51
|
+
|
52
|
+
def inverse(self) -> BiMultiMap[K, V, MV]:
|
53
|
+
return self._m
|
54
|
+
|
55
|
+
def __getitem__(self, key: V, /) -> K:
|
56
|
+
return self._dct[key]
|
57
|
+
|
58
|
+
def __len__(self) -> int:
|
59
|
+
return len(self._dct)
|
60
|
+
|
61
|
+
def __iter__(self) -> ta.Iterator[V]:
|
62
|
+
return iter(self._dct)
|
63
|
+
|
64
|
+
|
65
|
+
class BaseBiMultiMap(BiMultiMap[K, V, MV], abc.ABC, ta.Generic[K, V, MV]):
|
66
|
+
def __init__(self, *args: ta.Any, **kwargs: ta.Any) -> None:
|
67
|
+
super().__init__()
|
68
|
+
|
69
|
+
dct: dict[K, MV] = {}
|
70
|
+
i_dct: dict[V, K] = {}
|
71
|
+
for k, mv in lang.yield_dict_init(*args, **kwargs):
|
72
|
+
l: list[V] = []
|
73
|
+
for v in mv:
|
74
|
+
if v in i_dct:
|
75
|
+
raise KeyError(v)
|
76
|
+
l.append(v)
|
77
|
+
i_dct[v] = k
|
78
|
+
dct[k] = self._aggregate_values(l)
|
79
|
+
|
80
|
+
self._dct = dct
|
81
|
+
self._i: InverseBiMultiMap[K, V, MV] = InverseBiMultiMapImpl(self, i_dct)
|
82
|
+
|
83
|
+
@abc.abstractmethod
|
84
|
+
def _aggregate_values(self, vs: list[V]) -> MV:
|
85
|
+
raise NotImplementedError
|
86
|
+
|
87
|
+
def inverse(self) -> 'InverseBiMultiMap[K, V, MV]':
|
88
|
+
return self._i
|
89
|
+
|
90
|
+
def __getitem__(self, key: K, /) -> MV:
|
91
|
+
return self._dct[key]
|
92
|
+
|
93
|
+
def __len__(self) -> int:
|
94
|
+
return len(self._dct)
|
95
|
+
|
96
|
+
def __iter__(self) -> ta.Iterator[K]:
|
97
|
+
return iter(self._dct)
|
98
|
+
|
99
|
+
|
100
|
+
#
|
101
|
+
|
102
|
+
|
103
|
+
class TupleBiMultiMap(BaseBiMultiMap[K, V, tuple[V, ...]], ta.Generic[K, V]):
|
104
|
+
def _aggregate_values(self, vs: list[V]) -> tuple[V, ...]:
|
105
|
+
return tuple(vs)
|
106
|
+
|
107
|
+
|
108
|
+
# FIXME: lame
|
109
|
+
# lang.static_check_issubclass[BiMultiMap[int, str, tuple[str, ...]]](TupleBiMultiMap[int, str])
|
110
|
+
# lang.static_check_issubclass[BiMultiMap[int, str, ta.Sequence[str]]](TupleBiMultiMap[int, str])
|
111
|
+
# lang.static_check_issubclass[SequenceBiMultiMap[int, str]](TupleBiMultiMap[int, str])
|
112
|
+
|
113
|
+
|
114
|
+
@ta.overload
|
115
|
+
def seq_bi_multi_map(dct: ta.Mapping[K, ta.Iterable[V]]) -> SequenceBiMultiMap[K, V]:
|
116
|
+
...
|
117
|
+
|
118
|
+
|
119
|
+
@ta.overload
|
120
|
+
def seq_bi_multi_map(items: ta.Iterable[tuple[K, ta.Iterable[V]]]) -> SequenceBiMultiMap[K, V]:
|
121
|
+
...
|
122
|
+
|
123
|
+
|
124
|
+
def seq_bi_multi_map(*args, **kwargs):
|
125
|
+
return TupleBiMultiMap(*args, **kwargs)
|
126
|
+
|
127
|
+
|
128
|
+
#
|
129
|
+
|
130
|
+
|
131
|
+
class FrozensetBiMultiMap(BaseBiMultiMap[K, V, frozenset[V]], ta.Generic[K, V]):
|
132
|
+
def _aggregate_values(self, vs: list[V]) -> frozenset[V]:
|
133
|
+
return frozenset(vs)
|
134
|
+
|
135
|
+
|
136
|
+
# FIXME: lame
|
137
|
+
# lang.static_check_issubclass[AbstractSetBiMultiMap[int, str]](FrozensetBiMultiMap[int, str])
|
138
|
+
|
139
|
+
|
140
|
+
@ta.overload
|
141
|
+
def abs_set_bi_multi_map(dct: ta.Mapping[K, ta.Iterable[V]]) -> AbstractSetBiMultiMap[K, V]:
|
142
|
+
...
|
143
|
+
|
144
|
+
|
145
|
+
@ta.overload
|
146
|
+
def abs_set_bi_multi_map(items: ta.Iterable[tuple[K, ta.Iterable[V]]]) -> AbstractSetBiMultiMap[K, V]:
|
147
|
+
...
|
148
|
+
|
149
|
+
|
150
|
+
def abs_set_bi_multi_map(*args, **kwargs):
|
151
|
+
return FrozensetBiMultiMap(*args, **kwargs)
|
omlish/formats/json/__init__.py
CHANGED
@@ -12,10 +12,21 @@ from ... import lang as _lang
|
|
12
12
|
from .backends import ( # noqa
|
13
13
|
Backend,
|
14
14
|
|
15
|
-
|
15
|
+
default_backend,
|
16
16
|
|
17
17
|
StdBackend,
|
18
|
-
|
18
|
+
std_backend,
|
19
|
+
)
|
20
|
+
|
21
|
+
from .backends.default import ( # noqa
|
22
|
+
dump,
|
23
|
+
dump_compact,
|
24
|
+
dump_pretty,
|
25
|
+
dumps,
|
26
|
+
dumps_compact,
|
27
|
+
dumps_pretty,
|
28
|
+
load,
|
29
|
+
loads,
|
19
30
|
)
|
20
31
|
|
21
32
|
from .consts import ( # noqa
|
@@ -32,17 +43,6 @@ from .encoding import ( # noqa
|
|
32
43
|
detect_encoding,
|
33
44
|
)
|
34
45
|
|
35
|
-
from .json import ( # noqa
|
36
|
-
dump,
|
37
|
-
dump_compact,
|
38
|
-
dump_pretty,
|
39
|
-
dumps,
|
40
|
-
dumps_compact,
|
41
|
-
dumps_pretty,
|
42
|
-
load,
|
43
|
-
loads,
|
44
|
-
)
|
45
|
-
|
46
46
|
if _ta.TYPE_CHECKING:
|
47
47
|
from .rendering import ( # noqa
|
48
48
|
JsonRenderer,
|
@@ -1,13 +1,57 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from .... import lang
|
1
4
|
from .base import Backend
|
2
|
-
from .orjson import
|
3
|
-
from .std import
|
4
|
-
from .ujson import
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
from .orjson import orjson_backend
|
6
|
+
from .std import std_backend
|
7
|
+
from .ujson import ujson_backend
|
8
|
+
|
9
|
+
|
10
|
+
##
|
11
|
+
|
12
|
+
|
13
|
+
@lang.cached_function
|
14
|
+
def default_backend() -> Backend:
|
15
|
+
for fn in [
|
16
|
+
orjson_backend,
|
17
|
+
ujson_backend,
|
18
|
+
]:
|
19
|
+
if (be := fn()) is not None:
|
20
|
+
return be
|
21
|
+
|
22
|
+
return std_backend()
|
23
|
+
|
24
|
+
|
25
|
+
##
|
26
|
+
|
27
|
+
|
28
|
+
def dump(obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
29
|
+
return default_backend().dump(obj, fp, **kwargs)
|
30
|
+
|
31
|
+
|
32
|
+
def dumps(obj: ta.Any, **kwargs: ta.Any) -> str:
|
33
|
+
return default_backend().dumps(obj, **kwargs)
|
34
|
+
|
35
|
+
|
36
|
+
def load(fp: ta.Any, **kwargs: ta.Any) -> ta.Any:
|
37
|
+
return default_backend().load(fp, **kwargs)
|
38
|
+
|
39
|
+
|
40
|
+
def loads(s: str | bytes | bytearray, **kwargs: ta.Any) -> ta.Any:
|
41
|
+
return default_backend().loads(s, **kwargs)
|
42
|
+
|
43
|
+
|
44
|
+
def dump_pretty(obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
45
|
+
return default_backend().dump_pretty(obj, fp, **kwargs)
|
46
|
+
|
47
|
+
|
48
|
+
def dumps_pretty(obj: ta.Any, **kwargs: ta.Any) -> str:
|
49
|
+
return default_backend().dumps_pretty(obj, **kwargs)
|
50
|
+
|
51
|
+
|
52
|
+
def dump_compact(obj: ta.Any, fp: ta.Any, **kwargs: ta.Any) -> None:
|
53
|
+
return default_backend().dump_compact(obj, fp, **kwargs)
|
54
|
+
|
55
|
+
|
56
|
+
def dumps_compact(obj: ta.Any, **kwargs: ta.Any) -> str:
|
57
|
+
return default_backend().dumps_compact(obj, **kwargs)
|
@@ -109,8 +109,9 @@ class OrjsonBackend(Backend):
|
|
109
109
|
return self.dumps(obj, **kwargs)
|
110
110
|
|
111
111
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
112
|
+
@lang.cached_function
|
113
|
+
def orjson_backend() -> OrjsonBackend | None:
|
114
|
+
if lang.can_import('orjson'):
|
115
|
+
return OrjsonBackend()
|
116
|
+
else:
|
117
|
+
return None
|
@@ -8,6 +8,7 @@ import dataclasses as dc
|
|
8
8
|
import json
|
9
9
|
import typing as ta
|
10
10
|
|
11
|
+
from .... import lang
|
11
12
|
from ..consts import COMPACT_KWARGS
|
12
13
|
from ..consts import PRETTY_KWARGS
|
13
14
|
from .base import Backend
|
@@ -73,4 +74,6 @@ class StdBackend(Backend):
|
|
73
74
|
return json.dumps(obj, **COMPACT_KWARGS, **kwargs)
|
74
75
|
|
75
76
|
|
76
|
-
|
77
|
+
@lang.cached_function
|
78
|
+
def std_backend() -> StdBackend:
|
79
|
+
return StdBackend()
|
@@ -67,8 +67,9 @@ class UjsonBackend(Backend):
|
|
67
67
|
return uj.dumps(obj, **kwargs)
|
68
68
|
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
70
|
+
@lang.cached_function
|
71
|
+
def ujson_backend() -> UjsonBackend | None:
|
72
|
+
if lang.can_import('ujson'):
|
73
|
+
return UjsonBackend()
|
74
|
+
else:
|
75
|
+
return None
|