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.

Files changed (124) hide show
  1. omlish/__about__.py +1 -2
  2. omlish/__init__.py +8 -0
  3. omlish/argparse.py +4 -4
  4. omlish/asyncs/__init__.py +23 -2
  5. omlish/asyncs/anyio.py +13 -11
  6. omlish/asyncs/asyncs.py +1 -3
  7. omlish/asyncs/flavors.py +201 -0
  8. omlish/asyncs/futures.py +10 -9
  9. omlish/asyncs/trio_asyncio.py +41 -0
  10. omlish/c3.py +1 -1
  11. omlish/check.py +3 -3
  12. omlish/collections/_abc.py +2 -0
  13. omlish/collections/_io_abc.py +4 -2
  14. omlish/collections/cache/__init__.py +1 -1
  15. omlish/collections/cache/descriptor.py +8 -8
  16. omlish/collections/cache/impl.py +24 -17
  17. omlish/collections/cache/types.py +1 -1
  18. omlish/collections/coerce.py +1 -1
  19. omlish/collections/frozen.py +6 -6
  20. omlish/collections/identity.py +3 -4
  21. omlish/collections/mappings.py +2 -2
  22. omlish/collections/ordered.py +7 -7
  23. omlish/collections/skiplist.py +1 -1
  24. omlish/collections/sorted.py +1 -1
  25. omlish/collections/treap.py +25 -0
  26. omlish/collections/treapmap.py +57 -5
  27. omlish/collections/unmodifiable.py +9 -9
  28. omlish/collections/utils.py +1 -1
  29. omlish/configs/flattening.py +7 -6
  30. omlish/configs/props.py +3 -3
  31. omlish/dataclasses/__init__.py +1 -1
  32. omlish/dataclasses/impl/__init__.py +17 -1
  33. omlish/dataclasses/impl/api.py +10 -11
  34. omlish/dataclasses/impl/as_.py +4 -4
  35. omlish/dataclasses/impl/exceptions.py +1 -1
  36. omlish/dataclasses/impl/fields.py +7 -7
  37. omlish/dataclasses/impl/frozen.py +2 -2
  38. omlish/dataclasses/impl/init.py +5 -5
  39. omlish/dataclasses/impl/internals.py +1 -1
  40. omlish/dataclasses/impl/metaclass.py +1 -1
  41. omlish/dataclasses/impl/order.py +1 -1
  42. omlish/dataclasses/impl/replace.py +1 -1
  43. omlish/dataclasses/impl/repr.py +4 -4
  44. omlish/dataclasses/impl/utils.py +6 -6
  45. omlish/defs.py +13 -17
  46. omlish/{procfs.py → diag/procfs.py} +22 -24
  47. omlish/diag/ps.py +47 -0
  48. omlish/{replserver → diag/replserver}/console.py +18 -20
  49. omlish/{replserver → diag/replserver}/server.py +8 -8
  50. omlish/dispatch/dispatch.py +5 -8
  51. omlish/dispatch/functions.py +1 -1
  52. omlish/dispatch/methods.py +4 -5
  53. omlish/docker.py +1 -1
  54. omlish/dynamic.py +10 -10
  55. omlish/fnpairs.py +400 -0
  56. omlish/graphs/trees.py +13 -13
  57. omlish/inject/__init__.py +7 -7
  58. omlish/inject/elements.py +1 -1
  59. omlish/inject/exceptions.py +7 -7
  60. omlish/inject/impl/elements.py +17 -5
  61. omlish/inject/impl/injector.py +12 -10
  62. omlish/inject/impl/inspect.py +2 -2
  63. omlish/inject/impl/scopes.py +12 -11
  64. omlish/inject/proxy.py +5 -5
  65. omlish/iterators.py +19 -24
  66. omlish/json.py +143 -6
  67. omlish/lang/__init__.py +13 -4
  68. omlish/lang/cached.py +2 -5
  69. omlish/lang/classes/__init__.py +2 -2
  70. omlish/lang/classes/abstract.py +2 -2
  71. omlish/lang/classes/restrict.py +14 -14
  72. omlish/lang/classes/simple.py +1 -1
  73. omlish/lang/classes/virtual.py +5 -5
  74. omlish/lang/clsdct.py +1 -1
  75. omlish/lang/cmp.py +2 -2
  76. omlish/lang/contextmanagers.py +12 -14
  77. omlish/lang/descriptors.py +16 -4
  78. omlish/lang/exceptions.py +1 -1
  79. omlish/lang/functions.py +58 -22
  80. omlish/lang/imports.py +22 -27
  81. omlish/lang/iterables.py +2 -2
  82. omlish/lang/maybes.py +1 -0
  83. omlish/lang/objects.py +15 -9
  84. omlish/lang/resolving.py +1 -1
  85. omlish/lang/strings.py +1 -1
  86. omlish/lang/sys.py +7 -0
  87. omlish/lang/typing.py +3 -3
  88. omlish/libc.py +9 -5
  89. omlish/logs/_abc.py +5 -1
  90. omlish/logs/filters.py +2 -0
  91. omlish/logs/formatters.py +6 -2
  92. omlish/logs/utils.py +1 -1
  93. omlish/marshal/base.py +3 -3
  94. omlish/marshal/exceptions.py +1 -1
  95. omlish/marshal/global_.py +10 -4
  96. omlish/marshal/objects.py +1 -2
  97. omlish/marshal/registries.py +3 -3
  98. omlish/marshal/utils.py +2 -2
  99. omlish/marshal/values.py +1 -1
  100. omlish/math.py +9 -9
  101. omlish/reflect.py +3 -3
  102. omlish/sql/__init__.py +9 -0
  103. omlish/sql/asyncs.py +148 -0
  104. omlish/stats.py +4 -5
  105. omlish/term.py +1 -1
  106. omlish/testing/pydevd.py +28 -6
  107. omlish/testing/pytest/inject/harness.py +1 -1
  108. omlish/testing/pytest/plugins/pydevd.py +1 -1
  109. omlish/testing/pytest/plugins/switches.py +1 -1
  110. omlish/text/delimit.py +3 -6
  111. {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/METADATA +4 -1
  112. omlish-0.0.0.dev4.dist-info/RECORD +195 -0
  113. {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/WHEEL +1 -1
  114. omlish/lang/classes/test/test_abstract.py +0 -89
  115. omlish/lang/classes/test/test_restrict.py +0 -71
  116. omlish/lang/classes/test/test_simple.py +0 -58
  117. omlish/lang/classes/test/test_virtual.py +0 -72
  118. omlish-0.0.0.dev2.dist-info/RECORD +0 -193
  119. /omlish/{lang/classes/test → diag}/__init__.py +0 -0
  120. /omlish/{replserver → diag/replserver}/__init__.py +0 -0
  121. /omlish/{replserver → diag/replserver}/__main__.py +0 -0
  122. /omlish/sql/{_abcs.py → _abc.py} +0 -0
  123. {omlish-0.0.0.dev2.dist-info → omlish-0.0.0.dev4.dist-info}/LICENSE +0 -0
  124. {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 .. import check
27
- from .. import dataclasses as dc
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 sock.timeout:
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 thread, console in self._consoles_by_threads.items():
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.keys():
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()
@@ -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, ta.FrozenSet[type]] = weakref.WeakKeyDictionary()
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) -> ta.FrozenSet[type]:
20
- try:
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
- try:
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
@@ -10,7 +10,7 @@ from .dispatch import get_impl_func_cls_set
10
10
  USE_EXTENSION = False
11
11
 
12
12
 
13
- def function(func):
13
+ def function(func): # noqa
14
14
  disp = Dispatcher() # type: ignore
15
15
  disp.register(func, [object])
16
16
 
@@ -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
- try:
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), 'r') as f:
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, # type: ignore
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 = dict((k, getattr(fn, k)) for k in functools.WRAPPER_ASSIGNMENTS)
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 NodeException(ta.Generic[NodeT], Exception):
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 DuplicateNodeException(NodeException[NodeT]):
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 UnknownNodeException(NodeException[NodeT]):
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 DuplicateNodeException(cur)
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 UnknownNodeException(parent)
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() # type: ignore
81
- children_by_node: ta.MutableMapping[NodeT | None, ta.Sequence[NodeT]] = self._dict_fac() # type: ignore
82
- child_sets_by_node: ta.MutableMapping[ta.Optional[NodeT], ta.AbstractSet[NodeT]] = self._dict_fac() # type: ignore # noqa
83
- parents_by_node: ta.MutableMapping[NodeT, NodeT | None] = self._dict_fac() # type: ignore
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]) # type: ignore
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) # type: ignore
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( # type: ignore
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