pydantic-rpc 0.5.0__tar.gz → 0.6.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.
- pydantic_rpc-0.5.0/README.md → pydantic_rpc-0.6.0/PKG-INFO +24 -3
- pydantic_rpc-0.5.0/PKG-INFO → pydantic_rpc-0.6.0/README.md +9 -18
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/README.md +1 -1
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeter_connecpy.py +24 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/pyproject.toml +9 -2
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/src/pydantic_rpc/core.py +108 -1
- pydantic_rpc-0.6.0/tests/asyncechoservice.proto +33 -0
- pydantic_rpc-0.6.0/tests/asyncechoservice_connecpy.py +101 -0
- pydantic_rpc-0.6.0/tests/asyncechoservice_pb2.py +40 -0
- pydantic_rpc-0.6.0/tests/asyncechoservice_pb2.pyi +17 -0
- pydantic_rpc-0.6.0/tests/asyncechoservice_pb2_grpc.py +110 -0
- pydantic_rpc-0.6.0/tests/echoservice.proto +33 -0
- pydantic_rpc-0.6.0/tests/echoservice_connecpy.py +101 -0
- pydantic_rpc-0.6.0/tests/echoservice_pb2.py +40 -0
- pydantic_rpc-0.6.0/tests/echoservice_pb2.pyi +17 -0
- pydantic_rpc-0.6.0/tests/echoservice_pb2_grpc.py +110 -0
- pydantic_rpc-0.6.0/tests/test_apps.py +190 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/uv.lock +55 -5
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/.gitignore +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/.python-version +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/LICENSE +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/agent_aio_grpc.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/agent_connecpy.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/asyncio_greeting.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/barservice.proto +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/barservice_pb2.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/barservice_pb2.pyi +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/barservice_pb2_grpc.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/foobar.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/foobar_client.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/fooservice.proto +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/fooservice_pb2.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/fooservice_pb2.pyi +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/fooservice_pb2_grpc.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/google/protobuf/duration.proto +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/google/protobuf/timestamp.proto +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeter.proto +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeter_client.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeter_connecpy_client.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeter_pb2.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeter_pb2.pyi +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeter_pb2_grpc.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeter_sonora_client.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeting.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeting_asgi.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeting_connecpy.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeting_using_exsiting_pb2_modules.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/greeting_wsgi.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/olympicsagent.proto +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/olympicsagent_pb2.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/olympicsagent_pb2.pyi +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/olympicsagent_pb2_grpc.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/olympicslocationagent.proto +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/olympicslocationagent_connecpy.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/olympicslocationagent_pb2.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/olympicslocationagent_pb2.pyi +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/examples/olympicslocationagent_pb2_grpc.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/src/pydantic_rpc/__init__.py +0 -0
- {pydantic_rpc-0.5.0 → pydantic_rpc-0.6.0}/src/pydantic_rpc/py.typed +0 -0
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pydantic-rpc
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
|
|
5
|
+
Author: Yasushi Itoh
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: connecpy>=1.3.2
|
|
9
|
+
Requires-Dist: grpcio-health-checking>=1.56.2
|
|
10
|
+
Requires-Dist: grpcio-reflection>=1.56.2
|
|
11
|
+
Requires-Dist: grpcio-tools>=1.56.2
|
|
12
|
+
Requires-Dist: pydantic>=2.1.1
|
|
13
|
+
Requires-Dist: sonora>=0.2.3
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
1
16
|
# 🚀 PydanticRPC
|
|
2
17
|
|
|
3
18
|
**PydanticRPC** is a Python library that enables you to rapidly expose [Pydantic](https://docs.pydantic.dev/) models via [gRPC](https://grpc.io/)/[Connect RPC](https://connectrpc.com/docs/protocol/) services without writing any protobuf files. Instead, it automatically generates protobuf files on the fly from the method signatures of your Python objects and the type signatures of your Pydantic models.
|
|
@@ -89,6 +104,7 @@ app.mount(OlympicsLocationAgent())
|
|
|
89
104
|
- 🌐 **WSGI/ASGI Support:** Create gRPC-Web services that can run as WSGI or ASGI applications powered by `Sonora`.
|
|
90
105
|
- **For Connect-RPC:**
|
|
91
106
|
- 🌐 **Connecpy Support:** Partially supports Connect-RPC via `Connecpy`.
|
|
107
|
+
- 🛠️ **Pre-generated Protobuf Files and Code:** Pre-generate proto files and corresponding code via the CLI. By setting the environment variable (PYDANTIC_RPC_SKIP_GENERATION), you can skip runtime generation.
|
|
92
108
|
|
|
93
109
|
## 📦 Installation
|
|
94
110
|
|
|
@@ -242,7 +258,9 @@ When this variable is set to "true", PydanticRPC will load existing pre-generate
|
|
|
242
258
|
## 💎 Advanced Features
|
|
243
259
|
|
|
244
260
|
### 🌊 Response Streaming
|
|
245
|
-
PydanticRPC supports streaming
|
|
261
|
+
PydanticRPC supports streaming responses only for asynchronous gRPC and gRPC-Web services.
|
|
262
|
+
If a service class method’s return type is `typing.AsyncIterator[T]`, the method is considered a streaming method.
|
|
263
|
+
|
|
246
264
|
|
|
247
265
|
Please see the sample code below:
|
|
248
266
|
|
|
@@ -304,6 +322,9 @@ if __name__ == "__main__":
|
|
|
304
322
|
loop.run_until_complete(s.run(OlympicsAgent()))
|
|
305
323
|
```
|
|
306
324
|
|
|
325
|
+
In the example above, the `ask_stream` method returns an `AsyncIterator[StreamingResult]` object, which is considered a streaming method. The `StreamingResult` class is a Pydantic model that defines the response type of the streaming method. You can use any Pydantic model as the response type.
|
|
326
|
+
|
|
327
|
+
|
|
307
328
|
### 🔗 Multiple Services with Custom Interceptors
|
|
308
329
|
|
|
309
330
|
PydanticRPC supports defining and running multiple services in a single server:
|
|
@@ -389,9 +410,9 @@ if __name__ == "__main__":
|
|
|
389
410
|
|
|
390
411
|
TODO
|
|
391
412
|
|
|
392
|
-
### 🗄️ Protobuf file and code (Python files) generation
|
|
413
|
+
### 🗄️ Protobuf file and code (Python files) generation using CLI
|
|
393
414
|
|
|
394
|
-
|
|
415
|
+
You can genereate protobuf files and code for a given module and a specified class using `pydantic-rpc` CLI command:
|
|
395
416
|
|
|
396
417
|
```bash
|
|
397
418
|
pydantic-rpc a_module.py aClassName
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: pydantic-rpc
|
|
3
|
-
Version: 0.5.0
|
|
4
|
-
Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
|
|
5
|
-
Author: Yasushi Itoh
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Requires-Python: >=3.11
|
|
8
|
-
Requires-Dist: connecpy>=1.2.1
|
|
9
|
-
Requires-Dist: grpcio-health-checking>=1.56.2
|
|
10
|
-
Requires-Dist: grpcio-reflection>=1.56.2
|
|
11
|
-
Requires-Dist: grpcio-tools>=1.56.2
|
|
12
|
-
Requires-Dist: pydantic>=2.1.1
|
|
13
|
-
Requires-Dist: sonora>=0.2.3
|
|
14
|
-
Description-Content-Type: text/markdown
|
|
15
|
-
|
|
16
1
|
# 🚀 PydanticRPC
|
|
17
2
|
|
|
18
3
|
**PydanticRPC** is a Python library that enables you to rapidly expose [Pydantic](https://docs.pydantic.dev/) models via [gRPC](https://grpc.io/)/[Connect RPC](https://connectrpc.com/docs/protocol/) services without writing any protobuf files. Instead, it automatically generates protobuf files on the fly from the method signatures of your Python objects and the type signatures of your Pydantic models.
|
|
@@ -104,6 +89,7 @@ app.mount(OlympicsLocationAgent())
|
|
|
104
89
|
- 🌐 **WSGI/ASGI Support:** Create gRPC-Web services that can run as WSGI or ASGI applications powered by `Sonora`.
|
|
105
90
|
- **For Connect-RPC:**
|
|
106
91
|
- 🌐 **Connecpy Support:** Partially supports Connect-RPC via `Connecpy`.
|
|
92
|
+
- 🛠️ **Pre-generated Protobuf Files and Code:** Pre-generate proto files and corresponding code via the CLI. By setting the environment variable (PYDANTIC_RPC_SKIP_GENERATION), you can skip runtime generation.
|
|
107
93
|
|
|
108
94
|
## 📦 Installation
|
|
109
95
|
|
|
@@ -257,7 +243,9 @@ When this variable is set to "true", PydanticRPC will load existing pre-generate
|
|
|
257
243
|
## 💎 Advanced Features
|
|
258
244
|
|
|
259
245
|
### 🌊 Response Streaming
|
|
260
|
-
PydanticRPC supports streaming
|
|
246
|
+
PydanticRPC supports streaming responses only for asynchronous gRPC and gRPC-Web services.
|
|
247
|
+
If a service class method’s return type is `typing.AsyncIterator[T]`, the method is considered a streaming method.
|
|
248
|
+
|
|
261
249
|
|
|
262
250
|
Please see the sample code below:
|
|
263
251
|
|
|
@@ -319,6 +307,9 @@ if __name__ == "__main__":
|
|
|
319
307
|
loop.run_until_complete(s.run(OlympicsAgent()))
|
|
320
308
|
```
|
|
321
309
|
|
|
310
|
+
In the example above, the `ask_stream` method returns an `AsyncIterator[StreamingResult]` object, which is considered a streaming method. The `StreamingResult` class is a Pydantic model that defines the response type of the streaming method. You can use any Pydantic model as the response type.
|
|
311
|
+
|
|
312
|
+
|
|
322
313
|
### 🔗 Multiple Services with Custom Interceptors
|
|
323
314
|
|
|
324
315
|
PydanticRPC supports defining and running multiple services in a single server:
|
|
@@ -404,9 +395,9 @@ if __name__ == "__main__":
|
|
|
404
395
|
|
|
405
396
|
TODO
|
|
406
397
|
|
|
407
|
-
### 🗄️ Protobuf file and code (Python files) generation
|
|
398
|
+
### 🗄️ Protobuf file and code (Python files) generation using CLI
|
|
408
399
|
|
|
409
|
-
|
|
400
|
+
You can genereate protobuf files and code for a given module and a specified class using `pydantic-rpc` CLI command:
|
|
410
401
|
|
|
411
402
|
```bash
|
|
412
403
|
pydantic-rpc a_module.py aClassName
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## 📝 Prerequisites
|
|
4
4
|
|
|
5
|
-
Ensure you have [
|
|
5
|
+
Ensure you have [uv](https://docs.astral.sh/uv/) installed on your system. If not, you can install it using the following command:
|
|
6
6
|
|
|
7
7
|
### Linux/macOS
|
|
8
8
|
|
|
@@ -43,6 +43,30 @@ class GreeterServer(ConnecpyServer):
|
|
|
43
43
|
return "greeter.v1.Greeter"
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
class GreeterSync(Protocol):
|
|
47
|
+
def SayHello(
|
|
48
|
+
self, req: _pb2.HelloRequest, ctx: ServiceContext
|
|
49
|
+
) -> _pb2.HelloReply: ...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class GreeterServerSync(ConnecpyServer):
|
|
53
|
+
def __init__(self, *, service: GreeterSync, server_path_prefix=""):
|
|
54
|
+
super().__init__()
|
|
55
|
+
self._prefix = f"{server_path_prefix}/greeter.v1.Greeter"
|
|
56
|
+
self._endpoints = {
|
|
57
|
+
"SayHello": Endpoint[_pb2.HelloRequest, _pb2.HelloReply](
|
|
58
|
+
service_name="Greeter",
|
|
59
|
+
name="SayHello",
|
|
60
|
+
function=getattr(service, "SayHello"),
|
|
61
|
+
input=_pb2.HelloRequest,
|
|
62
|
+
output=_pb2.HelloReply,
|
|
63
|
+
),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
def serviceName(self):
|
|
67
|
+
return "greeter.v1.Greeter"
|
|
68
|
+
|
|
69
|
+
|
|
46
70
|
class GreeterClient(ConnecpyClient):
|
|
47
71
|
def SayHello(
|
|
48
72
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pydantic-rpc"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "A Python library for building gRPC/ConnectRPC services with Pydantic models."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Yasushi Itoh" }
|
|
@@ -11,7 +11,7 @@ dependencies = [
|
|
|
11
11
|
"grpcio-reflection>=1.56.2",
|
|
12
12
|
"grpcio-health-checking>=1.56.2",
|
|
13
13
|
"sonora>=0.2.3",
|
|
14
|
-
"connecpy>=1.2
|
|
14
|
+
"connecpy>=1.3.2",
|
|
15
15
|
]
|
|
16
16
|
readme = "README.md"
|
|
17
17
|
requires-python = ">= 3.11"
|
|
@@ -28,8 +28,15 @@ managed = true
|
|
|
28
28
|
dev-dependencies = [
|
|
29
29
|
"hypercorn>=0.17.3",
|
|
30
30
|
"pydantic-ai==0.0.20",
|
|
31
|
+
"pytest>=8.3.4",
|
|
32
|
+
"pytest-asyncio>=0.20.3",
|
|
31
33
|
"ruff>=0.9.4",
|
|
32
34
|
]
|
|
33
35
|
|
|
34
36
|
[tool.hatch.metadata]
|
|
35
37
|
allow-direct-references = true
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
markers = [
|
|
41
|
+
"asyncio: mark test as asyncio",
|
|
42
|
+
]
|
|
@@ -31,6 +31,7 @@ from sonora.wsgi import grpcWSGI
|
|
|
31
31
|
from sonora.asgi import grpcASGI
|
|
32
32
|
from connecpy.asgi import ConnecpyASGIApp as ConnecpyASGI
|
|
33
33
|
from connecpy.errors import Errors
|
|
34
|
+
from connecpy.wsgi import ConnecpyWSGIApp as ConnecpyWSGI
|
|
34
35
|
|
|
35
36
|
# Protobuf Python modules for Timestamp, Duration (requires protobuf / grpcio)
|
|
36
37
|
from google.protobuf import timestamp_pb2, duration_pb2
|
|
@@ -356,6 +357,69 @@ def connect_obj_with_stub_async(pb2_grpc_module, pb2_module, obj: object) -> typ
|
|
|
356
357
|
return ConcreteServiceClass
|
|
357
358
|
|
|
358
359
|
|
|
360
|
+
def connect_obj_with_stub_connecpy(connecpy_module, pb2_module, obj: object) -> type:
|
|
361
|
+
"""
|
|
362
|
+
Connect a Python service object to a Connecpy stub.
|
|
363
|
+
"""
|
|
364
|
+
service_class = obj.__class__
|
|
365
|
+
stub_class_name = service_class.__name__
|
|
366
|
+
stub_class = getattr(connecpy_module, stub_class_name)
|
|
367
|
+
|
|
368
|
+
class ConcreteServiceClass(stub_class):
|
|
369
|
+
pass
|
|
370
|
+
|
|
371
|
+
def implement_stub_method(method):
|
|
372
|
+
sig = inspect.signature(method)
|
|
373
|
+
arg_type = get_request_arg_type(sig)
|
|
374
|
+
converter = generate_message_converter(arg_type)
|
|
375
|
+
response_type = sig.return_annotation
|
|
376
|
+
size_of_parameters = len(sig.parameters)
|
|
377
|
+
|
|
378
|
+
match size_of_parameters:
|
|
379
|
+
case 1:
|
|
380
|
+
|
|
381
|
+
def stub_method1(self, request, context, method=method):
|
|
382
|
+
try:
|
|
383
|
+
arg = converter(request)
|
|
384
|
+
resp_obj = method(arg)
|
|
385
|
+
return convert_python_message_to_proto(
|
|
386
|
+
resp_obj, response_type, pb2_module
|
|
387
|
+
)
|
|
388
|
+
except ValidationError as e:
|
|
389
|
+
return context.abort(Errors.InvalidArgument, str(e))
|
|
390
|
+
except Exception as e:
|
|
391
|
+
return context.abort(Errors.Internal, str(e))
|
|
392
|
+
|
|
393
|
+
return stub_method1
|
|
394
|
+
|
|
395
|
+
case 2:
|
|
396
|
+
|
|
397
|
+
def stub_method2(self, request, context, method=method):
|
|
398
|
+
try:
|
|
399
|
+
arg = converter(request)
|
|
400
|
+
resp_obj = method(arg, context)
|
|
401
|
+
return convert_python_message_to_proto(
|
|
402
|
+
resp_obj, response_type, pb2_module
|
|
403
|
+
)
|
|
404
|
+
except ValidationError as e:
|
|
405
|
+
return context.abort(Errors.InvalidArgument, str(e))
|
|
406
|
+
except Exception as e:
|
|
407
|
+
return context.abort(Errors.Internal, str(e))
|
|
408
|
+
|
|
409
|
+
return stub_method2
|
|
410
|
+
|
|
411
|
+
case _:
|
|
412
|
+
raise Exception("Method must have exactly one or two parameters")
|
|
413
|
+
|
|
414
|
+
for method_name, method in get_rpc_methods(obj):
|
|
415
|
+
if method.__name__.startswith("_"):
|
|
416
|
+
continue
|
|
417
|
+
a_method = implement_stub_method(method)
|
|
418
|
+
setattr(ConcreteServiceClass, method_name, a_method)
|
|
419
|
+
|
|
420
|
+
return ConcreteServiceClass
|
|
421
|
+
|
|
422
|
+
|
|
359
423
|
def connect_obj_with_stub_async_connecpy(
|
|
360
424
|
connecpy_module, pb2_module, obj: object
|
|
361
425
|
) -> type:
|
|
@@ -1330,11 +1394,54 @@ class ConnecpyASGIApp:
|
|
|
1330
1394
|
await self._app(scope, receive, send)
|
|
1331
1395
|
|
|
1332
1396
|
|
|
1397
|
+
class ConnecpyWSGIApp:
|
|
1398
|
+
"""
|
|
1399
|
+
A WSGI-compatible application that can serve Connect-RPC via Connecpy's ConnecpyWSGIApp.
|
|
1400
|
+
"""
|
|
1401
|
+
|
|
1402
|
+
def __init__(self):
|
|
1403
|
+
self._app = ConnecpyWSGI()
|
|
1404
|
+
self._service_names = []
|
|
1405
|
+
self._package_name = ""
|
|
1406
|
+
|
|
1407
|
+
def mount(self, obj: object, package_name: str = ""):
|
|
1408
|
+
"""Generate and compile proto files, then mount the async service implementation."""
|
|
1409
|
+
connecpy_module, pb2_module = generate_and_compile_proto_using_connecpy(
|
|
1410
|
+
obj, package_name
|
|
1411
|
+
)
|
|
1412
|
+
self.mount_using_pb2_modules(connecpy_module, pb2_module, obj)
|
|
1413
|
+
|
|
1414
|
+
def mount_using_pb2_modules(self, connecpy_module, pb2_module, obj: object):
|
|
1415
|
+
"""Connect the compiled connecpy and pb2 modules with the async service implementation."""
|
|
1416
|
+
concreteServiceClass = connect_obj_with_stub_connecpy(
|
|
1417
|
+
connecpy_module, pb2_module, obj
|
|
1418
|
+
)
|
|
1419
|
+
service_name = obj.__class__.__name__
|
|
1420
|
+
service_impl = concreteServiceClass()
|
|
1421
|
+
connecpy_server = get_connecpy_server_class(connecpy_module, service_name)
|
|
1422
|
+
self._app.add_service(connecpy_server(service=service_impl))
|
|
1423
|
+
full_service_name = pb2_module.DESCRIPTOR.services_by_name[
|
|
1424
|
+
service_name
|
|
1425
|
+
].full_name
|
|
1426
|
+
self._service_names.append(full_service_name)
|
|
1427
|
+
|
|
1428
|
+
def mount_objs(self, *objs):
|
|
1429
|
+
"""Mount multiple service objects into this WSGI app."""
|
|
1430
|
+
for obj in objs:
|
|
1431
|
+
self.mount(obj, self._package_name)
|
|
1432
|
+
|
|
1433
|
+
def __call__(self, environ, start_response):
|
|
1434
|
+
"""WSGI entry point."""
|
|
1435
|
+
return self._app(environ, start_response)
|
|
1436
|
+
|
|
1437
|
+
|
|
1333
1438
|
def main():
|
|
1334
1439
|
import argparse
|
|
1335
1440
|
|
|
1336
1441
|
parser = argparse.ArgumentParser(description="Generate and compile proto files.")
|
|
1337
|
-
parser.add_argument(
|
|
1442
|
+
parser.add_argument(
|
|
1443
|
+
"py_file", type=str, help="The Python file containing the service class."
|
|
1444
|
+
)
|
|
1338
1445
|
parser.add_argument("class_name", type=str, help="The name of the service class.")
|
|
1339
1446
|
args = parser.parse_args()
|
|
1340
1447
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package asyncecho.v1;
|
|
4
|
+
|
|
5
|
+
// Echo service.
|
|
6
|
+
// A simple service that echoes messages back in uppercase.
|
|
7
|
+
service AsyncEchoService {
|
|
8
|
+
// Echo the message back in uppercase.
|
|
9
|
+
//
|
|
10
|
+
// Args:
|
|
11
|
+
// request (EchoRequest): The request message.
|
|
12
|
+
//
|
|
13
|
+
// Returns:
|
|
14
|
+
// EchoResponse: The response message.
|
|
15
|
+
rpc Echo (EchoRequest) returns (EchoResponse);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Echo response message.
|
|
19
|
+
//
|
|
20
|
+
// Attributes:
|
|
21
|
+
// text (str): The echoed text.
|
|
22
|
+
message EchoResponse {
|
|
23
|
+
string text = 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Echo request message.
|
|
27
|
+
//
|
|
28
|
+
// Attributes:
|
|
29
|
+
// text (str): The text to echo.
|
|
30
|
+
message EchoRequest {
|
|
31
|
+
string text = 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Generated by https://github.com/i2y/connecpy/protoc-gen-connecpy. DO NOT EDIT!
|
|
3
|
+
# source: asyncechoservice.proto
|
|
4
|
+
|
|
5
|
+
from typing import Any, Protocol, Union
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from connecpy.async_client import AsyncConnecpyClient
|
|
10
|
+
from connecpy.base import Endpoint
|
|
11
|
+
from connecpy.server import ConnecpyServer
|
|
12
|
+
from connecpy.client import ConnecpyClient
|
|
13
|
+
from connecpy.context import ClientContext, ServiceContext
|
|
14
|
+
|
|
15
|
+
import asyncechoservice_pb2 as _pb2
|
|
16
|
+
|
|
17
|
+
from google.protobuf import symbol_database
|
|
18
|
+
|
|
19
|
+
_sym_db = symbol_database.Default()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AsyncEchoService(Protocol):
|
|
23
|
+
async def Echo(self, req: _pb2.EchoRequest, ctx: ServiceContext) -> _pb2.EchoResponse: ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AsyncEchoServiceServer(ConnecpyServer):
|
|
27
|
+
def __init__(self, *, service: AsyncEchoService, server_path_prefix=""):
|
|
28
|
+
super().__init__()
|
|
29
|
+
self._prefix = f"{server_path_prefix}/asyncecho.v1.AsyncEchoService"
|
|
30
|
+
self._endpoints = {
|
|
31
|
+
"Echo": Endpoint[_pb2.EchoRequest, _pb2.EchoResponse](
|
|
32
|
+
service_name="AsyncEchoService",
|
|
33
|
+
name="Echo",
|
|
34
|
+
function=getattr(service, "Echo"),
|
|
35
|
+
input=_pb2.EchoRequest,
|
|
36
|
+
output=_pb2.EchoResponse,
|
|
37
|
+
),
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def serviceName(self):
|
|
41
|
+
return "asyncecho.v1.AsyncEchoService"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AsyncEchoServiceSync(Protocol):
|
|
45
|
+
def Echo(self, req: _pb2.EchoRequest, ctx: ServiceContext) -> _pb2.EchoResponse: ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AsyncEchoServiceServerSync(ConnecpyServer):
|
|
49
|
+
def __init__(self, *, service: AsyncEchoServiceSync, server_path_prefix=""):
|
|
50
|
+
super().__init__()
|
|
51
|
+
self._prefix = f"{server_path_prefix}/asyncecho.v1.AsyncEchoService"
|
|
52
|
+
self._endpoints = {
|
|
53
|
+
"Echo": Endpoint[_pb2.EchoRequest, _pb2.EchoResponse](
|
|
54
|
+
service_name="AsyncEchoService",
|
|
55
|
+
name="Echo",
|
|
56
|
+
function=getattr(service, "Echo"),
|
|
57
|
+
input=_pb2.EchoRequest,
|
|
58
|
+
output=_pb2.EchoResponse,
|
|
59
|
+
),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
def serviceName(self):
|
|
63
|
+
return "asyncecho.v1.AsyncEchoService"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class AsyncEchoServiceClient(ConnecpyClient):
|
|
67
|
+
def Echo(
|
|
68
|
+
self,
|
|
69
|
+
*,
|
|
70
|
+
request: _pb2.EchoRequest,
|
|
71
|
+
ctx: ClientContext,
|
|
72
|
+
server_path_prefix: str = "",
|
|
73
|
+
**kwargs,
|
|
74
|
+
) -> _pb2.EchoResponse:
|
|
75
|
+
return self._make_request(
|
|
76
|
+
url=f"{server_path_prefix}/asyncecho.v1.AsyncEchoService/Echo",
|
|
77
|
+
ctx=ctx,
|
|
78
|
+
request=request,
|
|
79
|
+
response_obj=_pb2.EchoResponse,
|
|
80
|
+
**kwargs,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AsyncAsyncEchoServiceClient(AsyncConnecpyClient):
|
|
85
|
+
async def Echo(
|
|
86
|
+
self,
|
|
87
|
+
*,
|
|
88
|
+
request: _pb2.EchoRequest,
|
|
89
|
+
ctx: ClientContext,
|
|
90
|
+
server_path_prefix: str = "",
|
|
91
|
+
session: Union[httpx.AsyncClient, None] = None,
|
|
92
|
+
**kwargs,
|
|
93
|
+
) -> _pb2.EchoResponse:
|
|
94
|
+
return await self._make_request(
|
|
95
|
+
url=f"{server_path_prefix}/asyncecho.v1.AsyncEchoService/Echo",
|
|
96
|
+
ctx=ctx,
|
|
97
|
+
request=request,
|
|
98
|
+
response_obj=_pb2.EchoResponse,
|
|
99
|
+
session=session,
|
|
100
|
+
**kwargs,
|
|
101
|
+
)
|
|
@@ -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: asyncechoservice.proto
|
|
5
|
+
# Protobuf Python Version: 5.29.0
|
|
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
|
+
5,
|
|
15
|
+
29,
|
|
16
|
+
0,
|
|
17
|
+
'',
|
|
18
|
+
'asyncechoservice.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\x16\x61syncechoservice.proto\x12\x0c\x61syncecho.v1\"\x1c\n\x0c\x45\x63hoResponse\x12\x0c\n\x04text\x18\x01 \x01(\t\"\x1b\n\x0b\x45\x63hoRequest\x12\x0c\n\x04text\x18\x01 \x01(\t2Q\n\x10\x41syncEchoService\x12=\n\x04\x45\x63ho\x12\x19.asyncecho.v1.EchoRequest\x1a\x1a.asyncecho.v1.EchoResponseb\x06proto3')
|
|
28
|
+
|
|
29
|
+
_globals = globals()
|
|
30
|
+
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
31
|
+
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'asyncechoservice_pb2', _globals)
|
|
32
|
+
if not _descriptor._USE_C_DESCRIPTORS:
|
|
33
|
+
DESCRIPTOR._loaded_options = None
|
|
34
|
+
_globals['_ECHORESPONSE']._serialized_start=40
|
|
35
|
+
_globals['_ECHORESPONSE']._serialized_end=68
|
|
36
|
+
_globals['_ECHOREQUEST']._serialized_start=70
|
|
37
|
+
_globals['_ECHOREQUEST']._serialized_end=97
|
|
38
|
+
_globals['_ASYNCECHOSERVICE']._serialized_start=99
|
|
39
|
+
_globals['_ASYNCECHOSERVICE']._serialized_end=180
|
|
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 EchoResponse(_message.Message):
|
|
8
|
+
__slots__ = ("text",)
|
|
9
|
+
TEXT_FIELD_NUMBER: _ClassVar[int]
|
|
10
|
+
text: str
|
|
11
|
+
def __init__(self, text: _Optional[str] = ...) -> None: ...
|
|
12
|
+
|
|
13
|
+
class EchoRequest(_message.Message):
|
|
14
|
+
__slots__ = ("text",)
|
|
15
|
+
TEXT_FIELD_NUMBER: _ClassVar[int]
|
|
16
|
+
text: str
|
|
17
|
+
def __init__(self, text: _Optional[str] = ...) -> None: ...
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
import asyncechoservice_pb2 as asyncechoservice__pb2
|
|
7
|
+
|
|
8
|
+
GRPC_GENERATED_VERSION = '1.70.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
|
+
+ f' but the generated code in asyncechoservice_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 AsyncEchoServiceStub(object):
|
|
29
|
+
"""Echo service.
|
|
30
|
+
A simple service that echoes messages back in uppercase.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, channel):
|
|
34
|
+
"""Constructor.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
channel: A grpc.Channel.
|
|
38
|
+
"""
|
|
39
|
+
self.Echo = channel.unary_unary(
|
|
40
|
+
'/asyncecho.v1.AsyncEchoService/Echo',
|
|
41
|
+
request_serializer=asyncechoservice__pb2.EchoRequest.SerializeToString,
|
|
42
|
+
response_deserializer=asyncechoservice__pb2.EchoResponse.FromString,
|
|
43
|
+
_registered_method=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AsyncEchoServiceServicer(object):
|
|
47
|
+
"""Echo service.
|
|
48
|
+
A simple service that echoes messages back in uppercase.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def Echo(self, request, context):
|
|
52
|
+
"""Echo the message back in uppercase.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
request (EchoRequest): The request message.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
EchoResponse: The response message.
|
|
59
|
+
"""
|
|
60
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
61
|
+
context.set_details('Method not implemented!')
|
|
62
|
+
raise NotImplementedError('Method not implemented!')
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def add_AsyncEchoServiceServicer_to_server(servicer, server):
|
|
66
|
+
rpc_method_handlers = {
|
|
67
|
+
'Echo': grpc.unary_unary_rpc_method_handler(
|
|
68
|
+
servicer.Echo,
|
|
69
|
+
request_deserializer=asyncechoservice__pb2.EchoRequest.FromString,
|
|
70
|
+
response_serializer=asyncechoservice__pb2.EchoResponse.SerializeToString,
|
|
71
|
+
),
|
|
72
|
+
}
|
|
73
|
+
generic_handler = grpc.method_handlers_generic_handler(
|
|
74
|
+
'asyncecho.v1.AsyncEchoService', rpc_method_handlers)
|
|
75
|
+
server.add_generic_rpc_handlers((generic_handler,))
|
|
76
|
+
server.add_registered_method_handlers('asyncecho.v1.AsyncEchoService', rpc_method_handlers)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# This class is part of an EXPERIMENTAL API.
|
|
80
|
+
class AsyncEchoService(object):
|
|
81
|
+
"""Echo service.
|
|
82
|
+
A simple service that echoes messages back in uppercase.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def Echo(request,
|
|
87
|
+
target,
|
|
88
|
+
options=(),
|
|
89
|
+
channel_credentials=None,
|
|
90
|
+
call_credentials=None,
|
|
91
|
+
insecure=False,
|
|
92
|
+
compression=None,
|
|
93
|
+
wait_for_ready=None,
|
|
94
|
+
timeout=None,
|
|
95
|
+
metadata=None):
|
|
96
|
+
return grpc.experimental.unary_unary(
|
|
97
|
+
request,
|
|
98
|
+
target,
|
|
99
|
+
'/asyncecho.v1.AsyncEchoService/Echo',
|
|
100
|
+
asyncechoservice__pb2.EchoRequest.SerializeToString,
|
|
101
|
+
asyncechoservice__pb2.EchoResponse.FromString,
|
|
102
|
+
options,
|
|
103
|
+
channel_credentials,
|
|
104
|
+
insecure,
|
|
105
|
+
call_credentials,
|
|
106
|
+
compression,
|
|
107
|
+
wait_for_ready,
|
|
108
|
+
timeout,
|
|
109
|
+
metadata,
|
|
110
|
+
_registered_method=True)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package echo.v1;
|
|
4
|
+
|
|
5
|
+
// Echo service.
|
|
6
|
+
// A simple service that echoes messages back in uppercase.
|
|
7
|
+
service EchoService {
|
|
8
|
+
// Echo the message back in uppercase.
|
|
9
|
+
//
|
|
10
|
+
// Args:
|
|
11
|
+
// request (EchoRequest): The request message.
|
|
12
|
+
//
|
|
13
|
+
// Returns:
|
|
14
|
+
// EchoResponse: The response message.
|
|
15
|
+
rpc Echo (EchoRequest) returns (EchoResponse);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Echo response message.
|
|
19
|
+
//
|
|
20
|
+
// Attributes:
|
|
21
|
+
// text (str): The echoed text.
|
|
22
|
+
message EchoResponse {
|
|
23
|
+
string text = 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Echo request message.
|
|
27
|
+
//
|
|
28
|
+
// Attributes:
|
|
29
|
+
// text (str): The text to echo.
|
|
30
|
+
message EchoRequest {
|
|
31
|
+
string text = 1;
|
|
32
|
+
}
|
|
33
|
+
|