pydantic-rpc 0.4.1__py3-none-any.whl → 0.6.0__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.
- pydantic_rpc/core.py +119 -7
- {pydantic_rpc-0.4.1.dist-info → pydantic_rpc-0.6.0.dist-info}/METADATA +13 -6
- pydantic_rpc-0.6.0.dist-info/RECORD +8 -0
- pydantic_rpc-0.6.0.dist-info/entry_points.txt +2 -0
- pydantic_rpc-0.4.1.dist-info/RECORD +0 -7
- {pydantic_rpc-0.4.1.dist-info → pydantic_rpc-0.6.0.dist-info}/WHEEL +0 -0
- {pydantic_rpc-0.4.1.dist-info → pydantic_rpc-0.6.0.dist-info}/licenses/LICENSE +0 -0
pydantic_rpc/core.py
CHANGED
|
@@ -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,14 +1394,62 @@ class ConnecpyASGIApp:
|
|
|
1330
1394
|
await self._app(scope, receive, send)
|
|
1331
1395
|
|
|
1332
1396
|
|
|
1333
|
-
|
|
1397
|
+
class ConnecpyWSGIApp:
|
|
1334
1398
|
"""
|
|
1335
|
-
|
|
1336
|
-
Usage: python core.py some_module.py SomeServiceClass
|
|
1399
|
+
A WSGI-compatible application that can serve Connect-RPC via Connecpy's ConnecpyWSGIApp.
|
|
1337
1400
|
"""
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
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
|
+
|
|
1438
|
+
def main():
|
|
1439
|
+
import argparse
|
|
1440
|
+
|
|
1441
|
+
parser = argparse.ArgumentParser(description="Generate and compile proto files.")
|
|
1442
|
+
parser.add_argument(
|
|
1443
|
+
"py_file", type=str, help="The Python file containing the service class."
|
|
1444
|
+
)
|
|
1445
|
+
parser.add_argument("class_name", type=str, help="The name of the service class.")
|
|
1446
|
+
args = parser.parse_args()
|
|
1447
|
+
|
|
1448
|
+
module_name = os.path.splitext(basename(args.py_file))[0]
|
|
1341
1449
|
module = importlib.import_module(module_name)
|
|
1342
|
-
klass = getattr(module, class_name)
|
|
1450
|
+
klass = getattr(module, args.class_name)
|
|
1343
1451
|
generate_and_compile_proto(klass())
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
if __name__ == "__main__":
|
|
1455
|
+
main()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydantic-rpc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
|
|
5
5
|
Author: Yasushi Itoh
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Requires-Python: >=3.11
|
|
8
|
-
Requires-Dist: connecpy>=1.2
|
|
8
|
+
Requires-Dist: connecpy>=1.3.2
|
|
9
9
|
Requires-Dist: grpcio-health-checking>=1.56.2
|
|
10
10
|
Requires-Dist: grpcio-reflection>=1.56.2
|
|
11
11
|
Requires-Dist: grpcio-tools>=1.56.2
|
|
@@ -104,6 +104,7 @@ app.mount(OlympicsLocationAgent())
|
|
|
104
104
|
- 🌐 **WSGI/ASGI Support:** Create gRPC-Web services that can run as WSGI or ASGI applications powered by `Sonora`.
|
|
105
105
|
- **For Connect-RPC:**
|
|
106
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.
|
|
107
108
|
|
|
108
109
|
## 📦 Installation
|
|
109
110
|
|
|
@@ -257,7 +258,9 @@ When this variable is set to "true", PydanticRPC will load existing pre-generate
|
|
|
257
258
|
## 💎 Advanced Features
|
|
258
259
|
|
|
259
260
|
### 🌊 Response Streaming
|
|
260
|
-
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
|
+
|
|
261
264
|
|
|
262
265
|
Please see the sample code below:
|
|
263
266
|
|
|
@@ -319,6 +322,9 @@ if __name__ == "__main__":
|
|
|
319
322
|
loop.run_until_complete(s.run(OlympicsAgent()))
|
|
320
323
|
```
|
|
321
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
|
+
|
|
322
328
|
### 🔗 Multiple Services with Custom Interceptors
|
|
323
329
|
|
|
324
330
|
PydanticRPC supports defining and running multiple services in a single server:
|
|
@@ -404,16 +410,17 @@ if __name__ == "__main__":
|
|
|
404
410
|
|
|
405
411
|
TODO
|
|
406
412
|
|
|
407
|
-
### 🗄️ Protobuf file generation
|
|
413
|
+
### 🗄️ Protobuf file and code (Python files) generation using CLI
|
|
408
414
|
|
|
409
|
-
You can
|
|
415
|
+
You can genereate protobuf files and code for a given module and a specified class using `pydantic-rpc` CLI command:
|
|
410
416
|
|
|
411
417
|
```bash
|
|
412
|
-
|
|
418
|
+
pydantic-rpc a_module.py aClassName
|
|
413
419
|
```
|
|
414
420
|
|
|
415
421
|
Using this generated proto file and tools as `protoc`, `buf` and `BSR`, you could generate code for any desired language other than Python.
|
|
416
422
|
|
|
423
|
+
|
|
417
424
|
## 📖 Data Type Mapping
|
|
418
425
|
|
|
419
426
|
| Python Type | Protobuf Type |
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pydantic_rpc/__init__.py,sha256=oomSVGmh_zddQQaphQt1L2xSVh9dD1LVyaAq1cN1FW4,231
|
|
2
|
+
pydantic_rpc/core.py,sha256=SB9GDZRxsMWQFxphinYQUAqrOx10eUn40fPTc7t1xYs,52107
|
|
3
|
+
pydantic_rpc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pydantic_rpc-0.6.0.dist-info/METADATA,sha256=oTCJJH4yB2VW-eVGXN3EoGtinuC9kllMQFThk2QuPlo,12698
|
|
5
|
+
pydantic_rpc-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
+
pydantic_rpc-0.6.0.dist-info/entry_points.txt,sha256=LeZJ6UN-fhjKrEGkcmsAAKuA-fIe7MpvzKMPSZfi0NE,56
|
|
7
|
+
pydantic_rpc-0.6.0.dist-info/licenses/LICENSE,sha256=Y6jkAm2VqPqoGIGQ-mEQCecNfteQ2LwdpYhC5XiH_cA,1069
|
|
8
|
+
pydantic_rpc-0.6.0.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
pydantic_rpc/__init__.py,sha256=oomSVGmh_zddQQaphQt1L2xSVh9dD1LVyaAq1cN1FW4,231
|
|
2
|
-
pydantic_rpc/core.py,sha256=e6zO5qx9vJvOZR8yiBVx41jnIGEg2d_uDHzp6WgkN70,47938
|
|
3
|
-
pydantic_rpc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
pydantic_rpc-0.4.1.dist-info/METADATA,sha256=4EwQ-24LOPI8h0eNivTcN9aI9n0db80HWEzLi9QZBqI,12007
|
|
5
|
-
pydantic_rpc-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
-
pydantic_rpc-0.4.1.dist-info/licenses/LICENSE,sha256=Y6jkAm2VqPqoGIGQ-mEQCecNfteQ2LwdpYhC5XiH_cA,1069
|
|
7
|
-
pydantic_rpc-0.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|