grpc-dc 0.32.15__tar.gz
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.
- grpc_dc-0.32.15/PKG-INFO +49 -0
- grpc_dc-0.32.15/README.md +33 -0
- grpc_dc-0.32.15/grpc_dc.egg-info/PKG-INFO +49 -0
- grpc_dc-0.32.15/grpc_dc.egg-info/SOURCES.txt +34 -0
- grpc_dc-0.32.15/grpc_dc.egg-info/dependency_links.txt +1 -0
- grpc_dc-0.32.15/grpc_dc.egg-info/top_level.txt +1 -0
- grpc_dc-0.32.15/grpcdc/__init__.py +74 -0
- grpc_dc-0.32.15/grpcdc/channel.py +322 -0
- grpc_dc-0.32.15/grpcdc/codec.py +273 -0
- grpc_dc-0.32.15/grpcdc/dynamic.py +594 -0
- grpc_dc-0.32.15/grpcdc/endpoint.py +375 -0
- grpc_dc-0.32.15/grpcdc/http2.py +479 -0
- grpc_dc-0.32.15/grpcdc/loopback.py +38 -0
- grpc_dc-0.32.15/grpcdc/server.py +104 -0
- grpc_dc-0.32.15/grpcdc/sidecar.py +67 -0
- grpc_dc-0.32.15/grpcdc/wire_gen.py +56 -0
- grpc_dc-0.32.15/pyproject.toml +28 -0
- grpc_dc-0.32.15/setup.cfg +4 -0
- grpc_dc-0.32.15/tests/test_async.py +127 -0
- grpc_dc-0.32.15/tests/test_bdd.py +153 -0
- grpc_dc-0.32.15/tests/test_bdd_schemas.py +57 -0
- grpc_dc-0.32.15/tests/test_bdd_sidecar.py +213 -0
- grpc_dc-0.32.15/tests/test_codec.py +53 -0
- grpc_dc-0.32.15/tests/test_deadline.py +131 -0
- grpc_dc-0.32.15/tests/test_dynamic.py +200 -0
- grpc_dc-0.32.15/tests/test_echo.py +89 -0
- grpc_dc-0.32.15/tests/test_http2_oracle.py +170 -0
- grpc_dc-0.32.15/tests/test_interop.py +149 -0
- grpc_dc-0.32.15/tests/test_interop_oracle.py +481 -0
- grpc_dc-0.32.15/tests/test_interop_server_oracle.py +225 -0
- grpc_dc-0.32.15/tests/test_oracle.py +197 -0
- grpc_dc-0.32.15/tests/test_otlp_tracer.py +67 -0
- grpc_dc-0.32.15/tests/test_pb_messages.py +254 -0
- grpc_dc-0.32.15/tests/test_sidecar_direct.py +120 -0
- grpc_dc-0.32.15/tests/test_sidecar_multi.py +117 -0
- grpc_dc-0.32.15/tests/test_tracing.py +134 -0
grpc_dc-0.32.15/PKG-INFO
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: grpc-dc
|
|
3
|
+
Version: 0.32.15
|
|
4
|
+
Summary: gRPC over WebRTC DataChannels — dependency-free generated Python runtime
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Project-URL: Homepage, https://browser-grpc.github.io/grpc-over-datachannels
|
|
7
|
+
Project-URL: Repository, https://github.com/browser-grpc/grpc-over-datachannels
|
|
8
|
+
Keywords: grpc,webrtc,datachannel,rpc
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Python — grpc-dc runtime
|
|
18
|
+
|
|
19
|
+
Start here if you are new to the project. The runtime in
|
|
20
|
+
this directory is generated, dependency-free, and speaks
|
|
21
|
+
the gRPC wire protocol over WebRTC DataChannels.
|
|
22
|
+
|
|
23
|
+
## First run
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
cd python && python3 -m unittest discover -s tests
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Use it in your project
|
|
30
|
+
|
|
31
|
+
pip users: `pip install ./python` installs the `grpcdc` package from this tree (pyproject.toml included, no dependencies).
|
|
32
|
+
|
|
33
|
+
## Regenerate stubs for your schema
|
|
34
|
+
|
|
35
|
+
From the repository root (one wrapper per language in `bin/`):
|
|
36
|
+
|
|
37
|
+
protoc --plugin=protoc-gen-grpc-dc-python=$PWD/bin/protoc-gen-grpc-dc-python \
|
|
38
|
+
--grpc-dc-python_out=out -I . your.proto
|
|
39
|
+
|
|
40
|
+
Or generate in the browser and download the zip:
|
|
41
|
+
https://browser-grpc.github.io/grpc-over-datachannels/app/
|
|
42
|
+
|
|
43
|
+
## Where things live
|
|
44
|
+
|
|
45
|
+
- Runtime: this directory (see its generated sources).
|
|
46
|
+
- Worked example: `examples/` (Echo in every method shape).
|
|
47
|
+
- Tests: `tests/` — the same BDD scenarios every language runs.
|
|
48
|
+
|
|
49
|
+
Full project quickstart: [`../QUICKSTART.md`](../QUICKSTART.md).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python — grpc-dc runtime
|
|
2
|
+
|
|
3
|
+
Start here if you are new to the project. The runtime in
|
|
4
|
+
this directory is generated, dependency-free, and speaks
|
|
5
|
+
the gRPC wire protocol over WebRTC DataChannels.
|
|
6
|
+
|
|
7
|
+
## First run
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
cd python && python3 -m unittest discover -s tests
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Use it in your project
|
|
14
|
+
|
|
15
|
+
pip users: `pip install ./python` installs the `grpcdc` package from this tree (pyproject.toml included, no dependencies).
|
|
16
|
+
|
|
17
|
+
## Regenerate stubs for your schema
|
|
18
|
+
|
|
19
|
+
From the repository root (one wrapper per language in `bin/`):
|
|
20
|
+
|
|
21
|
+
protoc --plugin=protoc-gen-grpc-dc-python=$PWD/bin/protoc-gen-grpc-dc-python \
|
|
22
|
+
--grpc-dc-python_out=out -I . your.proto
|
|
23
|
+
|
|
24
|
+
Or generate in the browser and download the zip:
|
|
25
|
+
https://browser-grpc.github.io/grpc-over-datachannels/app/
|
|
26
|
+
|
|
27
|
+
## Where things live
|
|
28
|
+
|
|
29
|
+
- Runtime: this directory (see its generated sources).
|
|
30
|
+
- Worked example: `examples/` (Echo in every method shape).
|
|
31
|
+
- Tests: `tests/` — the same BDD scenarios every language runs.
|
|
32
|
+
|
|
33
|
+
Full project quickstart: [`../QUICKSTART.md`](../QUICKSTART.md).
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: grpc-dc
|
|
3
|
+
Version: 0.32.15
|
|
4
|
+
Summary: gRPC over WebRTC DataChannels — dependency-free generated Python runtime
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Project-URL: Homepage, https://browser-grpc.github.io/grpc-over-datachannels
|
|
7
|
+
Project-URL: Repository, https://github.com/browser-grpc/grpc-over-datachannels
|
|
8
|
+
Keywords: grpc,webrtc,datachannel,rpc
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Python — grpc-dc runtime
|
|
18
|
+
|
|
19
|
+
Start here if you are new to the project. The runtime in
|
|
20
|
+
this directory is generated, dependency-free, and speaks
|
|
21
|
+
the gRPC wire protocol over WebRTC DataChannels.
|
|
22
|
+
|
|
23
|
+
## First run
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
cd python && python3 -m unittest discover -s tests
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Use it in your project
|
|
30
|
+
|
|
31
|
+
pip users: `pip install ./python` installs the `grpcdc` package from this tree (pyproject.toml included, no dependencies).
|
|
32
|
+
|
|
33
|
+
## Regenerate stubs for your schema
|
|
34
|
+
|
|
35
|
+
From the repository root (one wrapper per language in `bin/`):
|
|
36
|
+
|
|
37
|
+
protoc --plugin=protoc-gen-grpc-dc-python=$PWD/bin/protoc-gen-grpc-dc-python \
|
|
38
|
+
--grpc-dc-python_out=out -I . your.proto
|
|
39
|
+
|
|
40
|
+
Or generate in the browser and download the zip:
|
|
41
|
+
https://browser-grpc.github.io/grpc-over-datachannels/app/
|
|
42
|
+
|
|
43
|
+
## Where things live
|
|
44
|
+
|
|
45
|
+
- Runtime: this directory (see its generated sources).
|
|
46
|
+
- Worked example: `examples/` (Echo in every method shape).
|
|
47
|
+
- Tests: `tests/` — the same BDD scenarios every language runs.
|
|
48
|
+
|
|
49
|
+
Full project quickstart: [`../QUICKSTART.md`](../QUICKSTART.md).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
grpc_dc.egg-info/PKG-INFO
|
|
4
|
+
grpc_dc.egg-info/SOURCES.txt
|
|
5
|
+
grpc_dc.egg-info/dependency_links.txt
|
|
6
|
+
grpc_dc.egg-info/top_level.txt
|
|
7
|
+
grpcdc/__init__.py
|
|
8
|
+
grpcdc/channel.py
|
|
9
|
+
grpcdc/codec.py
|
|
10
|
+
grpcdc/dynamic.py
|
|
11
|
+
grpcdc/endpoint.py
|
|
12
|
+
grpcdc/http2.py
|
|
13
|
+
grpcdc/loopback.py
|
|
14
|
+
grpcdc/server.py
|
|
15
|
+
grpcdc/sidecar.py
|
|
16
|
+
grpcdc/wire_gen.py
|
|
17
|
+
tests/test_async.py
|
|
18
|
+
tests/test_bdd.py
|
|
19
|
+
tests/test_bdd_schemas.py
|
|
20
|
+
tests/test_bdd_sidecar.py
|
|
21
|
+
tests/test_codec.py
|
|
22
|
+
tests/test_deadline.py
|
|
23
|
+
tests/test_dynamic.py
|
|
24
|
+
tests/test_echo.py
|
|
25
|
+
tests/test_http2_oracle.py
|
|
26
|
+
tests/test_interop.py
|
|
27
|
+
tests/test_interop_oracle.py
|
|
28
|
+
tests/test_interop_server_oracle.py
|
|
29
|
+
tests/test_oracle.py
|
|
30
|
+
tests/test_otlp_tracer.py
|
|
31
|
+
tests/test_pb_messages.py
|
|
32
|
+
tests/test_sidecar_direct.py
|
|
33
|
+
tests/test_sidecar_multi.py
|
|
34
|
+
tests/test_tracing.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
grpcdc
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# @generated by grpc-dc codegen. DO NOT EDIT.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""grpcdc: gRPC over WebRTC DataChannels (generated runtime)."""
|
|
4
|
+
|
|
5
|
+
from .channel import AsyncChannel, AsyncClientStream, Channel, ClientStream
|
|
6
|
+
from .codec import (
|
|
7
|
+
CallClosed,
|
|
8
|
+
Headers,
|
|
9
|
+
StatusError,
|
|
10
|
+
decode_frame,
|
|
11
|
+
encode_frame,
|
|
12
|
+
set_tracer,
|
|
13
|
+
set_trace_filter,
|
|
14
|
+
status_name,
|
|
15
|
+
to_status,
|
|
16
|
+
)
|
|
17
|
+
from .endpoint import CallContext, Endpoint, Transport
|
|
18
|
+
from .dynamic import (
|
|
19
|
+
DynamicClient,
|
|
20
|
+
FileModel,
|
|
21
|
+
GenericMessage,
|
|
22
|
+
parse_descriptor_set,
|
|
23
|
+
register_dynamic_service,
|
|
24
|
+
)
|
|
25
|
+
from .loopback import memory_transport_pair
|
|
26
|
+
from .http2 import Http2Transport
|
|
27
|
+
from .sidecar import SidecarTransport
|
|
28
|
+
from .wire_gen import CODE_INTERNAL
|
|
29
|
+
from .server import (
|
|
30
|
+
KIND_BIDI,
|
|
31
|
+
KIND_CLIENT_STREAMING,
|
|
32
|
+
KIND_SERVER_STREAMING,
|
|
33
|
+
KIND_UNARY,
|
|
34
|
+
Method,
|
|
35
|
+
RequestStream,
|
|
36
|
+
Server,
|
|
37
|
+
ServiceDesc,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"CODE_INTERNAL",
|
|
42
|
+
"CallClosed",
|
|
43
|
+
"DynamicClient",
|
|
44
|
+
"FileModel",
|
|
45
|
+
"GenericMessage",
|
|
46
|
+
"CallContext",
|
|
47
|
+
"AsyncChannel",
|
|
48
|
+
"AsyncClientStream",
|
|
49
|
+
"Channel",
|
|
50
|
+
"ClientStream",
|
|
51
|
+
"Endpoint",
|
|
52
|
+
"Headers",
|
|
53
|
+
"KIND_BIDI",
|
|
54
|
+
"KIND_CLIENT_STREAMING",
|
|
55
|
+
"KIND_SERVER_STREAMING",
|
|
56
|
+
"KIND_UNARY",
|
|
57
|
+
"Method",
|
|
58
|
+
"RequestStream",
|
|
59
|
+
"Server",
|
|
60
|
+
"ServiceDesc",
|
|
61
|
+
"StatusError",
|
|
62
|
+
"Transport",
|
|
63
|
+
"decode_frame",
|
|
64
|
+
"encode_frame",
|
|
65
|
+
"SidecarTransport",
|
|
66
|
+
"Http2Transport",
|
|
67
|
+
"memory_transport_pair",
|
|
68
|
+
"parse_descriptor_set",
|
|
69
|
+
"register_dynamic_service",
|
|
70
|
+
"set_tracer",
|
|
71
|
+
"set_trace_filter",
|
|
72
|
+
"status_name",
|
|
73
|
+
"to_status",
|
|
74
|
+
]
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# @generated by grpc-dc codegen. DO NOT EDIT.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Channel: the client side of one Endpoint."""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
|
|
7
|
+
from .codec import (
|
|
8
|
+
StatusError,
|
|
9
|
+
encode_frame,
|
|
10
|
+
encode_timeout,
|
|
11
|
+
format_traceparent,
|
|
12
|
+
get_tracer,
|
|
13
|
+
headers_frame,
|
|
14
|
+
parse_traceparent,
|
|
15
|
+
status_name,
|
|
16
|
+
trace_end,
|
|
17
|
+
trace_start,
|
|
18
|
+
)
|
|
19
|
+
from .endpoint import (
|
|
20
|
+
EVENT_DATA,
|
|
21
|
+
EVENT_HEADERS,
|
|
22
|
+
EVENT_RST,
|
|
23
|
+
EVENT_TRAILERS,
|
|
24
|
+
TIMEOUT,
|
|
25
|
+
Endpoint,
|
|
26
|
+
_monotonic,
|
|
27
|
+
)
|
|
28
|
+
from .wire_gen import (
|
|
29
|
+
CODE_DEADLINE_EXCEEDED,
|
|
30
|
+
CODE_INTERNAL,
|
|
31
|
+
CODE_UNAVAILABLE,
|
|
32
|
+
FRAME_DATA,
|
|
33
|
+
FRAME_HALF_CLOSE,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Channel:
|
|
38
|
+
"""Client side; allocates odd stream ids on a fresh Endpoint."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, transport=None, endpoint=None):
|
|
41
|
+
if endpoint is not None:
|
|
42
|
+
self.endpoint = endpoint
|
|
43
|
+
else:
|
|
44
|
+
self.endpoint = Endpoint(transport, creator=True)
|
|
45
|
+
self.endpoint.start()
|
|
46
|
+
|
|
47
|
+
def _open(self, path, metadata, timeout=None):
|
|
48
|
+
deadline = None
|
|
49
|
+
trace_ctx = None
|
|
50
|
+
if timeout is not None or get_tracer() is not None:
|
|
51
|
+
merged = dict(metadata) if metadata else {}
|
|
52
|
+
if timeout is not None:
|
|
53
|
+
deadline = _monotonic() + timeout
|
|
54
|
+
merged["grpc-timeout"] = encode_timeout(timeout)
|
|
55
|
+
if get_tracer() is not None:
|
|
56
|
+
parent = parse_traceparent(merged.get("traceparent"))
|
|
57
|
+
trace_ctx = trace_start("client", path, parent)
|
|
58
|
+
if trace_ctx is not None:
|
|
59
|
+
merged["traceparent"] = format_traceparent(
|
|
60
|
+
trace_ctx["trace_id"], trace_ctx["span_id"]
|
|
61
|
+
)
|
|
62
|
+
metadata = merged
|
|
63
|
+
stream_id, stream_input = self.endpoint.open_stream()
|
|
64
|
+
try:
|
|
65
|
+
self.endpoint.send_frame(headers_frame(stream_id, path, metadata))
|
|
66
|
+
except Exception:
|
|
67
|
+
self.endpoint.close_stream(stream_id)
|
|
68
|
+
trace_end("client", path, trace_ctx, "UNAVAILABLE")
|
|
69
|
+
raise
|
|
70
|
+
stream = ClientStream(self.endpoint, stream_id, stream_input)
|
|
71
|
+
stream.deadline = deadline
|
|
72
|
+
stream.trace_ctx = trace_ctx
|
|
73
|
+
stream.trace_path = path
|
|
74
|
+
return stream
|
|
75
|
+
|
|
76
|
+
def _send_message(self, stream, msg):
|
|
77
|
+
self.endpoint.send_frame(
|
|
78
|
+
encode_frame(FRAME_DATA, stream.stream_id, msg.marshal_dc())
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def _half_close(self, stream):
|
|
82
|
+
self.endpoint.send_frame(encode_frame(FRAME_HALF_CLOSE, stream.stream_id, b""))
|
|
83
|
+
|
|
84
|
+
def _send_all(self, stream, reqs):
|
|
85
|
+
for req in reqs:
|
|
86
|
+
self._send_message(stream, req)
|
|
87
|
+
self._half_close(stream)
|
|
88
|
+
|
|
89
|
+
def cancel(self, stream):
|
|
90
|
+
"""Abort an open ClientStream (sends RST)."""
|
|
91
|
+
self.endpoint.cancel_stream(stream.stream_id)
|
|
92
|
+
|
|
93
|
+
def unary(self, path, req, resp, metadata=None, timeout=None):
|
|
94
|
+
"""Run a unary call, decoding the single response into resp.
|
|
95
|
+
|
|
96
|
+
timeout (seconds) sets a deadline: it is propagated as
|
|
97
|
+
grpc-timeout metadata and enforced locally — on expiry the
|
|
98
|
+
call is aborted with RST and DEADLINE_EXCEEDED is raised.
|
|
99
|
+
"""
|
|
100
|
+
return self._unary(path, req, resp, metadata, timeout)
|
|
101
|
+
|
|
102
|
+
def _unary(self, path, req, resp, metadata, timeout=None):
|
|
103
|
+
stream = self._open(path, metadata, timeout)
|
|
104
|
+
try:
|
|
105
|
+
self._send_message(stream, req)
|
|
106
|
+
self._half_close(stream)
|
|
107
|
+
got = False
|
|
108
|
+
while True:
|
|
109
|
+
try:
|
|
110
|
+
stream.recv(resp)
|
|
111
|
+
except EOFError:
|
|
112
|
+
if not got:
|
|
113
|
+
raise StatusError(
|
|
114
|
+
CODE_INTERNAL, "call closed with no response"
|
|
115
|
+
)
|
|
116
|
+
return resp
|
|
117
|
+
if got:
|
|
118
|
+
raise StatusError(
|
|
119
|
+
CODE_INTERNAL, "unary call received more than one response"
|
|
120
|
+
)
|
|
121
|
+
got = True
|
|
122
|
+
finally:
|
|
123
|
+
self.endpoint.close_stream(stream.stream_id)
|
|
124
|
+
|
|
125
|
+
def server_stream(self, path, req, metadata=None, timeout=None):
|
|
126
|
+
"""Start a server-streaming call; returns a ClientStream."""
|
|
127
|
+
stream = self._open(path, metadata, timeout)
|
|
128
|
+
self._send_message(stream, req)
|
|
129
|
+
self._half_close(stream)
|
|
130
|
+
return stream
|
|
131
|
+
|
|
132
|
+
def unary_stream(self, path, req, metadata=None, timeout=None):
|
|
133
|
+
"""Start a unary call; returns the ClientStream so
|
|
134
|
+
callers can inspect .headers/.trailers around recv."""
|
|
135
|
+
stream = self._open(path, metadata, timeout)
|
|
136
|
+
self._send_message(stream, req)
|
|
137
|
+
self._half_close(stream)
|
|
138
|
+
return stream
|
|
139
|
+
|
|
140
|
+
def client_stream(self, path, reqs, resp, metadata=None, timeout=None):
|
|
141
|
+
"""Send all requests, then decode the one response into resp."""
|
|
142
|
+
stream = self._open(path, metadata, timeout)
|
|
143
|
+
try:
|
|
144
|
+
self._send_all(stream, reqs)
|
|
145
|
+
got = False
|
|
146
|
+
while True:
|
|
147
|
+
try:
|
|
148
|
+
stream.recv(resp)
|
|
149
|
+
except EOFError:
|
|
150
|
+
if not got:
|
|
151
|
+
raise StatusError(
|
|
152
|
+
CODE_INTERNAL, "call closed with no response"
|
|
153
|
+
)
|
|
154
|
+
return resp
|
|
155
|
+
if got:
|
|
156
|
+
raise StatusError(
|
|
157
|
+
CODE_INTERNAL,
|
|
158
|
+
"client-streaming call received more than one response",
|
|
159
|
+
)
|
|
160
|
+
got = True
|
|
161
|
+
finally:
|
|
162
|
+
self.endpoint.close_stream(stream.stream_id)
|
|
163
|
+
|
|
164
|
+
def bidi(self, path, reqs, metadata=None, timeout=None):
|
|
165
|
+
"""Start a bidi call with all requests sent up front."""
|
|
166
|
+
stream = self._open(path, metadata, timeout)
|
|
167
|
+
self._send_all(stream, reqs)
|
|
168
|
+
return stream
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ClientStream:
|
|
172
|
+
"""Receives response messages of one call."""
|
|
173
|
+
|
|
174
|
+
def __init__(self, endpoint, stream_id, stream_input):
|
|
175
|
+
self._endpoint = endpoint
|
|
176
|
+
self.stream_id = stream_id
|
|
177
|
+
self._input = stream_input
|
|
178
|
+
self.headers = None
|
|
179
|
+
self.trailers = None
|
|
180
|
+
self.deadline = None
|
|
181
|
+
self.trace_ctx = None
|
|
182
|
+
self.trace_path = None
|
|
183
|
+
self._trace_ended = False
|
|
184
|
+
self._terminated = False
|
|
185
|
+
|
|
186
|
+
def _end_trace(self, status):
|
|
187
|
+
if not self._trace_ended:
|
|
188
|
+
self._trace_ended = True
|
|
189
|
+
trace_end("client", self.trace_path, self.trace_ctx, status)
|
|
190
|
+
|
|
191
|
+
def cancel(self):
|
|
192
|
+
"""Abort the call (RST) unless it already terminated."""
|
|
193
|
+
if not self._terminated:
|
|
194
|
+
self._terminated = True
|
|
195
|
+
self._endpoint.cancel_stream(self.stream_id)
|
|
196
|
+
|
|
197
|
+
def _fail_deadline(self):
|
|
198
|
+
self._end_trace("DEADLINE_EXCEEDED")
|
|
199
|
+
self._terminated = True
|
|
200
|
+
self._endpoint.cancel_stream(self.stream_id)
|
|
201
|
+
raise StatusError(CODE_DEADLINE_EXCEEDED, "deadline exceeded")
|
|
202
|
+
|
|
203
|
+
def recv(self, resp):
|
|
204
|
+
"""Decode the next response into resp.
|
|
205
|
+
|
|
206
|
+
Raises EOFError when the call completed OK; StatusError
|
|
207
|
+
reports the trailers status (or DEADLINE_EXCEEDED when
|
|
208
|
+
the stream's deadline passed waiting for a response).
|
|
209
|
+
"""
|
|
210
|
+
while True:
|
|
211
|
+
wait = None
|
|
212
|
+
if self.deadline is not None:
|
|
213
|
+
wait = self.deadline - _monotonic()
|
|
214
|
+
if wait <= 0:
|
|
215
|
+
self._fail_deadline()
|
|
216
|
+
event = self._input.get(timeout=wait)
|
|
217
|
+
if event is TIMEOUT:
|
|
218
|
+
self._fail_deadline()
|
|
219
|
+
if event is None:
|
|
220
|
+
self._terminated = True
|
|
221
|
+
raise StatusError(CODE_UNAVAILABLE, "data channel closed")
|
|
222
|
+
if event.kind == EVENT_HEADERS:
|
|
223
|
+
self.headers = event.headers
|
|
224
|
+
elif event.kind == EVENT_DATA:
|
|
225
|
+
resp.unmarshal_dc(event.payload)
|
|
226
|
+
return resp
|
|
227
|
+
elif event.kind == EVENT_TRAILERS:
|
|
228
|
+
self._terminated = True
|
|
229
|
+
self.trailers = event.status.trailers
|
|
230
|
+
self._end_trace(status_name(event.status.code))
|
|
231
|
+
if event.status.code != 0:
|
|
232
|
+
raise event.status
|
|
233
|
+
raise EOFError("call completed")
|
|
234
|
+
elif event.kind == EVENT_RST:
|
|
235
|
+
self._terminated = True
|
|
236
|
+
self.trailers = event.status.trailers
|
|
237
|
+
self._end_trace(status_name(event.status.code))
|
|
238
|
+
raise event.status
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class AsyncClientStream:
|
|
242
|
+
"""Asyncio receiving stream wrapping a ClientStream.
|
|
243
|
+
|
|
244
|
+
The blocking wait runs on the event loop's default executor,
|
|
245
|
+
so awaiting never stalls the loop. Cancelling the awaiting
|
|
246
|
+
task aborts the stream with RST (CODE_CANCELLED).
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
def __init__(self, stream):
|
|
250
|
+
self._stream = stream
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def stream_id(self):
|
|
254
|
+
return self._stream.stream_id
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def headers(self):
|
|
258
|
+
return self._stream.headers
|
|
259
|
+
|
|
260
|
+
async def recv(self, resp):
|
|
261
|
+
loop = asyncio.get_running_loop()
|
|
262
|
+
# shield: task cancellation surfaces immediately (the
|
|
263
|
+
# executor thread cannot be cancelled); we then abort
|
|
264
|
+
# the stream, which wakes that thread with RST.
|
|
265
|
+
future = loop.run_in_executor(None, self._stream.recv, resp)
|
|
266
|
+
try:
|
|
267
|
+
return await asyncio.shield(future)
|
|
268
|
+
except asyncio.CancelledError:
|
|
269
|
+
self.cancel()
|
|
270
|
+
raise
|
|
271
|
+
|
|
272
|
+
def cancel(self):
|
|
273
|
+
self._stream.cancel()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class AsyncChannel:
|
|
277
|
+
"""Asyncio client facade over Channel (mirrors grpc.aio shape).
|
|
278
|
+
|
|
279
|
+
Unary and client-streaming calls are awaited on executor
|
|
280
|
+
threads; server-streaming and bidi calls return an
|
|
281
|
+
AsyncClientStream. Construct directly over a transport or
|
|
282
|
+
wrap an existing sync Channel to share its Endpoint.
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
def __init__(self, channel=None, transport=None, endpoint=None):
|
|
286
|
+
if isinstance(channel, Channel):
|
|
287
|
+
self._channel = channel
|
|
288
|
+
elif channel is not None and transport is None and endpoint is None:
|
|
289
|
+
self._channel = Channel(channel) # a bare transport
|
|
290
|
+
else:
|
|
291
|
+
self._channel = Channel(transport, endpoint)
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def channel(self):
|
|
295
|
+
return self._channel
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def endpoint(self):
|
|
299
|
+
return self._channel.endpoint
|
|
300
|
+
|
|
301
|
+
async def unary(self, path, req, resp, metadata=None, timeout=None):
|
|
302
|
+
loop = asyncio.get_running_loop()
|
|
303
|
+
return await loop.run_in_executor(
|
|
304
|
+
None, lambda: self._channel.unary(path, req, resp, metadata, timeout))
|
|
305
|
+
|
|
306
|
+
async def client_stream(self, path, reqs, resp, metadata=None, timeout=None):
|
|
307
|
+
loop = asyncio.get_running_loop()
|
|
308
|
+
return await loop.run_in_executor(
|
|
309
|
+
None,
|
|
310
|
+
lambda: self._channel.client_stream(path, reqs, resp, metadata, timeout))
|
|
311
|
+
|
|
312
|
+
async def server_stream(self, path, req, metadata=None, timeout=None):
|
|
313
|
+
loop = asyncio.get_running_loop()
|
|
314
|
+
stream = await loop.run_in_executor(
|
|
315
|
+
None, lambda: self._channel.server_stream(path, req, metadata, timeout))
|
|
316
|
+
return AsyncClientStream(stream)
|
|
317
|
+
|
|
318
|
+
async def bidi(self, path, reqs, metadata=None, timeout=None):
|
|
319
|
+
loop = asyncio.get_running_loop()
|
|
320
|
+
stream = await loop.run_in_executor(
|
|
321
|
+
None, lambda: self._channel.bidi(path, reqs, metadata, timeout))
|
|
322
|
+
return AsyncClientStream(stream)
|