omlish 0.0.0.dev133__py3-none-any.whl → 0.0.0.dev177__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 (210) hide show
  1. omlish/.manifests.json +265 -7
  2. omlish/__about__.py +5 -3
  3. omlish/antlr/_runtime/__init__.py +0 -22
  4. omlish/antlr/_runtime/_all.py +24 -0
  5. omlish/antlr/_runtime/atn/ParserATNSimulator.py +1 -1
  6. omlish/antlr/_runtime/dfa/DFASerializer.py +1 -1
  7. omlish/antlr/_runtime/error/DiagnosticErrorListener.py +2 -1
  8. omlish/antlr/_runtime/xpath/XPath.py +7 -1
  9. omlish/antlr/_runtime/xpath/XPathLexer.py +1 -1
  10. omlish/antlr/delimit.py +106 -0
  11. omlish/antlr/dot.py +31 -0
  12. omlish/antlr/errors.py +11 -0
  13. omlish/antlr/input.py +96 -0
  14. omlish/antlr/parsing.py +19 -0
  15. omlish/antlr/runtime.py +102 -0
  16. omlish/antlr/utils.py +38 -0
  17. omlish/argparse/all.py +45 -0
  18. omlish/{argparse.py → argparse/cli.py} +112 -107
  19. omlish/asyncs/__init__.py +0 -35
  20. omlish/asyncs/all.py +35 -0
  21. omlish/asyncs/asyncio/all.py +7 -0
  22. omlish/asyncs/asyncio/channels.py +40 -0
  23. omlish/asyncs/asyncio/streams.py +45 -0
  24. omlish/asyncs/asyncio/subprocesses.py +238 -0
  25. omlish/asyncs/asyncio/timeouts.py +16 -0
  26. omlish/asyncs/bluelet/LICENSE +6 -0
  27. omlish/asyncs/bluelet/all.py +67 -0
  28. omlish/asyncs/bluelet/api.py +23 -0
  29. omlish/asyncs/bluelet/core.py +178 -0
  30. omlish/asyncs/bluelet/events.py +78 -0
  31. omlish/asyncs/bluelet/files.py +80 -0
  32. omlish/asyncs/bluelet/runner.py +416 -0
  33. omlish/asyncs/bluelet/sockets.py +214 -0
  34. omlish/bootstrap/sys.py +3 -3
  35. omlish/cached.py +2 -2
  36. omlish/check.py +49 -460
  37. omlish/codecs/__init__.py +72 -0
  38. omlish/codecs/base.py +106 -0
  39. omlish/codecs/bytes.py +119 -0
  40. omlish/codecs/chain.py +23 -0
  41. omlish/codecs/funcs.py +39 -0
  42. omlish/codecs/registry.py +139 -0
  43. omlish/codecs/standard.py +4 -0
  44. omlish/codecs/text.py +217 -0
  45. omlish/collections/cache/impl.py +50 -57
  46. omlish/collections/coerce.py +1 -0
  47. omlish/collections/mappings.py +1 -1
  48. omlish/configs/flattening.py +1 -1
  49. omlish/defs.py +1 -1
  50. omlish/diag/_pycharm/runhack.py +8 -2
  51. omlish/diag/procfs.py +8 -8
  52. omlish/docker/__init__.py +0 -36
  53. omlish/docker/all.py +31 -0
  54. omlish/docker/consts.py +4 -0
  55. omlish/{lite/docker.py → docker/detect.py} +18 -0
  56. omlish/docker/{helpers.py → timebomb.py} +0 -21
  57. omlish/formats/cbor.py +31 -0
  58. omlish/formats/cloudpickle.py +31 -0
  59. omlish/formats/codecs.py +93 -0
  60. omlish/formats/json/codecs.py +29 -0
  61. omlish/formats/json/delimted.py +4 -0
  62. omlish/formats/json/stream/errors.py +2 -0
  63. omlish/formats/json/stream/lex.py +12 -6
  64. omlish/formats/json/stream/parse.py +38 -22
  65. omlish/formats/json5.py +31 -0
  66. omlish/formats/pickle.py +31 -0
  67. omlish/formats/repr.py +25 -0
  68. omlish/formats/toml.py +17 -0
  69. omlish/formats/yaml.py +25 -0
  70. omlish/funcs/__init__.py +0 -0
  71. omlish/{genmachine.py → funcs/genmachine.py} +5 -4
  72. omlish/{matchfns.py → funcs/match.py} +1 -1
  73. omlish/funcs/pairs.py +215 -0
  74. omlish/http/__init__.py +0 -48
  75. omlish/http/all.py +48 -0
  76. omlish/http/coro/__init__.py +0 -0
  77. omlish/{lite/fdio/corohttp.py → http/coro/fdio.py} +21 -19
  78. omlish/{lite/http/coroserver.py → http/coro/server.py} +20 -21
  79. omlish/{lite/http → http}/handlers.py +3 -2
  80. omlish/{lite/http → http}/parsing.py +1 -0
  81. omlish/http/sessions.py +1 -1
  82. omlish/{lite/http → http}/versions.py +1 -0
  83. omlish/inject/managed.py +2 -2
  84. omlish/io/__init__.py +0 -3
  85. omlish/{lite/io.py → io/buffers.py} +8 -9
  86. omlish/io/compress/__init__.py +9 -0
  87. omlish/io/compress/abc.py +104 -0
  88. omlish/io/compress/adapters.py +148 -0
  89. omlish/io/compress/base.py +24 -0
  90. omlish/io/compress/brotli.py +47 -0
  91. omlish/io/compress/bz2.py +61 -0
  92. omlish/io/compress/codecs.py +78 -0
  93. omlish/io/compress/gzip.py +350 -0
  94. omlish/io/compress/lz4.py +91 -0
  95. omlish/io/compress/lzma.py +81 -0
  96. omlish/io/compress/snappy.py +34 -0
  97. omlish/io/compress/zlib.py +74 -0
  98. omlish/io/compress/zstd.py +44 -0
  99. omlish/io/fdio/__init__.py +1 -0
  100. omlish/{lite → io}/fdio/handlers.py +5 -5
  101. omlish/{lite → io}/fdio/kqueue.py +8 -8
  102. omlish/{lite → io}/fdio/manager.py +7 -7
  103. omlish/{lite → io}/fdio/pollers.py +13 -13
  104. omlish/io/generators/__init__.py +56 -0
  105. omlish/io/generators/consts.py +1 -0
  106. omlish/io/generators/direct.py +13 -0
  107. omlish/io/generators/readers.py +189 -0
  108. omlish/io/generators/stepped.py +191 -0
  109. omlish/io/pyio.py +5 -2
  110. omlish/iterators/__init__.py +24 -0
  111. omlish/iterators/iterators.py +132 -0
  112. omlish/iterators/recipes.py +18 -0
  113. omlish/iterators/tools.py +96 -0
  114. omlish/iterators/unique.py +67 -0
  115. omlish/lang/__init__.py +13 -1
  116. omlish/lang/functions.py +11 -2
  117. omlish/lang/generators.py +243 -0
  118. omlish/lang/iterables.py +46 -49
  119. omlish/lang/maybes.py +4 -4
  120. omlish/lite/cached.py +39 -6
  121. omlish/lite/check.py +438 -75
  122. omlish/lite/contextmanagers.py +17 -4
  123. omlish/lite/dataclasses.py +42 -0
  124. omlish/lite/inject.py +28 -45
  125. omlish/lite/logs.py +0 -270
  126. omlish/lite/marshal.py +309 -144
  127. omlish/lite/pycharm.py +47 -0
  128. omlish/lite/reflect.py +33 -0
  129. omlish/lite/resources.py +8 -0
  130. omlish/lite/runtime.py +4 -4
  131. omlish/lite/shlex.py +12 -0
  132. omlish/lite/socketserver.py +2 -2
  133. omlish/lite/strings.py +31 -0
  134. omlish/logs/__init__.py +0 -32
  135. omlish/logs/{_abc.py → abc.py} +0 -1
  136. omlish/logs/all.py +37 -0
  137. omlish/logs/{formatters.py → color.py} +1 -2
  138. omlish/logs/configs.py +7 -38
  139. omlish/logs/filters.py +10 -0
  140. omlish/logs/handlers.py +4 -1
  141. omlish/logs/json.py +56 -0
  142. omlish/logs/proxy.py +99 -0
  143. omlish/logs/standard.py +128 -0
  144. omlish/logs/utils.py +2 -2
  145. omlish/manifests/__init__.py +2 -0
  146. omlish/manifests/load.py +209 -0
  147. omlish/manifests/types.py +17 -0
  148. omlish/marshal/base.py +1 -1
  149. omlish/marshal/factories.py +1 -1
  150. omlish/marshal/forbidden.py +1 -1
  151. omlish/marshal/iterables.py +1 -1
  152. omlish/marshal/literals.py +50 -0
  153. omlish/marshal/mappings.py +1 -1
  154. omlish/marshal/maybes.py +1 -1
  155. omlish/marshal/standard.py +5 -1
  156. omlish/marshal/unions.py +1 -1
  157. omlish/os/__init__.py +0 -0
  158. omlish/os/atomics.py +205 -0
  159. omlish/os/deathsig.py +23 -0
  160. omlish/{os.py → os/files.py} +0 -9
  161. omlish/{lite → os}/journald.py +2 -1
  162. omlish/os/linux.py +484 -0
  163. omlish/os/paths.py +36 -0
  164. omlish/{lite → os}/pidfile.py +1 -0
  165. omlish/os/sizes.py +9 -0
  166. omlish/reflect/__init__.py +3 -0
  167. omlish/reflect/subst.py +2 -1
  168. omlish/reflect/types.py +126 -44
  169. omlish/secrets/pwhash.py +1 -1
  170. omlish/secrets/subprocesses.py +3 -1
  171. omlish/specs/jsonrpc/marshal.py +1 -1
  172. omlish/specs/openapi/marshal.py +1 -1
  173. omlish/sql/alchemy/asyncs.py +1 -1
  174. omlish/sql/queries/__init__.py +9 -1
  175. omlish/sql/queries/building.py +3 -0
  176. omlish/sql/queries/exprs.py +10 -27
  177. omlish/sql/queries/idents.py +48 -10
  178. omlish/sql/queries/names.py +80 -13
  179. omlish/sql/queries/params.py +64 -0
  180. omlish/sql/queries/rendering.py +1 -1
  181. omlish/subprocesses.py +340 -0
  182. omlish/term.py +29 -14
  183. omlish/testing/pytest/marks.py +2 -2
  184. omlish/testing/pytest/plugins/asyncs.py +6 -1
  185. omlish/testing/pytest/plugins/logging.py +1 -1
  186. omlish/testing/pytest/plugins/switches.py +1 -1
  187. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/METADATA +7 -5
  188. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/RECORD +200 -117
  189. omlish/fnpairs.py +0 -496
  190. omlish/formats/json/cli/__main__.py +0 -11
  191. omlish/formats/json/cli/cli.py +0 -298
  192. omlish/formats/json/cli/formats.py +0 -71
  193. omlish/formats/json/cli/io.py +0 -74
  194. omlish/formats/json/cli/parsing.py +0 -82
  195. omlish/formats/json/cli/processing.py +0 -48
  196. omlish/formats/json/cli/rendering.py +0 -92
  197. omlish/iterators.py +0 -300
  198. omlish/lite/subprocesses.py +0 -130
  199. /omlish/{formats/json/cli → argparse}/__init__.py +0 -0
  200. /omlish/{lite/fdio → asyncs/asyncio}/__init__.py +0 -0
  201. /omlish/asyncs/{asyncio.py → asyncio/asyncio.py} +0 -0
  202. /omlish/{lite/http → asyncs/bluelet}/__init__.py +0 -0
  203. /omlish/collections/{_abc.py → abc.py} +0 -0
  204. /omlish/{fnpipes.py → funcs/pipes.py} +0 -0
  205. /omlish/io/{_abc.py → abc.py} +0 -0
  206. /omlish/sql/{_abc.py → abc.py} +0 -0
  207. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/LICENSE +0 -0
  208. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/WHEEL +0 -0
  209. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/entry_points.txt +0 -0
  210. {omlish-0.0.0.dev133.dist-info → omlish-0.0.0.dev177.dist-info}/top_level.txt +0 -0
omlish/lite/check.py CHANGED
@@ -1,99 +1,462 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ """
3
+ TODO:
4
+ - def maybe(v: lang.Maybe[T])
5
+ - def not_ ?
6
+ - ** class @dataclass Raise - user message should be able to be an exception type or instance or factory
7
+ """
8
+ import collections
9
+ import threading
2
10
  import typing as ta
3
11
 
4
12
 
5
13
  T = ta.TypeVar('T')
6
14
  SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
7
15
 
16
+ CheckMessage = ta.Union[str, ta.Callable[..., ta.Optional[str]], None] # ta.TypeAlias
17
+ CheckLateConfigureFn = ta.Callable[['Checks'], None] # ta.TypeAlias
18
+ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
19
+ CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
20
+ CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
8
21
 
9
- def check_isinstance(v: ta.Any, spec: ta.Union[ta.Type[T], tuple]) -> T:
10
- if not isinstance(v, spec):
11
- raise TypeError(v)
12
- return v
13
22
 
23
+ ##
14
24
 
15
- def check_not_isinstance(v: T, spec: ta.Union[type, tuple]) -> T:
16
- if isinstance(v, spec):
17
- raise TypeError(v)
18
- return v
19
25
 
26
+ class Checks:
27
+ def __init__(self) -> None:
28
+ super().__init__()
20
29
 
21
- def check_none(v: T) -> None:
22
- if v is not None:
23
- raise ValueError(v)
30
+ self._config_lock = threading.RLock()
31
+ self._on_raise_fns: ta.Sequence[CheckOnRaiseFn] = []
32
+ self._exception_factory: CheckExceptionFactory = Checks.default_exception_factory
33
+ self._args_renderer: ta.Optional[CheckArgsRenderer] = None
34
+ self._late_configure_fns: ta.Sequence[CheckLateConfigureFn] = []
24
35
 
36
+ @staticmethod
37
+ def default_exception_factory(exc_cls: ta.Type[Exception], *args, **kwargs) -> Exception:
38
+ return exc_cls(*args, **kwargs) # noqa
25
39
 
26
- def check_not_none(v: ta.Optional[T]) -> T:
27
- if v is None:
28
- raise ValueError
29
- return v
40
+ #
30
41
 
42
+ def register_on_raise(self, fn: CheckOnRaiseFn) -> None:
43
+ with self._config_lock:
44
+ self._on_raise_fns = [*self._on_raise_fns, fn]
31
45
 
32
- def check_not(v: ta.Any) -> None:
33
- if v:
34
- raise ValueError(v)
35
- return v
46
+ def unregister_on_raise(self, fn: CheckOnRaiseFn) -> None:
47
+ with self._config_lock:
48
+ self._on_raise_fns = [e for e in self._on_raise_fns if e != fn]
36
49
 
50
+ #
37
51
 
38
- def check_non_empty_str(v: ta.Optional[str]) -> str:
39
- if not v:
40
- raise ValueError
41
- return v
52
+ def set_exception_factory(self, factory: CheckExceptionFactory) -> None:
53
+ self._exception_factory = factory
42
54
 
55
+ def set_args_renderer(self, renderer: ta.Optional[CheckArgsRenderer]) -> None:
56
+ self._args_renderer = renderer
43
57
 
44
- def check_state(v: bool, msg: str = 'Illegal state') -> None:
45
- if not v:
46
- raise ValueError(msg)
58
+ #
59
+
60
+ def register_late_configure(self, fn: CheckLateConfigureFn) -> None:
61
+ with self._config_lock:
62
+ self._late_configure_fns = [*self._late_configure_fns, fn]
47
63
 
64
+ def _late_configure(self) -> None:
65
+ if not self._late_configure_fns:
66
+ return
48
67
 
49
- def check_equal(l: T, r: T) -> T:
50
- if l != r:
51
- raise ValueError(l, r)
52
- return l
68
+ with self._config_lock:
69
+ if not (lc := self._late_configure_fns):
70
+ return
53
71
 
72
+ for fn in lc:
73
+ fn(self)
74
+
75
+ self._late_configure_fns = []
76
+
77
+ #
78
+
79
+ class _ArgsKwargs:
80
+ def __init__(self, *args, **kwargs):
81
+ self.args = args
82
+ self.kwargs = kwargs
83
+
84
+ def _raise(
85
+ self,
86
+ exception_type: ta.Type[Exception],
87
+ default_message: str,
88
+ message: CheckMessage,
89
+ ak: _ArgsKwargs = _ArgsKwargs(),
90
+ *,
91
+ render_fmt: ta.Optional[str] = None,
92
+ ) -> ta.NoReturn:
93
+ exc_args = ()
94
+ if callable(message):
95
+ message = ta.cast(ta.Callable, message)(*ak.args, **ak.kwargs)
96
+ if isinstance(message, tuple):
97
+ message, *exc_args = message # type: ignore
98
+
99
+ if message is None:
100
+ message = default_message
54
101
 
55
- def check_not_equal(l: T, r: T) -> T:
56
- if l == r:
57
- raise ValueError(l, r)
58
- return l
59
-
60
-
61
- def check_is(l: T, r: T) -> T:
62
- if l is not r:
63
- raise ValueError(l, r)
64
- return l
65
-
66
-
67
- def check_is_not(l: T, r: ta.Any) -> T:
68
- if l is r:
69
- raise ValueError(l, r)
70
- return l
71
-
72
-
73
- def check_in(v: T, c: ta.Container[T]) -> T:
74
- if v not in c:
75
- raise ValueError(v, c)
76
- return v
77
-
78
-
79
- def check_not_in(v: T, c: ta.Container[T]) -> T:
80
- if v in c:
81
- raise ValueError(v, c)
82
- return v
83
-
84
-
85
- def check_single(vs: ta.Iterable[T]) -> T:
86
- [v] = vs
87
- return v
88
-
89
-
90
- def check_empty(v: SizedT) -> SizedT:
91
- if len(v):
92
- raise ValueError(v)
93
- return v
94
-
95
-
96
- def check_non_empty(v: SizedT) -> SizedT:
97
- if not len(v):
98
- raise ValueError(v)
99
- return v
102
+ self._late_configure()
103
+
104
+ if render_fmt is not None and (af := self._args_renderer) is not None:
105
+ rendered_args = af(render_fmt, *ak.args)
106
+ if rendered_args is not None:
107
+ message = f'{message} : {rendered_args}'
108
+
109
+ exc = self._exception_factory(
110
+ exception_type,
111
+ message,
112
+ *exc_args,
113
+ *ak.args,
114
+ **ak.kwargs,
115
+ )
116
+
117
+ for fn in self._on_raise_fns:
118
+ fn(exc)
119
+
120
+ raise exc
121
+
122
+ #
123
+
124
+ def _unpack_isinstance_spec(self, spec: ta.Any) -> tuple:
125
+ if isinstance(spec, type):
126
+ return (spec,)
127
+ if not isinstance(spec, tuple):
128
+ spec = (spec,)
129
+ if None in spec:
130
+ spec = tuple(filter(None, spec)) + (None.__class__,) # noqa
131
+ if ta.Any in spec:
132
+ spec = (object,)
133
+ return spec
134
+
135
+ def isinstance(self, v: ta.Any, spec: ta.Union[ta.Type[T], tuple], msg: CheckMessage = None) -> T: # noqa
136
+ if not isinstance(v, self._unpack_isinstance_spec(spec)):
137
+ self._raise(
138
+ TypeError,
139
+ 'Must be instance',
140
+ msg,
141
+ Checks._ArgsKwargs(v, spec),
142
+ render_fmt='not isinstance(%s, %s)',
143
+ )
144
+
145
+ return v
146
+
147
+ def of_isinstance(self, spec: ta.Union[ta.Type[T], tuple], msg: CheckMessage = None) -> ta.Callable[[ta.Any], T]:
148
+ def inner(v):
149
+ return self.isinstance(v, self._unpack_isinstance_spec(spec), msg)
150
+
151
+ return inner
152
+
153
+ def cast(self, v: ta.Any, cls: ta.Type[T], msg: CheckMessage = None) -> T: # noqa
154
+ if not isinstance(v, cls):
155
+ self._raise(
156
+ TypeError,
157
+ 'Must be instance',
158
+ msg,
159
+ Checks._ArgsKwargs(v, cls),
160
+ )
161
+
162
+ return v
163
+
164
+ def of_cast(self, cls: ta.Type[T], msg: CheckMessage = None) -> ta.Callable[[T], T]:
165
+ def inner(v):
166
+ return self.cast(v, cls, msg)
167
+
168
+ return inner
169
+
170
+ def not_isinstance(self, v: T, spec: ta.Any, msg: CheckMessage = None) -> T: # noqa
171
+ if isinstance(v, self._unpack_isinstance_spec(spec)):
172
+ self._raise(
173
+ TypeError,
174
+ 'Must not be instance',
175
+ msg,
176
+ Checks._ArgsKwargs(v, spec),
177
+ render_fmt='isinstance(%s, %s)',
178
+ )
179
+
180
+ return v
181
+
182
+ def of_not_isinstance(self, spec: ta.Any, msg: CheckMessage = None) -> ta.Callable[[T], T]:
183
+ def inner(v):
184
+ return self.not_isinstance(v, self._unpack_isinstance_spec(spec), msg)
185
+
186
+ return inner
187
+
188
+ ##
189
+
190
+ def issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]: # noqa
191
+ if not issubclass(v, spec):
192
+ self._raise(
193
+ TypeError,
194
+ 'Must be subclass',
195
+ msg,
196
+ Checks._ArgsKwargs(v, spec),
197
+ render_fmt='not issubclass(%s, %s)',
198
+ )
199
+
200
+ return v
201
+
202
+ def not_issubclass(self, v: ta.Type[T], spec: ta.Any, msg: CheckMessage = None) -> ta.Type[T]: # noqa
203
+ if issubclass(v, spec):
204
+ self._raise(
205
+ TypeError,
206
+ 'Must not be subclass',
207
+ msg,
208
+ Checks._ArgsKwargs(v, spec),
209
+ render_fmt='issubclass(%s, %s)',
210
+ )
211
+
212
+ return v
213
+
214
+ #
215
+
216
+ def in_(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
217
+ if v not in c:
218
+ self._raise(
219
+ ValueError,
220
+ 'Must be in',
221
+ msg,
222
+ Checks._ArgsKwargs(v, c),
223
+ render_fmt='%s not in %s',
224
+ )
225
+
226
+ return v
227
+
228
+ def not_in(self, v: T, c: ta.Container[T], msg: CheckMessage = None) -> T:
229
+ if v in c:
230
+ self._raise(
231
+ ValueError,
232
+ 'Must not be in',
233
+ msg,
234
+ Checks._ArgsKwargs(v, c),
235
+ render_fmt='%s in %s',
236
+ )
237
+
238
+ return v
239
+
240
+ def empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
241
+ if len(v) != 0:
242
+ self._raise(
243
+ ValueError,
244
+ 'Must be empty',
245
+ msg,
246
+ Checks._ArgsKwargs(v),
247
+ render_fmt='%s',
248
+ )
249
+
250
+ return v
251
+
252
+ def iterempty(self, v: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
253
+ it = iter(v)
254
+ try:
255
+ next(it)
256
+ except StopIteration:
257
+ pass
258
+ else:
259
+ self._raise(
260
+ ValueError,
261
+ 'Must be empty',
262
+ msg,
263
+ Checks._ArgsKwargs(v),
264
+ render_fmt='%s',
265
+ )
266
+
267
+ return v
268
+
269
+ def not_empty(self, v: SizedT, msg: CheckMessage = None) -> SizedT:
270
+ if len(v) == 0:
271
+ self._raise(
272
+ ValueError,
273
+ 'Must not be empty',
274
+ msg,
275
+ Checks._ArgsKwargs(v),
276
+ render_fmt='%s',
277
+ )
278
+
279
+ return v
280
+
281
+ def unique(self, it: ta.Iterable[T], msg: CheckMessage = None) -> ta.Iterable[T]:
282
+ dupes = [e for e, c in collections.Counter(it).items() if c > 1]
283
+ if dupes:
284
+ self._raise(
285
+ ValueError,
286
+ 'Must be unique',
287
+ msg,
288
+ Checks._ArgsKwargs(it, dupes),
289
+ )
290
+
291
+ return it
292
+
293
+ def single(self, obj: ta.Iterable[T], message: CheckMessage = None) -> T:
294
+ try:
295
+ [value] = obj
296
+ except ValueError:
297
+ self._raise(
298
+ ValueError,
299
+ 'Must be single',
300
+ message,
301
+ Checks._ArgsKwargs(obj),
302
+ render_fmt='%s',
303
+ )
304
+
305
+ return value
306
+
307
+ def opt_single(self, obj: ta.Iterable[T], message: CheckMessage = None) -> ta.Optional[T]:
308
+ it = iter(obj)
309
+ try:
310
+ value = next(it)
311
+ except StopIteration:
312
+ return None
313
+
314
+ try:
315
+ next(it)
316
+ except StopIteration:
317
+ return value # noqa
318
+
319
+ self._raise(
320
+ ValueError,
321
+ 'Must be empty or single',
322
+ message,
323
+ Checks._ArgsKwargs(obj),
324
+ render_fmt='%s',
325
+ )
326
+
327
+ raise RuntimeError # noqa
328
+
329
+ #
330
+
331
+ def none(self, v: ta.Any, msg: CheckMessage = None) -> None:
332
+ if v is not None:
333
+ self._raise(
334
+ ValueError,
335
+ 'Must be None',
336
+ msg,
337
+ Checks._ArgsKwargs(v),
338
+ render_fmt='%s',
339
+ )
340
+
341
+ def not_none(self, v: ta.Optional[T], msg: CheckMessage = None) -> T:
342
+ if v is None:
343
+ self._raise(
344
+ ValueError,
345
+ 'Must not be None',
346
+ msg,
347
+ Checks._ArgsKwargs(v),
348
+ render_fmt='%s',
349
+ )
350
+
351
+ return v
352
+
353
+ #
354
+
355
+ def equal(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
356
+ if o != v:
357
+ self._raise(
358
+ ValueError,
359
+ 'Must be equal',
360
+ msg,
361
+ Checks._ArgsKwargs(v, o),
362
+ render_fmt='%s != %s',
363
+ )
364
+
365
+ return v
366
+
367
+ def is_(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
368
+ if o is not v:
369
+ self._raise(
370
+ ValueError,
371
+ 'Must be the same',
372
+ msg,
373
+ Checks._ArgsKwargs(v, o),
374
+ render_fmt='%s is not %s',
375
+ )
376
+
377
+ return v
378
+
379
+ def is_not(self, v: T, o: ta.Any, msg: CheckMessage = None) -> T:
380
+ if o is v:
381
+ self._raise(
382
+ ValueError,
383
+ 'Must not be the same',
384
+ msg,
385
+ Checks._ArgsKwargs(v, o),
386
+ render_fmt='%s is %s',
387
+ )
388
+
389
+ return v
390
+
391
+ def callable(self, v: T, msg: CheckMessage = None) -> T: # noqa
392
+ if not callable(v):
393
+ self._raise(
394
+ TypeError,
395
+ 'Must be callable',
396
+ msg,
397
+ Checks._ArgsKwargs(v),
398
+ render_fmt='%s',
399
+ )
400
+
401
+ return v # type: ignore
402
+
403
+ def non_empty_str(self, v: ta.Optional[str], msg: CheckMessage = None) -> str:
404
+ if not isinstance(v, str) or not v:
405
+ self._raise(
406
+ ValueError,
407
+ 'Must be non-empty str',
408
+ msg,
409
+ Checks._ArgsKwargs(v),
410
+ render_fmt='%s',
411
+ )
412
+
413
+ return v
414
+
415
+ def replacing(self, expected: ta.Any, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
416
+ if old != expected:
417
+ self._raise(
418
+ ValueError,
419
+ 'Must be replacing',
420
+ msg,
421
+ Checks._ArgsKwargs(expected, old, new),
422
+ render_fmt='%s -> %s -> %s',
423
+ )
424
+
425
+ return new
426
+
427
+ def replacing_none(self, old: ta.Any, new: T, msg: CheckMessage = None) -> T:
428
+ if old is not None:
429
+ self._raise(
430
+ ValueError,
431
+ 'Must be replacing None',
432
+ msg,
433
+ Checks._ArgsKwargs(old, new),
434
+ render_fmt='%s -> %s',
435
+ )
436
+
437
+ return new
438
+
439
+ #
440
+
441
+ def arg(self, v: bool, msg: CheckMessage = None) -> None:
442
+ if not v:
443
+ self._raise(
444
+ RuntimeError,
445
+ 'Argument condition not met',
446
+ msg,
447
+ Checks._ArgsKwargs(v),
448
+ render_fmt='%s',
449
+ )
450
+
451
+ def state(self, v: bool, msg: CheckMessage = None) -> None:
452
+ if not v:
453
+ self._raise(
454
+ RuntimeError,
455
+ 'State condition not met',
456
+ msg,
457
+ Checks._ArgsKwargs(v),
458
+ render_fmt='%s',
459
+ )
460
+
461
+
462
+ check = Checks()
@@ -2,8 +2,7 @@
2
2
  import contextlib
3
3
  import typing as ta
4
4
 
5
- from .check import check_not_none
6
- from .check import check_state
5
+ from .check import check
7
6
 
8
7
 
9
8
  T = ta.TypeVar('T')
@@ -17,7 +16,7 @@ class ExitStacked:
17
16
  _exit_stack: ta.Optional[contextlib.ExitStack] = None
18
17
 
19
18
  def __enter__(self: ExitStackedT) -> ExitStackedT:
20
- check_state(self._exit_stack is None)
19
+ check.state(self._exit_stack is None)
21
20
  es = self._exit_stack = contextlib.ExitStack()
22
21
  es.__enter__()
23
22
  return self
@@ -32,7 +31,7 @@ class ExitStacked:
32
31
  pass
33
32
 
34
33
  def _enter_context(self, cm: ta.ContextManager[T]) -> T:
35
- es = check_not_none(self._exit_stack)
34
+ es = check.not_none(self._exit_stack)
36
35
  return es.enter_context(cm)
37
36
 
38
37
 
@@ -62,3 +61,17 @@ def attr_setting(obj, attr, val, *, default=None): # noqa
62
61
  delattr(obj, attr)
63
62
  else:
64
63
  setattr(obj, attr, orig)
64
+
65
+
66
+ ##
67
+
68
+
69
+ class aclosing(contextlib.AbstractAsyncContextManager): # noqa
70
+ def __init__(self, thing):
71
+ self.thing = thing
72
+
73
+ async def __aenter__(self):
74
+ return self.thing
75
+
76
+ async def __aexit__(self, *exc_info):
77
+ await self.thing.aclose()
@@ -0,0 +1,42 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+
6
+ def dataclass_cache_hash(
7
+ *,
8
+ cached_hash_attr: str = '__dataclass_hash__',
9
+ ):
10
+ def inner(cls):
11
+ if not isinstance(cls, type) and dc.is_dataclass(cls):
12
+ raise TypeError(cls)
13
+
14
+ if (
15
+ cls.__hash__ is object.__hash__ or
16
+ '__hash__' not in cls.__dict__
17
+ ):
18
+ raise TypeError(cls)
19
+
20
+ real_hash = cls.__hash__
21
+
22
+ def cached_hash(self) -> int:
23
+ try:
24
+ return object.__getattribute__(self, cached_hash_attr)
25
+ except AttributeError:
26
+ object.__setattr__(self, cached_hash_attr, h := real_hash(self)) # type: ignore[call-arg]
27
+ return h
28
+
29
+ cls.__hash__ = cached_hash # type: ignore[method-assign]
30
+
31
+ return cls
32
+
33
+ return inner
34
+
35
+
36
+ def dataclass_maybe_post_init(sup: ta.Any) -> bool:
37
+ try:
38
+ fn = sup.__post_init__
39
+ except AttributeError:
40
+ return False
41
+ fn()
42
+ return True