omlish 0.0.0.dev338__py3-none-any.whl → 0.0.0.dev340__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 (43) 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/collections/__init__.py +19 -0
  10. omlish/collections/multimaps.py +151 -0
  11. omlish/formats/json/__init__.py +13 -13
  12. omlish/formats/json/backends/__init__.py +2 -2
  13. omlish/formats/json/backends/default.py +56 -12
  14. omlish/formats/json/backends/orjson.py +6 -5
  15. omlish/formats/json/backends/std.py +4 -1
  16. omlish/formats/json/backends/ujson.py +6 -5
  17. omlish/formats/json/codecs.py +4 -4
  18. omlish/graphs/dags.py +112 -48
  19. omlish/graphs/domination.py +5 -1
  20. omlish/graphs/dot/items.py +3 -0
  21. omlish/graphs/dot/make.py +3 -0
  22. omlish/graphs/dot/rendering.py +3 -0
  23. omlish/graphs/dot/utils.py +3 -0
  24. omlish/graphs/trees.py +5 -4
  25. omlish/lang/__init__.py +9 -0
  26. omlish/lang/classes/bindable.py +43 -0
  27. omlish/lang/classes/protocols.py +26 -0
  28. omlish/math/__init__.py +15 -15
  29. omlish/math/fixed.py +25 -15
  30. omlish/os/forkhooks.py +4 -4
  31. omlish/testing/pytest/plugins/switches/plugin.py +2 -0
  32. omlish/testing/pytest/skip.py +7 -8
  33. omlish/typedvalues/__init__.py +4 -0
  34. omlish/typedvalues/accessor.py +5 -1
  35. omlish/typedvalues/collection.py +1 -0
  36. omlish/typedvalues/of_.py +51 -0
  37. {omlish-0.0.0.dev338.dist-info → omlish-0.0.0.dev340.dist-info}/METADATA +1 -1
  38. {omlish-0.0.0.dev338.dist-info → omlish-0.0.0.dev340.dist-info}/RECORD +42 -39
  39. omlish/formats/json/json.py +0 -17
  40. {omlish-0.0.0.dev338.dist-info → omlish-0.0.0.dev340.dist-info}/WHEEL +0 -0
  41. {omlish-0.0.0.dev338.dist-info → omlish-0.0.0.dev340.dist-info}/entry_points.txt +0 -0
  42. {omlish-0.0.0.dev338.dist-info → omlish-0.0.0.dev340.dist-info}/licenses/LICENSE +0 -0
  43. {omlish-0.0.0.dev338.dist-info → omlish-0.0.0.dev340.dist-info}/top_level.txt +0 -0
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev338'
2
- __revision__ = '8d983648c5770796c8addb2cf217a0d7235a9b29'
1
+ __version__ = '0.0.0.dev340'
2
+ __revision__ = 'de9a88b0e8d5578e5348e4f23db01e09dd3ebbd0'
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.
@@ -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
@@ -1,9 +1,9 @@
1
1
  from ..codecs import make_object_lazy_loaded_codec
2
2
  from ..codecs import make_str_object_codec
3
- from .json import dumps
4
- from .json import dumps_compact
5
- from .json import dumps_pretty
6
- from .json import loads
3
+ from .backends.default import dumps
4
+ from .backends.default import dumps_compact
5
+ from .backends.default import dumps_pretty
6
+ from .backends.default import loads
7
7
 
8
8
 
9
9
  ##
omlish/graphs/dags.py CHANGED
@@ -12,100 +12,164 @@ from .. import check
12
12
  from .. import lang
13
13
 
14
14
 
15
- K = ta.TypeVar('K')
16
- V = ta.TypeVar('V')
17
15
  T = ta.TypeVar('T')
18
- U = ta.TypeVar('U')
19
16
 
20
17
 
21
- def traverse_links(data: ta.Mapping[T, ta.Iterable[T]], keys: ta.Iterable[T]) -> set[T]:
22
- keys = set(keys)
23
- todo = set(keys)
18
+ ##
19
+
20
+
21
+ class LinkError(KeyError):
22
+ pass
23
+
24
+
25
+ def traverse_links(
26
+ links: ta.Mapping[T, ta.Iterable[T]],
27
+ roots: ta.Iterable[T],
28
+ *,
29
+ include_roots: bool = False,
30
+ strict: bool = False,
31
+ ) -> set[T]:
32
+ """Returns all keys deeply reachable from given roots. Handles cycles."""
33
+
34
+ roots = set(roots)
35
+
36
+ todo = set(roots)
24
37
  seen: set[T] = set()
25
38
  while todo:
26
39
  key = todo.pop()
27
40
  seen.add(key)
28
- cur = data.get(key, [])
29
- todo.update(set(cur) - seen)
30
- return seen - keys
31
-
32
41
 
33
- def invert_set_map(src: ta.Mapping[K, ta.Iterable[V]]) -> dict[V, set[K]]:
34
- dst: dict[V, set[K]] = {}
35
- for l, rs in src.items():
36
- for r in rs:
42
+ try:
43
+ cur = links[key]
44
+ except KeyError:
45
+ if strict:
46
+ raise LinkError(key) from None
47
+ else:
48
+ todo.update(set(cur) - seen)
49
+
50
+ if include_roots:
51
+ return seen
52
+ else:
53
+ return seen - roots
54
+
55
+
56
+ def invert_links(
57
+ links: ta.Mapping[T, ta.Iterable[T]],
58
+ *,
59
+ auto_add_roots: bool = False,
60
+ if_absent: ta.Literal['raise', 'ignore', 'add'] = 'add',
61
+ ) -> dict[T, set[T]]:
62
+ check.in_(if_absent, ('raise', 'ignore', 'add'))
63
+ if if_absent != 'add':
64
+ check.arg(auto_add_roots, 'auto_add_roots must be True with given if_absent is not "add"')
65
+
66
+ ret: dict[T, set[T]]
67
+ if auto_add_roots:
68
+ ret = {src: set() for src in links}
69
+ else:
70
+ ret = {}
71
+
72
+ for src, dsts in links.items():
73
+ for dst in dsts:
37
74
  try:
38
- s = dst[r]
75
+ tgt = ret[dst]
39
76
  except KeyError:
40
- s = dst[r] = set()
41
- s.add(l)
42
- return dst
77
+ if if_absent == 'raise':
78
+ raise LinkError(dst) from None
79
+ elif if_absent == 'ignore':
80
+ continue
81
+ elif if_absent == 'add':
82
+ tgt = ret[dst] = set()
83
+ else:
84
+ raise RuntimeError from None
85
+
86
+ tgt.add(src)
43
87
 
88
+ return ret
44
89
 
45
- def invert_symmetric_set_map(src: ta.Mapping[T, ta.Iterable[T]]) -> dict[T, set[T]]:
46
- dst: dict[T, set[T]] = {l: set() for l in src}
47
- for l, rs in src.items():
48
- for r in rs:
49
- dst[r].add(l)
50
- return dst
90
+
91
+ ##
51
92
 
52
93
 
53
94
  class Dag(ta.Generic[T]):
54
- def __init__(self, input_its_by_outputs: ta.Mapping[T, ta.Iterable[T]]) -> None:
95
+ """Given 'input_its_by_outputs', or a map from nodes to that node's dependencies."""
96
+
97
+ def __init__(
98
+ self,
99
+ input_its_by_outputs: ta.Mapping[T, ta.Iterable[T]],
100
+ *,
101
+ auto_add_outputs: bool = True,
102
+ if_absent: ta.Literal['raise', 'ignore', 'add'] = 'add',
103
+ ) -> None:
55
104
  super().__init__()
56
105
 
57
106
  self._input_sets_by_output = {u: set(d) for u, d in input_its_by_outputs.items()}
58
107
 
108
+ self._output_sets_by_input = invert_links(
109
+ self._input_sets_by_output,
110
+ auto_add_roots=auto_add_outputs,
111
+ if_absent=if_absent,
112
+ )
113
+
59
114
  @property
60
115
  def input_sets_by_output(self) -> ta.Mapping[T, ta.AbstractSet[T]]:
61
116
  return self._input_sets_by_output
62
117
 
63
- @lang.cached_property
118
+ @property
64
119
  def output_sets_by_input(self) -> ta.Mapping[T, ta.AbstractSet[T]]:
65
- return invert_symmetric_set_map(self._input_sets_by_output)
120
+ return self._output_sets_by_input
66
121
 
67
- def subdag(self, *args, **kwargs) -> 'Subdag[T]':
68
- return Subdag(self, *args, **kwargs)
122
+ def subdag(
123
+ self,
124
+ roots: ta.Iterable[T],
125
+ *,
126
+ ignored: ta.Iterable[T] | None = None,
127
+ ) -> 'Subdag[T]':
128
+ return Subdag(
129
+ self,
130
+ roots,
131
+ ignored=ignored,
132
+ )
69
133
 
70
134
 
71
- class Subdag(ta.Generic[U]):
135
+ class Subdag(ta.Generic[T]):
72
136
  def __init__(
73
137
  self,
74
- dag: 'Dag[U]',
75
- targets: ta.Iterable[U],
138
+ dag: Dag[T],
139
+ roots: ta.Iterable[T],
76
140
  *,
77
- ignored: ta.Iterable[U] | None = None,
141
+ ignored: ta.Iterable[T] | None = None,
78
142
  ) -> None:
79
143
  super().__init__()
80
144
 
81
- self._dag: Dag[U] = check.isinstance(dag, Dag)
82
- self._targets = set(targets)
83
- self._ignored = set(ignored or []) - self._targets
145
+ self._dag: Dag[T] = check.isinstance(dag, Dag)
146
+ self._roots = set(roots)
147
+ self._ignored = set(ignored or []) - self._roots
84
148
 
85
149
  @property
86
- def dag(self) -> 'Dag[U]':
150
+ def dag(self) -> Dag[T]:
87
151
  return self._dag
88
152
 
89
153
  @property
90
- def targets(self) -> ta.AbstractSet[U]:
91
- return self._targets
154
+ def roots(self) -> ta.AbstractSet[T]:
155
+ return self._roots
92
156
 
93
157
  @property
94
- def ignored(self) -> ta.AbstractSet[U]:
158
+ def ignored(self) -> ta.AbstractSet[T]:
95
159
  return self._ignored
96
160
 
97
161
  @lang.cached_property
98
- def inputs(self) -> ta.AbstractSet[U]:
99
- return traverse_links(self.dag.input_sets_by_output, self.targets) - self.ignored
162
+ def inputs(self) -> ta.AbstractSet[T]:
163
+ return traverse_links(self._dag.input_sets_by_output, self._roots) - self._ignored
100
164
 
101
165
  @lang.cached_property
102
- def outputs(self) -> ta.AbstractSet[U]:
103
- return traverse_links(self.dag.output_sets_by_input, self.targets) - self.ignored
166
+ def outputs(self) -> ta.AbstractSet[T]:
167
+ return traverse_links(self._dag.output_sets_by_input, self._roots) - self._ignored
104
168
 
105
169
  @lang.cached_property
106
- def output_inputs(self) -> ta.AbstractSet[U]:
107
- return traverse_links(self.dag.input_sets_by_output, self.outputs) - self.ignored
170
+ def output_inputs(self) -> ta.AbstractSet[T]:
171
+ return traverse_links(self._dag.input_sets_by_output, self.outputs) - self._ignored
108
172
 
109
173
  @lang.cached_property
110
- def all(self) -> ta.AbstractSet[U]:
111
- return self.targets | self.inputs | self.outputs | self.output_inputs
174
+ def all(self) -> ta.AbstractSet[T]:
175
+ return self.roots | self.inputs | self.outputs | self.output_inputs
@@ -9,7 +9,11 @@ from .. import lang
9
9
  V = ta.TypeVar('V')
10
10
  MK = ta.TypeVar('MK')
11
11
  MV = ta.TypeVar('MV')
12
- SetMap = ta.Mapping[MK, ta.AbstractSet[MV]]
12
+
13
+ SetMap: ta.TypeAlias = ta.Mapping[MK, ta.AbstractSet[MV]]
14
+
15
+
16
+ ##
13
17
 
14
18
 
15
19
  class DirectedGraph(ta.Generic[V], lang.Abstract):