omlish 0.0.0.dev255__py3-none-any.whl → 0.0.0.dev256__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 +10 -0
- omlish/{outcome.py → lang/outcomes.py} +61 -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.dev256.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev255.dist-info → omlish-0.0.0.dev256.dist-info}/RECORD +22 -14
- omlish/asyncs/anyio.py +0 -273
- {omlish-0.0.0.dev255.dist-info → omlish-0.0.0.dev256.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev255.dist-info → omlish-0.0.0.dev256.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev255.dist-info → omlish-0.0.0.dev256.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev255.dist-info → omlish-0.0.0.dev256.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,16 @@ 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
|
+
)
|
221
|
+
|
212
222
|
from .params import ( # noqa
|
213
223
|
ArgsParam,
|
214
224
|
KwOnlyParam,
|
@@ -18,8 +18,6 @@ import abc
|
|
18
18
|
import dataclasses as dc
|
19
19
|
import typing as ta
|
20
20
|
|
21
|
-
from . import check
|
22
|
-
|
23
21
|
|
24
22
|
ValueT_co = ta.TypeVar('ValueT_co', covariant=True)
|
25
23
|
ResultT = ta.TypeVar('ResultT')
|
@@ -29,14 +27,15 @@ ArgsT = ta.ParamSpec('ArgsT')
|
|
29
27
|
##
|
30
28
|
|
31
29
|
|
32
|
-
class
|
30
|
+
class OutcomeAlreadyUnwrappedError(RuntimeError):
|
33
31
|
"""An Outcome can only be unwrapped once."""
|
34
32
|
|
35
33
|
|
36
34
|
def _remove_tb_frames(exc: BaseException, n: int) -> BaseException:
|
37
35
|
tb: ta.Any = exc.__traceback__
|
38
36
|
for _ in range(n):
|
39
|
-
|
37
|
+
if tb is None:
|
38
|
+
raise RuntimeError
|
40
39
|
tb = tb.tb_next
|
41
40
|
return exc.with_traceback(tb)
|
42
41
|
|
@@ -141,11 +140,24 @@ class Outcome(abc.ABC, ta.Generic[ValueT_co]):
|
|
141
140
|
:class:`Outcome` objects are hashable if the contained objects are hashable.
|
142
141
|
"""
|
143
142
|
|
143
|
+
@property
|
144
|
+
@abc.abstractmethod
|
145
|
+
def is_error(self) -> bool:
|
146
|
+
raise NotImplementedError
|
147
|
+
|
148
|
+
def value(self) -> ValueT_co:
|
149
|
+
raise TypeError
|
150
|
+
|
151
|
+
def error(self) -> BaseException:
|
152
|
+
raise TypeError
|
153
|
+
|
154
|
+
#
|
155
|
+
|
144
156
|
_unwrapped: bool = dc.field(default=False, compare=False, init=False)
|
145
157
|
|
146
158
|
def _set_unwrapped(self) -> None:
|
147
159
|
if self._unwrapped:
|
148
|
-
raise
|
160
|
+
raise OutcomeAlreadyUnwrappedError
|
149
161
|
object.__setattr__(self, '_unwrapped', True)
|
150
162
|
|
151
163
|
@abc.abstractmethod
|
@@ -178,27 +190,44 @@ class Outcome(abc.ABC, ta.Generic[ValueT_co]):
|
|
178
190
|
"""
|
179
191
|
|
180
192
|
|
193
|
+
#
|
194
|
+
|
195
|
+
|
181
196
|
@ta.final
|
182
197
|
@dc.dataclass(frozen=True, repr=False, slots=True, order=True)
|
183
198
|
class Value(Outcome[ValueT_co], ta.Generic[ValueT_co]):
|
184
199
|
"""Concrete :class:`Outcome` subclass representing a regular value."""
|
185
200
|
|
186
|
-
|
201
|
+
_value: ValueT_co
|
187
202
|
|
188
203
|
def __repr__(self) -> str:
|
189
|
-
return f'Value({self.
|
204
|
+
return f'Value({self._value!r})'
|
205
|
+
|
206
|
+
#
|
207
|
+
|
208
|
+
@property
|
209
|
+
def is_error(self) -> bool:
|
210
|
+
return True
|
211
|
+
|
212
|
+
def value(self) -> ValueT_co:
|
213
|
+
return self._value
|
214
|
+
|
215
|
+
#
|
190
216
|
|
191
217
|
def unwrap(self) -> ValueT_co:
|
192
218
|
self._set_unwrapped()
|
193
|
-
return self.
|
219
|
+
return self._value
|
194
220
|
|
195
221
|
def send(self, gen: ta.Generator[ResultT, ValueT_co, object]) -> ResultT:
|
196
222
|
self._set_unwrapped()
|
197
|
-
return gen.send(self.
|
223
|
+
return gen.send(self._value)
|
198
224
|
|
199
225
|
async def asend(self, agen: ta.AsyncGenerator[ResultT, ValueT_co]) -> ResultT:
|
200
226
|
self._set_unwrapped()
|
201
|
-
return await agen.asend(self.
|
227
|
+
return await agen.asend(self._value)
|
228
|
+
|
229
|
+
|
230
|
+
#
|
202
231
|
|
203
232
|
|
204
233
|
@ta.final
|
@@ -206,21 +235,32 @@ class Value(Outcome[ValueT_co], ta.Generic[ValueT_co]):
|
|
206
235
|
class Error(Outcome[ta.NoReturn]):
|
207
236
|
"""Concrete :class:`Outcome` subclass representing a raised exception."""
|
208
237
|
|
209
|
-
|
238
|
+
_error: BaseException
|
210
239
|
|
211
240
|
def __post_init__(self) -> None:
|
212
|
-
if not isinstance(self.
|
213
|
-
raise TypeError(self.
|
241
|
+
if not isinstance(self._error, BaseException):
|
242
|
+
raise TypeError(self._error)
|
214
243
|
|
215
244
|
def __repr__(self) -> str:
|
216
|
-
return f'Error({self.
|
245
|
+
return f'Error({self._error!r})'
|
246
|
+
|
247
|
+
#
|
248
|
+
|
249
|
+
@property
|
250
|
+
def is_error(self) -> bool:
|
251
|
+
return True
|
252
|
+
|
253
|
+
def error(self) -> BaseException:
|
254
|
+
return self._error
|
255
|
+
|
256
|
+
#
|
217
257
|
|
218
258
|
def unwrap(self) -> ta.NoReturn:
|
219
259
|
self._set_unwrapped()
|
220
260
|
|
221
261
|
# Tracebacks show the 'raise' line below out of context, so let's give this variable a name that makes sense out
|
222
262
|
# of context.
|
223
|
-
captured_error = self.
|
263
|
+
captured_error = self._error
|
224
264
|
|
225
265
|
try:
|
226
266
|
raise captured_error
|
@@ -239,12 +279,15 @@ class Error(Outcome[ta.NoReturn]):
|
|
239
279
|
|
240
280
|
def send(self, gen: ta.Generator[ResultT, ta.NoReturn, object]) -> ResultT:
|
241
281
|
self._set_unwrapped()
|
242
|
-
return gen.throw(self.
|
282
|
+
return gen.throw(self._error)
|
243
283
|
|
244
284
|
async def asend(self, agen: ta.AsyncGenerator[ResultT, ta.NoReturn]) -> ResultT:
|
245
285
|
self._set_unwrapped()
|
246
|
-
return await agen.athrow(self.
|
286
|
+
return await agen.athrow(self._error)
|
287
|
+
|
288
|
+
|
289
|
+
##
|
247
290
|
|
248
291
|
|
249
292
|
# A convenience alias to a union of both results, allowing exhaustiveness checking.
|
250
|
-
|
293
|
+
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
|
|