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.
Files changed (40) hide show
  1. omlish/__about__.py +2 -2
  2. omlish/asyncs/bluelet/LICENSE +1 -1
  3. omlish/asyncs/bluelet/api.py +1 -1
  4. omlish/asyncs/bluelet/core.py +1 -1
  5. omlish/asyncs/bluelet/events.py +1 -1
  6. omlish/asyncs/bluelet/files.py +1 -1
  7. omlish/asyncs/bluelet/runner.py +1 -1
  8. omlish/asyncs/bluelet/sockets.py +1 -1
  9. omlish/check.py +197 -30
  10. omlish/collections/__init__.py +19 -0
  11. omlish/collections/multimaps.py +151 -0
  12. omlish/formats/json/__init__.py +13 -13
  13. omlish/formats/json/backends/__init__.py +2 -2
  14. omlish/formats/json/backends/default.py +56 -12
  15. omlish/formats/json/backends/orjson.py +6 -5
  16. omlish/formats/json/backends/std.py +4 -1
  17. omlish/formats/json/backends/ujson.py +6 -5
  18. omlish/formats/json/codecs.py +4 -4
  19. omlish/graphs/dags.py +112 -48
  20. omlish/graphs/domination.py +5 -1
  21. omlish/graphs/dot/items.py +3 -0
  22. omlish/graphs/dot/make.py +3 -0
  23. omlish/graphs/dot/rendering.py +3 -0
  24. omlish/graphs/dot/utils.py +3 -0
  25. omlish/graphs/trees.py +5 -4
  26. omlish/lang/classes/bindable.py +2 -0
  27. omlish/lite/check.py +6 -6
  28. omlish/math/__init__.py +15 -15
  29. omlish/math/fixed.py +25 -15
  30. omlish/os/forkhooks.py +4 -4
  31. omlish/typedvalues/__init__.py +4 -0
  32. omlish/typedvalues/collection.py +3 -0
  33. omlish/typedvalues/consumer.py +12 -0
  34. {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/METADATA +1 -1
  35. {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/RECORD +39 -39
  36. omlish/formats/json/json.py +0 -17
  37. {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/WHEEL +0 -0
  38. {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/entry_points.txt +0 -0
  39. {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/licenses/LICENSE +0 -0
  40. {omlish-0.0.0.dev339.dist-info → omlish-0.0.0.dev341.dist-info}/top_level.txt +0 -0
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev339'
2
- __revision__ = '0d8d4714d9a9ea7145586e0bce55833b86e980bd'
1
+ __version__ = '0.0.0.dev341'
2
+ __revision__ = '4154c2c5976e0a4a31e0d9647362a710be0805fa'
3
3
 
4
4
 
5
5
  #
@@ -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 AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
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.
@@ -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 AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
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.
@@ -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 AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
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.
@@ -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 AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
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.
@@ -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 AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
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.
@@ -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 AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
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.
@@ -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 AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
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 = check.register_on_raise
26
- unregister_on_raise = check.unregister_on_raise
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
- issubclass = check.issubclass # noqa
72
- not_issubclass = check.not_issubclass
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
- in_ = check.in_
77
- not_in = check.not_in
78
- empty = check.empty
79
- iterempty = check.iterempty
80
- not_empty = check.not_empty
81
- unique = check.unique
82
- single = check.single
83
- opt_single = check.opt_single
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
- none = check.none
88
- not_none = check.not_none
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
- equal = check.equal
93
- not_equal = check.not_equal
94
- is_ = check.is_
95
- is_not = check.is_not
96
- callable = check.callable # noqa
97
- non_empty_str = check.non_empty_str
98
- replacing = check.replacing
99
- replacing_none = check.replacing_none
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
- arg = check.arg
104
- state = check.state
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()
@@ -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)
@@ -12,10 +12,21 @@ from ... import lang as _lang
12
12
  from .backends import ( # noqa
13
13
  Backend,
14
14
 
15
- DEFAULT_BACKED,
15
+ default_backend,
16
16
 
17
17
  StdBackend,
18
- STD_BACKEND,
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,
@@ -3,10 +3,10 @@ from .base import ( # noqa
3
3
  )
4
4
 
5
5
  from .default import ( # noqa
6
- DEFAULT_BACKED,
6
+ default_backend,
7
7
  )
8
8
 
9
9
  from .std import ( # noqa
10
10
  StdBackend,
11
- STD_BACKEND,
11
+ std_backend,
12
12
  )
@@ -1,13 +1,57 @@
1
+ import typing as ta
2
+
3
+ from .... import lang
1
4
  from .base import Backend
2
- from .orjson import ORJSON_BACKEND
3
- from .std import STD_BACKEND
4
- from .ujson import UJSON_BACKEND
5
-
6
-
7
- DEFAULT_BACKED: Backend
8
- if ORJSON_BACKEND is not None:
9
- DEFAULT_BACKED = ORJSON_BACKEND
10
- elif UJSON_BACKEND is not None:
11
- DEFAULT_BACKED = UJSON_BACKEND
12
- else:
13
- DEFAULT_BACKED = STD_BACKEND
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
- ORJSON_BACKEND: OrjsonBackend | None
113
- if lang.can_import('orjson'):
114
- ORJSON_BACKEND = OrjsonBackend()
115
- else:
116
- ORJSON_BACKEND = None
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
- STD_BACKEND = StdBackend()
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
- UJSON_BACKEND: UjsonBackend | None
71
- if lang.can_import('ujson'):
72
- UJSON_BACKEND = UjsonBackend()
73
- else:
74
- UJSON_BACKEND = None
70
+ @lang.cached_function
71
+ def ujson_backend() -> UjsonBackend | None:
72
+ if lang.can_import('ujson'):
73
+ return UjsonBackend()
74
+ else:
75
+ return None