openframe-protocol-grpc 0.2.0__tar.gz → 0.4.0__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.
- {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/PKG-INFO +28 -6
- {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/README.md +26 -5
- {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/openframe/protocol/grpc/__init__.py +10 -2
- {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/openframe/protocol/grpc/generic.proto +4 -0
- openframe_protocol_grpc-0.4.0/openframe/protocol/grpc/generic_pb2.py +40 -0
- openframe_protocol_grpc-0.4.0/openframe/protocol/grpc/generic_pb2.pyi +17 -0
- openframe_protocol_grpc-0.4.0/openframe/protocol/grpc/generic_pb2_grpc.py +143 -0
- openframe_protocol_grpc-0.4.0/openframe/protocol/grpc/server.py +545 -0
- {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/pyproject.toml +7 -1
- openframe_protocol_grpc-0.4.0/tests/test_bind_service_input_model.py +134 -0
- openframe_protocol_grpc-0.4.0/tests/test_bind_service_stream.py +187 -0
- openframe_protocol_grpc-0.4.0/tests/test_grpc_session_conformance.py +211 -0
- openframe_protocol_grpc-0.2.0/openframe/protocol/grpc/generic_pb2.py +0 -40
- openframe_protocol_grpc-0.2.0/openframe/protocol/grpc/generic_pb2_grpc.py +0 -97
- openframe_protocol_grpc-0.2.0/openframe/protocol/grpc/server.py +0 -275
- {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/.gitignore +0 -0
- {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/openframe/protocol/grpc/client.py +0 -0
- {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/tests/conftest.py +0 -0
- {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/tests/test_client.py +0 -0
- {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/tests/test_conformance.py +0 -0
- {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/tests/test_create_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: openframe-protocol-grpc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: OpenFrame Microservice Suite - gRPC server and client binding for openframe.core.inbound/remote.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Furious-Meteors/openframe-protocol
|
|
6
6
|
Project-URL: Documentation, https://furious-meteors.github.io/openframe-protocol/
|
|
@@ -35,6 +35,7 @@ Requires-Dist: pytest-mock>=3.14; extra == 'dev'
|
|
|
35
35
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
36
36
|
Provides-Extra: server
|
|
37
37
|
Requires-Dist: grpcio>=1.60; extra == 'server'
|
|
38
|
+
Requires-Dist: pydantic>=2.0; extra == 'server'
|
|
38
39
|
Description-Content-Type: text/markdown
|
|
39
40
|
|
|
40
41
|
# openframe-protocol-grpc
|
|
@@ -87,10 +88,31 @@ get_item = GrpcRemoteQueryClient(target="items-service:50051", route="get_item")
|
|
|
87
88
|
item = await get_item.execute("abc-123")
|
|
88
89
|
```
|
|
89
90
|
|
|
91
|
+
## Streaming — `bind_service_stream`
|
|
92
|
+
|
|
93
|
+
`GenericService` also has `CallStream` (`stream GenericRequest` → `stream GenericResponse`), the
|
|
94
|
+
bidirectional-streaming counterpart to `Call` — the `SessionHandler` (ADR-008, `openframe-core`)
|
|
95
|
+
binding for long-lived connections, same route-multiplexing trick (`x-openframe-route` metadata).
|
|
96
|
+
Needed no new dependency — `grpcio` already supports streaming natively; this package simply hadn't
|
|
97
|
+
used it until now:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from openframe.protocol.grpc import bind_service_stream
|
|
101
|
+
|
|
102
|
+
bind_service_stream(server, "artifact_updates", handler_factory=lambda: ArtifactUpdates())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`on_open` runs — and can reject — before the method's first `yield`, mirroring `bind_service`'s own
|
|
106
|
+
reject path exactly. A real race not present in `-http`'s own session transports (SSE, WebSocket):
|
|
107
|
+
the inbound `request_iterator` and the method's own outbound `yield` loop are two
|
|
108
|
+
independently-scheduled `grpc.aio` signals for the same disconnect, confirmed by spiking to resolve
|
|
109
|
+
consistently (not randomly) in one specific order — see `openframe.protocol.grpc.server`'s own
|
|
110
|
+
module docstring for the full finding and fix (polling `context.cancelled()` for a short window
|
|
111
|
+
rather than trusting the inbound side ending on its own).
|
|
112
|
+
|
|
90
113
|
## Status
|
|
91
114
|
|
|
92
|
-
Experimental, M4 of the project's own build plan.
|
|
93
|
-
`
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
not just adding a second transport for its own sake.
|
|
115
|
+
Experimental, M4 of the project's own build plan. `TransportConformanceTests`,
|
|
116
|
+
`RemoteClientConformanceTests`, and `SessionConformanceTests` (all `openframe-protocol-base`) pass
|
|
117
|
+
against this package for real — the point of building it: proving the architecture generalizes past
|
|
118
|
+
HTTP, not just adding a second transport for its own sake.
|
|
@@ -48,10 +48,31 @@ get_item = GrpcRemoteQueryClient(target="items-service:50051", route="get_item")
|
|
|
48
48
|
item = await get_item.execute("abc-123")
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
## Streaming — `bind_service_stream`
|
|
52
|
+
|
|
53
|
+
`GenericService` also has `CallStream` (`stream GenericRequest` → `stream GenericResponse`), the
|
|
54
|
+
bidirectional-streaming counterpart to `Call` — the `SessionHandler` (ADR-008, `openframe-core`)
|
|
55
|
+
binding for long-lived connections, same route-multiplexing trick (`x-openframe-route` metadata).
|
|
56
|
+
Needed no new dependency — `grpcio` already supports streaming natively; this package simply hadn't
|
|
57
|
+
used it until now:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from openframe.protocol.grpc import bind_service_stream
|
|
61
|
+
|
|
62
|
+
bind_service_stream(server, "artifact_updates", handler_factory=lambda: ArtifactUpdates())
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`on_open` runs — and can reject — before the method's first `yield`, mirroring `bind_service`'s own
|
|
66
|
+
reject path exactly. A real race not present in `-http`'s own session transports (SSE, WebSocket):
|
|
67
|
+
the inbound `request_iterator` and the method's own outbound `yield` loop are two
|
|
68
|
+
independently-scheduled `grpc.aio` signals for the same disconnect, confirmed by spiking to resolve
|
|
69
|
+
consistently (not randomly) in one specific order — see `openframe.protocol.grpc.server`'s own
|
|
70
|
+
module docstring for the full finding and fix (polling `context.cancelled()` for a short window
|
|
71
|
+
rather than trusting the inbound side ending on its own).
|
|
72
|
+
|
|
51
73
|
## Status
|
|
52
74
|
|
|
53
|
-
Experimental, M4 of the project's own build plan.
|
|
54
|
-
`
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
not just adding a second transport for its own sake.
|
|
75
|
+
Experimental, M4 of the project's own build plan. `TransportConformanceTests`,
|
|
76
|
+
`RemoteClientConformanceTests`, and `SessionConformanceTests` (all `openframe-protocol-base`) pass
|
|
77
|
+
against this package for real — the point of building it: proving the architecture generalizes past
|
|
78
|
+
HTTP, not just adding a second transport for its own sake.
|
{openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/openframe/protocol/grpc/__init__.py
RENAMED
|
@@ -39,10 +39,18 @@ from openframe.protocol.grpc import generic_pb2, generic_pb2_grpc
|
|
|
39
39
|
|
|
40
40
|
if TYPE_CHECKING:
|
|
41
41
|
from openframe.protocol.grpc.client import GrpcRemoteCommandClient, GrpcRemoteQueryClient
|
|
42
|
-
from openframe.protocol.grpc.server import
|
|
42
|
+
from openframe.protocol.grpc.server import (
|
|
43
|
+
ROUTE_METADATA_KEY,
|
|
44
|
+
GrpcSession,
|
|
45
|
+
bind_service,
|
|
46
|
+
bind_service_stream,
|
|
47
|
+
create_server,
|
|
48
|
+
)
|
|
43
49
|
|
|
44
50
|
__all__ = [
|
|
45
51
|
"bind_service",
|
|
52
|
+
"bind_service_stream",
|
|
53
|
+
"GrpcSession",
|
|
46
54
|
"ROUTE_METADATA_KEY",
|
|
47
55
|
"create_server",
|
|
48
56
|
"GrpcRemoteQueryClient",
|
|
@@ -51,7 +59,7 @@ __all__ = [
|
|
|
51
59
|
"generic_pb2_grpc",
|
|
52
60
|
]
|
|
53
61
|
|
|
54
|
-
_FROM_SERVER = {"bind_service", "ROUTE_METADATA_KEY", "create_server"}
|
|
62
|
+
_FROM_SERVER = {"bind_service", "bind_service_stream", "GrpcSession", "ROUTE_METADATA_KEY", "create_server"}
|
|
55
63
|
_FROM_CLIENT = {"GrpcRemoteQueryClient", "GrpcRemoteCommandClient"}
|
|
56
64
|
|
|
57
65
|
|
|
@@ -15,4 +15,8 @@ message GenericResponse {
|
|
|
15
15
|
|
|
16
16
|
service GenericService {
|
|
17
17
|
rpc Call(GenericRequest) returns (GenericResponse);
|
|
18
|
+
// Bidirectional streaming counterpart to Call, same route-multiplexing
|
|
19
|
+
// trick (x-openframe-route metadata) — the SessionHandler (ADR-008)
|
|
20
|
+
// binding, not a per-route message type.
|
|
21
|
+
rpc CallStream(stream GenericRequest) returns (stream GenericResponse);
|
|
18
22
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
+
# NO CHECKED-IN PROTOBUF GENCODE
|
|
4
|
+
# source: openframe/protocol/grpc/generic.proto
|
|
5
|
+
# Protobuf Python Version: 7.35.1
|
|
6
|
+
"""Generated protocol buffer code."""
|
|
7
|
+
from google.protobuf import descriptor as _descriptor
|
|
8
|
+
from google.protobuf import descriptor_pool as _descriptor_pool
|
|
9
|
+
from google.protobuf import runtime_version as _runtime_version
|
|
10
|
+
from google.protobuf import symbol_database as _symbol_database
|
|
11
|
+
from google.protobuf.internal import builder as _builder
|
|
12
|
+
_runtime_version.ValidateProtobufRuntimeVersion(
|
|
13
|
+
_runtime_version.Domain.PUBLIC,
|
|
14
|
+
7,
|
|
15
|
+
35,
|
|
16
|
+
1,
|
|
17
|
+
'',
|
|
18
|
+
'openframe/protocol/grpc/generic.proto'
|
|
19
|
+
)
|
|
20
|
+
# @@protoc_insertion_point(imports)
|
|
21
|
+
|
|
22
|
+
_sym_db = _symbol_database.Default()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n%openframe/protocol/grpc/generic.proto\x12\x17openframe.protocol.grpc\"&\n\x0eGenericRequest\x12\x14\n\x0cjson_payload\x18\x01 \x01(\t\"\'\n\x0fGenericResponse\x12\x14\n\x0cjson_payload\x18\x01 \x01(\t2\xd0\x01\n\x0eGenericService\x12Y\n\x04\x43\x61ll\x12\'.openframe.protocol.grpc.GenericRequest\x1a(.openframe.protocol.grpc.GenericResponse\x12\x63\n\nCallStream\x12\'.openframe.protocol.grpc.GenericRequest\x1a(.openframe.protocol.grpc.GenericResponse(\x01\x30\x01\x62\x06proto3')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'openframe.protocol.grpc.generic_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
DESCRIPTOR._loaded_options = None
|
|
34
|
+
_globals['_GENERICREQUEST']._serialized_start=66
|
|
35
|
+
_globals['_GENERICREQUEST']._serialized_end=104
|
|
36
|
+
_globals['_GENERICRESPONSE']._serialized_start=106
|
|
37
|
+
_globals['_GENERICRESPONSE']._serialized_end=145
|
|
38
|
+
_globals['_GENERICSERVICE']._serialized_start=148
|
|
39
|
+
_globals['_GENERICSERVICE']._serialized_end=356
|
|
40
|
+
# @@protoc_insertion_point(module_scope)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from google.protobuf import descriptor as _descriptor
|
|
2
|
+
from google.protobuf import message as _message
|
|
3
|
+
from typing import ClassVar as _ClassVar, Optional as _Optional
|
|
4
|
+
|
|
5
|
+
DESCRIPTOR: _descriptor.FileDescriptor
|
|
6
|
+
|
|
7
|
+
class GenericRequest(_message.Message):
|
|
8
|
+
__slots__ = ("json_payload",)
|
|
9
|
+
JSON_PAYLOAD_FIELD_NUMBER: _ClassVar[int]
|
|
10
|
+
json_payload: str
|
|
11
|
+
def __init__(self, json_payload: _Optional[str] = ...) -> None: ...
|
|
12
|
+
|
|
13
|
+
class GenericResponse(_message.Message):
|
|
14
|
+
__slots__ = ("json_payload",)
|
|
15
|
+
JSON_PAYLOAD_FIELD_NUMBER: _ClassVar[int]
|
|
16
|
+
json_payload: str
|
|
17
|
+
def __init__(self, json_payload: _Optional[str] = ...) -> None: ...
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
|
|
2
|
+
"""Client and server classes corresponding to protobuf-defined services."""
|
|
3
|
+
import grpc
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from openframe.protocol.grpc import generic_pb2 as openframe_dot_protocol_dot_grpc_dot_generic__pb2
|
|
7
|
+
|
|
8
|
+
GRPC_GENERATED_VERSION = '1.84.0'
|
|
9
|
+
GRPC_VERSION = grpc.__version__
|
|
10
|
+
_version_not_supported = False
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from grpc._utilities import first_version_is_lower
|
|
14
|
+
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
|
|
15
|
+
except ImportError:
|
|
16
|
+
_version_not_supported = True
|
|
17
|
+
|
|
18
|
+
if _version_not_supported:
|
|
19
|
+
raise RuntimeError(
|
|
20
|
+
f'The grpc package installed is at version {GRPC_VERSION},'
|
|
21
|
+
+ ' but the generated code in openframe/protocol/grpc/generic_pb2_grpc.py depends on'
|
|
22
|
+
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
|
|
23
|
+
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
|
|
24
|
+
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GenericServiceStub:
|
|
29
|
+
"""Missing associated documentation comment in .proto file."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, channel):
|
|
32
|
+
"""Constructor.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
channel: A grpc.Channel.
|
|
36
|
+
"""
|
|
37
|
+
self.Call = channel.unary_unary(
|
|
38
|
+
'/openframe.protocol.grpc.GenericService/Call',
|
|
39
|
+
request_serializer=openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericRequest.SerializeToString,
|
|
40
|
+
response_deserializer=openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericResponse.FromString,
|
|
41
|
+
_registered_method=True)
|
|
42
|
+
self.CallStream = channel.stream_stream(
|
|
43
|
+
'/openframe.protocol.grpc.GenericService/CallStream',
|
|
44
|
+
request_serializer=openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericRequest.SerializeToString,
|
|
45
|
+
response_deserializer=openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericResponse.FromString,
|
|
46
|
+
_registered_method=True)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class GenericServiceServicer:
|
|
50
|
+
"""Missing associated documentation comment in .proto file."""
|
|
51
|
+
|
|
52
|
+
def Call(self, request, context):
|
|
53
|
+
"""Missing associated documentation comment in .proto file."""
|
|
54
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
55
|
+
context.set_details('Method not implemented!')
|
|
56
|
+
raise NotImplementedError('Method not implemented!')
|
|
57
|
+
|
|
58
|
+
def CallStream(self, request_iterator, context):
|
|
59
|
+
"""Bidirectional streaming counterpart to Call, same route-multiplexing
|
|
60
|
+
trick (x-openframe-route metadata) — the SessionHandler (ADR-008)
|
|
61
|
+
binding, not a per-route message type.
|
|
62
|
+
"""
|
|
63
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
64
|
+
context.set_details('Method not implemented!')
|
|
65
|
+
raise NotImplementedError('Method not implemented!')
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def add_GenericServiceServicer_to_server(servicer, server):
|
|
69
|
+
rpc_method_handlers = {
|
|
70
|
+
'Call': grpc.unary_unary_rpc_method_handler(
|
|
71
|
+
servicer.Call,
|
|
72
|
+
request_deserializer=openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericRequest.FromString,
|
|
73
|
+
response_serializer=openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericResponse.SerializeToString,
|
|
74
|
+
),
|
|
75
|
+
'CallStream': grpc.stream_stream_rpc_method_handler(
|
|
76
|
+
servicer.CallStream,
|
|
77
|
+
request_deserializer=openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericRequest.FromString,
|
|
78
|
+
response_serializer=openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericResponse.SerializeToString,
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
generic_handler = grpc.method_handlers_generic_handler(
|
|
82
|
+
'openframe.protocol.grpc.GenericService', rpc_method_handlers)
|
|
83
|
+
server.add_generic_rpc_handlers((generic_handler,))
|
|
84
|
+
server.add_registered_method_handlers('openframe.protocol.grpc.GenericService', rpc_method_handlers)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# This class is part of an EXPERIMENTAL API.
|
|
88
|
+
class GenericService:
|
|
89
|
+
"""Missing associated documentation comment in .proto file."""
|
|
90
|
+
|
|
91
|
+
@staticmethod
|
|
92
|
+
def Call(request,
|
|
93
|
+
target,
|
|
94
|
+
options=(),
|
|
95
|
+
channel_credentials=None,
|
|
96
|
+
call_credentials=None,
|
|
97
|
+
insecure=False,
|
|
98
|
+
compression=None,
|
|
99
|
+
wait_for_ready=None,
|
|
100
|
+
timeout=None,
|
|
101
|
+
metadata=None):
|
|
102
|
+
return grpc.experimental.unary_unary(
|
|
103
|
+
request,
|
|
104
|
+
target,
|
|
105
|
+
'/openframe.protocol.grpc.GenericService/Call',
|
|
106
|
+
openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericRequest.SerializeToString,
|
|
107
|
+
openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericResponse.FromString,
|
|
108
|
+
options,
|
|
109
|
+
channel_credentials,
|
|
110
|
+
insecure,
|
|
111
|
+
call_credentials,
|
|
112
|
+
compression,
|
|
113
|
+
wait_for_ready,
|
|
114
|
+
timeout,
|
|
115
|
+
metadata,
|
|
116
|
+
_registered_method=True)
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def CallStream(request_iterator,
|
|
120
|
+
target,
|
|
121
|
+
options=(),
|
|
122
|
+
channel_credentials=None,
|
|
123
|
+
call_credentials=None,
|
|
124
|
+
insecure=False,
|
|
125
|
+
compression=None,
|
|
126
|
+
wait_for_ready=None,
|
|
127
|
+
timeout=None,
|
|
128
|
+
metadata=None):
|
|
129
|
+
return grpc.experimental.stream_stream(
|
|
130
|
+
request_iterator,
|
|
131
|
+
target,
|
|
132
|
+
'/openframe.protocol.grpc.GenericService/CallStream',
|
|
133
|
+
openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericRequest.SerializeToString,
|
|
134
|
+
openframe_dot_protocol_dot_grpc_dot_generic__pb2.GenericResponse.FromString,
|
|
135
|
+
options,
|
|
136
|
+
channel_credentials,
|
|
137
|
+
insecure,
|
|
138
|
+
call_credentials,
|
|
139
|
+
compression,
|
|
140
|
+
wait_for_ready,
|
|
141
|
+
timeout,
|
|
142
|
+
metadata,
|
|
143
|
+
_registered_method=True)
|