omlish 0.0.0.dev255__py3-none-any.whl → 0.0.0.dev257__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 +2 -2
- omlish/asyncs/anyio/__init__.py +74 -0
- omlish/asyncs/anyio/backends.py +52 -0
- omlish/asyncs/anyio/futures.py +104 -0
- omlish/asyncs/anyio/signals.py +33 -0
- omlish/asyncs/anyio/streams.py +69 -0
- omlish/asyncs/anyio/sync.py +69 -0
- omlish/asyncs/anyio/utils.py +48 -0
- omlish/lang/__init__.py +12 -0
- omlish/{outcome.py → lang/outcomes.py} +74 -18
- omlish/specs/jsonrpc/__init__.py +20 -8
- omlish/specs/jsonrpc/types.py +33 -0
- omlish/sync.py +10 -0
- omlish/testing/pytest/plugins/asyncs/fixtures.py +2 -3
- omlish/text/go/__init__.py +0 -0
- omlish/text/go/quoting.py +260 -0
- {omlish-0.0.0.dev255.dist-info → omlish-0.0.0.dev257.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev255.dist-info → omlish-0.0.0.dev257.dist-info}/RECORD +22 -14
- omlish/asyncs/anyio.py +0 -273
- {omlish-0.0.0.dev255.dist-info → omlish-0.0.0.dev257.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev255.dist-info → omlish-0.0.0.dev257.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev255.dist-info → omlish-0.0.0.dev257.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev255.dist-info → omlish-0.0.0.dev257.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- bane
|
4
|
+
- owned lock
|
5
|
+
- async once
|
6
|
+
|
7
|
+
See:
|
8
|
+
- https://github.com/davidbrochart/sqlite-anyio/blob/a3ba4c6ef0535b14a5a60071fcd6ed565a514963/sqlite_anyio/sqlite.py
|
9
|
+
- https://github.com/rafalkrupinski/ratelimit-anyio/blob/2910a8a3d6fa54ed17ee6ba457686c9f7a4c4beb/src/ratelimit_anyio/__init__.py
|
10
|
+
- https://github.com/nekitdev/async-extensions/tree/main/async_extensions
|
11
|
+
- https://github.com/kinnay/anynet/tree/master/anynet
|
12
|
+
- https://github.com/M-o-a-T/asyncscope
|
13
|
+
- https://github.com/M-o-a-T/aevent
|
14
|
+
- https://github.com/florimondmanca/aiometer
|
15
|
+
- https://github.com/sanitizers/octomachinery/blob/b36c3d3d49da813ac46e361424132955a4e99ac8/octomachinery/utils/asynctools.py
|
16
|
+
|
17
|
+
==
|
18
|
+
|
19
|
+
async def killer(shutdown: anyio.Event, sleep_s: float) -> None:
|
20
|
+
log.warning('Killing in %d seconds', sleep_s)
|
21
|
+
await anyio.sleep(sleep_s)
|
22
|
+
log.warning('Killing')
|
23
|
+
shutdown.set()
|
24
|
+
|
25
|
+
""" # noqa
|
26
|
+
|
27
|
+
from .backends import ( # noqa
|
28
|
+
BackendTask,
|
29
|
+
get_backend_task,
|
30
|
+
get_current_backend_task,
|
31
|
+
)
|
32
|
+
|
33
|
+
from .futures import ( # noqa
|
34
|
+
FutureError,
|
35
|
+
FutureOutcomeAlreadySetError,
|
36
|
+
|
37
|
+
Future,
|
38
|
+
|
39
|
+
create_future,
|
40
|
+
)
|
41
|
+
|
42
|
+
from .signals import ( # noqa
|
43
|
+
install_shutdown_signal_handler,
|
44
|
+
)
|
45
|
+
|
46
|
+
from .streams import ( # noqa
|
47
|
+
MemoryObjectReceiveStream,
|
48
|
+
MemoryObjectSendStream,
|
49
|
+
|
50
|
+
StapledByteStream,
|
51
|
+
StapledObjectStream,
|
52
|
+
|
53
|
+
MemoryStapledObjectStream,
|
54
|
+
|
55
|
+
split_memory_object_streams,
|
56
|
+
create_stapled_memory_object_stream,
|
57
|
+
create_memory_object_stream,
|
58
|
+
staple_memory_object_stream,
|
59
|
+
staple_memory_object_stream2,
|
60
|
+
)
|
61
|
+
|
62
|
+
from .sync import ( # noqa
|
63
|
+
Once,
|
64
|
+
Lazy,
|
65
|
+
LazyFn,
|
66
|
+
)
|
67
|
+
|
68
|
+
from .utils import ( # noqa
|
69
|
+
eof_to_empty,
|
70
|
+
gather,
|
71
|
+
first,
|
72
|
+
|
73
|
+
get_current_task,
|
74
|
+
)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
import anyio
|
4
|
+
|
5
|
+
from .utils import get_current_task
|
6
|
+
|
7
|
+
|
8
|
+
##
|
9
|
+
|
10
|
+
|
11
|
+
BackendTask: ta.TypeAlias = ta.Union[ # noqa
|
12
|
+
# asyncio.tasks.Task,
|
13
|
+
# trio.lowlevel.Task,
|
14
|
+
ta.Any,
|
15
|
+
]
|
16
|
+
|
17
|
+
|
18
|
+
def _is_class_named(obj: ta.Any, m: str, n: str) -> bool:
|
19
|
+
cls = obj.__class__
|
20
|
+
return cls.__module__ == m and cls.__name__ == n
|
21
|
+
|
22
|
+
|
23
|
+
def get_backend_task(at: anyio.TaskInfo) -> BackendTask | None:
|
24
|
+
if _is_class_named(at, 'anyio._backends._asyncio', 'AsyncIOTaskInfo'):
|
25
|
+
# https://github.com/agronholm/anyio/blob/8907964926a24461840eee0925d3f355e729f15d/src/anyio/_backends/_asyncio.py#L1846 # noqa
|
26
|
+
# weakref.ref
|
27
|
+
obj = at._task() # type: ignore # noqa
|
28
|
+
if obj is not None and not (
|
29
|
+
_is_class_named(obj, '_asyncio', 'Task') or
|
30
|
+
_is_class_named(obj, 'asyncio.tasks', 'Task')
|
31
|
+
):
|
32
|
+
raise TypeError(obj)
|
33
|
+
return obj
|
34
|
+
|
35
|
+
elif _is_class_named(at, 'anyio._backends._trio', 'TrioTaskInfo'):
|
36
|
+
# https://github.com/agronholm/anyio/blob/8907964926a24461840eee0925d3f355e729f15d/src/anyio/_backends/_trio.py#L850 # noqa
|
37
|
+
# weakref.proxy
|
38
|
+
# https://stackoverflow.com/a/62144308 :|
|
39
|
+
obj = at._task.__repr__.__self__ # type: ignore # noqa
|
40
|
+
if obj is not None and not _is_class_named(obj, 'trio.lowlevel', 'Task'):
|
41
|
+
raise TypeError(obj)
|
42
|
+
return obj
|
43
|
+
|
44
|
+
else:
|
45
|
+
raise TypeError(at)
|
46
|
+
|
47
|
+
|
48
|
+
def get_current_backend_task() -> BackendTask | None:
|
49
|
+
if (at := get_current_task()) is not None:
|
50
|
+
return get_backend_task(at)
|
51
|
+
else:
|
52
|
+
return None
|
@@ -0,0 +1,104 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- CancellableFuture
|
4
|
+
"""
|
5
|
+
import abc
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
import anyio
|
9
|
+
|
10
|
+
from ... import lang
|
11
|
+
|
12
|
+
|
13
|
+
T = ta.TypeVar('T')
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
|
18
|
+
|
19
|
+
class FutureError(Exception):
|
20
|
+
pass
|
21
|
+
|
22
|
+
|
23
|
+
class FutureOutcomeAlreadySetError(FutureError):
|
24
|
+
pass
|
25
|
+
|
26
|
+
|
27
|
+
##
|
28
|
+
|
29
|
+
|
30
|
+
class Future(lang.Abstract, ta.Awaitable[lang.Outcome[T]]):
|
31
|
+
@abc.abstractmethod
|
32
|
+
def __await__(self):
|
33
|
+
raise NotImplementedError
|
34
|
+
|
35
|
+
@property
|
36
|
+
@abc.abstractmethod
|
37
|
+
def outcome(self) -> lang.Maybe[lang.Outcome[T]]:
|
38
|
+
raise NotImplementedError
|
39
|
+
|
40
|
+
@abc.abstractmethod
|
41
|
+
def set_outcome(self, o: lang.Outcome[T]) -> None:
|
42
|
+
raise NotImplementedError
|
43
|
+
|
44
|
+
def set_value(self, v: T) -> None:
|
45
|
+
self.set_outcome(lang.value(v))
|
46
|
+
|
47
|
+
def set_error(self, e: BaseException) -> None:
|
48
|
+
self.set_outcome(lang.error(e))
|
49
|
+
|
50
|
+
|
51
|
+
##
|
52
|
+
|
53
|
+
|
54
|
+
class _FutureImpl(Future[T]):
|
55
|
+
def __init__(self, *, event: anyio.Event | None = None) -> None:
|
56
|
+
super().__init__()
|
57
|
+
|
58
|
+
self._outcome: lang.Maybe[lang.Outcome[T]] = lang.empty()
|
59
|
+
|
60
|
+
if event is None:
|
61
|
+
event = anyio.Event()
|
62
|
+
self._event = event
|
63
|
+
|
64
|
+
def __await__(self):
|
65
|
+
if (o := self._outcome).present:
|
66
|
+
return o
|
67
|
+
yield from self._event.wait().__await__()
|
68
|
+
return self._outcome.must()
|
69
|
+
|
70
|
+
@property
|
71
|
+
def outcome(self) -> lang.Maybe[lang.Outcome[T]]:
|
72
|
+
return self._outcome
|
73
|
+
|
74
|
+
def set_outcome(self, o: lang.Outcome[T]) -> None:
|
75
|
+
if self._outcome.present:
|
76
|
+
raise FutureOutcomeAlreadySetError
|
77
|
+
self._outcome = lang.just(o)
|
78
|
+
self._event.set()
|
79
|
+
|
80
|
+
|
81
|
+
##
|
82
|
+
|
83
|
+
|
84
|
+
def _create_future() -> Future[T]:
|
85
|
+
return _FutureImpl()
|
86
|
+
|
87
|
+
|
88
|
+
# PEP695 workaround
|
89
|
+
class create_future(Future[T]): # noqa
|
90
|
+
def __new__(cls) -> Future[T]: # type: ignore[misc]
|
91
|
+
return _create_future()
|
92
|
+
|
93
|
+
def __init__(self) -> None:
|
94
|
+
raise TypeError
|
95
|
+
|
96
|
+
def __await__(self):
|
97
|
+
raise TypeError
|
98
|
+
|
99
|
+
@property
|
100
|
+
def outcome(self) -> lang.Maybe[lang.Outcome[T]]:
|
101
|
+
raise TypeError
|
102
|
+
|
103
|
+
def set_outcome(self, o: lang.Outcome[T]) -> None:
|
104
|
+
raise TypeError
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import signal
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
import anyio.abc
|
5
|
+
|
6
|
+
|
7
|
+
##
|
8
|
+
|
9
|
+
|
10
|
+
async def install_shutdown_signal_handler(
|
11
|
+
tg: anyio.abc.TaskGroup,
|
12
|
+
event: anyio.Event | None = None,
|
13
|
+
*,
|
14
|
+
signals: ta.Iterable[int] = (signal.SIGINT, signal.SIGTERM),
|
15
|
+
echo: bool = False,
|
16
|
+
) -> ta.Callable[..., ta.Awaitable[None]] | None:
|
17
|
+
if event is None:
|
18
|
+
event = anyio.Event()
|
19
|
+
|
20
|
+
async def _handler(*, task_status=anyio.TASK_STATUS_IGNORED):
|
21
|
+
with anyio.open_signal_receiver(*signals) as it: # type: ignore
|
22
|
+
task_status.started()
|
23
|
+
async for signum in it:
|
24
|
+
if echo:
|
25
|
+
if signum == signal.SIGINT:
|
26
|
+
print('Ctrl+C pressed!')
|
27
|
+
else:
|
28
|
+
print('Terminated!')
|
29
|
+
event.set()
|
30
|
+
return
|
31
|
+
|
32
|
+
await tg.start(_handler)
|
33
|
+
return event.wait
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
import anyio.streams.memory
|
5
|
+
import anyio.streams.stapled
|
6
|
+
|
7
|
+
from ... import check
|
8
|
+
|
9
|
+
|
10
|
+
T = ta.TypeVar('T')
|
11
|
+
|
12
|
+
|
13
|
+
##
|
14
|
+
|
15
|
+
|
16
|
+
MemoryObjectReceiveStream: ta.TypeAlias = anyio.streams.memory.MemoryObjectReceiveStream
|
17
|
+
MemoryObjectSendStream: ta.TypeAlias = anyio.streams.memory.MemoryObjectSendStream
|
18
|
+
|
19
|
+
StapledByteStream: ta.TypeAlias = anyio.streams.stapled.StapledByteStream
|
20
|
+
StapledObjectStream: ta.TypeAlias = anyio.streams.stapled.StapledObjectStream
|
21
|
+
|
22
|
+
|
23
|
+
@dc.dataclass(eq=False)
|
24
|
+
class MemoryStapledObjectStream(StapledObjectStream[T]):
|
25
|
+
send_stream: MemoryObjectSendStream[T]
|
26
|
+
receive_stream: MemoryObjectReceiveStream[T]
|
27
|
+
|
28
|
+
|
29
|
+
def split_memory_object_streams(
|
30
|
+
*args: anyio.create_memory_object_stream[T],
|
31
|
+
) -> tuple[
|
32
|
+
MemoryObjectSendStream[T],
|
33
|
+
MemoryObjectReceiveStream[T],
|
34
|
+
]:
|
35
|
+
[tup] = args
|
36
|
+
return tup # noqa
|
37
|
+
|
38
|
+
|
39
|
+
def create_stapled_memory_object_stream(max_buffer_size: float = 0) -> MemoryStapledObjectStream:
|
40
|
+
return MemoryStapledObjectStream(*anyio.create_memory_object_stream(max_buffer_size))
|
41
|
+
|
42
|
+
|
43
|
+
# FIXME: https://github.com/python/mypy/issues/15238
|
44
|
+
# FIXME: https://youtrack.jetbrains.com/issues?q=tag:%20%7BPEP%20695%7D
|
45
|
+
def create_memory_object_stream[T](max_buffer_size: float = 0) -> tuple[
|
46
|
+
MemoryObjectSendStream[T],
|
47
|
+
MemoryObjectReceiveStream[T],
|
48
|
+
]:
|
49
|
+
return anyio.create_memory_object_stream[T](max_buffer_size) # noqa
|
50
|
+
|
51
|
+
|
52
|
+
def staple_memory_object_stream(
|
53
|
+
*args: anyio.create_memory_object_stream[T],
|
54
|
+
) -> MemoryStapledObjectStream[T]:
|
55
|
+
send, receive = args
|
56
|
+
return MemoryStapledObjectStream(
|
57
|
+
check.isinstance(send, MemoryObjectSendStream), # type: ignore
|
58
|
+
check.isinstance(receive, MemoryObjectReceiveStream), # type: ignore
|
59
|
+
)
|
60
|
+
|
61
|
+
|
62
|
+
# FIXME: https://github.com/python/mypy/issues/15238
|
63
|
+
# FIXME: https://youtrack.jetbrains.com/issues?q=tag:%20%7BPEP%20695%7D
|
64
|
+
def staple_memory_object_stream2[T](max_buffer_size: float = 0) -> MemoryStapledObjectStream[T]:
|
65
|
+
send, receive = anyio.create_memory_object_stream[T](max_buffer_size)
|
66
|
+
return MemoryStapledObjectStream(
|
67
|
+
check.isinstance(send, MemoryObjectSendStream), # type: ignore
|
68
|
+
check.isinstance(receive, MemoryObjectReceiveStream), # type: ignore
|
69
|
+
)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
import anyio
|
4
|
+
|
5
|
+
from ... import lang
|
6
|
+
|
7
|
+
|
8
|
+
T = ta.TypeVar('T')
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
|
13
|
+
|
14
|
+
class Once:
|
15
|
+
def __init__(self) -> None:
|
16
|
+
super().__init__()
|
17
|
+
self._done = False
|
18
|
+
self._lock = anyio.Lock()
|
19
|
+
|
20
|
+
async def do(self, fn: ta.Callable[[], ta.Awaitable[None]]) -> bool:
|
21
|
+
if self._done:
|
22
|
+
return False
|
23
|
+
async with self._lock:
|
24
|
+
if self._done:
|
25
|
+
return False # type: ignore
|
26
|
+
try:
|
27
|
+
await fn()
|
28
|
+
finally:
|
29
|
+
self._done = True
|
30
|
+
return True
|
31
|
+
|
32
|
+
|
33
|
+
class Lazy(ta.Generic[T]):
|
34
|
+
def __init__(self) -> None:
|
35
|
+
super().__init__()
|
36
|
+
self._once = Once()
|
37
|
+
self._v: lang.Maybe[T] = lang.empty()
|
38
|
+
|
39
|
+
def peek(self) -> lang.Maybe[T]:
|
40
|
+
return self._v
|
41
|
+
|
42
|
+
def set(self, v: T) -> None:
|
43
|
+
self._v = lang.just(v)
|
44
|
+
|
45
|
+
async def get(self, fn: ta.Callable[[], ta.Awaitable[T]]) -> T:
|
46
|
+
async def do():
|
47
|
+
self._v = lang.just(await fn())
|
48
|
+
await self._once.do(do)
|
49
|
+
return self._v.must()
|
50
|
+
|
51
|
+
|
52
|
+
class LazyFn(ta.Generic[T]):
|
53
|
+
def __init__(self, fn: ta.Callable[[], ta.Awaitable[T]]) -> None:
|
54
|
+
super().__init__()
|
55
|
+
self._fn = fn
|
56
|
+
self._once = Once()
|
57
|
+
self._v: lang.Maybe[T] = lang.empty()
|
58
|
+
|
59
|
+
def peek(self) -> lang.Maybe[T]:
|
60
|
+
return self._v
|
61
|
+
|
62
|
+
def set(self, v: T) -> None:
|
63
|
+
self._v = lang.just(v)
|
64
|
+
|
65
|
+
async def get(self) -> T:
|
66
|
+
async def do():
|
67
|
+
self._v = lang.just(await self._fn())
|
68
|
+
await self._once.do(do)
|
69
|
+
return self._v.must()
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
import anyio
|
4
|
+
import sniffio
|
5
|
+
|
6
|
+
from ... import lang
|
7
|
+
|
8
|
+
|
9
|
+
T = ta.TypeVar('T')
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
|
14
|
+
|
15
|
+
async def eof_to_empty(fn: ta.Callable[..., ta.Awaitable[T]], *args: ta.Any, **kwargs: ta.Any) -> T | bytes:
|
16
|
+
try:
|
17
|
+
return await fn(*args, **kwargs)
|
18
|
+
except anyio.EndOfStream:
|
19
|
+
return b''
|
20
|
+
|
21
|
+
|
22
|
+
async def gather(*fns: ta.Callable[..., ta.Awaitable[T]], take_first: bool = False) -> list[lang.Maybe[T]]:
|
23
|
+
results: list[lang.Maybe[T]] = [lang.empty()] * len(fns)
|
24
|
+
|
25
|
+
async def inner(fn, i):
|
26
|
+
results[i] = lang.just(await fn())
|
27
|
+
if take_first:
|
28
|
+
tg.cancel_scope.cancel()
|
29
|
+
|
30
|
+
async with anyio.create_task_group() as tg:
|
31
|
+
for i, fn in enumerate(fns):
|
32
|
+
tg.start_soon(inner, fn, i)
|
33
|
+
|
34
|
+
return results
|
35
|
+
|
36
|
+
|
37
|
+
async def first(*fns: ta.Callable[..., ta.Awaitable[T]], **kwargs: ta.Any) -> list[lang.Maybe[T]]:
|
38
|
+
return await gather(*fns, take_first=True, **kwargs)
|
39
|
+
|
40
|
+
|
41
|
+
##
|
42
|
+
|
43
|
+
|
44
|
+
def get_current_task() -> anyio.TaskInfo | None:
|
45
|
+
try:
|
46
|
+
return anyio.get_current_task()
|
47
|
+
except sniffio.AsyncLibraryNotFoundError:
|
48
|
+
return None
|
omlish/lang/__init__.py
CHANGED
@@ -209,6 +209,18 @@ from .objects import ( # noqa
|
|
209
209
|
super_meta,
|
210
210
|
)
|
211
211
|
|
212
|
+
from .outcomes import ( # noqa
|
213
|
+
Either,
|
214
|
+
Error,
|
215
|
+
Outcome,
|
216
|
+
OutcomeAlreadyUnwrappedError,
|
217
|
+
Value,
|
218
|
+
acapture,
|
219
|
+
capture,
|
220
|
+
error,
|
221
|
+
value,
|
222
|
+
)
|
223
|
+
|
212
224
|
from .params import ( # noqa
|
213
225
|
ArgsParam,
|
214
226
|
KwOnlyParam,
|
@@ -14,13 +14,16 @@
|
|
14
14
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
15
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
16
|
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
"""
|
18
|
+
TODO:
|
19
|
+
- hide Value and Error classes like Maybes
|
20
|
+
"""
|
17
21
|
import abc
|
18
22
|
import dataclasses as dc
|
19
23
|
import typing as ta
|
20
24
|
|
21
|
-
from . import check
|
22
|
-
|
23
25
|
|
26
|
+
T = ta.TypeVar('T')
|
24
27
|
ValueT_co = ta.TypeVar('ValueT_co', covariant=True)
|
25
28
|
ResultT = ta.TypeVar('ResultT')
|
26
29
|
ArgsT = ta.ParamSpec('ArgsT')
|
@@ -29,14 +32,15 @@ ArgsT = ta.ParamSpec('ArgsT')
|
|
29
32
|
##
|
30
33
|
|
31
34
|
|
32
|
-
class
|
35
|
+
class OutcomeAlreadyUnwrappedError(RuntimeError):
|
33
36
|
"""An Outcome can only be unwrapped once."""
|
34
37
|
|
35
38
|
|
36
39
|
def _remove_tb_frames(exc: BaseException, n: int) -> BaseException:
|
37
40
|
tb: ta.Any = exc.__traceback__
|
38
41
|
for _ in range(n):
|
39
|
-
|
42
|
+
if tb is None:
|
43
|
+
raise RuntimeError
|
40
44
|
tb = tb.tb_next
|
41
45
|
return exc.with_traceback(tb)
|
42
46
|
|
@@ -141,11 +145,24 @@ class Outcome(abc.ABC, ta.Generic[ValueT_co]):
|
|
141
145
|
:class:`Outcome` objects are hashable if the contained objects are hashable.
|
142
146
|
"""
|
143
147
|
|
148
|
+
@property
|
149
|
+
@abc.abstractmethod
|
150
|
+
def is_error(self) -> bool:
|
151
|
+
raise NotImplementedError
|
152
|
+
|
153
|
+
def value(self) -> ValueT_co:
|
154
|
+
raise TypeError
|
155
|
+
|
156
|
+
def error(self) -> BaseException:
|
157
|
+
raise TypeError
|
158
|
+
|
159
|
+
#
|
160
|
+
|
144
161
|
_unwrapped: bool = dc.field(default=False, compare=False, init=False)
|
145
162
|
|
146
163
|
def _set_unwrapped(self) -> None:
|
147
164
|
if self._unwrapped:
|
148
|
-
raise
|
165
|
+
raise OutcomeAlreadyUnwrappedError
|
149
166
|
object.__setattr__(self, '_unwrapped', True)
|
150
167
|
|
151
168
|
@abc.abstractmethod
|
@@ -178,27 +195,48 @@ class Outcome(abc.ABC, ta.Generic[ValueT_co]):
|
|
178
195
|
"""
|
179
196
|
|
180
197
|
|
198
|
+
#
|
199
|
+
|
200
|
+
|
181
201
|
@ta.final
|
182
202
|
@dc.dataclass(frozen=True, repr=False, slots=True, order=True)
|
183
203
|
class Value(Outcome[ValueT_co], ta.Generic[ValueT_co]):
|
184
204
|
"""Concrete :class:`Outcome` subclass representing a regular value."""
|
185
205
|
|
186
|
-
|
206
|
+
_value: ValueT_co
|
187
207
|
|
188
208
|
def __repr__(self) -> str:
|
189
|
-
return f'Value({self.
|
209
|
+
return f'Value({self._value!r})'
|
210
|
+
|
211
|
+
#
|
212
|
+
|
213
|
+
@property
|
214
|
+
def is_error(self) -> bool:
|
215
|
+
return True
|
216
|
+
|
217
|
+
def value(self) -> ValueT_co:
|
218
|
+
return self._value
|
219
|
+
|
220
|
+
#
|
190
221
|
|
191
222
|
def unwrap(self) -> ValueT_co:
|
192
223
|
self._set_unwrapped()
|
193
|
-
return self.
|
224
|
+
return self._value
|
194
225
|
|
195
226
|
def send(self, gen: ta.Generator[ResultT, ValueT_co, object]) -> ResultT:
|
196
227
|
self._set_unwrapped()
|
197
|
-
return gen.send(self.
|
228
|
+
return gen.send(self._value)
|
198
229
|
|
199
230
|
async def asend(self, agen: ta.AsyncGenerator[ResultT, ValueT_co]) -> ResultT:
|
200
231
|
self._set_unwrapped()
|
201
|
-
return await agen.asend(self.
|
232
|
+
return await agen.asend(self._value)
|
233
|
+
|
234
|
+
|
235
|
+
def value(v: T) -> Outcome[T]:
|
236
|
+
return Value(v)
|
237
|
+
|
238
|
+
|
239
|
+
#
|
202
240
|
|
203
241
|
|
204
242
|
@ta.final
|
@@ -206,21 +244,32 @@ class Value(Outcome[ValueT_co], ta.Generic[ValueT_co]):
|
|
206
244
|
class Error(Outcome[ta.NoReturn]):
|
207
245
|
"""Concrete :class:`Outcome` subclass representing a raised exception."""
|
208
246
|
|
209
|
-
|
247
|
+
_error: BaseException
|
210
248
|
|
211
249
|
def __post_init__(self) -> None:
|
212
|
-
if not isinstance(self.
|
213
|
-
raise TypeError(self.
|
250
|
+
if not isinstance(self._error, BaseException):
|
251
|
+
raise TypeError(self._error)
|
214
252
|
|
215
253
|
def __repr__(self) -> str:
|
216
|
-
return f'Error({self.
|
254
|
+
return f'Error({self._error!r})'
|
255
|
+
|
256
|
+
#
|
257
|
+
|
258
|
+
@property
|
259
|
+
def is_error(self) -> bool:
|
260
|
+
return True
|
261
|
+
|
262
|
+
def error(self) -> BaseException:
|
263
|
+
return self._error
|
264
|
+
|
265
|
+
#
|
217
266
|
|
218
267
|
def unwrap(self) -> ta.NoReturn:
|
219
268
|
self._set_unwrapped()
|
220
269
|
|
221
270
|
# Tracebacks show the 'raise' line below out of context, so let's give this variable a name that makes sense out
|
222
271
|
# of context.
|
223
|
-
captured_error = self.
|
272
|
+
captured_error = self._error
|
224
273
|
|
225
274
|
try:
|
226
275
|
raise captured_error
|
@@ -239,12 +288,19 @@ class Error(Outcome[ta.NoReturn]):
|
|
239
288
|
|
240
289
|
def send(self, gen: ta.Generator[ResultT, ta.NoReturn, object]) -> ResultT:
|
241
290
|
self._set_unwrapped()
|
242
|
-
return gen.throw(self.
|
291
|
+
return gen.throw(self._error)
|
243
292
|
|
244
293
|
async def asend(self, agen: ta.AsyncGenerator[ResultT, ta.NoReturn]) -> ResultT:
|
245
294
|
self._set_unwrapped()
|
246
|
-
return await agen.athrow(self.
|
295
|
+
return await agen.athrow(self._error)
|
296
|
+
|
297
|
+
|
298
|
+
def error(e: BaseException) -> Outcome[T]:
|
299
|
+
return Error(e)
|
300
|
+
|
301
|
+
|
302
|
+
##
|
247
303
|
|
248
304
|
|
249
305
|
# A convenience alias to a union of both results, allowing exhaustiveness checking.
|
250
|
-
|
306
|
+
Either = Value[ValueT_co] | Error
|
omlish/specs/jsonrpc/__init__.py
CHANGED
@@ -1,22 +1,34 @@
|
|
1
1
|
from .errors import ( # noqa
|
2
|
-
CUSTOM_ERROR_BASE,
|
3
2
|
KnownError,
|
4
3
|
KnownErrors,
|
4
|
+
|
5
|
+
CUSTOM_ERROR_BASE,
|
5
6
|
)
|
6
7
|
|
7
8
|
from .types import ( # noqa
|
8
|
-
|
9
|
-
Id,
|
10
|
-
NotSpecified,
|
9
|
+
NUMBER_TYPES,
|
11
10
|
Number,
|
12
11
|
Object,
|
13
|
-
|
14
|
-
|
12
|
+
ID_TYPES,
|
13
|
+
Id,
|
14
|
+
|
15
15
|
VERSION,
|
16
|
-
|
17
|
-
|
16
|
+
|
17
|
+
NotSpecified,
|
18
|
+
is_not_specified,
|
19
|
+
|
20
|
+
Request,
|
18
21
|
request,
|
22
|
+
notification,
|
23
|
+
|
24
|
+
Response,
|
19
25
|
result,
|
26
|
+
|
27
|
+
Error,
|
28
|
+
error,
|
29
|
+
|
30
|
+
Message,
|
31
|
+
detect_message_type,
|
20
32
|
)
|
21
33
|
|
22
34
|
|