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 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
- if __name__ == "__main__":
1397
+ class ConnecpyWSGIApp:
1334
1398
  """
1335
- If executed as a script, generate the .proto files for a given class.
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
- py_file_name = sys.argv[1]
1339
- class_name = sys.argv[2]
1340
- module_name = os.path.splitext(basename(py_file_name))[0]
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.4.1
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.1
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 for responses in asynchronous gRPC and gRPC-Web services only.
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 generate protobuf files for a given module and a specified class using `core.py`:
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
- python core.py a_module.py aClass
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,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pydantic-rpc = pydantic_rpc.core:main
@@ -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,,