omlish 0.0.0.dev307__py3-none-any.whl → 0.0.0.dev308__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/subprocesses.py +4 -4
- omlish/formats/edn/lexing.py +11 -21
- omlish/formats/edn/parsing.py +11 -31
- omlish/formats/json/stream/lexing.py +17 -2
- omlish/formats/json/stream/parsing.py +14 -5
- omlish/lang/__init__.py +2 -3
- omlish/lang/generators.py +19 -28
- omlish/specs/jsonrpc/__init__.py +1 -0
- omlish/specs/jsonrpc/conns.py +222 -0
- omlish/specs/jsonrpc/types.py +19 -0
- {omlish-0.0.0.dev307.dist-info → omlish-0.0.0.dev308.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev307.dist-info → omlish-0.0.0.dev308.dist-info}/RECORD +17 -16
- {omlish-0.0.0.dev307.dist-info → omlish-0.0.0.dev308.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev307.dist-info → omlish-0.0.0.dev308.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev307.dist-info → omlish-0.0.0.dev308.dist-info}/licenses/LICENSE +0 -0
- {omlish-0.0.0.dev307.dist-info → omlish-0.0.0.dev308.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -50,11 +50,11 @@ class AnyioSubprocesses(AbstractAsyncSubprocesses):
|
|
50
50
|
stderr = io.BytesIO()
|
51
51
|
tg.start_soon(read_output, proc.stderr, stderr)
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
if proc.stdin and run.input is not None:
|
54
|
+
await proc.stdin.send(run.input)
|
55
|
+
await proc.stdin.aclose()
|
56
56
|
|
57
|
-
|
57
|
+
await proc.wait()
|
58
58
|
|
59
59
|
if run.check and proc.returncode != 0:
|
60
60
|
raise subprocess.CalledProcessError(
|
omlish/formats/edn/lexing.py
CHANGED
@@ -32,6 +32,8 @@ TokenKind: ta.TypeAlias = ta.Literal[
|
|
32
32
|
'HASH_UNDERSCORE',
|
33
33
|
'META',
|
34
34
|
'QUOTE',
|
35
|
+
|
36
|
+
'SPACE',
|
35
37
|
]
|
36
38
|
|
37
39
|
|
@@ -88,7 +90,13 @@ class StreamLexError(Exception):
|
|
88
90
|
|
89
91
|
|
90
92
|
class StreamLexer(GenMachine[str, Token]):
|
91
|
-
def __init__(
|
93
|
+
def __init__(
|
94
|
+
self,
|
95
|
+
*,
|
96
|
+
include_space: bool = False,
|
97
|
+
) -> None:
|
98
|
+
self._include_space = include_space
|
99
|
+
|
92
100
|
self._ofs = 0
|
93
101
|
self._line = 1
|
94
102
|
self._col = 0
|
@@ -155,6 +163,8 @@ class StreamLexer(GenMachine[str, Token]):
|
|
155
163
|
return None
|
156
164
|
|
157
165
|
if c.isspace() or c == ',':
|
166
|
+
if self._include_space:
|
167
|
+
yield self._make_tok('SPACE', c, self.pos)
|
158
168
|
continue
|
159
169
|
|
160
170
|
if c in SINGLE_TOKENS:
|
@@ -303,23 +313,3 @@ class StreamLexer(GenMachine[str, Token]):
|
|
303
313
|
src = self._flip_buf()
|
304
314
|
yield self._make_tok('WORD', src, pos)
|
305
315
|
return self._do_main(c)
|
306
|
-
|
307
|
-
|
308
|
-
##
|
309
|
-
|
310
|
-
|
311
|
-
def test_lex():
|
312
|
-
for s in [
|
313
|
-
'"abc"',
|
314
|
-
'{"a" "b"}',
|
315
|
-
'1',
|
316
|
-
'-1',
|
317
|
-
'{a :b c 420}',
|
318
|
-
'#{a}',
|
319
|
-
]:
|
320
|
-
print(s)
|
321
|
-
with StreamLexer() as lex:
|
322
|
-
for c in [*s, '']:
|
323
|
-
for t in lex(c):
|
324
|
-
print(t)
|
325
|
-
print()
|
omlish/formats/edn/parsing.py
CHANGED
@@ -178,10 +178,15 @@ class StreamParser(GenMachine[Token, ta.Any]):
|
|
178
178
|
else:
|
179
179
|
raise
|
180
180
|
|
181
|
-
|
181
|
+
# ignored
|
182
|
+
|
183
|
+
if tok.kind in ('SPACE', 'COMMENT'):
|
184
|
+
continue
|
182
185
|
|
183
186
|
# scalars
|
184
187
|
|
188
|
+
value: ta.Any
|
189
|
+
|
185
190
|
if tok.kind == 'STRING':
|
186
191
|
value = self._parse_string(tok)
|
187
192
|
|
@@ -197,9 +202,6 @@ class StreamParser(GenMachine[Token, ta.Any]):
|
|
197
202
|
else:
|
198
203
|
value = self._parse_word(tok)
|
199
204
|
|
200
|
-
elif tok.kind == 'COMMENT':
|
201
|
-
continue
|
202
|
-
|
203
205
|
# open
|
204
206
|
|
205
207
|
elif tok.kind == 'LPAREN':
|
@@ -324,8 +326,7 @@ class StreamParser(GenMachine[Token, ta.Any]):
|
|
324
326
|
|
325
327
|
return self._char_maker(c)
|
326
328
|
|
327
|
-
_INT_PAT = re.compile(r'[-+]?(0|[1-9][0-9]*)')
|
328
|
-
_BIGINT_PAT = re.compile(r'[-+]?(0|[1-9][0-9]*)N')
|
329
|
+
_INT_PAT = re.compile(r'[-+]?(0|[1-9][0-9]*)N?')
|
329
330
|
_FLOAT_PAT = re.compile(r'[-+]?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?(0|[1-9][0-9]*))?M?')
|
330
331
|
|
331
332
|
def _parse_word(self, tok: Token) -> ta.Any:
|
@@ -345,10 +346,11 @@ class StreamParser(GenMachine[Token, ta.Any]):
|
|
345
346
|
# 2r101010, 052, 8r52, 0x2a, 36r16, and 42 are all the same Long.
|
346
347
|
# Floating point numbers are read as Doubles; with M suffix they are read as BigDecimals.
|
347
348
|
# Ratios are supported, e.g. 22/7.
|
348
|
-
|
349
|
+
if src.endswith('N'):
|
350
|
+
return int(src[:-1])
|
349
351
|
|
350
|
-
|
351
|
-
|
352
|
+
else:
|
353
|
+
return int(src)
|
352
354
|
|
353
355
|
elif self._FLOAT_PAT.fullmatch(src):
|
354
356
|
return float(src)
|
@@ -376,25 +378,3 @@ def parse(src: str, **kwargs: ta.Any) -> ta.Any | None:
|
|
376
378
|
if not values:
|
377
379
|
return None
|
378
380
|
return check.single(values)
|
379
|
-
|
380
|
-
|
381
|
-
##
|
382
|
-
|
383
|
-
|
384
|
-
def test_parse():
|
385
|
-
for s in [
|
386
|
-
'"abc"',
|
387
|
-
'"a\\bc"',
|
388
|
-
'{"a" "b"}',
|
389
|
-
'1',
|
390
|
-
'-1',
|
391
|
-
'{a :b c 420}',
|
392
|
-
'#{a}',
|
393
|
-
'(1 #_ 2 3)',
|
394
|
-
'"foo\u1234bar"',
|
395
|
-
'\\x',
|
396
|
-
'\\u1234',
|
397
|
-
]:
|
398
|
-
print(s)
|
399
|
-
print(parse(s))
|
400
|
-
print()
|
@@ -40,7 +40,14 @@ ControlTokenKind: ta.TypeAlias = ta.Literal[
|
|
40
40
|
'COLON',
|
41
41
|
]
|
42
42
|
|
43
|
-
|
43
|
+
SpaceTokenKind: ta.TypeAlias = ta.Literal['SPACE']
|
44
|
+
|
45
|
+
TokenKind: ta.TypeAlias = ta.Union[ # noqa
|
46
|
+
ValueTokenKind,
|
47
|
+
ControlTokenKind,
|
48
|
+
SpaceTokenKind,
|
49
|
+
]
|
50
|
+
|
44
51
|
|
45
52
|
#
|
46
53
|
|
@@ -109,8 +116,10 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
109
116
|
self,
|
110
117
|
*,
|
111
118
|
include_raw: bool = False,
|
119
|
+
include_space: bool = False,
|
112
120
|
) -> None:
|
113
121
|
self._include_raw = include_raw
|
122
|
+
self._include_space = include_space
|
114
123
|
|
115
124
|
self._ofs = 0
|
116
125
|
self._line = 1
|
@@ -174,6 +183,8 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
174
183
|
return None
|
175
184
|
|
176
185
|
if c.isspace():
|
186
|
+
if self._include_space:
|
187
|
+
yield self._make_tok('SPACE', c, c, self.pos)
|
177
188
|
continue
|
178
189
|
|
179
190
|
if c in CONTROL_TOKENS:
|
@@ -282,7 +293,11 @@ class JsonStreamLexer(GenMachine[str, Token]):
|
|
282
293
|
if c in CONTROL_TOKENS:
|
283
294
|
yield self._make_tok(CONTROL_TOKENS[c], c, c, pos)
|
284
295
|
|
285
|
-
elif
|
296
|
+
elif c.isspace():
|
297
|
+
if self._include_space:
|
298
|
+
yield self._make_tok('SPACE', c, c, self.pos)
|
299
|
+
|
300
|
+
else:
|
286
301
|
self._raise(f'Unexpected character after number: {c}')
|
287
302
|
|
288
303
|
return self._do_main()
|
@@ -102,6 +102,15 @@ class JsonStreamParser(GenMachine[Token, JsonStreamParserEvent]):
|
|
102
102
|
|
103
103
|
#
|
104
104
|
|
105
|
+
def _next_tok(self):
|
106
|
+
while True:
|
107
|
+
tok = yield None
|
108
|
+
|
109
|
+
if tok.kind != 'SPACE':
|
110
|
+
return tok
|
111
|
+
|
112
|
+
#
|
113
|
+
|
105
114
|
def _emit_event(self, v):
|
106
115
|
if not self._stack:
|
107
116
|
return ((v,), self._do_value())
|
@@ -129,7 +138,7 @@ class JsonStreamParser(GenMachine[Token, JsonStreamParserEvent]):
|
|
129
138
|
|
130
139
|
def _do_value(self, *, must_be_present: bool = False):
|
131
140
|
try:
|
132
|
-
tok = yield
|
141
|
+
tok = yield from self._next_tok()
|
133
142
|
except GeneratorExit:
|
134
143
|
if self._stack:
|
135
144
|
raise JsonStreamParseError('Expected value') from None
|
@@ -180,7 +189,7 @@ class JsonStreamParser(GenMachine[Token, JsonStreamParserEvent]):
|
|
180
189
|
|
181
190
|
def _do_object_body(self, *, must_be_present: bool = False):
|
182
191
|
try:
|
183
|
-
tok = yield
|
192
|
+
tok = yield from self._next_tok()
|
184
193
|
except GeneratorExit:
|
185
194
|
raise JsonStreamParseError('Expected object body') from None
|
186
195
|
|
@@ -188,7 +197,7 @@ class JsonStreamParser(GenMachine[Token, JsonStreamParserEvent]):
|
|
188
197
|
k = tok.value
|
189
198
|
|
190
199
|
try:
|
191
|
-
tok = yield
|
200
|
+
tok = yield from self._next_tok()
|
192
201
|
except GeneratorExit:
|
193
202
|
raise JsonStreamParseError('Expected key') from None
|
194
203
|
if tok.kind != 'COLON':
|
@@ -211,7 +220,7 @@ class JsonStreamParser(GenMachine[Token, JsonStreamParserEvent]):
|
|
211
220
|
|
212
221
|
def _do_after_pair(self):
|
213
222
|
try:
|
214
|
-
tok = yield
|
223
|
+
tok = yield from self._next_tok()
|
215
224
|
except GeneratorExit:
|
216
225
|
raise JsonStreamParseError('Expected continuation') from None
|
217
226
|
|
@@ -244,7 +253,7 @@ class JsonStreamParser(GenMachine[Token, JsonStreamParserEvent]):
|
|
244
253
|
|
245
254
|
def _do_after_element(self):
|
246
255
|
try:
|
247
|
-
tok = yield
|
256
|
+
tok = yield from self._next_tok()
|
248
257
|
except GeneratorExit:
|
249
258
|
raise JsonStreamParseError('Expected continuation') from None
|
250
259
|
|
omlish/lang/__init__.py
CHANGED
@@ -184,12 +184,11 @@ from .functions import ( # noqa
|
|
184
184
|
)
|
185
185
|
|
186
186
|
from .generators import ( # noqa
|
187
|
-
CoroutineGenerator,
|
188
|
-
Generator,
|
189
187
|
GeneratorLike,
|
190
188
|
GeneratorMappedIterator,
|
191
189
|
autostart,
|
192
|
-
|
190
|
+
capture_coroutine,
|
191
|
+
capture_generator,
|
193
192
|
genmap,
|
194
193
|
nextgen,
|
195
194
|
)
|
omlish/lang/generators.py
CHANGED
@@ -2,6 +2,7 @@ import abc
|
|
2
2
|
import functools
|
3
3
|
import typing as ta
|
4
4
|
|
5
|
+
from .classes.restrict import Abstract
|
5
6
|
from .maybes import Maybe
|
6
7
|
from .maybes import empty
|
7
8
|
from .maybes import just
|
@@ -79,15 +80,29 @@ def adapt_generator_like(gl):
|
|
79
80
|
##
|
80
81
|
|
81
82
|
|
82
|
-
class
|
83
|
+
class AbstractGeneratorCapture(Abstract, ta.Generic[O, I, R]):
|
83
84
|
def __init__(self, g: ta.Generator[O, I, R]) -> None:
|
84
85
|
super().__init__()
|
86
|
+
|
85
87
|
self._g = g
|
86
88
|
|
87
89
|
@property
|
88
90
|
def g(self) -> ta.Generator[O, I, R]:
|
89
91
|
return self._g
|
90
92
|
|
93
|
+
#
|
94
|
+
|
95
|
+
def close(self) -> None:
|
96
|
+
self._g.close()
|
97
|
+
|
98
|
+
def __enter__(self) -> ta.Self:
|
99
|
+
return self
|
100
|
+
|
101
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
102
|
+
self._g.close()
|
103
|
+
|
104
|
+
|
105
|
+
class GeneratorCapture(AbstractGeneratorCapture[O, I, R], ta.Generator[O, I, R]):
|
91
106
|
value: R
|
92
107
|
|
93
108
|
def __iter__(self):
|
@@ -114,35 +129,11 @@ class Generator(ta.Generator[O, I, R]):
|
|
114
129
|
self.value = e.value
|
115
130
|
raise
|
116
131
|
|
117
|
-
def close(self):
|
118
|
-
self._g.close()
|
119
132
|
|
133
|
+
capture_generator = GeneratorCapture
|
120
134
|
|
121
|
-
##
|
122
|
-
|
123
|
-
|
124
|
-
class CoroutineGenerator(ta.Generic[O, I, R]):
|
125
|
-
def __init__(self, g: ta.Generator[O, I, R]) -> None:
|
126
|
-
super().__init__()
|
127
|
-
self._g = g
|
128
|
-
|
129
|
-
@property
|
130
|
-
def g(self) -> ta.Generator[O, I, R]:
|
131
|
-
return self._g
|
132
|
-
|
133
|
-
#
|
134
|
-
|
135
|
-
def close(self) -> None:
|
136
|
-
self._g.close()
|
137
|
-
|
138
|
-
def __enter__(self) -> ta.Self:
|
139
|
-
return self
|
140
|
-
|
141
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
142
|
-
self._g.close()
|
143
|
-
|
144
|
-
#
|
145
135
|
|
136
|
+
class CoroutineGeneratorCapture(AbstractGeneratorCapture[O, I, R]):
|
146
137
|
class Output(ta.NamedTuple, ta.Generic[T]):
|
147
138
|
v: T
|
148
139
|
|
@@ -192,7 +183,7 @@ class CoroutineGenerator(ta.Generic[O, I, R]):
|
|
192
183
|
return self.Yield(o)
|
193
184
|
|
194
185
|
|
195
|
-
|
186
|
+
capture_coroutine = CoroutineGeneratorCapture
|
196
187
|
|
197
188
|
|
198
189
|
##
|
omlish/specs/jsonrpc/__init__.py
CHANGED
@@ -0,0 +1,222 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- kill receive loop on __aexit__
|
4
|
+
"""
|
5
|
+
import builtins
|
6
|
+
import json
|
7
|
+
import typing as ta
|
8
|
+
import uuid
|
9
|
+
|
10
|
+
import anyio.abc
|
11
|
+
|
12
|
+
from ... import lang
|
13
|
+
from ... import marshal as msh
|
14
|
+
from ...asyncs import anyio as aiu
|
15
|
+
from ...io.buffers import DelimitingBuffer
|
16
|
+
from .types import Error
|
17
|
+
from .types import Id
|
18
|
+
from .types import Message
|
19
|
+
from .types import NotSpecified
|
20
|
+
from .types import Object
|
21
|
+
from .types import Request
|
22
|
+
from .types import Response
|
23
|
+
from .types import detect_message_type
|
24
|
+
from .types import notification
|
25
|
+
from .types import request
|
26
|
+
|
27
|
+
|
28
|
+
##
|
29
|
+
|
30
|
+
|
31
|
+
@lang.cached_function
|
32
|
+
def _create_id() -> str:
|
33
|
+
return str(uuid.uuid4())
|
34
|
+
|
35
|
+
|
36
|
+
class JsonrpcConnection:
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
tg: anyio.abc.TaskGroup,
|
40
|
+
stream: anyio.abc.ByteStream,
|
41
|
+
*,
|
42
|
+
request_handler: ta.Callable[['JsonrpcConnection', Request], ta.Awaitable[None]] | None = None,
|
43
|
+
notification_handler: ta.Callable[['JsonrpcConnection', Request], ta.Awaitable[None]] | None = None,
|
44
|
+
default_timeout: float | None = 30.,
|
45
|
+
) -> None:
|
46
|
+
super().__init__()
|
47
|
+
|
48
|
+
self._tg = tg
|
49
|
+
self._stream = stream
|
50
|
+
self._request_handler = request_handler
|
51
|
+
self._notification_handler = notification_handler
|
52
|
+
self._default_timeout = default_timeout
|
53
|
+
|
54
|
+
self._buf = DelimitingBuffer(b'\n')
|
55
|
+
self._response_futures_by_id: dict[Id, aiu.Future[Response]] = {}
|
56
|
+
self._send_lock = anyio.Lock()
|
57
|
+
self._received_eof = False
|
58
|
+
self._running = True
|
59
|
+
|
60
|
+
#
|
61
|
+
|
62
|
+
class Error(Exception):
|
63
|
+
"""Base class for JSON-RPC related errors."""
|
64
|
+
|
65
|
+
class TimeoutError(Error, builtins.TimeoutError): # noqa
|
66
|
+
"""Raised when a request times out."""
|
67
|
+
|
68
|
+
class ConnectionError(Error, builtins.ConnectionError): # noqa
|
69
|
+
"""Raised when there are connection-related issues."""
|
70
|
+
|
71
|
+
class ProtocolError(Error):
|
72
|
+
"""Raised when there are protocol-related issues."""
|
73
|
+
|
74
|
+
#
|
75
|
+
|
76
|
+
async def __aenter__(self) -> 'JsonrpcConnection':
|
77
|
+
await self._tg.start(self._receive_loop)
|
78
|
+
return self
|
79
|
+
|
80
|
+
async def __aexit__(self, exc_type: type[BaseException] | None, *_: object) -> None:
|
81
|
+
self._running = False
|
82
|
+
|
83
|
+
##
|
84
|
+
|
85
|
+
async def _handle_message(self, msg: Message) -> None:
|
86
|
+
if isinstance(msg, Response):
|
87
|
+
msg_id = msg.id
|
88
|
+
try:
|
89
|
+
resp_fut = self._response_futures_by_id[msg_id]
|
90
|
+
except KeyError:
|
91
|
+
raise NotImplementedError from None
|
92
|
+
resp_fut.set_value(msg)
|
93
|
+
|
94
|
+
elif isinstance(msg, Request):
|
95
|
+
if msg.is_notification:
|
96
|
+
if (mh := self._notification_handler) is not None:
|
97
|
+
await mh(self, msg)
|
98
|
+
|
99
|
+
else: # noqa
|
100
|
+
if (rh := self._request_handler) is not None:
|
101
|
+
await rh(self, msg)
|
102
|
+
|
103
|
+
else:
|
104
|
+
raise TypeError(msg)
|
105
|
+
|
106
|
+
#
|
107
|
+
|
108
|
+
CLOSED_EXCEPTIONS: ta.ClassVar[tuple[type[Exception], ...]] = (
|
109
|
+
anyio.ClosedResourceError,
|
110
|
+
anyio.EndOfStream,
|
111
|
+
)
|
112
|
+
|
113
|
+
ERROR_EXCEPTIONS: ta.ClassVar[tuple[type[Exception], ...]] = (
|
114
|
+
OSError,
|
115
|
+
anyio.BrokenResourceError,
|
116
|
+
)
|
117
|
+
|
118
|
+
async def _receive_message_batch(self) -> list[Message] | None:
|
119
|
+
if self._received_eof:
|
120
|
+
return None
|
121
|
+
|
122
|
+
while True:
|
123
|
+
try:
|
124
|
+
data = await self._stream.receive()
|
125
|
+
except self.CLOSED_EXCEPTIONS:
|
126
|
+
data = b''
|
127
|
+
except self.ERROR_EXCEPTIONS as e:
|
128
|
+
raise JsonrpcConnection.ConnectionError('Failed to receive message') from e
|
129
|
+
|
130
|
+
if not data:
|
131
|
+
self._received_eof = True
|
132
|
+
|
133
|
+
lines = list(self._buf.feed(data))
|
134
|
+
if lines:
|
135
|
+
break
|
136
|
+
|
137
|
+
if not data:
|
138
|
+
return None
|
139
|
+
|
140
|
+
msgs: list[Message] = []
|
141
|
+
for line in lines:
|
142
|
+
if isinstance(line, DelimitingBuffer.Incomplete):
|
143
|
+
raise ConnectionError('Received incomplete message')
|
144
|
+
|
145
|
+
try:
|
146
|
+
dct = json.loads(line.decode('utf-8'))
|
147
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as e:
|
148
|
+
raise JsonrpcConnection.ProtocolError from e
|
149
|
+
|
150
|
+
mcls = detect_message_type(dct)
|
151
|
+
try:
|
152
|
+
msg = msh.unmarshal(dct, mcls)
|
153
|
+
except Exception as e:
|
154
|
+
raise JsonrpcConnection.ProtocolError from e
|
155
|
+
|
156
|
+
msgs.append(msg)
|
157
|
+
|
158
|
+
return msgs
|
159
|
+
|
160
|
+
async def _receive_loop(
|
161
|
+
self,
|
162
|
+
*,
|
163
|
+
task_status: anyio.abc.TaskStatus[ta.Any] = anyio.TASK_STATUS_IGNORED,
|
164
|
+
) -> None:
|
165
|
+
task_status.started()
|
166
|
+
|
167
|
+
while self._running:
|
168
|
+
msgs = await self._receive_message_batch()
|
169
|
+
if msgs is None:
|
170
|
+
break
|
171
|
+
|
172
|
+
for msg in msgs:
|
173
|
+
await self._handle_message(msg)
|
174
|
+
|
175
|
+
##
|
176
|
+
|
177
|
+
async def send_message(self, msg: Message) -> None:
|
178
|
+
async with self._send_lock:
|
179
|
+
try:
|
180
|
+
await self._stream.send(json.dumps(msh.marshal(msg)).encode() + b'\n')
|
181
|
+
except self.ERROR_EXCEPTIONS as e:
|
182
|
+
raise ConnectionError('Failed to send message') from e
|
183
|
+
|
184
|
+
#
|
185
|
+
|
186
|
+
async def request(
|
187
|
+
self,
|
188
|
+
method: str,
|
189
|
+
params: Object | None = None,
|
190
|
+
*,
|
191
|
+
timeout: float | None = None,
|
192
|
+
) -> ta.Any:
|
193
|
+
msg_id = _create_id()
|
194
|
+
req = request(msg_id, method, params)
|
195
|
+
|
196
|
+
fut = aiu.create_future[Response]()
|
197
|
+
self._response_futures_by_id[msg_id] = fut
|
198
|
+
|
199
|
+
try:
|
200
|
+
await self.send_message(req)
|
201
|
+
|
202
|
+
timeout_val = timeout if timeout is not None else self._default_timeout
|
203
|
+
try:
|
204
|
+
with anyio.fail_after(timeout_val):
|
205
|
+
await fut
|
206
|
+
except TimeoutError as e:
|
207
|
+
raise JsonrpcConnection.TimeoutError(f'Request timed out after {timeout_val} seconds') from e
|
208
|
+
|
209
|
+
response = fut.outcome.must().unwrap()
|
210
|
+
|
211
|
+
if response.error is not NotSpecified:
|
212
|
+
error = ta.cast(Error, response.error)
|
213
|
+
raise JsonrpcConnection.Error(f'Error {error.code}: {error.message}')
|
214
|
+
|
215
|
+
return response.result
|
216
|
+
|
217
|
+
finally:
|
218
|
+
self._response_futures_by_id.pop(msg_id, None) # noqa
|
219
|
+
|
220
|
+
async def notify(self, method: str, params: Object | None = None) -> None:
|
221
|
+
msg = notification(method, params)
|
222
|
+
await self.send_message(msg)
|
omlish/specs/jsonrpc/types.py
CHANGED
@@ -18,6 +18,8 @@ from ... import lang
|
|
18
18
|
from ... import marshal as msh
|
19
19
|
|
20
20
|
|
21
|
+
T = ta.TypeVar('T')
|
22
|
+
|
21
23
|
NUMBER_TYPES: tuple[type, ...] = (int, float)
|
22
24
|
Number: ta.TypeAlias = int | float
|
23
25
|
|
@@ -44,6 +46,11 @@ def is_not_specified(v: ta.Any) -> bool:
|
|
44
46
|
return v is NotSpecified
|
45
47
|
|
46
48
|
|
49
|
+
def check_not_not_specified(v: T | type[NotSpecified]) -> T:
|
50
|
+
check.arg(not is_not_specified(v))
|
51
|
+
return ta.cast(T, v)
|
52
|
+
|
53
|
+
|
47
54
|
##
|
48
55
|
|
49
56
|
|
@@ -87,10 +94,22 @@ class Response(lang.Final):
|
|
87
94
|
|
88
95
|
_: dc.KW_ONLY
|
89
96
|
|
97
|
+
#
|
98
|
+
|
90
99
|
result: ta.Any = dc.field(default=NotSpecified)
|
91
100
|
error: ta.Union['Error', type[NotSpecified]] = dc.field(default=NotSpecified)
|
92
101
|
dc.validate(lambda self: is_not_specified(self.result) ^ is_not_specified(self.error))
|
93
102
|
|
103
|
+
@property
|
104
|
+
def is_result(self) -> bool:
|
105
|
+
return not is_not_specified(self.result)
|
106
|
+
|
107
|
+
@property
|
108
|
+
def is_error(self) -> bool:
|
109
|
+
return not is_not_specified(self.error)
|
110
|
+
|
111
|
+
#
|
112
|
+
|
94
113
|
jsonrpc: str = dc.field(default=VERSION)
|
95
114
|
dc.validate(lambda self: self.jsonrpc == VERSION)
|
96
115
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=orgsRvtpHu8tdhaCvlP9v3P495OJopYYiHKjK68WtWg,8587
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=8uwTWCC1I_m-H13aDRaDT3swnjnC8reB6e7B8azZYSA,3478
|
3
3
|
omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
|
4
4
|
omlish/c3.py,sha256=rer-TPOFDU6fYq_AWio_AmA-ckZ8JDY5shIzQ_yXfzA,8414
|
5
5
|
omlish/cached.py,sha256=MLap_p0rdGoDIMVhXVHm1tsbcWobJF0OanoodV03Ju8,542
|
@@ -102,7 +102,7 @@ omlish/asyncs/anyio/backends.py,sha256=jJIymWoiedaEJJm82gvKiJ41EWLQZ-bcyNHpbDpKK
|
|
102
102
|
omlish/asyncs/anyio/futures.py,sha256=Nm1gLerZEnHk-rlsmr0UfK168IWIK6zA8EebZFtoY_E,2052
|
103
103
|
omlish/asyncs/anyio/signals.py,sha256=ySSut5prdnoy0-5Ws5V1M4cC2ON_vY550vU10d2NHk8,893
|
104
104
|
omlish/asyncs/anyio/streams.py,sha256=Zum2qd1t3EiH6yzGWFwxFw79m-IH2VY5sTUTiluFfIY,2164
|
105
|
-
omlish/asyncs/anyio/subprocesses.py,sha256=
|
105
|
+
omlish/asyncs/anyio/subprocesses.py,sha256=nyl1A9z3rymxQMvIekWHU3IAiKBu1CcEqm-Ag1cGRPY,3018
|
106
106
|
omlish/asyncs/anyio/sync.py,sha256=ZmSNhSsEkPwlXThrpefhtVTxw4GJ9F0P-yKyo5vbbSk,1574
|
107
107
|
omlish/asyncs/anyio/utils.py,sha256=X2Rz1DGrCJ0zkt1O5cHoMRaYKTPndBj6dzLhb09mVtE,1672
|
108
108
|
omlish/asyncs/asyncio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -321,8 +321,8 @@ omlish/formats/xml.py,sha256=VJfqHR60dhAtjeG8WXFMozFqesTBSGvv264d67eDFXc,3514
|
|
321
321
|
omlish/formats/yaml.py,sha256=jGPQlTE0vSV-p0O7TJRNlf6o1uq4gx8PrHZe1ApJ_o8,7386
|
322
322
|
omlish/formats/edn/__init__.py,sha256=H3q5B-dibXvQV8pmuWizTo6Xk75M7M0M7VPCLt86rpo,195
|
323
323
|
omlish/formats/edn/codec.py,sha256=k6-Ra3P3Rlv6JA69-jPLI4nCe5XVes_QJbcsj5DYzMM,454
|
324
|
-
omlish/formats/edn/lexing.py,sha256=
|
325
|
-
omlish/formats/edn/parsing.py,sha256=
|
324
|
+
omlish/formats/edn/lexing.py,sha256=LaIaGql9NtRlgi6bs4XhZ-wtabiUs99PoYN7_yAKMNE,6892
|
325
|
+
omlish/formats/edn/parsing.py,sha256=Y1CBxtdlbnn5zwU5nlxX3uw17u2POST8tabtVV1q0X4,9835
|
326
326
|
omlish/formats/edn/values.py,sha256=jf0g88KJIMALxcuH51SoaMWg1HqTUqc1ugldmyyXWoc,3707
|
327
327
|
omlish/formats/ini/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
328
328
|
omlish/formats/ini/codec.py,sha256=omuFg0kiDksv8rRlWd_v32ebzEcKlgmiPgGID3bRi2M,631
|
@@ -345,8 +345,8 @@ omlish/formats/json/backends/ujson.py,sha256=BNJCU4kluGHdqTUKLJEuHhE2m2TmqR7HEN2
|
|
345
345
|
omlish/formats/json/stream/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
346
346
|
omlish/formats/json/stream/building.py,sha256=SGbExmaerqOEiNSom2AERlpyXTj4dpM0QbMW-2WWM2o,2550
|
347
347
|
omlish/formats/json/stream/errors.py,sha256=c8M8UAYmIZ-vWZLeKD2jMj4EDCJbr9QR8Jq_DyHjujQ,43
|
348
|
-
omlish/formats/json/stream/lexing.py,sha256=
|
349
|
-
omlish/formats/json/stream/parsing.py,sha256=
|
348
|
+
omlish/formats/json/stream/lexing.py,sha256=0XbkpKm4rmn5DLRyqnVpyvBXMi_MjVEI1k6-TIgQWRM,7208
|
349
|
+
omlish/formats/json/stream/parsing.py,sha256=AXsg0N5s0jEvdfEQrzprplZDNkB62l6Q3Pv8rIx3984,6454
|
350
350
|
omlish/formats/json/stream/rendering.py,sha256=uuJc__MR0G5kypYMAAudBNjBfiIzA_GGli-DWT90428,3730
|
351
351
|
omlish/formats/json/stream/utils.py,sha256=UhBRuWbb25wrdQWl8Ttq7xGRLoa329TvNdecGCZxgzg,1197
|
352
352
|
omlish/formats/json5/Json5.g4,sha256=ZUmgJPvj8lSMUD_v3wijp10ZQExYB5mu5Q089dYEJSU,2389
|
@@ -470,7 +470,7 @@ omlish/iterators/iterators.py,sha256=RxW35yQ5ed8vBQ22IqpDXFx-i5JiLQdp7-pkMZXhJJ8
|
|
470
470
|
omlish/iterators/recipes.py,sha256=wOwOZg-zWG9Zc3wcAxJFSe2rtavVBYwZOfG09qYEx_4,472
|
471
471
|
omlish/iterators/tools.py,sha256=c4hArZEVV8y9_dFfmRwakusv1cWJLT4MkTkGRjnGN5U,2556
|
472
472
|
omlish/iterators/unique.py,sha256=Nw0pSaNEcHAkve0ugfLPvJcirDOn9ECyC5wIL8JlJKI,1395
|
473
|
-
omlish/lang/__init__.py,sha256=
|
473
|
+
omlish/lang/__init__.py,sha256=yJvuoESt6-nZckXYcHctZYEnHCRCjQ2SkRV3ctUljPI,5856
|
474
474
|
omlish/lang/attrs.py,sha256=i7euRF81uNF8QDmUVXSK_BtqLGshaMi4VVdUnMjiMwg,5050
|
475
475
|
omlish/lang/casing.py,sha256=cFUlbDdXLhwnWwcYx4qnM5c4zGX7hIRUfcjiZbxUD28,4636
|
476
476
|
omlish/lang/clsdct.py,sha256=HAGIvBSbCefzRjXriwYSBLO7QHKRv2UsE78jixOb-fA,1828
|
@@ -481,7 +481,7 @@ omlish/lang/datetimes.py,sha256=mrTtA67JYpfQwSlzdPcBtvm6dAyYM_dXNnlxFwFQH0M,228
|
|
481
481
|
omlish/lang/descriptors.py,sha256=zBtgO9LjdSTGHNUgiIqswh78WOVoGH6KzS0NbgB1Wls,6572
|
482
482
|
omlish/lang/enums.py,sha256=F9tflHfaAoV2MpyuhZzpfX9-H55M3zNa9hCszsngEo8,111
|
483
483
|
omlish/lang/functions.py,sha256=51CoKtH_-CXUsKvtCexaR3OLZOtIwSdv4f4DtGBZdpA,6029
|
484
|
-
omlish/lang/generators.py,sha256=
|
484
|
+
omlish/lang/generators.py,sha256=yd3ebG2LlA3XQImP8rplt6tSToXwMxhlGRGCkdJ87ME,5230
|
485
485
|
omlish/lang/imports.py,sha256=y9W9Y-d_cQ35QCLuSIPoa6vnEqSErFCz8b-34IH128U,10552
|
486
486
|
omlish/lang/iterables.py,sha256=HOjcxOwyI5bBApDLsxRAGGhTTmw7fdZl2kEckxRVl-0,1994
|
487
487
|
omlish/lang/maybes.py,sha256=pb1YrxmpXy-hWKmWR89GxXqZq1MoUD1uuTaTX30peh0,3697
|
@@ -688,10 +688,11 @@ omlish/specs/jmespath/lexer.py,sha256=WGxkwQe_dcHWcJcGg9q6K-8_Q0oRdWkw09dYGFNTHb
|
|
688
688
|
omlish/specs/jmespath/parser.py,sha256=yfkydotVR4LBhrUTsptL_kLYDoGZrRN9zSEs_76kvZM,24441
|
689
689
|
omlish/specs/jmespath/scope.py,sha256=UyDsl9rv_c8DCjJBuVIA2ESu1jrgYvuwEKiaJDQKnT0,1590
|
690
690
|
omlish/specs/jmespath/visitor.py,sha256=HVro_6aBGL0CMBy8NRy6vJzWgwsHGB1qJXldX8H7wSg,16592
|
691
|
-
omlish/specs/jsonrpc/__init__.py,sha256=
|
691
|
+
omlish/specs/jsonrpc/__init__.py,sha256=ugIdqHXWZjSy1R1SkwoxstO2GCHEF4W95Ogl3_5_DV4,544
|
692
|
+
omlish/specs/jsonrpc/conns.py,sha256=Js7DnC48mdnO56-A3wIsgc5ri8CbpAC53USD43eTQSw,6453
|
692
693
|
omlish/specs/jsonrpc/errors.py,sha256=-Zgmlo6bV6J8w5f8h9axQgLquIFBHDgIwcpufEH5NsE,707
|
693
694
|
omlish/specs/jsonrpc/marshal.py,sha256=HM736piPGnBZrg8CMLDX-L5fZpegyF6l6JUjzLoSDtk,1852
|
694
|
-
omlish/specs/jsonrpc/types.py,sha256=
|
695
|
+
omlish/specs/jsonrpc/types.py,sha256=Se9ecG-_k-kY_Qlt9QD2t3y26oY4sXTcskp6XZfVans,3054
|
695
696
|
omlish/specs/jsonschema/__init__.py,sha256=55P7Yg2MprqDyaifac2ExNzK6blTZuDP4ejrUXZWpt8,1129
|
696
697
|
omlish/specs/jsonschema/types.py,sha256=_H7ma99hD3_Xu42BFGHOXRI5p79tY8WBX8QE36k7lbw,472
|
697
698
|
omlish/specs/jsonschema/keywords/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -852,9 +853,9 @@ omlish/typedvalues/holder.py,sha256=ZTnHiw-K38ciOBLEdwgrltr7Xp8jjEs_0Lp69DH-G-o,
|
|
852
853
|
omlish/typedvalues/marshal.py,sha256=hWHRLcrGav7lvXJDtb9bNI0ickl4SKPQ6F4BbTpqw3A,4219
|
853
854
|
omlish/typedvalues/reflect.py,sha256=Ih1YgU-srUjsvBn_P7C66f73_VCvcwqE3ffeBnZBgt4,674
|
854
855
|
omlish/typedvalues/values.py,sha256=ym46I-q2QJ_6l4UlERqv3yj87R-kp8nCKMRph0xQ3UA,1307
|
855
|
-
omlish-0.0.0.
|
856
|
-
omlish-0.0.0.
|
857
|
-
omlish-0.0.0.
|
858
|
-
omlish-0.0.0.
|
859
|
-
omlish-0.0.0.
|
860
|
-
omlish-0.0.0.
|
856
|
+
omlish-0.0.0.dev308.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
857
|
+
omlish-0.0.0.dev308.dist-info/METADATA,sha256=-f4JyYLhz7RVYUCBHyIx0neYBnC4SadbuFbwS9NKl6E,4416
|
858
|
+
omlish-0.0.0.dev308.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
859
|
+
omlish-0.0.0.dev308.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
860
|
+
omlish-0.0.0.dev308.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
861
|
+
omlish-0.0.0.dev308.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|