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.
Files changed (21) hide show
  1. {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/PKG-INFO +28 -6
  2. {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/README.md +26 -5
  3. {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/openframe/protocol/grpc/__init__.py +10 -2
  4. {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/openframe/protocol/grpc/generic.proto +4 -0
  5. openframe_protocol_grpc-0.4.0/openframe/protocol/grpc/generic_pb2.py +40 -0
  6. openframe_protocol_grpc-0.4.0/openframe/protocol/grpc/generic_pb2.pyi +17 -0
  7. openframe_protocol_grpc-0.4.0/openframe/protocol/grpc/generic_pb2_grpc.py +143 -0
  8. openframe_protocol_grpc-0.4.0/openframe/protocol/grpc/server.py +545 -0
  9. {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/pyproject.toml +7 -1
  10. openframe_protocol_grpc-0.4.0/tests/test_bind_service_input_model.py +134 -0
  11. openframe_protocol_grpc-0.4.0/tests/test_bind_service_stream.py +187 -0
  12. openframe_protocol_grpc-0.4.0/tests/test_grpc_session_conformance.py +211 -0
  13. openframe_protocol_grpc-0.2.0/openframe/protocol/grpc/generic_pb2.py +0 -40
  14. openframe_protocol_grpc-0.2.0/openframe/protocol/grpc/generic_pb2_grpc.py +0 -97
  15. openframe_protocol_grpc-0.2.0/openframe/protocol/grpc/server.py +0 -275
  16. {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/.gitignore +0 -0
  17. {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/openframe/protocol/grpc/client.py +0 -0
  18. {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/tests/conftest.py +0 -0
  19. {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/tests/test_client.py +0 -0
  20. {openframe_protocol_grpc-0.2.0 → openframe_protocol_grpc-0.4.0}/tests/test_conformance.py +0 -0
  21. {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.2.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. Both
93
- `TransportConformanceTests` and `RemoteClientConformanceTests`
94
- (`openframe-protocol-base`) pass against this package for real — the
95
- point of building it: proving the architecture generalizes past HTTP,
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. Both
54
- `TransportConformanceTests` and `RemoteClientConformanceTests`
55
- (`openframe-protocol-base`) pass against this package for real — the
56
- point of building it: proving the architecture generalizes past HTTP,
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.
@@ -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 ROUTE_METADATA_KEY, bind_service, create_server
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)