omlish 0.0.0.dev22__py3-none-any.whl → 0.0.0.dev24__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 (46) hide show
  1. omlish/__about__.py +10 -3
  2. omlish/asyncs/bridge.py +3 -0
  3. omlish/bootstrap/__init__.py +39 -0
  4. omlish/bootstrap/base.py +3 -1
  5. omlish/bootstrap/diag.py +48 -1
  6. omlish/bootstrap/main.py +2 -1
  7. omlish/bootstrap/marshal.py +18 -0
  8. omlish/check.py +305 -37
  9. omlish/collections/__init__.py +2 -2
  10. omlish/collections/utils.py +3 -3
  11. omlish/concurrent/threadlets.py +5 -0
  12. omlish/dataclasses/__init__.py +1 -0
  13. omlish/dataclasses/impl/init.py +10 -2
  14. omlish/dataclasses/impl/metadata.py +3 -3
  15. omlish/dataclasses/impl/reflect.py +1 -1
  16. omlish/dataclasses/utils.py +16 -3
  17. omlish/diag/asts.py +132 -0
  18. omlish/diag/pycharm.py +139 -0
  19. omlish/docker.py +19 -11
  20. omlish/genmachine.py +59 -0
  21. omlish/graphs/trees.py +1 -1
  22. omlish/lang/__init__.py +13 -1
  23. omlish/lang/cached.py +5 -2
  24. omlish/lang/descriptors.py +33 -16
  25. omlish/lang/resources.py +60 -0
  26. omlish/lite/logs.py +133 -4
  27. omlish/logs/__init__.py +17 -2
  28. omlish/logs/configs.py +13 -1
  29. omlish/logs/formatters.py +0 -1
  30. omlish/marshal/__init__.py +6 -0
  31. omlish/marshal/base64.py +4 -0
  32. omlish/marshal/helpers.py +27 -0
  33. omlish/marshal/primitives.py +6 -0
  34. omlish/marshal/standard.py +4 -0
  35. omlish/marshal/unions.py +101 -0
  36. omlish/matchfns.py +3 -3
  37. omlish/specs/jsonschema/keywords/base.py +2 -2
  38. omlish/specs/jsonschema/keywords/parse.py +1 -1
  39. omlish/sql/__init__.py +18 -0
  40. omlish/sql/qualifiedname.py +82 -0
  41. omlish/stats.py +1 -0
  42. {omlish-0.0.0.dev22.dist-info → omlish-0.0.0.dev24.dist-info}/METADATA +1 -1
  43. {omlish-0.0.0.dev22.dist-info → omlish-0.0.0.dev24.dist-info}/RECORD +46 -39
  44. {omlish-0.0.0.dev22.dist-info → omlish-0.0.0.dev24.dist-info}/LICENSE +0 -0
  45. {omlish-0.0.0.dev22.dist-info → omlish-0.0.0.dev24.dist-info}/WHEEL +0 -0
  46. {omlish-0.0.0.dev22.dist-info → omlish-0.0.0.dev24.dist-info}/top_level.txt +0 -0
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev22'
2
- __revision__ = '3d902899000237758f62e46319a8da3bccd2d447'
1
+ __version__ = '0.0.0.dev24'
2
+ __revision__ = '4ab439ef967fda27804fe7d241924c6f08757b54'
3
3
 
4
4
 
5
5
  #
@@ -80,7 +80,11 @@ class Project(ProjectBase):
80
80
  'sqlalchemy[asyncio] ~= 2.0',
81
81
 
82
82
  'pg8000 ~= 1.31',
83
+ # 'psycopg2 ~= 2.9',
84
+
83
85
  'pymysql ~= 1.1',
86
+ # 'mysql-connector-python ~= 9.0',
87
+ # 'mysqlclient ~= 2.2',
84
88
 
85
89
  'aiomysql ~= 0.2',
86
90
  'aiosqlite ~= 0.20',
@@ -110,7 +114,10 @@ class SetuptoolsBase:
110
114
  include_package_data = False
111
115
 
112
116
  find_packages = {
113
- 'exclude': ['*.tests', '*.tests.*'],
117
+ 'exclude': [
118
+ '*.tests',
119
+ '*.tests.*',
120
+ ],
114
121
  }
115
122
 
116
123
 
omlish/asyncs/bridge.py CHANGED
@@ -8,6 +8,9 @@ KEEP THE SPACE SHUTTLE FLYING.
8
8
 
9
9
  TODO:
10
10
  - reuse greenlet if nested somehow?
11
+
12
+ See:
13
+ - https://greenback.readthedocs.io/en/latest/
11
14
  """
12
15
  import itertools
13
16
  import sys
@@ -1,3 +1,42 @@
1
+ from .base import ( # noqa
2
+ Bootstrap,
3
+ ContextBootstrap,
4
+ SimpleBootstrap,
5
+ )
6
+
7
+ from .diag import ( # noqa
8
+ CheckBootstrap,
9
+ CprofileBootstrap,
10
+ PycharmBootstrap,
11
+ ThreadDumpBootstrap,
12
+ TimebombBootstrap,
13
+ )
14
+
1
15
  from .harness import ( # noqa
2
16
  bootstrap,
3
17
  )
18
+
19
+ from .sys import ( # noqa
20
+ CwdBootstrap,
21
+ EnvBootstrap,
22
+ FaulthandlerBootstrap,
23
+ FdsBootstrap,
24
+ GcBootstrap,
25
+ GcDebugFlag,
26
+ ImportBootstrap,
27
+ LogBootstrap,
28
+ NiceBootstrap,
29
+ PidfileBootstrap,
30
+ PrctlBootstrap,
31
+ PrintPidBootstrap,
32
+ RlimitBootstrap,
33
+ SetuidBootstrap,
34
+ )
35
+
36
+
37
+ ##
38
+
39
+
40
+ from ..lang.imports import _register_conditional_import # noqa
41
+
42
+ _register_conditional_import('..marshal', '.marshal', __package__)
omlish/bootstrap/base.py CHANGED
@@ -2,6 +2,8 @@ import abc
2
2
  import dataclasses as dc
3
3
  import typing as ta
4
4
 
5
+ from omlish import lang
6
+
5
7
 
6
8
  BootstrapConfigT = ta.TypeVar('BootstrapConfigT', bound='Bootstrap.Config')
7
9
 
@@ -9,7 +11,7 @@ BootstrapConfigT = ta.TypeVar('BootstrapConfigT', bound='Bootstrap.Config')
9
11
  ##
10
12
 
11
13
 
12
- class Bootstrap(abc.ABC, ta.Generic[BootstrapConfigT]):
14
+ class Bootstrap(abc.ABC, lang.PackageSealed, ta.Generic[BootstrapConfigT]):
13
15
  @dc.dataclass(frozen=True)
14
16
  class Config(abc.ABC): # noqa
15
17
  pass
omlish/bootstrap/diag.py CHANGED
@@ -5,28 +5,56 @@ import signal
5
5
  import sys
6
6
  import typing as ta
7
7
 
8
+ from .. import check
8
9
  from .. import lang
9
10
  from .base import Bootstrap
10
11
  from .base import ContextBootstrap
12
+ from .base import SimpleBootstrap
11
13
 
12
14
 
13
15
  if ta.TYPE_CHECKING:
14
16
  import cProfile # noqa
15
17
  import pstats
16
18
 
19
+ from ..diag import pycharm as diagpc
17
20
  from ..diag import threads as diagt
18
21
 
19
22
  else:
20
23
  cProfile = lang.proxy_import('cProfile') # noqa
21
24
  pstats = lang.proxy_import('pstats')
22
25
 
26
+ diagpc = lang.proxy_import('..diag.pycharm', __package__)
23
27
  diagt = lang.proxy_import('..diag.threads', __package__)
24
28
 
25
29
 
26
30
  ##
27
31
 
28
32
 
29
- class ProfilingBootstrap(ContextBootstrap['ProfilingBootstrap.Config']):
33
+ class CheckBootstrap(ContextBootstrap['CheckBootstrap.Config']):
34
+ @dc.dataclass(frozen=True)
35
+ class Config(Bootstrap.Config):
36
+ breakpoint: bool = False
37
+
38
+ @staticmethod
39
+ def _breakpoint(exc: Exception) -> None: # noqa
40
+ breakpoint() # noqa
41
+
42
+ @contextlib.contextmanager
43
+ def enter(self) -> ta.Iterator[None]:
44
+ if not self._config.breakpoint:
45
+ return
46
+
47
+ check.register_on_raise(CheckBootstrap._breakpoint)
48
+ try:
49
+ yield
50
+ finally:
51
+ check.unregister_on_raise(CheckBootstrap._breakpoint)
52
+
53
+
54
+ ##
55
+
56
+
57
+ class CprofileBootstrap(ContextBootstrap['CprofileBootstrap.Config']):
30
58
  @dc.dataclass(frozen=True)
31
59
  class Config(Bootstrap.Config):
32
60
  enable: bool = False
@@ -128,3 +156,22 @@ class TimebombBootstrap(ContextBootstrap['TimebombBootstrap.Config']):
128
156
  yield
129
157
  finally:
130
158
  tbt.stop_nowait()
159
+
160
+
161
+ ##
162
+
163
+
164
+ class PycharmBootstrap(SimpleBootstrap['PycharmBootstrap.Config']):
165
+ @dc.dataclass(frozen=True)
166
+ class Config(Bootstrap.Config):
167
+ debug_host: ta.Optional[str] = None
168
+ debug_port: ta.Optional[int] = None
169
+ version: ta.Optional[str] = None
170
+
171
+ def run(self) -> None:
172
+ if self._config.debug_port is not None:
173
+ diagpc.pycharm_remote_debugger_attach(
174
+ self._config.debug_host,
175
+ self._config.debug_port,
176
+ version=self._config.version,
177
+ )
omlish/bootstrap/main.py CHANGED
@@ -156,8 +156,9 @@ def _main() -> int:
156
156
  with bootstrap(*cfgs):
157
157
  tgt = args.target
158
158
 
159
+ sys.argv = [tgt, *(args.args or ())]
160
+
159
161
  if args.module:
160
- sys.argv = [tgt, *(args.args or ())]
161
162
  runpy._run_module_as_main(tgt) # type: ignore # noqa
162
163
 
163
164
  else:
@@ -0,0 +1,18 @@
1
+ from .. import lang
2
+ from .. import marshal as msh
3
+ from .base import Bootstrap
4
+ from .harness import BOOTSTRAP_TYPES_BY_NAME
5
+
6
+
7
+ @lang.cached_function
8
+ def _install_standard_marshalling() -> None:
9
+ cfgs_poly = msh.Polymorphism(
10
+ Bootstrap.Config,
11
+ [msh.Impl(b.Config, n) for n, b in BOOTSTRAP_TYPES_BY_NAME.items()],
12
+ )
13
+
14
+ msh.STANDARD_MARSHALER_FACTORIES[0:0] = [msh.PolymorphismMarshalerFactory(cfgs_poly)]
15
+ msh.STANDARD_UNMARSHALER_FACTORIES[0:0] = [msh.PolymorphismUnmarshalerFactory(cfgs_poly)]
16
+
17
+
18
+ _install_standard_marshalling()
omlish/check.py CHANGED
@@ -3,6 +3,7 @@ TODO:
3
3
  - def maybe(v: lang.Maybe[T])
4
4
  """
5
5
  import collections
6
+ import threading
6
7
  import typing as ta
7
8
 
8
9
 
@@ -21,6 +22,65 @@ _callable = callable
21
22
  ##
22
23
 
23
24
 
25
+ _CONFIG_LOCK = threading.RLock()
26
+
27
+
28
+ OnRaiseFn: ta.TypeAlias = ta.Callable[[Exception], None]
29
+ _ON_RAISE: ta.Sequence[OnRaiseFn] = []
30
+
31
+
32
+ def register_on_raise(fn: OnRaiseFn) -> None:
33
+ global _ON_RAISE
34
+ with _CONFIG_LOCK:
35
+ _ON_RAISE = [*_ON_RAISE, fn]
36
+
37
+
38
+ def unregister_on_raise(fn: OnRaiseFn) -> None:
39
+ global _ON_RAISE
40
+ with _CONFIG_LOCK:
41
+ _ON_RAISE = [e for e in _ON_RAISE if e != fn]
42
+
43
+
44
+ #
45
+
46
+
47
+ _render_args: ta.Callable[..., str | None] | None = None
48
+
49
+
50
+ def enable_args_rendering() -> bool:
51
+ global _render_args
52
+ if _render_args is not None:
53
+ return True
54
+
55
+ with _CONFIG_LOCK:
56
+ if _render_args is not None:
57
+ return True # type: ignore
58
+
59
+ try:
60
+ from .diag.asts import ArgsRenderer
61
+
62
+ ArgsRenderer.smoketest()
63
+
64
+ except Exception: # noqa
65
+ return False
66
+
67
+ def _real_render_args(fmt: str, *args: ta.Any) -> str | None:
68
+ ra = ArgsRenderer(back=3).render_args(*args)
69
+ if ra is None:
70
+ return None
71
+
72
+ return fmt % tuple(str(a) for a in ra)
73
+
74
+ _render_args = _real_render_args
75
+ return True
76
+
77
+
78
+ enable_args_rendering()
79
+
80
+
81
+ #
82
+
83
+
24
84
  def _default_exception_factory(exc_cls: type[Exception], *args, **kwargs) -> Exception:
25
85
  return exc_cls(*args, **kwargs) # noqa
26
86
 
@@ -28,20 +88,45 @@ def _default_exception_factory(exc_cls: type[Exception], *args, **kwargs) -> Exc
28
88
  _EXCEPTION_FACTORY = _default_exception_factory
29
89
 
30
90
 
91
+ class _Args:
92
+ def __init__(self, *args, **kwargs):
93
+ self.args = args
94
+ self.kwargs = kwargs
95
+
96
+
31
97
  def _raise(
32
98
  exception_type: type[Exception],
33
99
  default_message: str,
34
100
  message: Message,
35
- *args: ta.Any,
36
- **kwargs: ta.Any,
101
+ ak: _Args = _Args(),
102
+ *,
103
+ render_fmt: str | None = None,
37
104
  ) -> ta.NoReturn:
105
+ exc_args = ()
38
106
  if _callable(message):
39
- message = ta.cast(ta.Callable, message)(*args, **kwargs)
107
+ message = ta.cast(ta.Callable, message)(*ak.args, **ak.kwargs)
40
108
  if _isinstance(message, tuple):
41
- message, *args = message # type: ignore
109
+ message, *exc_args = message # type: ignore
110
+
42
111
  if message is None:
43
112
  message = default_message
44
- exc = _EXCEPTION_FACTORY(exception_type, message, *args, **kwargs)
113
+
114
+ if render_fmt is not None and _render_args is not None:
115
+ rendered_args = _render_args(render_fmt, *ak.args)
116
+ if rendered_args is not None:
117
+ message = f'{message} : {rendered_args}'
118
+
119
+ exc = _EXCEPTION_FACTORY(
120
+ exception_type,
121
+ message,
122
+ *exc_args,
123
+ *ak.args,
124
+ **ak.kwargs,
125
+ )
126
+
127
+ for fn in _ON_RAISE:
128
+ fn(exc)
129
+
45
130
  raise exc
46
131
 
47
132
 
@@ -60,7 +145,14 @@ def _unpack_isinstance_spec(spec: ta.Any) -> tuple:
60
145
 
61
146
  def isinstance(v: ta.Any, spec: type[T] | tuple, msg: Message = None) -> T: # noqa
62
147
  if not _isinstance(v, _unpack_isinstance_spec(spec)):
63
- _raise(TypeError, 'Must be instance', msg, v, spec)
148
+ _raise(
149
+ TypeError,
150
+ 'Must be instance',
151
+ msg,
152
+ _Args(v, spec),
153
+ render_fmt='not isinstance(%s, %s)',
154
+ )
155
+
64
156
  return v
65
157
 
66
158
 
@@ -73,7 +165,13 @@ def of_isinstance(spec: type[T] | tuple, msg: Message = None) -> ta.Callable[[ta
73
165
 
74
166
  def cast(v: ta.Any, cls: type[T], msg: Message = None) -> T: # noqa
75
167
  if not _isinstance(v, cls):
76
- _raise(TypeError, 'Must be instance', msg, v, cls)
168
+ _raise(
169
+ TypeError,
170
+ 'Must be instance',
171
+ msg,
172
+ _Args(v, cls),
173
+ )
174
+
77
175
  return v
78
176
 
79
177
 
@@ -86,7 +184,14 @@ def of_cast(cls: type[T], msg: Message = None) -> ta.Callable[[T], T]:
86
184
 
87
185
  def not_isinstance(v: T, spec: ta.Any, msg: Message = None) -> T: # noqa
88
186
  if _isinstance(v, _unpack_isinstance_spec(spec)):
89
- _raise(TypeError, 'Must not be instance', msg, v, spec)
187
+ _raise(
188
+ TypeError,
189
+ 'Must not be instance',
190
+ msg,
191
+ _Args(v, spec),
192
+ render_fmt='isinstance(%s, %s)',
193
+ )
194
+
90
195
  return v
91
196
 
92
197
 
@@ -102,13 +207,27 @@ def of_not_isinstance(spec: ta.Any, msg: Message = None) -> ta.Callable[[T], T]:
102
207
 
103
208
  def issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: # noqa
104
209
  if not _issubclass(v, spec):
105
- _raise(TypeError, 'Must be subclass', msg, v, spec)
210
+ _raise(
211
+ TypeError,
212
+ 'Must be subclass',
213
+ msg,
214
+ _Args(v, spec),
215
+ render_fmt='not issubclass(%s, %s)',
216
+ )
217
+
106
218
  return v
107
219
 
108
220
 
109
221
  def not_issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: # noqa
110
222
  if _issubclass(v, spec):
111
- _raise(TypeError, 'Must not be subclass', msg, v, spec)
223
+ _raise(
224
+ TypeError,
225
+ 'Must not be subclass',
226
+ msg,
227
+ _Args(v, spec),
228
+ render_fmt='issubclass(%s, %s)',
229
+ )
230
+
112
231
  return v
113
232
 
114
233
 
@@ -117,32 +236,84 @@ def not_issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: #
117
236
 
118
237
  def in_(v: T, c: ta.Container[T], msg: Message = None) -> T:
119
238
  if v not in c:
120
- _raise(ValueError, 'Must be in', msg, v, c)
239
+ _raise(
240
+ ValueError,
241
+ 'Must be in',
242
+ msg,
243
+ _Args(v, c),
244
+ render_fmt='%s not in %s',
245
+ )
246
+
121
247
  return v
122
248
 
123
249
 
124
250
  def not_in(v: T, c: ta.Container[T], msg: Message = None) -> T:
125
251
  if v in c:
126
- _raise(ValueError, 'Must not be in', msg, v, c)
252
+ _raise(
253
+ ValueError,
254
+ 'Must not be in',
255
+ msg,
256
+ _Args(v, c),
257
+ render_fmt='%s in %s',
258
+ )
259
+
127
260
  return v
128
261
 
129
262
 
130
263
  def empty(v: SizedT, msg: Message = None) -> SizedT:
131
264
  if len(v) != 0:
132
- _raise(ValueError, 'Must be empty', msg, v)
265
+ _raise(
266
+ ValueError,
267
+ 'Must be empty',
268
+ msg,
269
+ _Args(v),
270
+ render_fmt='%s',
271
+ )
272
+
273
+ return v
274
+
275
+
276
+ def iterempty(v: ta.Iterable[T], msg: Message = None) -> ta.Iterable[T]:
277
+ it = iter(v)
278
+ try:
279
+ next(it)
280
+ except StopIteration:
281
+ pass
282
+ else:
283
+ _raise(
284
+ ValueError,
285
+ 'Must be empty',
286
+ msg,
287
+ _Args(v),
288
+ render_fmt='%s',
289
+ )
290
+
133
291
  return v
134
292
 
135
293
 
136
294
  def not_empty(v: SizedT, msg: Message = None) -> SizedT:
137
295
  if len(v) == 0:
138
- _raise(ValueError, 'Must not be empty', msg, v)
296
+ _raise(
297
+ ValueError,
298
+ 'Must not be empty',
299
+ msg,
300
+ _Args(v),
301
+ render_fmt='%s',
302
+ )
303
+
139
304
  return v
140
305
 
141
306
 
142
307
  def unique(it: ta.Iterable[T], msg: Message = None) -> ta.Iterable[T]:
143
308
  dupes = [e for e, c in collections.Counter(it).items() if c > 1]
144
309
  if dupes:
145
- _raise(ValueError, 'Must be unique', msg, it, dupes)
310
+ _raise(
311
+ ValueError,
312
+ 'Must be unique',
313
+ msg,
314
+ _Args(it, dupes),
315
+ )
316
+
146
317
  return it
147
318
 
148
319
 
@@ -150,9 +321,15 @@ def single(obj: ta.Iterable[T], message: Message = None) -> T:
150
321
  try:
151
322
  [value] = obj
152
323
  except ValueError:
153
- _raise(ValueError, 'Must be single', message, obj)
154
- else:
155
- return value
324
+ _raise(
325
+ ValueError,
326
+ 'Must be single',
327
+ message,
328
+ _Args(obj),
329
+ render_fmt='%s',
330
+ )
331
+
332
+ return value
156
333
 
157
334
 
158
335
  def optional_single(obj: ta.Iterable[T], message: Message = None) -> T | None:
@@ -161,11 +338,19 @@ def optional_single(obj: ta.Iterable[T], message: Message = None) -> T | None:
161
338
  value = next(it)
162
339
  except StopIteration:
163
340
  return None
341
+
164
342
  try:
165
343
  next(it)
166
344
  except StopIteration:
167
345
  return value # noqa
168
- _raise(ValueError, 'Must be empty or single', message, obj)
346
+
347
+ _raise(
348
+ ValueError,
349
+ 'Must be empty or single',
350
+ message,
351
+ _Args(obj),
352
+ render_fmt='%s',
353
+ )
169
354
 
170
355
 
171
356
  ##
@@ -173,59 +358,142 @@ def optional_single(obj: ta.Iterable[T], message: Message = None) -> T | None:
173
358
 
174
359
  def none(v: ta.Any, msg: Message = None) -> None:
175
360
  if v is not None:
176
- _raise(ValueError, 'Must be None', msg, v)
361
+ _raise(
362
+ ValueError,
363
+ 'Must be None',
364
+ msg,
365
+ _Args(v),
366
+ render_fmt='%s',
367
+ )
177
368
 
178
369
 
179
370
  def not_none(v: T | None, msg: Message = None) -> T:
180
371
  if v is None:
181
- _raise(ValueError, 'Must not be None', msg, v)
372
+ _raise(
373
+ ValueError,
374
+ 'Must not be None',
375
+ msg,
376
+ _Args(v),
377
+ render_fmt='%s',
378
+ )
379
+
182
380
  return v
183
381
 
184
382
 
185
383
  ##
186
384
 
187
385
 
188
- def equal(v: T, *os: ta.Any, msg: Message = None) -> T:
189
- for o in os:
190
- if o != v:
191
- _raise(ValueError, 'Must be equal', msg, v, os)
386
+ def equal(v: T, o: ta.Any, msg: Message = None) -> T:
387
+ if o != v:
388
+ _raise(
389
+ ValueError,
390
+ 'Must be equal',
391
+ msg,
392
+ _Args(v, o),
393
+ render_fmt='%s != %s',
394
+ )
395
+
192
396
  return v
193
397
 
194
398
 
195
- def is_(v: T, *os: ta.Any, msg: Message = None) -> T:
196
- for o in os:
197
- if o is not v:
198
- _raise(ValueError, 'Must be the same', msg, v, os)
399
+ def is_(v: T, o: ta.Any, msg: Message = None) -> T:
400
+ if o is not v:
401
+ _raise(
402
+ ValueError,
403
+ 'Must be the same',
404
+ msg,
405
+ _Args(v, o),
406
+ render_fmt='%s is not %s',
407
+ )
408
+
199
409
  return v
200
410
 
201
411
 
202
- def is_not(v: T, *os: ta.Any, msg: Message = None) -> T:
203
- for o in os:
204
- if o is v:
205
- _raise(ValueError, 'Must not be the same', msg, v, os)
412
+ def is_not(v: T, o: ta.Any, msg: Message = None) -> T:
413
+ if o is v:
414
+ _raise(
415
+ ValueError,
416
+ 'Must not be the same',
417
+ msg,
418
+ _Args(v, o),
419
+ render_fmt='%s is %s',
420
+ )
421
+
206
422
  return v
207
423
 
208
424
 
209
425
  def callable(v: T, msg: Message = None) -> T: # noqa
210
426
  if not _callable(v):
211
- _raise(TypeError, 'Must be callable', msg, v)
427
+ _raise(
428
+ TypeError,
429
+ 'Must be callable',
430
+ msg,
431
+ _Args(v),
432
+ render_fmt='%s',
433
+ )
434
+
212
435
  return v # type: ignore
213
436
 
214
437
 
215
438
  def non_empty_str(v: str | None, msg: Message = None) -> str:
216
439
  if not _isinstance(v, str) or not v:
217
- _raise(ValueError, 'Must be non-empty str', msg, v)
440
+ _raise(
441
+ ValueError,
442
+ 'Must be non-empty str',
443
+ msg,
444
+ _Args(v),
445
+ render_fmt='%s',
446
+ )
447
+
218
448
  return v
219
449
 
220
450
 
451
+ def replacing(expected: ta.Any, old: ta.Any, new: T, msg: Message = None) -> T:
452
+ if old != expected:
453
+ _raise(
454
+ ValueError,
455
+ 'Must be replacing',
456
+ msg,
457
+ _Args(expected, old, new),
458
+ render_fmt='%s -> %s -> %s',
459
+ )
460
+
461
+ return new
462
+
463
+
464
+ def replacing_none(old: ta.Any, new: T, msg: Message = None) -> T:
465
+ if old is not None:
466
+ _raise(
467
+ ValueError,
468
+ 'Must be replacing None',
469
+ msg,
470
+ _Args(old, new),
471
+ render_fmt='%s -> %s',
472
+ )
473
+
474
+ return new
475
+
476
+
221
477
  ##
222
478
 
223
479
 
224
480
  def arg(v: bool, msg: Message = None) -> None:
225
481
  if not v:
226
- _raise(RuntimeError, 'Argument condition not met', msg)
482
+ _raise(
483
+ RuntimeError,
484
+ 'Argument condition not met',
485
+ msg,
486
+ _Args(v),
487
+ render_fmt='%s',
488
+ )
227
489
 
228
490
 
229
491
  def state(v: bool, msg: Message = None) -> None:
230
492
  if not v:
231
- _raise(RuntimeError, 'State condition not met', msg)
493
+ _raise(
494
+ RuntimeError,
495
+ 'State condition not met',
496
+ msg,
497
+ _Args(v),
498
+ render_fmt='%s',
499
+ )