pydantic-rpc 0.8.0__py3-none-any.whl → 0.10.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
@@ -9,12 +9,13 @@ import sys
9
9
  import time
10
10
  import types
11
11
  from typing import Union
12
- from collections.abc import AsyncIterator, Awaitable, Callable
12
+ from collections.abc import AsyncIterator, Awaitable, Callable, Iterable
13
13
  from concurrent import futures
14
14
  from pathlib import Path
15
15
  from posixpath import basename
16
16
  from typing import (
17
17
  Any,
18
+ Optional,
18
19
  TypeAlias,
19
20
  get_args,
20
21
  get_origin,
@@ -35,8 +36,8 @@ from grpc_health.v1.health import HealthServicer
35
36
  from grpc_reflection.v1alpha import reflection
36
37
  from grpc_tools import protoc
37
38
  from pydantic import BaseModel, ValidationError
38
- from sonora.asgi import grpcASGI
39
- from sonora.wsgi import grpcWSGI
39
+ from .decorators import get_method_options, has_http_option
40
+ from .tls import GrpcTLSConfig
40
41
 
41
42
  ###############################################################################
42
43
  # 1. Message definitions & converter extensions
@@ -838,7 +839,8 @@ def connect_obj_with_stub_connecpy(
838
839
  if method.__name__.startswith("_"):
839
840
  continue
840
841
  a_method = implement_stub_method(method)
841
- setattr(ConcreteServiceClass, method_name, a_method)
842
+ # Use the original snake_case method name for Connecpy v2.2.0 compatibility
843
+ setattr(ConcreteServiceClass, method.__name__, a_method)
842
844
 
843
845
  return ConcreteServiceClass
844
846
 
@@ -847,7 +849,7 @@ def connect_obj_with_stub_async_connecpy(
847
849
  connecpy_module: Any, pb2_module: Any, obj: object
848
850
  ) -> type:
849
851
  """
850
- Connect a Python service object to a Connecpy stub for async methods.
852
+ Connect a Python service object to a Connecpy stub for async methods with streaming support.
851
853
  """
852
854
  service_class = obj.__class__
853
855
  stub_class_name = service_class.__name__
@@ -857,12 +859,13 @@ def connect_obj_with_stub_async_connecpy(
857
859
  pass
858
860
 
859
861
  def implement_stub_method(
860
- method: Callable[..., Awaitable[Message]],
862
+ method: Callable[..., Any],
861
863
  ) -> Callable[[object, Any, Any], Any]:
862
864
  sig = inspect.signature(method)
863
- arg_type = get_request_arg_type(sig)
864
- converter = generate_message_converter(arg_type)
865
+ input_type = get_request_arg_type(sig)
866
+ is_input_stream = is_stream_type(input_type)
865
867
  response_type = sig.return_annotation
868
+ is_output_stream = is_stream_type(response_type)
866
869
  size_of_parameters = len(sig.parameters)
867
870
 
868
871
  match size_of_parameters:
@@ -891,63 +894,215 @@ def connect_obj_with_stub_async_connecpy(
891
894
 
892
895
  return stub_method0
893
896
 
894
- case 1:
895
-
896
- async def stub_method1(
897
- self: object,
898
- request: Any,
899
- context: Any,
900
- method: Callable[..., Awaitable[Message]] = method,
901
- ) -> Any:
902
- _ = self
903
- try:
904
- if is_none_type(arg_type):
905
- resp_obj = await method(None)
906
- else:
907
- arg = converter(request)
908
- resp_obj = await method(arg)
909
-
910
- if is_none_type(response_type):
911
- return empty_pb2.Empty() # type: ignore
912
- else:
913
- return convert_python_message_to_proto(
914
- resp_obj, response_type, pb2_module
915
- )
916
- except ValidationError as e:
917
- await context.abort(Errors.INVALID_ARGUMENT, str(e))
918
- except Exception as e:
919
- await context.abort(Errors.INTERNAL, str(e))
920
-
921
- return stub_method1
922
-
923
- case 2:
924
-
925
- async def stub_method2(
926
- self: object,
927
- request: Any,
928
- context: Any,
929
- method: Callable[..., Awaitable[Message]] = method,
930
- ) -> Any:
931
- _ = self
932
- try:
933
- if is_none_type(arg_type):
934
- resp_obj = await method(None, context)
935
- else:
936
- arg = converter(request)
937
- resp_obj = await method(arg, context)
938
-
939
- if is_none_type(response_type):
940
- return empty_pb2.Empty() # type: ignore
941
- else:
942
- return convert_python_message_to_proto(
943
- resp_obj, response_type, pb2_module
944
- )
945
- except ValidationError as e:
946
- await context.abort(Errors.INVALID_ARGUMENT, str(e))
947
- except Exception as e:
948
- await context.abort(Errors.INTERNAL, str(e))
949
-
950
- return stub_method2
897
+ case 1 | 2:
898
+ if is_input_stream:
899
+ # Client streaming or bidirectional streaming
900
+ input_item_type = get_args(input_type)[0]
901
+ item_converter = generate_message_converter(input_item_type)
902
+
903
+ async def convert_iterator(
904
+ proto_iter: AsyncIterator[Any],
905
+ ) -> AsyncIterator[Message]:
906
+ async for proto in proto_iter:
907
+ result = item_converter(proto)
908
+ if result is None:
909
+ raise TypeError(
910
+ f"Unexpected None result from converter for type {input_item_type}"
911
+ )
912
+ yield result
913
+
914
+ if is_output_stream:
915
+ # Bidirectional streaming
916
+ output_item_type = get_args(response_type)[0]
917
+
918
+ if size_of_parameters == 1:
919
+
920
+ async def stub_method(
921
+ self: object,
922
+ request_iterator: AsyncIterator[Any],
923
+ context: Any,
924
+ ) -> AsyncIterator[Any]:
925
+ _ = self
926
+ try:
927
+ arg_iter = convert_iterator(request_iterator)
928
+ async for resp_obj in method(arg_iter):
929
+ yield convert_python_message_to_proto(
930
+ resp_obj, output_item_type, pb2_module
931
+ )
932
+ except ValidationError as e:
933
+ await context.abort(Errors.INVALID_ARGUMENT, str(e))
934
+ except Exception as e:
935
+ await context.abort(Errors.INTERNAL, str(e))
936
+ else: # size_of_parameters == 2
937
+
938
+ async def stub_method(
939
+ self: object,
940
+ request_iterator: AsyncIterator[Any],
941
+ context: Any,
942
+ ) -> AsyncIterator[Any]:
943
+ _ = self
944
+ try:
945
+ arg_iter = convert_iterator(request_iterator)
946
+ async for resp_obj in method(arg_iter, context):
947
+ yield convert_python_message_to_proto(
948
+ resp_obj, output_item_type, pb2_module
949
+ )
950
+ except ValidationError as e:
951
+ await context.abort(Errors.INVALID_ARGUMENT, str(e))
952
+ except Exception as e:
953
+ await context.abort(Errors.INTERNAL, str(e))
954
+
955
+ return stub_method
956
+ else:
957
+ # Client streaming
958
+ if size_of_parameters == 1:
959
+
960
+ async def stub_method(
961
+ self: object,
962
+ request_iterator: AsyncIterator[Any],
963
+ context: Any,
964
+ ) -> Any:
965
+ _ = self
966
+ try:
967
+ arg_iter = convert_iterator(request_iterator)
968
+ resp_obj = await method(arg_iter)
969
+ if is_none_type(response_type):
970
+ return empty_pb2.Empty() # type: ignore
971
+ return convert_python_message_to_proto(
972
+ resp_obj, response_type, pb2_module
973
+ )
974
+ except ValidationError as e:
975
+ await context.abort(Errors.INVALID_ARGUMENT, str(e))
976
+ except Exception as e:
977
+ await context.abort(Errors.INTERNAL, str(e))
978
+ else: # size_of_parameters == 2
979
+
980
+ async def stub_method(
981
+ self: object,
982
+ request_iterator: AsyncIterator[Any],
983
+ context: Any,
984
+ ) -> Any:
985
+ _ = self
986
+ try:
987
+ arg_iter = convert_iterator(request_iterator)
988
+ resp_obj = await method(arg_iter, context)
989
+ if is_none_type(response_type):
990
+ return empty_pb2.Empty() # type: ignore
991
+ return convert_python_message_to_proto(
992
+ resp_obj, response_type, pb2_module
993
+ )
994
+ except ValidationError as e:
995
+ await context.abort(Errors.INVALID_ARGUMENT, str(e))
996
+ except Exception as e:
997
+ await context.abort(Errors.INTERNAL, str(e))
998
+
999
+ return stub_method
1000
+ else:
1001
+ # Unary request
1002
+ converter = generate_message_converter(input_type)
1003
+
1004
+ if is_output_stream:
1005
+ # Server streaming
1006
+ output_item_type = get_args(response_type)[0]
1007
+
1008
+ if size_of_parameters == 1:
1009
+
1010
+ async def stub_method(
1011
+ self: object,
1012
+ request: Any,
1013
+ context: Any,
1014
+ ) -> AsyncIterator[Any]:
1015
+ _ = self
1016
+ try:
1017
+ if is_none_type(input_type):
1018
+ arg = None
1019
+ else:
1020
+ arg = converter(request)
1021
+ async for resp_obj in method(arg):
1022
+ yield convert_python_message_to_proto(
1023
+ resp_obj, output_item_type, pb2_module
1024
+ )
1025
+ except ValidationError as e:
1026
+ await context.abort(Errors.INVALID_ARGUMENT, str(e))
1027
+ except Exception as e:
1028
+ await context.abort(Errors.INTERNAL, str(e))
1029
+ else: # size_of_parameters == 2
1030
+
1031
+ async def stub_method(
1032
+ self: object,
1033
+ request: Any,
1034
+ context: Any,
1035
+ ) -> AsyncIterator[Any]:
1036
+ _ = self
1037
+ try:
1038
+ if is_none_type(input_type):
1039
+ arg = None
1040
+ else:
1041
+ arg = converter(request)
1042
+ async for resp_obj in method(arg, context):
1043
+ yield convert_python_message_to_proto(
1044
+ resp_obj, output_item_type, pb2_module
1045
+ )
1046
+ except ValidationError as e:
1047
+ await context.abort(Errors.INVALID_ARGUMENT, str(e))
1048
+ except Exception as e:
1049
+ await context.abort(Errors.INTERNAL, str(e))
1050
+
1051
+ return stub_method
1052
+ else:
1053
+ # Unary RPC
1054
+ if size_of_parameters == 1:
1055
+
1056
+ async def stub_method(
1057
+ self: object,
1058
+ request: Any,
1059
+ context: Any,
1060
+ ) -> Any:
1061
+ _ = self
1062
+ try:
1063
+ if is_none_type(input_type):
1064
+ resp_obj = await method(None)
1065
+ else:
1066
+ arg = converter(request)
1067
+ resp_obj = await method(arg)
1068
+
1069
+ if is_none_type(response_type):
1070
+ return empty_pb2.Empty() # type: ignore
1071
+ else:
1072
+ return convert_python_message_to_proto(
1073
+ resp_obj, response_type, pb2_module
1074
+ )
1075
+ except ValidationError as e:
1076
+ await context.abort(Errors.INVALID_ARGUMENT, str(e))
1077
+ except Exception as e:
1078
+ await context.abort(Errors.INTERNAL, str(e))
1079
+ else: # size_of_parameters == 2
1080
+
1081
+ async def stub_method(
1082
+ self: object,
1083
+ request: Any,
1084
+ context: Any,
1085
+ ) -> Any:
1086
+ _ = self
1087
+ try:
1088
+ if is_none_type(input_type):
1089
+ resp_obj = await method(None, context)
1090
+ else:
1091
+ arg = converter(request)
1092
+ resp_obj = await method(arg, context)
1093
+
1094
+ if is_none_type(response_type):
1095
+ return empty_pb2.Empty() # type: ignore
1096
+ else:
1097
+ return convert_python_message_to_proto(
1098
+ resp_obj, response_type, pb2_module
1099
+ )
1100
+ except ValidationError as e:
1101
+ await context.abort(Errors.INVALID_ARGUMENT, str(e))
1102
+ except Exception as e:
1103
+ await context.abort(Errors.INTERNAL, str(e))
1104
+
1105
+ return stub_method
951
1106
 
952
1107
  case _:
953
1108
  raise Exception("Method must have 0, 1, or 2 parameters")
@@ -955,10 +1110,14 @@ def connect_obj_with_stub_async_connecpy(
955
1110
  for method_name, method in get_rpc_methods(obj):
956
1111
  if method.__name__.startswith("_"):
957
1112
  continue
958
- if not asyncio.iscoroutinefunction(method):
959
- raise Exception("Method must be async", method_name)
1113
+ # Check for async generator functions for streaming support
1114
+ if not (
1115
+ asyncio.iscoroutinefunction(method) or inspect.isasyncgenfunction(method)
1116
+ ):
1117
+ raise Exception(f"Method {method_name} must be async or async generator")
960
1118
  a_method = implement_stub_method(method)
961
- setattr(ConcreteServiceClass, method_name, a_method)
1119
+ # Use the original snake_case method name for Connecpy v2.2.0 compatibility
1120
+ setattr(ConcreteServiceClass, method.__name__, a_method)
962
1121
 
963
1122
  return ConcreteServiceClass
964
1123
 
@@ -1510,11 +1669,11 @@ def generate_message_definition(
1510
1669
  fields.append("// length of " + str(metadata_item.len))
1511
1670
  case annotated_types.MinLen:
1512
1671
  fields.append(
1513
- "// minimum length of " + str(metadata_item.min_len)
1672
+ "// minimum length of " + str(metadata_item.min_length)
1514
1673
  )
1515
1674
  case annotated_types.MaxLen:
1516
1675
  fields.append(
1517
- "// maximum length of " + str(metadata_item.max_len)
1676
+ "// maximum length of " + str(metadata_item.max_length)
1518
1677
  )
1519
1678
  case _:
1520
1679
  fields.append("// " + str(metadata_item))
@@ -1550,6 +1709,39 @@ def is_generic_alias(annotation: Any) -> bool:
1550
1709
  return get_origin(annotation) is not None
1551
1710
 
1552
1711
 
1712
+ def format_method_options(method: Any) -> list[str]:
1713
+ """
1714
+ Format protobuf options for a method.
1715
+
1716
+ Args:
1717
+ method: The method to get options from
1718
+
1719
+ Returns:
1720
+ List of formatted option strings
1721
+ """
1722
+ metadata = get_method_options(method)
1723
+ if metadata is None:
1724
+ return []
1725
+
1726
+ return metadata.to_proto_strings()
1727
+
1728
+
1729
+ def check_uses_http_options(obj: object) -> bool:
1730
+ """
1731
+ Check if any method in the service uses HTTP options.
1732
+
1733
+ Args:
1734
+ obj: Service instance
1735
+
1736
+ Returns:
1737
+ True if any method has HTTP options
1738
+ """
1739
+ for method_name, method in get_rpc_methods(obj):
1740
+ if has_http_option(method):
1741
+ return True
1742
+ return False
1743
+
1744
+
1553
1745
  def generate_proto(obj: object, package_name: str = "") -> str:
1554
1746
  """
1555
1747
  Generate a .proto definition from a service class.
@@ -1570,6 +1762,7 @@ def generate_proto(obj: object, package_name: str = "") -> str:
1570
1762
  uses_timestamp = False
1571
1763
  uses_duration = False
1572
1764
  uses_empty = False
1765
+ uses_http_options = check_uses_http_options(obj)
1573
1766
 
1574
1767
  def check_and_set_well_known_types_for_fields(py_type: Any):
1575
1768
  """Check well-known types for field annotations (excludes None/Empty)."""
@@ -1729,9 +1922,24 @@ def generate_proto(obj: object, package_name: str = "") -> str:
1729
1922
  else output_msg_type.__name__
1730
1923
  )
1731
1924
 
1732
- rpc_definitions.append(
1733
- f"rpc {method_name} ({input_str}) returns ({output_str});"
1734
- )
1925
+ # Get method options
1926
+ method_options = format_method_options(method)
1927
+
1928
+ if method_options:
1929
+ # RPC with options - use block format
1930
+ rpc_definitions.append(
1931
+ f"rpc {method_name} ({input_str}) returns ({output_str}) {{"
1932
+ )
1933
+ for option_str in method_options:
1934
+ # Indent each option line
1935
+ for line in option_str.split("\n"):
1936
+ rpc_definitions.append(f" {line}")
1937
+ rpc_definitions.append("}")
1938
+ else:
1939
+ # RPC without options - use simple format
1940
+ rpc_definitions.append(
1941
+ f"rpc {method_name} ({input_str}) returns ({output_str});"
1942
+ )
1735
1943
 
1736
1944
  if not package_name:
1737
1945
  if service_name.endswith("Service"):
@@ -1741,6 +1949,8 @@ def generate_proto(obj: object, package_name: str = "") -> str:
1741
1949
  package_name = package_name.lower() + ".v1"
1742
1950
 
1743
1951
  imports: list[str] = []
1952
+ if uses_http_options:
1953
+ imports.append('import "google/api/annotations.proto";')
1744
1954
  if uses_timestamp:
1745
1955
  imports.append('import "google/protobuf/timestamp.proto";')
1746
1956
  if uses_duration:
@@ -2394,13 +2604,19 @@ def generate_combined_descriptor_set(
2394
2604
  class Server:
2395
2605
  """A simple gRPC server that uses ThreadPoolExecutor for concurrency."""
2396
2606
 
2397
- def __init__(self, max_workers: int = 8, *interceptors: Any) -> None:
2607
+ def __init__(
2608
+ self,
2609
+ max_workers: int = 8,
2610
+ *interceptors: Any,
2611
+ tls: Optional["GrpcTLSConfig"] = None,
2612
+ ) -> None:
2398
2613
  self._server: grpc.Server = grpc.server(
2399
2614
  futures.ThreadPoolExecutor(max_workers), interceptors=interceptors
2400
2615
  )
2401
2616
  self._service_names: list[str] = []
2402
2617
  self._package_name: str = ""
2403
2618
  self._port: int = 50051
2619
+ self._tls_config = tls
2404
2620
 
2405
2621
  def set_package_name(self, package_name: str):
2406
2622
  """Set the package name for .proto generation."""
@@ -2450,7 +2666,16 @@ class Server:
2450
2666
  health_pb2_grpc.add_HealthServicer_to_server(health_servicer, self._server)
2451
2667
  reflection.enable_server_reflection(SERVICE_NAMES, self._server)
2452
2668
 
2453
- self._server.add_insecure_port(f"[::]:{self._port}")
2669
+ if self._tls_config:
2670
+ # Use secure port with TLS
2671
+ credentials = self._tls_config.to_server_credentials()
2672
+ self._server.add_secure_port(f"[::]:{self._port}", credentials)
2673
+ print(f"gRPC server starting with TLS on port {self._port}...")
2674
+ else:
2675
+ # Use insecure port
2676
+ self._server.add_insecure_port(f"[::]:{self._port}")
2677
+ print(f"gRPC server starting on port {self._port}...")
2678
+
2454
2679
  self._server.start()
2455
2680
 
2456
2681
  def handle_signal(signum: signal.Signals, frame: Any):
@@ -2472,11 +2697,16 @@ class Server:
2472
2697
  class AsyncIOServer:
2473
2698
  """An async gRPC server using asyncio."""
2474
2699
 
2475
- def __init__(self, *interceptors: grpc.ServerInterceptor) -> None:
2700
+ def __init__(
2701
+ self,
2702
+ *interceptors: grpc.ServerInterceptor,
2703
+ tls: Optional["GrpcTLSConfig"] = None,
2704
+ ) -> None:
2476
2705
  self._server: grpc.aio.Server = grpc.aio.server(interceptors=interceptors)
2477
2706
  self._service_names: list[str] = []
2478
2707
  self._package_name: str = ""
2479
2708
  self._port: int = 50051
2709
+ self._tls_config = tls
2480
2710
 
2481
2711
  def set_package_name(self, package_name: str):
2482
2712
  """Set the package name for .proto generation."""
@@ -2528,7 +2758,16 @@ class AsyncIOServer:
2528
2758
  health_pb2_grpc.add_HealthServicer_to_server(health_servicer, self._server)
2529
2759
  reflection.enable_server_reflection(SERVICE_NAMES, self._server)
2530
2760
 
2531
- _ = self._server.add_insecure_port(f"[::]:{self._port}")
2761
+ if self._tls_config:
2762
+ # Use secure port with TLS
2763
+ credentials = self._tls_config.to_server_credentials()
2764
+ _ = self._server.add_secure_port(f"[::]:{self._port}", credentials)
2765
+ print(f"gRPC server starting with TLS on port {self._port}...")
2766
+ else:
2767
+ # Use insecure port
2768
+ _ = self._server.add_insecure_port(f"[::]:{self._port}")
2769
+ print(f"gRPC server starting on port {self._port}...")
2770
+
2532
2771
  await self._server.start()
2533
2772
 
2534
2773
  shutdown_event = asyncio.Event()
@@ -2548,116 +2787,19 @@ class AsyncIOServer:
2548
2787
  print("gRPC server shutdown.")
2549
2788
 
2550
2789
 
2551
- class WSGIApp:
2552
- """
2553
- A WSGI-compatible application that can serve gRPC via sonora's grpcWSGI.
2554
- Useful for embedding gRPC within an existing WSGI stack.
2555
- """
2556
-
2557
- def __init__(self, app: Any):
2558
- self._app: grpcWSGI = grpcWSGI(app)
2559
- self._service_names: list[str] = []
2560
- self._package_name: str = ""
2561
-
2562
- def mount(self, obj: object, package_name: str = ""):
2563
- """Generate and compile proto files, then mount the service implementation."""
2564
- pb2_grpc_module, pb2_module = generate_and_compile_proto(obj, package_name) or (
2565
- None,
2566
- None,
2567
- )
2568
- self.mount_using_pb2_modules(pb2_grpc_module, pb2_module, obj)
2569
-
2570
- def mount_using_pb2_modules(
2571
- self, pb2_grpc_module: Any, pb2_module: Any, obj: object
2572
- ):
2573
- """Connect the compiled gRPC modules with the service implementation."""
2574
- concreteServiceClass = connect_obj_with_stub(pb2_grpc_module, pb2_module, obj)
2575
- service_name = obj.__class__.__name__
2576
- service_impl = concreteServiceClass()
2577
- getattr(pb2_grpc_module, f"add_{service_name}Servicer_to_server")(
2578
- service_impl, self._app
2579
- )
2580
- full_service_name = pb2_module.DESCRIPTOR.services_by_name[
2581
- service_name
2582
- ].full_name
2583
- self._service_names.append(full_service_name)
2584
-
2585
- def mount_objs(self, *objs: object):
2586
- """Mount multiple service objects into this WSGI app."""
2587
- for obj in objs:
2588
- self.mount(obj, self._package_name)
2589
-
2590
- def __call__(
2591
- self,
2592
- environ: dict[str, Any],
2593
- start_response: Callable[[str, list[tuple[str, str]]], None],
2594
- ) -> Any:
2595
- """WSGI entry point."""
2596
- return self._app(environ, start_response)
2597
-
2598
-
2599
- class ASGIApp:
2600
- """
2601
- An ASGI-compatible application that can serve gRPC via sonora's grpcASGI.
2602
- Useful for embedding gRPC within an existing ASGI stack.
2603
- """
2604
-
2605
- def __init__(self, app: Any):
2606
- self._app: grpcASGI = grpcASGI(app)
2607
- self._service_names: list[str] = []
2608
- self._package_name: str = ""
2609
-
2610
- def mount(self, obj: object, package_name: str = ""):
2611
- """Generate and compile proto files, then mount the async service implementation."""
2612
- pb2_grpc_module, pb2_module = generate_and_compile_proto(obj, package_name) or (
2613
- None,
2614
- None,
2615
- )
2616
- self.mount_using_pb2_modules(pb2_grpc_module, pb2_module, obj)
2617
-
2618
- def mount_using_pb2_modules(
2619
- self, pb2_grpc_module: Any, pb2_module: Any, obj: object
2620
- ):
2621
- """Connect the compiled gRPC modules with the async service implementation."""
2622
- concreteServiceClass = connect_obj_with_stub_async(
2623
- pb2_grpc_module, pb2_module, obj
2624
- )
2625
- service_name = obj.__class__.__name__
2626
- service_impl = concreteServiceClass()
2627
- getattr(pb2_grpc_module, f"add_{service_name}Servicer_to_server")(
2628
- service_impl, self._app
2629
- )
2630
- full_service_name = pb2_module.DESCRIPTOR.services_by_name[
2631
- service_name
2632
- ].full_name
2633
- self._service_names.append(full_service_name)
2634
-
2635
- def mount_objs(self, *objs: object):
2636
- """Mount multiple service objects into this ASGI app."""
2637
- for obj in objs:
2638
- self.mount(obj, self._package_name)
2639
-
2640
- async def __call__(
2641
- self,
2642
- scope: dict[str, Any],
2643
- receive: Callable[[], Any],
2644
- send: Callable[[dict[str, Any]], Any],
2645
- ) -> Any:
2646
- """ASGI entry point."""
2647
- _ = await self._app(scope, receive, send)
2648
-
2649
-
2650
2790
  def get_connecpy_asgi_app_class(connecpy_module: Any, service_name: str):
2791
+ """Get the ASGI application class from connecpy module (Connecpy v2.x)."""
2651
2792
  return getattr(connecpy_module, f"{service_name}ASGIApplication")
2652
2793
 
2653
2794
 
2654
2795
  def get_connecpy_wsgi_app_class(connecpy_module: Any, service_name: str):
2796
+ """Get the WSGI application class from connecpy module (Connecpy v2.x)."""
2655
2797
  return getattr(connecpy_module, f"{service_name}WSGIApplication")
2656
2798
 
2657
2799
 
2658
- class ConnecpyASGIApp:
2800
+ class ASGIApp:
2659
2801
  """
2660
- An ASGI-compatible application that can serve Connect-RPC via Connecpy's ConnecpyASGIApp.
2802
+ An ASGI-compatible application that can serve Connect-RPC via Connecpy.
2661
2803
  """
2662
2804
 
2663
2805
  def __init__(self):
@@ -2727,9 +2869,9 @@ class ConnecpyASGIApp:
2727
2869
  await send({"type": "http.response.body", "body": b"Not Found"})
2728
2870
 
2729
2871
 
2730
- class ConnecpyWSGIApp:
2872
+ class WSGIApp:
2731
2873
  """
2732
- A WSGI-compatible application that can serve Connect-RPC via Connecpy's ConnecpyWSGIApp.
2874
+ A WSGI-compatible application that can serve Connect-RPC via Connecpy.
2733
2875
  """
2734
2876
 
2735
2877
  def __init__(self):
@@ -2774,8 +2916,10 @@ class ConnecpyWSGIApp:
2774
2916
  def __call__(
2775
2917
  self,
2776
2918
  environ: dict[str, Any],
2777
- start_response: Callable[[str, list[tuple[str, str]]], None],
2778
- ) -> Any:
2919
+ start_response: Callable[
2920
+ [str, list[tuple[str, str]]], Callable[[bytes], object]
2921
+ ],
2922
+ ) -> Iterable[bytes]:
2779
2923
  """WSGI entry point with routing for multiple services."""
2780
2924
  path = environ.get("PATH_INFO", "")
2781
2925