omlish 0.0.0.dev21__py3-none-any.whl → 0.0.0.dev23__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.
- omlish/__about__.py +17 -8
- omlish/asyncs/bridge.py +35 -19
- omlish/bootstrap/__init__.py +39 -0
- omlish/bootstrap/base.py +3 -1
- omlish/bootstrap/diag.py +51 -2
- omlish/bootstrap/main.py +2 -1
- omlish/bootstrap/marshal.py +18 -0
- omlish/check.py +123 -33
- omlish/concurrent/__init__.py +11 -0
- omlish/concurrent/executors.py +52 -0
- omlish/{concurrent.py → concurrent/futures.py} +0 -44
- omlish/concurrent/threadlets.py +91 -0
- omlish/diag/asts.py +132 -0
- omlish/diag/pycharm.py +80 -0
- omlish/docker.py +3 -0
- omlish/genmachine.py +58 -0
- omlish/lang/functions.py +3 -0
- omlish/logs/__init__.py +4 -0
- omlish/logs/handlers.py +10 -0
- omlish/marshal/__init__.py +4 -0
- omlish/marshal/base64.py +4 -0
- omlish/marshal/primitives.py +6 -0
- omlish/marshal/standard.py +4 -0
- omlish/marshal/unions.py +101 -0
- omlish/matchfns.py +3 -3
- omlish/secrets/openssl.py +2 -2
- omlish/sql/__init__.py +18 -0
- omlish/sql/qualifiedname.py +82 -0
- {omlish-0.0.0.dev21.dist-info → omlish-0.0.0.dev23.dist-info}/METADATA +13 -9
- {omlish-0.0.0.dev21.dist-info → omlish-0.0.0.dev23.dist-info}/RECORD +33 -23
- {omlish-0.0.0.dev21.dist-info → omlish-0.0.0.dev23.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev21.dist-info → omlish-0.0.0.dev23.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev21.dist-info → omlish-0.0.0.dev23.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
__version__ = '0.0.0.
|
2
|
-
__revision__ = '
|
1
|
+
__version__ = '0.0.0.dev23'
|
2
|
+
__revision__ = '1d019b6879a9552ea31622e40f50d260d6b08824'
|
3
3
|
|
4
4
|
|
5
5
|
#
|
@@ -33,10 +33,10 @@ class Project(ProjectBase):
|
|
33
33
|
'anyio ~= 4.4',
|
34
34
|
'sniffio ~= 1.3',
|
35
35
|
|
36
|
-
'greenlet ~= 3.
|
36
|
+
'greenlet ~= 3.1',
|
37
37
|
|
38
38
|
'trio ~= 0.26',
|
39
|
-
'trio-asyncio ~= 0.15
|
39
|
+
'trio-asyncio ~= 0.15',
|
40
40
|
],
|
41
41
|
|
42
42
|
'compression': [
|
@@ -46,12 +46,15 @@ class Project(ProjectBase):
|
|
46
46
|
],
|
47
47
|
|
48
48
|
'diag': [
|
49
|
+
'asttokens ~= 2.4',
|
50
|
+
'executing ~= 2.1',
|
51
|
+
|
49
52
|
'psutil ~= 6.0',
|
50
53
|
],
|
51
54
|
|
52
55
|
'formats': [
|
53
56
|
'orjson ~= 3.10',
|
54
|
-
|
57
|
+
'ujson ~= 5.10',
|
55
58
|
|
56
59
|
'json5 ~= 0.9',
|
57
60
|
|
@@ -74,11 +77,14 @@ class Project(ProjectBase):
|
|
74
77
|
],
|
75
78
|
|
76
79
|
'sql': [
|
77
|
-
'sqlalchemy ~= 2.0
|
78
|
-
'sqlalchemy[asyncio] ~= 2.0; python_version < "3.13"',
|
80
|
+
'sqlalchemy[asyncio] ~= 2.0',
|
79
81
|
|
80
82
|
'pg8000 ~= 1.31',
|
83
|
+
# 'psycopg2 ~= 2.9',
|
84
|
+
|
81
85
|
'pymysql ~= 1.1',
|
86
|
+
# 'mysql-connector-python ~= 9.0',
|
87
|
+
# 'mysqlclient ~= 2.2',
|
82
88
|
|
83
89
|
'aiomysql ~= 0.2',
|
84
90
|
'aiosqlite ~= 0.20',
|
@@ -108,7 +114,10 @@ class SetuptoolsBase:
|
|
108
114
|
include_package_data = False
|
109
115
|
|
110
116
|
find_packages = {
|
111
|
-
'exclude': [
|
117
|
+
'exclude': [
|
118
|
+
'*.tests',
|
119
|
+
'*.tests.*',
|
120
|
+
],
|
112
121
|
}
|
113
122
|
|
114
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
|
@@ -15,22 +18,21 @@ import types
|
|
15
18
|
import typing as ta
|
16
19
|
import weakref
|
17
20
|
|
21
|
+
from .. import check
|
18
22
|
from .. import lang
|
23
|
+
from .. import sync
|
24
|
+
from ..concurrent import threadlets
|
19
25
|
from .asyncs import sync_await
|
20
26
|
|
21
27
|
|
22
28
|
if ta.TYPE_CHECKING:
|
23
29
|
import asyncio
|
24
30
|
|
25
|
-
import greenlet
|
26
|
-
|
27
31
|
from . import anyio as aiu
|
28
32
|
|
29
33
|
else:
|
30
34
|
asyncio = lang.proxy_import('asyncio')
|
31
35
|
|
32
|
-
greenlet = lang.proxy_import('greenlet')
|
33
|
-
|
34
36
|
aiu = lang.proxy_import('.anyio', __package__)
|
35
37
|
|
36
38
|
|
@@ -56,6 +58,20 @@ def trivial_a_to_s(fn):
|
|
56
58
|
# https://gist.github.com/snaury/202bf4f22c41ca34e56297bae5f33fef
|
57
59
|
|
58
60
|
|
61
|
+
_THREADLETS_IMPL = threadlets.GreenletThreadlets
|
62
|
+
# from ..concurrent.tests.real import RealThreadlets
|
63
|
+
# _THREADLETS_IMPL = RealThreadlets
|
64
|
+
|
65
|
+
_THREADLETS = sync.LazyFn(lambda: _THREADLETS_IMPL())
|
66
|
+
|
67
|
+
|
68
|
+
def _threadlets() -> threadlets.Threadlets:
|
69
|
+
return _THREADLETS.get()
|
70
|
+
|
71
|
+
|
72
|
+
#
|
73
|
+
|
74
|
+
|
59
75
|
class BridgeAwaitRequiredError(Exception):
|
60
76
|
pass
|
61
77
|
|
@@ -101,7 +117,7 @@ def _make_transition(seq: int, a_to_s: bool, obj: ta.Any) -> _BridgeTransition:
|
|
101
117
|
|
102
118
|
_BRIDGED_TASKS: ta.MutableMapping[ta.Any, list[_BridgeTransition]] = weakref.WeakKeyDictionary()
|
103
119
|
|
104
|
-
|
120
|
+
_BRIDGE_THREADLET_ATTR = f'__{__package__.replace(".", "__")}__bridge_threadlet__'
|
105
121
|
|
106
122
|
|
107
123
|
def _push_transition(a_to_s: bool, l: list[_BridgeTransition], t: _BridgeTransition) -> _BridgeTransition:
|
@@ -129,9 +145,9 @@ def _get_transitions() -> list[_BridgeTransition]:
|
|
129
145
|
else:
|
130
146
|
l.extend(tl)
|
131
147
|
|
132
|
-
g =
|
148
|
+
g = _threadlets().get_current()
|
133
149
|
try:
|
134
|
-
gl = getattr(g,
|
150
|
+
gl = getattr(g.underlying, _BRIDGE_THREADLET_ATTR)
|
135
151
|
except AttributeError:
|
136
152
|
pass
|
137
153
|
else:
|
@@ -158,9 +174,9 @@ def is_in_bridge() -> bool:
|
|
158
174
|
else:
|
159
175
|
last_t = None
|
160
176
|
|
161
|
-
g =
|
177
|
+
g = _threadlets().get_current()
|
162
178
|
try:
|
163
|
-
gl = getattr(g,
|
179
|
+
gl = getattr(g.underlying, _BRIDGE_THREADLET_ATTR)
|
164
180
|
except AttributeError:
|
165
181
|
last_g = None
|
166
182
|
else:
|
@@ -194,13 +210,13 @@ def _safe_cancel_awaitable(awaitable: ta.Awaitable[ta.Any]) -> None:
|
|
194
210
|
|
195
211
|
|
196
212
|
def s_to_a_await(awaitable: ta.Awaitable[T]) -> T:
|
197
|
-
g =
|
213
|
+
g = _threadlets().get_current()
|
198
214
|
|
199
|
-
if not getattr(g,
|
215
|
+
if not getattr(g.underlying, _BRIDGE_THREADLET_ATTR, False):
|
200
216
|
_safe_cancel_awaitable(awaitable)
|
201
217
|
raise MissingBridgeGreenletError
|
202
218
|
|
203
|
-
return g.parent.switch(awaitable)
|
219
|
+
return check.not_none(g.parent).switch(awaitable)
|
204
220
|
|
205
221
|
|
206
222
|
def s_to_a(fn, *, require_await=False):
|
@@ -210,7 +226,7 @@ def s_to_a(fn, *, require_await=False):
|
|
210
226
|
try:
|
211
227
|
return fn(*args, **kwargs)
|
212
228
|
finally:
|
213
|
-
if (gl2 := getattr(g,
|
229
|
+
if (gl2 := getattr(g.underlying, _BRIDGE_THREADLET_ATTR)) is not gl: # noqa
|
214
230
|
raise UnexpectedBridgeNestingError
|
215
231
|
if (cur_g := _pop_transition(False, gl)) is not added_g: # noqa
|
216
232
|
raise UnexpectedBridgeNestingError
|
@@ -219,8 +235,8 @@ def s_to_a(fn, *, require_await=False):
|
|
219
235
|
|
220
236
|
seq = next(_BRIDGE_TRANSITIONS_SEQ)
|
221
237
|
|
222
|
-
g =
|
223
|
-
setattr(g,
|
238
|
+
g = _threadlets().spawn(inner)
|
239
|
+
setattr(g.underlying, _BRIDGE_THREADLET_ATTR, gl := []) # type: ignore
|
224
240
|
added_g = _push_transition(False, gl, _make_transition(seq, False, g))
|
225
241
|
|
226
242
|
if (t := aiu.get_current_backend_task()) is not None:
|
@@ -270,11 +286,11 @@ def a_to_s(fn):
|
|
270
286
|
else:
|
271
287
|
added_t = None
|
272
288
|
|
273
|
-
g =
|
289
|
+
g = _threadlets().get_current()
|
274
290
|
try:
|
275
|
-
gl = getattr(g,
|
291
|
+
gl = getattr(g.underlying, _BRIDGE_THREADLET_ATTR)
|
276
292
|
except AttributeError:
|
277
|
-
setattr(g,
|
293
|
+
setattr(g.underlying, _BRIDGE_THREADLET_ATTR, gl := [])
|
278
294
|
added_g = _push_transition(True, gl, _make_transition(seq, True, g))
|
279
295
|
|
280
296
|
try:
|
@@ -306,7 +322,7 @@ def a_to_s(fn):
|
|
306
322
|
if (cur_t := _pop_transition(True, tl)) is not added_t: # noqa
|
307
323
|
raise UnexpectedBridgeNestingError
|
308
324
|
|
309
|
-
if (gl2 := getattr(g,
|
325
|
+
if (gl2 := getattr(g.underlying, _BRIDGE_THREADLET_ATTR)) is not gl: # noqa
|
310
326
|
raise UnexpectedBridgeNestingError
|
311
327
|
if (cur_g := _pop_transition(True, gl)) is not added_g: # noqa
|
312
328
|
raise UnexpectedBridgeNestingError
|
omlish/bootstrap/__init__.py
CHANGED
@@ -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
|
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
|
@@ -70,6 +98,7 @@ class ThreadDumpBootstrap(ContextBootstrap['ThreadDumpBootstrap.Config']):
|
|
70
98
|
@dc.dataclass(frozen=True)
|
71
99
|
class Config(Bootstrap.Config):
|
72
100
|
interval_s: ta.Optional[float] = None
|
101
|
+
nodaemon: bool = False
|
73
102
|
|
74
103
|
on_sigquit: bool = False
|
75
104
|
|
@@ -79,6 +108,7 @@ class ThreadDumpBootstrap(ContextBootstrap['ThreadDumpBootstrap.Config']):
|
|
79
108
|
tdt = diagt.create_thread_dump_thread(
|
80
109
|
interval_s=self._config.interval_s,
|
81
110
|
start=True,
|
111
|
+
nodaemon=self._config.nodaemon,
|
82
112
|
)
|
83
113
|
else:
|
84
114
|
tdt = None
|
@@ -97,7 +127,7 @@ class ThreadDumpBootstrap(ContextBootstrap['ThreadDumpBootstrap.Config']):
|
|
97
127
|
yield
|
98
128
|
|
99
129
|
finally:
|
100
|
-
if tdt is not None:
|
130
|
+
if tdt is not None and not self._config.nodaemon:
|
101
131
|
tdt.stop_nowait()
|
102
132
|
|
103
133
|
if prev_sq.present:
|
@@ -126,3 +156,22 @@ class TimebombBootstrap(ContextBootstrap['TimebombBootstrap.Config']):
|
|
126
156
|
yield
|
127
157
|
finally:
|
128
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
|
+
debug_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.debug_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,62 @@ _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
|
+
#
|
79
|
+
|
80
|
+
|
24
81
|
def _default_exception_factory(exc_cls: type[Exception], *args, **kwargs) -> Exception:
|
25
82
|
return exc_cls(*args, **kwargs) # noqa
|
26
83
|
|
@@ -28,20 +85,45 @@ def _default_exception_factory(exc_cls: type[Exception], *args, **kwargs) -> Exc
|
|
28
85
|
_EXCEPTION_FACTORY = _default_exception_factory
|
29
86
|
|
30
87
|
|
88
|
+
class _Args:
|
89
|
+
def __init__(self, *args, **kwargs):
|
90
|
+
self.args = args
|
91
|
+
self.kwargs = kwargs
|
92
|
+
|
93
|
+
|
31
94
|
def _raise(
|
32
95
|
exception_type: type[Exception],
|
33
96
|
default_message: str,
|
34
97
|
message: Message,
|
35
|
-
|
36
|
-
|
98
|
+
ak: _Args = _Args(),
|
99
|
+
*,
|
100
|
+
render_fmt: str | None = None,
|
37
101
|
) -> ta.NoReturn:
|
102
|
+
exc_args = ()
|
38
103
|
if _callable(message):
|
39
|
-
message = ta.cast(ta.Callable, message)(*args, **kwargs)
|
104
|
+
message = ta.cast(ta.Callable, message)(*ak.args, **ak.kwargs)
|
40
105
|
if _isinstance(message, tuple):
|
41
|
-
message, *
|
106
|
+
message, *exc_args = message # type: ignore
|
107
|
+
|
42
108
|
if message is None:
|
43
109
|
message = default_message
|
44
|
-
|
110
|
+
|
111
|
+
if render_fmt is not None and _render_args is not None:
|
112
|
+
rendered_args = _render_args(render_fmt, *ak.args)
|
113
|
+
if rendered_args is not None:
|
114
|
+
message = f'{message} : {rendered_args}'
|
115
|
+
|
116
|
+
exc = _EXCEPTION_FACTORY(
|
117
|
+
exception_type,
|
118
|
+
message,
|
119
|
+
*exc_args,
|
120
|
+
*ak.args,
|
121
|
+
**ak.kwargs,
|
122
|
+
)
|
123
|
+
|
124
|
+
for fn in _ON_RAISE:
|
125
|
+
fn(exc)
|
126
|
+
|
45
127
|
raise exc
|
46
128
|
|
47
129
|
|
@@ -60,7 +142,7 @@ def _unpack_isinstance_spec(spec: ta.Any) -> tuple:
|
|
60
142
|
|
61
143
|
def isinstance(v: ta.Any, spec: type[T] | tuple, msg: Message = None) -> T: # noqa
|
62
144
|
if not _isinstance(v, _unpack_isinstance_spec(spec)):
|
63
|
-
_raise(TypeError, 'Must be instance', msg, v, spec)
|
145
|
+
_raise(TypeError, 'Must be instance', msg, _Args(v, spec))
|
64
146
|
return v
|
65
147
|
|
66
148
|
|
@@ -73,7 +155,7 @@ def of_isinstance(spec: type[T] | tuple, msg: Message = None) -> ta.Callable[[ta
|
|
73
155
|
|
74
156
|
def cast(v: ta.Any, cls: type[T], msg: Message = None) -> T: # noqa
|
75
157
|
if not _isinstance(v, cls):
|
76
|
-
_raise(TypeError, 'Must be instance', msg, v, cls)
|
158
|
+
_raise(TypeError, 'Must be instance', msg, _Args(v, cls))
|
77
159
|
return v
|
78
160
|
|
79
161
|
|
@@ -86,7 +168,7 @@ def of_cast(cls: type[T], msg: Message = None) -> ta.Callable[[T], T]:
|
|
86
168
|
|
87
169
|
def not_isinstance(v: T, spec: ta.Any, msg: Message = None) -> T: # noqa
|
88
170
|
if _isinstance(v, _unpack_isinstance_spec(spec)):
|
89
|
-
_raise(TypeError, 'Must not be instance', msg, v, spec)
|
171
|
+
_raise(TypeError, 'Must not be instance', msg, _Args(v, spec))
|
90
172
|
return v
|
91
173
|
|
92
174
|
|
@@ -102,13 +184,13 @@ def of_not_isinstance(spec: ta.Any, msg: Message = None) -> ta.Callable[[T], T]:
|
|
102
184
|
|
103
185
|
def issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: # noqa
|
104
186
|
if not _issubclass(v, spec):
|
105
|
-
_raise(TypeError, 'Must be subclass', msg, v, spec)
|
187
|
+
_raise(TypeError, 'Must be subclass', msg, _Args(v, spec))
|
106
188
|
return v
|
107
189
|
|
108
190
|
|
109
191
|
def not_issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: # noqa
|
110
192
|
if _issubclass(v, spec):
|
111
|
-
_raise(TypeError, 'Must not be subclass', msg, v, spec)
|
193
|
+
_raise(TypeError, 'Must not be subclass', msg, _Args(v, spec))
|
112
194
|
return v
|
113
195
|
|
114
196
|
|
@@ -117,32 +199,43 @@ def not_issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: #
|
|
117
199
|
|
118
200
|
def in_(v: T, c: ta.Container[T], msg: Message = None) -> T:
|
119
201
|
if v not in c:
|
120
|
-
_raise(ValueError, 'Must be in', msg, v, c)
|
202
|
+
_raise(ValueError, 'Must be in', msg, _Args(v, c))
|
121
203
|
return v
|
122
204
|
|
123
205
|
|
124
206
|
def not_in(v: T, c: ta.Container[T], msg: Message = None) -> T:
|
125
207
|
if v in c:
|
126
|
-
_raise(ValueError, 'Must not be in', msg, v, c)
|
208
|
+
_raise(ValueError, 'Must not be in', msg, _Args(v, c))
|
127
209
|
return v
|
128
210
|
|
129
211
|
|
130
212
|
def empty(v: SizedT, msg: Message = None) -> SizedT:
|
131
213
|
if len(v) != 0:
|
132
|
-
_raise(ValueError, 'Must be empty', msg, v)
|
214
|
+
_raise(ValueError, 'Must be empty', msg, _Args(v))
|
215
|
+
return v
|
216
|
+
|
217
|
+
|
218
|
+
def iterempty(v: ta.Iterable[T], msg: Message = None) -> ta.Iterable[T]:
|
219
|
+
it = iter(v)
|
220
|
+
try:
|
221
|
+
next(it)
|
222
|
+
except StopIteration:
|
223
|
+
pass
|
224
|
+
else:
|
225
|
+
_raise(ValueError, 'Must be empty', msg, _Args(v))
|
133
226
|
return v
|
134
227
|
|
135
228
|
|
136
229
|
def not_empty(v: SizedT, msg: Message = None) -> SizedT:
|
137
230
|
if len(v) == 0:
|
138
|
-
_raise(ValueError, 'Must not be empty', msg, v)
|
231
|
+
_raise(ValueError, 'Must not be empty', msg, _Args(v))
|
139
232
|
return v
|
140
233
|
|
141
234
|
|
142
235
|
def unique(it: ta.Iterable[T], msg: Message = None) -> ta.Iterable[T]:
|
143
236
|
dupes = [e for e, c in collections.Counter(it).items() if c > 1]
|
144
237
|
if dupes:
|
145
|
-
_raise(ValueError, 'Must be unique', msg, it, dupes)
|
238
|
+
_raise(ValueError, 'Must be unique', msg, _Args(it, dupes))
|
146
239
|
return it
|
147
240
|
|
148
241
|
|
@@ -150,7 +243,7 @@ def single(obj: ta.Iterable[T], message: Message = None) -> T:
|
|
150
243
|
try:
|
151
244
|
[value] = obj
|
152
245
|
except ValueError:
|
153
|
-
_raise(ValueError, 'Must be single', message, obj)
|
246
|
+
_raise(ValueError, 'Must be single', message, _Args(obj))
|
154
247
|
else:
|
155
248
|
return value
|
156
249
|
|
@@ -165,7 +258,7 @@ def optional_single(obj: ta.Iterable[T], message: Message = None) -> T | None:
|
|
165
258
|
next(it)
|
166
259
|
except StopIteration:
|
167
260
|
return value # noqa
|
168
|
-
_raise(ValueError, 'Must be empty or single', message, obj)
|
261
|
+
_raise(ValueError, 'Must be empty or single', message, _Args(obj))
|
169
262
|
|
170
263
|
|
171
264
|
##
|
@@ -173,48 +266,45 @@ def optional_single(obj: ta.Iterable[T], message: Message = None) -> T | None:
|
|
173
266
|
|
174
267
|
def none(v: ta.Any, msg: Message = None) -> None:
|
175
268
|
if v is not None:
|
176
|
-
_raise(ValueError, 'Must be None', msg, v)
|
269
|
+
_raise(ValueError, 'Must be None', msg, _Args(v))
|
177
270
|
|
178
271
|
|
179
272
|
def not_none(v: T | None, msg: Message = None) -> T:
|
180
273
|
if v is None:
|
181
|
-
_raise(ValueError, 'Must not be None', msg, v)
|
274
|
+
_raise(ValueError, 'Must not be None', msg, _Args(v))
|
182
275
|
return v
|
183
276
|
|
184
277
|
|
185
278
|
##
|
186
279
|
|
187
280
|
|
188
|
-
def equal(v: T,
|
189
|
-
|
190
|
-
|
191
|
-
_raise(ValueError, 'Must be equal', msg, v, os)
|
281
|
+
def equal(v: T, o: ta.Any, msg: Message = None) -> T:
|
282
|
+
if o != v:
|
283
|
+
_raise(ValueError, 'Must be equal', msg, _Args(v, o), render_fmt='%s != %s')
|
192
284
|
return v
|
193
285
|
|
194
286
|
|
195
|
-
def is_(v: T,
|
196
|
-
|
197
|
-
|
198
|
-
_raise(ValueError, 'Must be the same', msg, v, os)
|
287
|
+
def is_(v: T, o: ta.Any, msg: Message = None) -> T:
|
288
|
+
if o is not v:
|
289
|
+
_raise(ValueError, 'Must be the same', msg, _Args(v, o), render_fmt='%s is not %s')
|
199
290
|
return v
|
200
291
|
|
201
292
|
|
202
|
-
def is_not(v: T,
|
203
|
-
|
204
|
-
|
205
|
-
_raise(ValueError, 'Must not be the same', msg, v, os)
|
293
|
+
def is_not(v: T, o: ta.Any, msg: Message = None) -> T:
|
294
|
+
if o is v:
|
295
|
+
_raise(ValueError, 'Must not be the same', msg, _Args(v, o), render_fmt='%s is %s')
|
206
296
|
return v
|
207
297
|
|
208
298
|
|
209
299
|
def callable(v: T, msg: Message = None) -> T: # noqa
|
210
300
|
if not _callable(v):
|
211
|
-
_raise(TypeError, 'Must be callable', msg, v)
|
301
|
+
_raise(TypeError, 'Must be callable', msg, _Args(v))
|
212
302
|
return v # type: ignore
|
213
303
|
|
214
304
|
|
215
305
|
def non_empty_str(v: str | None, msg: Message = None) -> str:
|
216
306
|
if not _isinstance(v, str) or not v:
|
217
|
-
_raise(ValueError, 'Must be non-empty str', msg, v)
|
307
|
+
_raise(ValueError, 'Must be non-empty str', msg, _Args(v))
|
218
308
|
return v
|
219
309
|
|
220
310
|
|