omlish 0.0.0.dev2__py3-none-any.whl → 0.0.0.dev4__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 -2
- omlish/__init__.py +8 -0
- omlish/argparse.py +4 -4
- omlish/asyncs/__init__.py +23 -2
- omlish/asyncs/anyio.py +13 -11
- omlish/asyncs/asyncs.py +1 -3
- omlish/asyncs/flavors.py +201 -0
- omlish/asyncs/futures.py +10 -9
- omlish/asyncs/trio_asyncio.py +41 -0
- omlish/c3.py +1 -1
- omlish/check.py +3 -3
- omlish/collections/_abc.py +2 -0
- omlish/collections/_io_abc.py +4 -2
- omlish/collections/cache/__init__.py +1 -1
- omlish/collections/cache/descriptor.py +8 -8
- omlish/collections/cache/impl.py +24 -17
- omlish/collections/cache/types.py +1 -1
- omlish/collections/coerce.py +1 -1
- omlish/collections/frozen.py +6 -6
- omlish/collections/identity.py +3 -4
- omlish/collections/mappings.py +2 -2
- omlish/collections/ordered.py +7 -7
- omlish/collections/skiplist.py +1 -1
- omlish/collections/sorted.py +1 -1
- omlish/collections/treap.py +25 -0
- omlish/collections/treapmap.py +57 -5
- omlish/collections/unmodifiable.py +9 -9
- omlish/collections/utils.py +1 -1
- omlish/configs/flattening.py +7 -6
- omlish/configs/props.py +3 -3
- omlish/dataclasses/__init__.py +1 -1
- omlish/dataclasses/impl/__init__.py +17 -1
- omlish/dataclasses/impl/api.py +10 -11
- omlish/dataclasses/impl/as_.py +4 -4
- omlish/dataclasses/impl/exceptions.py +1 -1
- omlish/dataclasses/impl/fields.py +7 -7
- omlish/dataclasses/impl/frozen.py +2 -2
- omlish/dataclasses/impl/init.py +5 -5
- omlish/dataclasses/impl/internals.py +1 -1
- omlish/dataclasses/impl/metaclass.py +1 -1
- omlish/dataclasses/impl/order.py +1 -1
- omlish/dataclasses/impl/replace.py +1 -1
- omlish/dataclasses/impl/repr.py +4 -4
- omlish/dataclasses/impl/utils.py +6 -6
- omlish/defs.py +13 -17
- omlish/{procfs.py → diag/procfs.py} +22 -24
- omlish/diag/ps.py +47 -0
- omlish/{replserver → diag/replserver}/console.py +18 -20
- omlish/{replserver → diag/replserver}/server.py +8 -8
- omlish/dispatch/dispatch.py +5 -8
- omlish/dispatch/functions.py +1 -1
- omlish/dispatch/methods.py +4 -5
- omlish/docker.py +1 -1
- omlish/dynamic.py +10 -10
- omlish/fnpairs.py +400 -0
- omlish/graphs/trees.py +13 -13
- omlish/inject/__init__.py +7 -7
- omlish/inject/elements.py +1 -1
- omlish/inject/exceptions.py +7 -7
- omlish/inject/impl/elements.py +17 -5
- omlish/inject/impl/injector.py +12 -10
- omlish/inject/impl/inspect.py +2 -2
- omlish/inject/impl/scopes.py +12 -11
- omlish/inject/proxy.py +5 -5
- omlish/iterators.py +19 -24
- omlish/json.py +143 -6
- omlish/lang/__init__.py +13 -4
- omlish/lang/cached.py +2 -5
- omlish/lang/classes/__init__.py +2 -2
- omlish/lang/classes/abstract.py +2 -2
- omlish/lang/classes/restrict.py +14 -14
- omlish/lang/classes/simple.py +1 -1
- omlish/lang/classes/virtual.py +5 -5
- omlish/lang/clsdct.py +1 -1
- omlish/lang/cmp.py +2 -2
- omlish/lang/contextmanagers.py +12 -14
- omlish/lang/descriptors.py +16 -4
- omlish/lang/exceptions.py +1 -1
- omlish/lang/functions.py +58 -22
- omlish/lang/imports.py +22 -27
- omlish/lang/iterables.py +2 -2
- omlish/lang/maybes.py +1 -0
- omlish/lang/objects.py +15 -9
- omlish/lang/resolving.py +1 -1
- omlish/lang/strings.py +1 -1
- omlish/lang/sys.py +7 -0
- omlish/lang/typing.py +3 -3
- omlish/libc.py +9 -5
- omlish/logs/_abc.py +5 -1
- omlish/logs/filters.py +2 -0
- omlish/logs/formatters.py +6 -2
- omlish/logs/utils.py +1 -1
- omlish/marshal/base.py +3 -3
- omlish/marshal/exceptions.py +1 -1
- omlish/marshal/global_.py +10 -4
- omlish/marshal/objects.py +1 -2
- omlish/marshal/registries.py +3 -3
- omlish/marshal/utils.py +2 -2
- omlish/marshal/values.py +1 -1
- omlish/math.py +9 -9
- omlish/reflect.py +3 -3
- omlish/sql/__init__.py +9 -0
- omlish/sql/asyncs.py +148 -0
- omlish/stats.py +4 -5
- omlish/term.py +1 -1
- omlish/testing/pydevd.py +28 -6
- omlish/testing/pytest/inject/harness.py +1 -1
- omlish/testing/pytest/plugins/pydevd.py +1 -1
- omlish/testing/pytest/plugins/switches.py +1 -1
- omlish/text/delimit.py +3 -6
- {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/METADATA +4 -1
- omlish-0.0.0.dev4.dist-info/RECORD +195 -0
- {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/WHEEL +1 -1
- omlish/lang/classes/test/test_abstract.py +0 -89
- omlish/lang/classes/test/test_restrict.py +0 -71
- omlish/lang/classes/test/test_simple.py +0 -58
- omlish/lang/classes/test/test_virtual.py +0 -72
- omlish-0.0.0.dev2.dist-info/RECORD +0 -193
- /omlish/{lang/classes/test → diag}/__init__.py +0 -0
- /omlish/{replserver → diag/replserver}/__init__.py +0 -0
- /omlish/{replserver → diag/replserver}/__main__.py +0 -0
- /omlish/sql/{_abcs.py → _abc.py} +0 -0
- {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/top_level.txt +0 -0
|
@@ -23,8 +23,8 @@ import threading
|
|
|
23
23
|
import typing as ta
|
|
24
24
|
import weakref
|
|
25
25
|
|
|
26
|
-
from
|
|
27
|
-
from
|
|
26
|
+
from ... import check
|
|
27
|
+
from ... import dataclasses as dc
|
|
28
28
|
from .console import InteractiveSocketConsole
|
|
29
29
|
|
|
30
30
|
|
|
@@ -62,12 +62,12 @@ class ReplServer:
|
|
|
62
62
|
def path(self) -> str:
|
|
63
63
|
return self._config.path
|
|
64
64
|
|
|
65
|
-
def __enter__(self):
|
|
65
|
+
def __enter__(self) -> ta.Self:
|
|
66
66
|
check.state(not self._is_running)
|
|
67
67
|
check.state(not self._is_shutdown.is_set())
|
|
68
68
|
return self
|
|
69
69
|
|
|
70
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
70
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
71
71
|
if not self._is_shutdown.is_set():
|
|
72
72
|
self.shutdown(True, self._config.exit_timeout)
|
|
73
73
|
|
|
@@ -91,7 +91,7 @@ class ReplServer:
|
|
|
91
91
|
while not self._should_shutdown:
|
|
92
92
|
try:
|
|
93
93
|
conn, _ = self._socket.accept()
|
|
94
|
-
except
|
|
94
|
+
except TimeoutError:
|
|
95
95
|
continue
|
|
96
96
|
|
|
97
97
|
log.info(f'Got repl server connection on file {self._config.path}')
|
|
@@ -117,13 +117,13 @@ class ReplServer:
|
|
|
117
117
|
name=self.CONNECTION_THREAD_NAME)
|
|
118
118
|
thread.start()
|
|
119
119
|
|
|
120
|
-
for
|
|
120
|
+
for console in self._consoles_by_threads.values():
|
|
121
121
|
try:
|
|
122
122
|
console.conn.close()
|
|
123
123
|
except Exception:
|
|
124
124
|
log.exception('Error shutting down')
|
|
125
125
|
|
|
126
|
-
for thread in self._consoles_by_threads
|
|
126
|
+
for thread in self._consoles_by_threads:
|
|
127
127
|
try:
|
|
128
128
|
thread.join(self._config.exit_timeout)
|
|
129
129
|
except Exception:
|
|
@@ -141,6 +141,6 @@ class ReplServer:
|
|
|
141
141
|
self._is_shutdown.wait(timeout=timeout)
|
|
142
142
|
|
|
143
143
|
|
|
144
|
-
def run():
|
|
144
|
+
def run() -> None:
|
|
145
145
|
with ReplServer(ReplServer.Config('repl.sock')) as repl_server:
|
|
146
146
|
repl_server.run()
|
omlish/dispatch/dispatch.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import abc
|
|
2
|
+
import contextlib
|
|
2
3
|
import typing as ta
|
|
3
4
|
import weakref
|
|
4
5
|
|
|
@@ -13,14 +14,12 @@ T = ta.TypeVar('T')
|
|
|
13
14
|
##
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
_IMPL_FUNC_CLS_SET_CACHE: ta.MutableMapping[ta.Callable,
|
|
17
|
+
_IMPL_FUNC_CLS_SET_CACHE: ta.MutableMapping[ta.Callable, frozenset[type]] = weakref.WeakKeyDictionary()
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def get_impl_func_cls_set(func: ta.Callable) ->
|
|
20
|
-
|
|
20
|
+
def get_impl_func_cls_set(func: ta.Callable) -> frozenset[type]:
|
|
21
|
+
with contextlib.suppress(KeyError):
|
|
21
22
|
return _IMPL_FUNC_CLS_SET_CACHE[func]
|
|
22
|
-
except KeyError:
|
|
23
|
-
pass
|
|
24
23
|
|
|
25
24
|
ann = getattr(func, '__annotations__', {})
|
|
26
25
|
if not ann:
|
|
@@ -78,10 +77,8 @@ class Dispatcher(ta.Generic[T]):
|
|
|
78
77
|
def cache_remove(k, self_ref=weakref.ref(self)):
|
|
79
78
|
if (ref_self := self_ref()) is not None:
|
|
80
79
|
cache = ref_self._get_dispatch_cache() # noqa
|
|
81
|
-
|
|
80
|
+
with contextlib.suppress(KeyError):
|
|
82
81
|
del cache[k]
|
|
83
|
-
except KeyError:
|
|
84
|
-
pass
|
|
85
82
|
|
|
86
83
|
cache_token: ta.Any = None
|
|
87
84
|
self._get_cache_token = lambda: cache_token
|
omlish/dispatch/functions.py
CHANGED
omlish/dispatch/methods.py
CHANGED
|
@@ -4,6 +4,7 @@ TODO:
|
|
|
4
4
|
- ALT: A.f(super(), ... ? :/
|
|
5
5
|
- classmethod/staticmethod
|
|
6
6
|
"""
|
|
7
|
+
import contextlib
|
|
7
8
|
import functools
|
|
8
9
|
import typing as ta
|
|
9
10
|
import weakref
|
|
@@ -23,7 +24,7 @@ def build_mro_dct(instance_cls: type, owner_cls: type | None = None) -> ta.Mappi
|
|
|
23
24
|
try:
|
|
24
25
|
pos = mro.index(owner_cls)
|
|
25
26
|
except ValueError:
|
|
26
|
-
raise TypeError(f'Owner class {owner_cls} not in mro of instance class {instance_cls}')
|
|
27
|
+
raise TypeError(f'Owner class {owner_cls} not in mro of instance class {instance_cls}') from None
|
|
27
28
|
dct: dict[str, ta.Any] = {}
|
|
28
29
|
for cur_cls in mro[:pos + 1]:
|
|
29
30
|
dct.update(cur_cls.__dict__)
|
|
@@ -55,10 +56,8 @@ class Method:
|
|
|
55
56
|
def dispatch_func_cache_remove(k, self_ref=weakref.ref(self)):
|
|
56
57
|
if (ref_self := self_ref()) is not None:
|
|
57
58
|
cache = ref_self._dispatch_func_cache # noqa
|
|
58
|
-
|
|
59
|
+
with contextlib.suppress(KeyError):
|
|
59
60
|
del cache[k]
|
|
60
|
-
except KeyError:
|
|
61
|
-
pass
|
|
62
61
|
|
|
63
62
|
self._dispatch_func_cache_remove = dispatch_func_cache_remove
|
|
64
63
|
|
|
@@ -158,5 +157,5 @@ class Method:
|
|
|
158
157
|
return func.__get__(instance)(*args, **kwargs) # noqa
|
|
159
158
|
|
|
160
159
|
|
|
161
|
-
def method(func):
|
|
160
|
+
def method(func): # noqa
|
|
162
161
|
return Method(func)
|
omlish/docker.py
CHANGED
|
@@ -127,7 +127,7 @@ class ComposeConfig:
|
|
|
127
127
|
|
|
128
128
|
@lang.cached_function
|
|
129
129
|
def get_config(self) -> ta.Mapping[str, ta.Any]:
|
|
130
|
-
with open(check.not_none(self._file_path)
|
|
130
|
+
with open(check.not_none(self._file_path)) as f:
|
|
131
131
|
buf = f.read()
|
|
132
132
|
return yaml.safe_load(buf)
|
|
133
133
|
|
omlish/dynamic.py
CHANGED
|
@@ -25,7 +25,7 @@ _HOISTED_CODE_DEPTH: ta.MutableMapping[types.CodeType, int] = weakref.WeakKeyDic
|
|
|
25
25
|
_MAX_HOIST_DEPTH = 0
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def hoist(depth=0):
|
|
28
|
+
def hoist(depth=0): # noqa
|
|
29
29
|
def inner(fn):
|
|
30
30
|
_HOISTED_CODE_DEPTH[fn.__code__] = depth
|
|
31
31
|
global _MAX_HOIST_DEPTH
|
|
@@ -49,7 +49,7 @@ class Var(ta.Generic[T]):
|
|
|
49
49
|
|
|
50
50
|
def __init__(
|
|
51
51
|
self,
|
|
52
|
-
default: type[MISSING] | T = MISSING,
|
|
52
|
+
default: type[MISSING] | T = MISSING,
|
|
53
53
|
*,
|
|
54
54
|
new: ta.Callable[[], T] | type[MISSING] = MISSING,
|
|
55
55
|
validate: ta.Callable[[T], None] | None = None,
|
|
@@ -87,7 +87,7 @@ class Var(ta.Generic[T]):
|
|
|
87
87
|
self._validate(self.value)
|
|
88
88
|
return Binding(self, value, offset=offset)
|
|
89
89
|
|
|
90
|
-
def with_binding(self, value):
|
|
90
|
+
def with_binding(self, value): # noqa
|
|
91
91
|
def outer(fn):
|
|
92
92
|
@functools.wraps(fn)
|
|
93
93
|
def inner(*args, **kwargs):
|
|
@@ -96,7 +96,7 @@ class Var(ta.Generic[T]):
|
|
|
96
96
|
return inner
|
|
97
97
|
return outer
|
|
98
98
|
|
|
99
|
-
def with_binding_fn(self, binding_fn):
|
|
99
|
+
def with_binding_fn(self, binding_fn): # noqa
|
|
100
100
|
this = self
|
|
101
101
|
|
|
102
102
|
def outer(fn):
|
|
@@ -104,7 +104,7 @@ class Var(ta.Generic[T]):
|
|
|
104
104
|
|
|
105
105
|
@staticmethod
|
|
106
106
|
@functools.wraps(fn)
|
|
107
|
-
def __call__(*args, **kwargs):
|
|
107
|
+
def __call__(*args, **kwargs): # noqa
|
|
108
108
|
with this.binding(binding_fn(*args, **kwargs)):
|
|
109
109
|
return fn(*args, **kwargs)
|
|
110
110
|
|
|
@@ -119,7 +119,7 @@ class Var(ta.Generic[T]):
|
|
|
119
119
|
|
|
120
120
|
return inner
|
|
121
121
|
|
|
122
|
-
dct =
|
|
122
|
+
dct: dict[str, ta.Any] = {k: getattr(fn, k) for k in functools.WRAPPER_ASSIGNMENTS}
|
|
123
123
|
return lang.new_type(fn.__name__, (Descriptor,), dct)()
|
|
124
124
|
|
|
125
125
|
return outer
|
|
@@ -133,12 +133,12 @@ class Var(ta.Generic[T]):
|
|
|
133
133
|
except KeyError:
|
|
134
134
|
pass
|
|
135
135
|
else:
|
|
136
|
-
for level, frame_binding in sorted(frame_bindings.items()):
|
|
136
|
+
for level, frame_binding in sorted(frame_bindings.items()): # noqa
|
|
137
137
|
yield frame_binding._value # noqa
|
|
138
138
|
frame = frame.f_back
|
|
139
139
|
|
|
140
140
|
if self._new is not MISSING:
|
|
141
|
-
yield self._new()
|
|
141
|
+
yield self._new() # type: ignore
|
|
142
142
|
|
|
143
143
|
def __iter__(self) -> ta.Iterator[T]:
|
|
144
144
|
return self.values
|
|
@@ -148,7 +148,7 @@ class Var(ta.Generic[T]):
|
|
|
148
148
|
try:
|
|
149
149
|
return next(self.values)
|
|
150
150
|
except StopIteration:
|
|
151
|
-
raise UnboundVarError
|
|
151
|
+
raise UnboundVarError from None
|
|
152
152
|
|
|
153
153
|
|
|
154
154
|
class Binding(ta.Generic[T]):
|
|
@@ -213,7 +213,7 @@ class _GeneratorContextManager(contextlib._GeneratorContextManager): # noqa
|
|
|
213
213
|
return super().__enter__()
|
|
214
214
|
|
|
215
215
|
|
|
216
|
-
def contextmanager(fn):
|
|
216
|
+
def contextmanager(fn): # noqa
|
|
217
217
|
@functools.wraps(fn)
|
|
218
218
|
def helper(*args, **kwds):
|
|
219
219
|
return _GeneratorContextManager(fn, args, kwds)
|
omlish/fnpairs.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- objects
|
|
4
|
+
- csv
|
|
5
|
+
- csvloader
|
|
6
|
+
- cbor
|
|
7
|
+
- cloudpickle
|
|
8
|
+
- alt json backends
|
|
9
|
+
- compression
|
|
10
|
+
- snappy
|
|
11
|
+
- lz4
|
|
12
|
+
- wrapped (wait for usecase)
|
|
13
|
+
"""
|
|
14
|
+
import abc
|
|
15
|
+
import codecs
|
|
16
|
+
import dataclasses as dc
|
|
17
|
+
import typing as ta
|
|
18
|
+
|
|
19
|
+
from . import lang
|
|
20
|
+
|
|
21
|
+
if ta.TYPE_CHECKING:
|
|
22
|
+
import bz2 as _bz2
|
|
23
|
+
import cloudpickle as _cloudpickle
|
|
24
|
+
import gzip as _gzip
|
|
25
|
+
import json as _json
|
|
26
|
+
import lz4.frame as _lz4_frame
|
|
27
|
+
import lzma as _lzma
|
|
28
|
+
import pickle as _pickle
|
|
29
|
+
import snappy as _snappy
|
|
30
|
+
import struct as _struct
|
|
31
|
+
import tomllib as _tomllib
|
|
32
|
+
import yaml as _yaml
|
|
33
|
+
import zstd as _zstd
|
|
34
|
+
|
|
35
|
+
else:
|
|
36
|
+
_bz2 = lang.proxy_import('bz2')
|
|
37
|
+
_cloudpickle = lang.proxy_import('cloudpickle')
|
|
38
|
+
_gzip = lang.proxy_import('gzip')
|
|
39
|
+
_json = lang.proxy_import('json')
|
|
40
|
+
_lz4_frame = lang.proxy_import('lz4.frame')
|
|
41
|
+
_lzma = lang.proxy_import('lzma')
|
|
42
|
+
_pickle = lang.proxy_import('pickle')
|
|
43
|
+
_snappy = lang.proxy_import('snappy')
|
|
44
|
+
_struct = lang.proxy_import('struct')
|
|
45
|
+
_tomllib = lang.proxy_import('tomllib')
|
|
46
|
+
_yaml = lang.proxy_import('yaml')
|
|
47
|
+
_zstd = lang.proxy_import('zstd')
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
F = ta.TypeVar('F')
|
|
54
|
+
F2 = ta.TypeVar('F2')
|
|
55
|
+
T = ta.TypeVar('T')
|
|
56
|
+
T2 = ta.TypeVar('T2')
|
|
57
|
+
U = ta.TypeVar('U')
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class FnPair(ta.Generic[F, T], abc.ABC):
|
|
61
|
+
@abc.abstractmethod
|
|
62
|
+
def forward(self, f: F) -> T:
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
@abc.abstractmethod
|
|
66
|
+
def backward(self, t: T) -> F:
|
|
67
|
+
raise NotImplementedError
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
|
|
71
|
+
def __call__(self, f: F) -> T:
|
|
72
|
+
return self.forward(f)
|
|
73
|
+
|
|
74
|
+
def invert(self) -> 'FnPair[T, F]':
|
|
75
|
+
if isinstance(self, Inverted):
|
|
76
|
+
return self.fp
|
|
77
|
+
return Inverted(self)
|
|
78
|
+
|
|
79
|
+
def compose(self, nxt: 'FnPair[T, U]') -> 'FnPair[F, U]':
|
|
80
|
+
return Composite((self, nxt))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dc.dataclass(frozen=True)
|
|
87
|
+
class Simple(FnPair[F, T]):
|
|
88
|
+
forward: ta.Callable[[F], T] # type: ignore
|
|
89
|
+
backward: ta.Callable[[T], F] # type: ignore
|
|
90
|
+
|
|
91
|
+
def _forward(self, f: F) -> T:
|
|
92
|
+
return self.forward(f)
|
|
93
|
+
|
|
94
|
+
def _backward(self, t: T) -> F:
|
|
95
|
+
return self.backward(t)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# HACK: ABC workaround. Our dataclasses handle this with `override=True` but we don't want to dep that in here.
|
|
99
|
+
Simple.forward = Simple._forward # type: ignore # noqa
|
|
100
|
+
Simple.backward = Simple._backward # type: ignore # noqa
|
|
101
|
+
Simple.__abstractmethods__ = frozenset() # noqa
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
of = Simple
|
|
105
|
+
|
|
106
|
+
NOP: FnPair[ta.Any, ta.Any] = of(lang.identity, lang.identity)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
##
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dc.dataclass(frozen=True)
|
|
113
|
+
class Inverted(FnPair[F, T]):
|
|
114
|
+
fp: FnPair[T, F]
|
|
115
|
+
|
|
116
|
+
def forward(self, f: F) -> T:
|
|
117
|
+
return self.fp.backward(f)
|
|
118
|
+
|
|
119
|
+
def backward(self, t: T) -> F:
|
|
120
|
+
return self.fp.forward(t)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dc.dataclass(frozen=True)
|
|
124
|
+
class Composite(FnPair[F, T]):
|
|
125
|
+
children: ta.Sequence[FnPair]
|
|
126
|
+
|
|
127
|
+
def forward(self, f: F) -> T:
|
|
128
|
+
for c in self.children:
|
|
129
|
+
f = c.forward(f)
|
|
130
|
+
return ta.cast(T, f)
|
|
131
|
+
|
|
132
|
+
def backward(self, t: T) -> F:
|
|
133
|
+
for c in reversed(self.children):
|
|
134
|
+
t = c.backward(t)
|
|
135
|
+
return ta.cast(F, t)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def compose(*ps: FnPair) -> FnPair:
|
|
139
|
+
if not ps:
|
|
140
|
+
return NOP
|
|
141
|
+
if len(ps) == 1:
|
|
142
|
+
return ps[0]
|
|
143
|
+
return Composite(ps)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
##
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dc.dataclass(frozen=True)
|
|
150
|
+
class Text(FnPair[str, bytes]):
|
|
151
|
+
ci: codecs.CodecInfo
|
|
152
|
+
encode_errors: str = dc.field(default='strict', kw_only=True)
|
|
153
|
+
decode_errors: str = dc.field(default='strict', kw_only=True)
|
|
154
|
+
|
|
155
|
+
def forward(self, f: str) -> bytes:
|
|
156
|
+
# Python ignores the returned length:
|
|
157
|
+
# https://github.com/python/cpython/blob/7431c3799efbd06ed03ee70b64420f45e83b3667/Python/codecs.c#L424
|
|
158
|
+
t, _ = self.ci.encode(f, self.encode_errors)
|
|
159
|
+
return t
|
|
160
|
+
|
|
161
|
+
def backward(self, t: bytes) -> str:
|
|
162
|
+
f, _ = self.ci.decode(t, self.decode_errors)
|
|
163
|
+
return f
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def text(name: str, *, encode_errors: str = 'strict', decode_errors: str = 'strict') -> Text:
|
|
167
|
+
ci = codecs.lookup(name)
|
|
168
|
+
if not ci._is_text_encoding: # noqa
|
|
169
|
+
raise TypeError(f'must be text codec: {name}')
|
|
170
|
+
return Text(ci, encode_errors=encode_errors, decode_errors=decode_errors)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
UTF8 = text('utf-8')
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
#
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dc.dataclass(frozen=True)
|
|
180
|
+
class Optional(FnPair[ta.Optional[F], ta.Optional[T]]):
|
|
181
|
+
fp: FnPair[F, T]
|
|
182
|
+
|
|
183
|
+
def forward(self, f: ta.Optional[F]) -> ta.Optional[T]:
|
|
184
|
+
return None if f is None else self.fp.forward(f)
|
|
185
|
+
|
|
186
|
+
def backward(self, t: ta.Optional[T]) -> ta.Optional[F]:
|
|
187
|
+
return None if t is None else self.fp.backward(t)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class Lines(FnPair[ta.Sequence[str], str]):
|
|
191
|
+
def forward(self, f: ta.Sequence[str]) -> str:
|
|
192
|
+
return '\n'.join(f)
|
|
193
|
+
|
|
194
|
+
def backward(self, t: str) -> ta.Sequence[str]:
|
|
195
|
+
return t.splitlines()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
##
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
_EXTENSION_REGISTRY: dict[str, type[FnPair]] = {}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _register_extension(*ss):
|
|
205
|
+
def inner(cls):
|
|
206
|
+
for s in ss:
|
|
207
|
+
if s in _EXTENSION_REGISTRY:
|
|
208
|
+
raise Exception(s)
|
|
209
|
+
_EXTENSION_REGISTRY[s] = cls
|
|
210
|
+
return cls
|
|
211
|
+
return inner
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def get_for_extension(ext: str) -> FnPair:
|
|
215
|
+
return compose(*[_EXTENSION_REGISTRY[p]() for p in ext.split('.')])
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
##
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class Compression(FnPair[bytes, bytes], abc.ABC):
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@_register_extension('bz2')
|
|
226
|
+
@dc.dataclass(frozen=True)
|
|
227
|
+
class Bz2(Compression):
|
|
228
|
+
compresslevel: int = 9
|
|
229
|
+
|
|
230
|
+
def forward(self, f: bytes) -> bytes:
|
|
231
|
+
return _bz2.compress(f, compresslevel=self.compresslevel)
|
|
232
|
+
|
|
233
|
+
def backward(self, t: bytes) -> bytes:
|
|
234
|
+
return _bz2.decompress(t)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@_register_extension('gz')
|
|
238
|
+
@dc.dataclass(frozen=True)
|
|
239
|
+
class Gzip(Compression):
|
|
240
|
+
compresslevel: int = 9
|
|
241
|
+
|
|
242
|
+
def forward(self, f: bytes) -> bytes:
|
|
243
|
+
return _gzip.compress(f, compresslevel=self.compresslevel)
|
|
244
|
+
|
|
245
|
+
def backward(self, t: bytes) -> bytes:
|
|
246
|
+
return _gzip.decompress(t)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@_register_extension('lzma')
|
|
250
|
+
class Lzma(Compression):
|
|
251
|
+
def forward(self, f: bytes) -> bytes:
|
|
252
|
+
return _lzma.compress(f)
|
|
253
|
+
|
|
254
|
+
def backward(self, t: bytes) -> bytes:
|
|
255
|
+
return _lzma.decompress(t)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
#
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@_register_extension('lz4')
|
|
262
|
+
@dc.dataclass(frozen=True)
|
|
263
|
+
class Lz4(Compression):
|
|
264
|
+
compression_level: int = 0
|
|
265
|
+
|
|
266
|
+
def forward(self, f: bytes) -> bytes:
|
|
267
|
+
return _lz4_frame.compress(f, compression_level=self.compression_level)
|
|
268
|
+
|
|
269
|
+
def backward(self, t: bytes) -> bytes:
|
|
270
|
+
return _lz4_frame.decompress(t)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@_register_extension('snappy')
|
|
274
|
+
class Snappy(Compression):
|
|
275
|
+
def forward(self, f: bytes) -> bytes:
|
|
276
|
+
return _snappy.compress(f)
|
|
277
|
+
|
|
278
|
+
def backward(self, t: bytes) -> bytes:
|
|
279
|
+
return _snappy.decompress(t)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@_register_extension('zstd')
|
|
283
|
+
class Zstd(Compression):
|
|
284
|
+
def forward(self, f: bytes) -> bytes:
|
|
285
|
+
return _zstd.compress(f)
|
|
286
|
+
|
|
287
|
+
def backward(self, t: bytes) -> bytes:
|
|
288
|
+
return _zstd.decompress(t)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
##
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@dc.dataclass(frozen=True)
|
|
295
|
+
class Struct(FnPair[tuple, bytes]):
|
|
296
|
+
fmt: str
|
|
297
|
+
|
|
298
|
+
def forward(self, f: tuple) -> bytes:
|
|
299
|
+
return _struct.pack(self.fmt, *f)
|
|
300
|
+
|
|
301
|
+
def backward(self, t: bytes) -> tuple:
|
|
302
|
+
return _struct.unpack(self.fmt, t)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
##
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class Object(FnPair[ta.Any, T], lang.Abstract): # noqa
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class ObjectStr(Object[str], lang.Abstract): # noqa
|
|
313
|
+
pass
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class ObjectBytes(Object[bytes], lang.Abstract): # noqa
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
#
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@_register_extension('pkl')
|
|
324
|
+
@dc.dataclass(frozen=True)
|
|
325
|
+
class Pickle(ObjectBytes):
|
|
326
|
+
protocol: int | None = None
|
|
327
|
+
|
|
328
|
+
def forward(self, f: ta.Any) -> bytes:
|
|
329
|
+
return _pickle.dumps(f, protocol=self.protocol)
|
|
330
|
+
|
|
331
|
+
def backward(self, t: bytes) -> ta.Any:
|
|
332
|
+
return _pickle.loads(t)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
@_register_extension('json')
|
|
336
|
+
@dc.dataclass(frozen=True)
|
|
337
|
+
class Json(ObjectStr):
|
|
338
|
+
indent: int | str | None = dc.field(default=None, kw_only=True)
|
|
339
|
+
separators: tuple[str, str] | None = dc.field(default=None, kw_only=True)
|
|
340
|
+
|
|
341
|
+
def forward(self, f: ta.Any) -> str:
|
|
342
|
+
return _json.dumps(f, indent=self.indent, separators=self.separators)
|
|
343
|
+
|
|
344
|
+
def backward(self, t: str) -> ta.Any:
|
|
345
|
+
return _json.loads(t)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
JSON = Json()
|
|
349
|
+
PRETTY_JSON = Json(indent=2)
|
|
350
|
+
COMPACT_JSON = Json(separators=(',', ':'))
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@_register_extension('jsonl')
|
|
354
|
+
class JsonLines(FnPair[ta.Sequence[ta.Any], str]):
|
|
355
|
+
def forward(self, f: ta.Sequence[ta.Any]) -> str:
|
|
356
|
+
return '\n'.join(_json.dumps(e) for e in f)
|
|
357
|
+
|
|
358
|
+
def backward(self, t: str) -> ta.Sequence[ta.Any]:
|
|
359
|
+
return [_json.loads(l) for l in t.splitlines()]
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@_register_extension('toml')
|
|
363
|
+
class Toml(ObjectStr):
|
|
364
|
+
def forward(self, f: ta.Any) -> str:
|
|
365
|
+
raise NotImplementedError
|
|
366
|
+
|
|
367
|
+
def backward(self, t: str) -> ta.Any:
|
|
368
|
+
return _tomllib.loads(t)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
#
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@_register_extension('cpkl')
|
|
375
|
+
@dc.dataclass(frozen=True)
|
|
376
|
+
class Cloudpickle(ObjectBytes):
|
|
377
|
+
protocol: int | None = None
|
|
378
|
+
|
|
379
|
+
def forward(self, f: ta.Any) -> bytes:
|
|
380
|
+
return _cloudpickle.dumps(f, protocol=self.protocol)
|
|
381
|
+
|
|
382
|
+
def backward(self, t: bytes) -> ta.Any:
|
|
383
|
+
return _cloudpickle.loads(t)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@_register_extension('yml', 'yaml')
|
|
387
|
+
class Yaml(ObjectStr):
|
|
388
|
+
def forward(self, f: ta.Any) -> str:
|
|
389
|
+
return _yaml.dump(f)
|
|
390
|
+
|
|
391
|
+
def backward(self, t: str) -> ta.Any:
|
|
392
|
+
return _yaml.safe_load(t)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class YamlUnsafe(ObjectStr):
|
|
396
|
+
def forward(self, f: ta.Any) -> str:
|
|
397
|
+
return _yaml.dump(f)
|
|
398
|
+
|
|
399
|
+
def backward(self, t: str) -> ta.Any:
|
|
400
|
+
return _yaml.load(t, _yaml.FullLoader)
|
omlish/graphs/trees.py
CHANGED
|
@@ -18,7 +18,7 @@ NodeWalker = ta.Callable[[NodeT], ta.Iterable[NodeT]]
|
|
|
18
18
|
NodeGenerator = ta.Generator[NodeT, None, None]
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class
|
|
21
|
+
class NodeError(ta.Generic[NodeT], Exception):
|
|
22
22
|
def __init__(self, node: NodeT, msg: str, *args, **kwargs) -> None:
|
|
23
23
|
super().__init__(msg, *args, **kwargs) # noqa
|
|
24
24
|
self._node = node
|
|
@@ -28,12 +28,12 @@ class NodeException(ta.Generic[NodeT], Exception):
|
|
|
28
28
|
return self._node
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
class
|
|
31
|
+
class DuplicateNodeError(NodeError[NodeT]):
|
|
32
32
|
def __init__(self, node: NodeT, *args, **kwargs) -> None:
|
|
33
33
|
super().__init__(node, f'Duplicate node: {node!r}', *args, **kwargs)
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
class
|
|
36
|
+
class UnknownNodeError(NodeError[NodeT]):
|
|
37
37
|
def __init__(self, node: NodeT, *args, **kwargs) -> None:
|
|
38
38
|
super().__init__(node, f'Unknown node: {node!r}', *args, **kwargs)
|
|
39
39
|
|
|
@@ -55,19 +55,19 @@ class BasicTreeAnalysis(ta.Generic[NodeT]):
|
|
|
55
55
|
|
|
56
56
|
self._set_fac: ta.Callable[..., ta.MutableSet[NodeT]] = col.IdentitySet if identity else set
|
|
57
57
|
self._dict_fac: ta.Callable[..., ta.MutableMapping[NodeT, ta.Any]] = col.IdentityKeyDict if identity else dict
|
|
58
|
-
self._idx_seq_fac: ta.Callable[..., col.IndexedSeq[NodeT]] = functools.partial(col.IndexedSeq, identity=identity) # noqa
|
|
58
|
+
self._idx_seq_fac: ta.Callable[..., col.IndexedSeq[NodeT]] = functools.partial(col.IndexedSeq, identity=identity) # type: ignore # noqa
|
|
59
59
|
|
|
60
60
|
def walk(cur: NodeT, parent: NodeT | None) -> None:
|
|
61
61
|
check.not_none(cur)
|
|
62
62
|
if cur in node_set:
|
|
63
|
-
raise
|
|
63
|
+
raise DuplicateNodeError(cur)
|
|
64
64
|
|
|
65
65
|
nodes.append(cur)
|
|
66
66
|
node_set.add(cur)
|
|
67
67
|
if parent is None:
|
|
68
68
|
check.state(cur is root)
|
|
69
69
|
elif parent not in node_set:
|
|
70
|
-
raise
|
|
70
|
+
raise UnknownNodeError(parent)
|
|
71
71
|
|
|
72
72
|
parents_by_node[cur] = parent
|
|
73
73
|
|
|
@@ -77,19 +77,19 @@ class BasicTreeAnalysis(ta.Generic[NodeT]):
|
|
|
77
77
|
walk(child, cur)
|
|
78
78
|
|
|
79
79
|
nodes: list[NodeT] = []
|
|
80
|
-
node_set: ta.MutableSet[NodeT] = self._set_fac()
|
|
81
|
-
children_by_node: ta.MutableMapping[NodeT | None, ta.Sequence[NodeT]] = self._dict_fac()
|
|
82
|
-
child_sets_by_node: ta.MutableMapping[ta.Optional[NodeT], ta.AbstractSet[NodeT]] = self._dict_fac()
|
|
83
|
-
parents_by_node: ta.MutableMapping[NodeT, NodeT | None] = self._dict_fac()
|
|
80
|
+
node_set: ta.MutableSet[NodeT] = self._set_fac()
|
|
81
|
+
children_by_node: ta.MutableMapping[NodeT | None, ta.Sequence[NodeT]] = self._dict_fac()
|
|
82
|
+
child_sets_by_node: ta.MutableMapping[ta.Optional[NodeT], ta.AbstractSet[NodeT]] = self._dict_fac()
|
|
83
|
+
parents_by_node: ta.MutableMapping[NodeT, NodeT | None] = self._dict_fac()
|
|
84
84
|
|
|
85
85
|
children_by_node[None] = [root]
|
|
86
|
-
child_sets_by_node[None] = self._set_fac([root])
|
|
86
|
+
child_sets_by_node[None] = self._set_fac([root])
|
|
87
87
|
|
|
88
88
|
walk(root, None)
|
|
89
89
|
|
|
90
|
-
self._nodes = self._idx_seq_fac(nodes)
|
|
90
|
+
self._nodes = self._idx_seq_fac(nodes)
|
|
91
91
|
self._node_set: ta.AbstractSet[NodeT] = node_set
|
|
92
|
-
self._children_by_node: ta.Mapping[NodeT | None, col.IndexedSeq[NodeT]] = self._dict_fac(
|
|
92
|
+
self._children_by_node: ta.Mapping[NodeT | None, col.IndexedSeq[NodeT]] = self._dict_fac(
|
|
93
93
|
[(n, self._idx_seq_fac(cs)) for n, cs in children_by_node.items()])
|
|
94
94
|
self._child_sets_by_node: ta.Mapping[NodeT | None, ta.AbstractSet[NodeT]] = child_sets_by_node
|
|
95
95
|
self._parents_by_node: ta.Mapping[NodeT, NodeT | None] = parents_by_node
|