pydantic-rpc 0.11.0__py3-none-any.whl → 0.12.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
|
@@ -27,7 +27,7 @@ import annotated_types
|
|
|
27
27
|
import grpc
|
|
28
28
|
from grpc import ServicerContext
|
|
29
29
|
import grpc_tools
|
|
30
|
-
from
|
|
30
|
+
from connectrpc.code import Code as Errors
|
|
31
31
|
|
|
32
32
|
# Protobuf Python modules for Timestamp, Duration (requires protobuf / grpcio)
|
|
33
33
|
from google.protobuf import duration_pb2, timestamp_pb2, empty_pb2
|
|
@@ -726,15 +726,15 @@ def connect_obj_with_stub_async(
|
|
|
726
726
|
return ConcreteServiceClass
|
|
727
727
|
|
|
728
728
|
|
|
729
|
-
def
|
|
730
|
-
|
|
729
|
+
def connect_obj_with_stub_connect_python(
|
|
730
|
+
connect_python_module: Any, pb2_module: Any, obj: object
|
|
731
731
|
) -> type:
|
|
732
732
|
"""
|
|
733
|
-
Connect a Python service object to a
|
|
733
|
+
Connect a Python service object to a Connect Python stub.
|
|
734
734
|
"""
|
|
735
735
|
service_class = obj.__class__
|
|
736
736
|
stub_class_name = service_class.__name__
|
|
737
|
-
stub_class = getattr(
|
|
737
|
+
stub_class = getattr(connect_python_module, stub_class_name)
|
|
738
738
|
|
|
739
739
|
class ConcreteServiceClass(stub_class):
|
|
740
740
|
pass
|
|
@@ -845,15 +845,15 @@ def connect_obj_with_stub_connecpy(
|
|
|
845
845
|
return ConcreteServiceClass
|
|
846
846
|
|
|
847
847
|
|
|
848
|
-
def
|
|
849
|
-
|
|
848
|
+
def connect_obj_with_stub_async_connect_python(
|
|
849
|
+
connect_python_module: Any, pb2_module: Any, obj: object
|
|
850
850
|
) -> type:
|
|
851
851
|
"""
|
|
852
|
-
Connect a Python service object to a
|
|
852
|
+
Connect a Python service object to a Connect Python stub for async methods with streaming support.
|
|
853
853
|
"""
|
|
854
854
|
service_class = obj.__class__
|
|
855
855
|
stub_class_name = service_class.__name__
|
|
856
|
-
stub_class = getattr(
|
|
856
|
+
stub_class = getattr(connect_python_module, stub_class_name)
|
|
857
857
|
|
|
858
858
|
class ConcreteServiceClass(stub_class):
|
|
859
859
|
pass
|
|
@@ -2030,10 +2030,10 @@ def generate_grpc_code(proto_path: Path) -> types.ModuleType | None:
|
|
|
2030
2030
|
return module
|
|
2031
2031
|
|
|
2032
2032
|
|
|
2033
|
-
def
|
|
2033
|
+
def generate_connect_python_code(proto_path: Path) -> types.ModuleType | None:
|
|
2034
2034
|
"""
|
|
2035
|
-
Run protoc with the
|
|
2036
|
-
Writes
|
|
2035
|
+
Run protoc with the Connect Python plugin to generate Python Connect code from proto_path.
|
|
2036
|
+
Writes foo_connect_python.py next to proto_path, then imports and returns that module.
|
|
2037
2037
|
"""
|
|
2038
2038
|
# 1) Ensure the .proto exists
|
|
2039
2039
|
if not proto_path.is_file():
|
|
@@ -2051,7 +2051,7 @@ def generate_connecpy_code(proto_path: Path) -> types.ModuleType | None:
|
|
|
2051
2051
|
"protoc", # Dummy program name (required for protoc.main)
|
|
2052
2052
|
"-I.",
|
|
2053
2053
|
f"-I{well_known_path}",
|
|
2054
|
-
f"--
|
|
2054
|
+
f"--connect-python_out={out_str}",
|
|
2055
2055
|
proto_path.name,
|
|
2056
2056
|
]
|
|
2057
2057
|
|
|
@@ -2065,7 +2065,7 @@ def generate_connecpy_code(proto_path: Path) -> types.ModuleType | None:
|
|
|
2065
2065
|
|
|
2066
2066
|
# 4) Locate the generated file
|
|
2067
2067
|
base_name = proto_path.stem # "foo"
|
|
2068
|
-
generated_filename = f"{base_name}
|
|
2068
|
+
generated_filename = f"{base_name}_connect.py" # "foo_connect.py"
|
|
2069
2069
|
generated_filepath = out_dir / generated_filename
|
|
2070
2070
|
|
|
2071
2071
|
# 5) Add out_dir to sys.path so we can import by filename
|
|
@@ -2074,7 +2074,7 @@ def generate_connecpy_code(proto_path: Path) -> types.ModuleType | None:
|
|
|
2074
2074
|
|
|
2075
2075
|
# 6) Load and return the module
|
|
2076
2076
|
spec = importlib.util.spec_from_file_location(
|
|
2077
|
-
base_name + "
|
|
2077
|
+
base_name + "_connect", str(generated_filepath)
|
|
2078
2078
|
)
|
|
2079
2079
|
if spec is None or spec.loader is None:
|
|
2080
2080
|
return None
|
|
@@ -2259,7 +2259,7 @@ def get_proto_path(proto_filename: str) -> Path:
|
|
|
2259
2259
|
return base / proto_filename
|
|
2260
2260
|
|
|
2261
2261
|
|
|
2262
|
-
def
|
|
2262
|
+
def generate_and_compile_proto_using_connect_python(
|
|
2263
2263
|
obj: object,
|
|
2264
2264
|
package_name: str = "",
|
|
2265
2265
|
existing_proto_path: Path | None = None,
|
|
@@ -2268,7 +2268,7 @@ def generate_and_compile_proto_using_connecpy(
|
|
|
2268
2268
|
import importlib
|
|
2269
2269
|
|
|
2270
2270
|
pb2_module = None
|
|
2271
|
-
|
|
2271
|
+
connect_python_module = None
|
|
2272
2272
|
|
|
2273
2273
|
try:
|
|
2274
2274
|
pb2_module = importlib.import_module(
|
|
@@ -2278,14 +2278,14 @@ def generate_and_compile_proto_using_connecpy(
|
|
|
2278
2278
|
pass
|
|
2279
2279
|
|
|
2280
2280
|
try:
|
|
2281
|
-
|
|
2282
|
-
f"{obj.__class__.__name__.lower()}
|
|
2281
|
+
connect_python_module = importlib.import_module(
|
|
2282
|
+
f"{obj.__class__.__name__.lower()}_connect"
|
|
2283
2283
|
)
|
|
2284
2284
|
except ImportError:
|
|
2285
2285
|
pass
|
|
2286
2286
|
|
|
2287
|
-
if
|
|
2288
|
-
return
|
|
2287
|
+
if connect_python_module is not None and pb2_module is not None:
|
|
2288
|
+
return connect_python_module, pb2_module
|
|
2289
2289
|
|
|
2290
2290
|
# If the modules are not found, generate and compile the proto files.
|
|
2291
2291
|
|
|
@@ -2306,10 +2306,10 @@ def generate_and_compile_proto_using_connecpy(
|
|
|
2306
2306
|
if gen_pb is None:
|
|
2307
2307
|
raise Exception("Generating pb code")
|
|
2308
2308
|
|
|
2309
|
-
|
|
2310
|
-
if
|
|
2311
|
-
raise Exception("Generating
|
|
2312
|
-
return
|
|
2309
|
+
gen_connect_python = generate_connect_python_code(proto_file_path)
|
|
2310
|
+
if gen_connect_python is None:
|
|
2311
|
+
raise Exception("Generating Connect Python code")
|
|
2312
|
+
return gen_connect_python, gen_pb
|
|
2313
2313
|
|
|
2314
2314
|
|
|
2315
2315
|
def is_combined_proto_enabled() -> bool:
|
|
@@ -2804,20 +2804,24 @@ class AsyncIOServer:
|
|
|
2804
2804
|
await self._server.stop(10)
|
|
2805
2805
|
print("gRPC server shutdown.")
|
|
2806
2806
|
|
|
2807
|
+
async def stop(self, grace: float = 10.0):
|
|
2808
|
+
"""Stop the gRPC server gracefully."""
|
|
2809
|
+
await self._server.stop(grace)
|
|
2807
2810
|
|
|
2808
|
-
def get_connecpy_asgi_app_class(connecpy_module: Any, service_name: str):
|
|
2809
|
-
"""Get the ASGI application class from connecpy module (Connecpy v2.x)."""
|
|
2810
|
-
return getattr(connecpy_module, f"{service_name}ASGIApplication")
|
|
2811
2811
|
|
|
2812
|
+
def get_connect_python_asgi_app_class(connect_python_module: Any, service_name: str):
|
|
2813
|
+
"""Get the ASGI application class from connect-python module."""
|
|
2814
|
+
return getattr(connect_python_module, f"{service_name}ASGIApplication")
|
|
2812
2815
|
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
+
|
|
2817
|
+
def get_connect_python_wsgi_app_class(connect_python_module: Any, service_name: str):
|
|
2818
|
+
"""Get the WSGI application class from connect-python module."""
|
|
2819
|
+
return getattr(connect_python_module, f"{service_name}WSGIApplication")
|
|
2816
2820
|
|
|
2817
2821
|
|
|
2818
2822
|
class ASGIApp:
|
|
2819
2823
|
"""
|
|
2820
|
-
An ASGI-compatible application that can serve Connect-RPC via
|
|
2824
|
+
An ASGI-compatible application that can serve Connect-RPC via connect-python.
|
|
2821
2825
|
"""
|
|
2822
2826
|
|
|
2823
2827
|
def __init__(self, service: Optional[object] = None, package_name: str = ""):
|
|
@@ -2828,23 +2832,25 @@ class ASGIApp:
|
|
|
2828
2832
|
|
|
2829
2833
|
def mount(self, obj: object, package_name: str = ""):
|
|
2830
2834
|
"""Generate and compile proto files, then mount the async service implementation."""
|
|
2831
|
-
|
|
2832
|
-
obj, package_name
|
|
2835
|
+
connect_python_module, pb2_module = (
|
|
2836
|
+
generate_and_compile_proto_using_connect_python(obj, package_name)
|
|
2833
2837
|
)
|
|
2834
|
-
self.mount_using_pb2_modules(
|
|
2838
|
+
self.mount_using_pb2_modules(connect_python_module, pb2_module, obj)
|
|
2835
2839
|
|
|
2836
2840
|
def mount_using_pb2_modules(
|
|
2837
|
-
self,
|
|
2841
|
+
self, connect_python_module: Any, pb2_module: Any, obj: object
|
|
2838
2842
|
):
|
|
2839
|
-
"""Connect the compiled
|
|
2840
|
-
concreteServiceClass =
|
|
2841
|
-
|
|
2843
|
+
"""Connect the compiled connect-python and pb2 modules with the async service implementation."""
|
|
2844
|
+
concreteServiceClass = connect_obj_with_stub_async_connect_python(
|
|
2845
|
+
connect_python_module, pb2_module, obj
|
|
2842
2846
|
)
|
|
2843
2847
|
service_name = obj.__class__.__name__
|
|
2844
2848
|
service_impl = concreteServiceClass()
|
|
2845
2849
|
|
|
2846
2850
|
# Get the service-specific ASGI application class
|
|
2847
|
-
app_class =
|
|
2851
|
+
app_class = get_connect_python_asgi_app_class(
|
|
2852
|
+
connect_python_module, service_name
|
|
2853
|
+
)
|
|
2848
2854
|
app = app_class(service=service_impl)
|
|
2849
2855
|
|
|
2850
2856
|
# Store the app and its path for routing
|
|
@@ -2899,7 +2905,7 @@ class ASGIApp:
|
|
|
2899
2905
|
|
|
2900
2906
|
class WSGIApp:
|
|
2901
2907
|
"""
|
|
2902
|
-
A WSGI-compatible application that can serve Connect-RPC via
|
|
2908
|
+
A WSGI-compatible application that can serve Connect-RPC via connect-python.
|
|
2903
2909
|
"""
|
|
2904
2910
|
|
|
2905
2911
|
def __init__(self, service: Optional[object] = None, package_name: str = ""):
|
|
@@ -2910,23 +2916,25 @@ class WSGIApp:
|
|
|
2910
2916
|
|
|
2911
2917
|
def mount(self, obj: object, package_name: str = ""):
|
|
2912
2918
|
"""Generate and compile proto files, then mount the sync service implementation."""
|
|
2913
|
-
|
|
2914
|
-
obj, package_name
|
|
2919
|
+
connect_python_module, pb2_module = (
|
|
2920
|
+
generate_and_compile_proto_using_connect_python(obj, package_name)
|
|
2915
2921
|
)
|
|
2916
|
-
self.mount_using_pb2_modules(
|
|
2922
|
+
self.mount_using_pb2_modules(connect_python_module, pb2_module, obj)
|
|
2917
2923
|
|
|
2918
2924
|
def mount_using_pb2_modules(
|
|
2919
|
-
self,
|
|
2925
|
+
self, connect_python_module: Any, pb2_module: Any, obj: object
|
|
2920
2926
|
):
|
|
2921
|
-
"""Connect the compiled
|
|
2922
|
-
concreteServiceClass =
|
|
2923
|
-
|
|
2927
|
+
"""Connect the compiled connect-python and pb2 modules with the sync service implementation."""
|
|
2928
|
+
concreteServiceClass = connect_obj_with_stub_connect_python(
|
|
2929
|
+
connect_python_module, pb2_module, obj
|
|
2924
2930
|
)
|
|
2925
2931
|
service_name = obj.__class__.__name__
|
|
2926
2932
|
service_impl = concreteServiceClass()
|
|
2927
2933
|
|
|
2928
2934
|
# Get the service-specific WSGI application class
|
|
2929
|
-
app_class =
|
|
2935
|
+
app_class = get_connect_python_wsgi_app_class(
|
|
2936
|
+
connect_python_module, service_name
|
|
2937
|
+
)
|
|
2930
2938
|
app = app_class(service=service_impl)
|
|
2931
2939
|
|
|
2932
2940
|
# Store the app and its path for routing
|
pydantic_rpc/decorators.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from typing import Any, Callable, Dict, List, Optional, TypeVar, Type
|
|
4
4
|
from functools import wraps
|
|
5
5
|
import grpc
|
|
6
|
-
from
|
|
6
|
+
from connectrpc.code import Code as ConnectErrors
|
|
7
7
|
|
|
8
8
|
from .options import OptionMetadata, OPTION_METADATA_ATTR
|
|
9
9
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pydantic-rpc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
|
|
5
5
|
Author: Yasushi Itoh
|
|
6
6
|
Requires-Dist: annotated-types==0.7.0
|
|
@@ -9,7 +9,8 @@ Requires-Dist: grpcio>=1.56.2
|
|
|
9
9
|
Requires-Dist: grpcio-tools>=1.56.2
|
|
10
10
|
Requires-Dist: grpcio-reflection>=1.56.2
|
|
11
11
|
Requires-Dist: grpcio-health-checking>=1.56.2
|
|
12
|
-
Requires-Dist:
|
|
12
|
+
Requires-Dist: connect-python>=0.5.0
|
|
13
|
+
Requires-Dist: protoc-gen-connect-python>=0.1.0
|
|
13
14
|
Requires-Dist: mcp>=1.9.4
|
|
14
15
|
Requires-Dist: starlette>=0.27.0
|
|
15
16
|
Requires-Python: >=3.11
|
|
@@ -1247,6 +1248,64 @@ class MyMessage(Message):
|
|
|
1247
1248
|
|
|
1248
1249
|
This approach works because protobuf allows message types within `oneof` fields, and the collections are contained within those messages.
|
|
1249
1250
|
|
|
1251
|
+
## 🔧 Development
|
|
1252
|
+
|
|
1253
|
+
This project uses [`just`](https://github.com/casey/just) as a command runner for development tasks.
|
|
1254
|
+
|
|
1255
|
+
### Installing just
|
|
1256
|
+
|
|
1257
|
+
**macOS:**
|
|
1258
|
+
```bash
|
|
1259
|
+
brew install just
|
|
1260
|
+
```
|
|
1261
|
+
|
|
1262
|
+
**Linux:**
|
|
1263
|
+
```bash
|
|
1264
|
+
curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to ~/bin
|
|
1265
|
+
```
|
|
1266
|
+
|
|
1267
|
+
**Windows:**
|
|
1268
|
+
Download from [GitHub releases](https://github.com/casey/just/releases)
|
|
1269
|
+
|
|
1270
|
+
### Quick Start
|
|
1271
|
+
|
|
1272
|
+
```bash
|
|
1273
|
+
# Install dependencies
|
|
1274
|
+
just install
|
|
1275
|
+
|
|
1276
|
+
# Run tests
|
|
1277
|
+
just test # or just t
|
|
1278
|
+
|
|
1279
|
+
# Format and lint code
|
|
1280
|
+
just format # or just f
|
|
1281
|
+
just lint # or just l
|
|
1282
|
+
|
|
1283
|
+
# Run all checks (lint + tests)
|
|
1284
|
+
just check # or just c
|
|
1285
|
+
|
|
1286
|
+
# See all available commands
|
|
1287
|
+
just --list
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1290
|
+
### Running Examples
|
|
1291
|
+
|
|
1292
|
+
```bash
|
|
1293
|
+
# Start servers
|
|
1294
|
+
just greeting-server # gRPC server on port 50051
|
|
1295
|
+
just greeting-asgi # Connect RPC ASGI on port 8000
|
|
1296
|
+
just greeting-wsgi # Connect RPC WSGI on port 3000
|
|
1297
|
+
|
|
1298
|
+
# Test with buf curl (in another terminal)
|
|
1299
|
+
just greet # gRPC request
|
|
1300
|
+
just connect-greet # Connect RPC request
|
|
1301
|
+
just wsgi-greet # WSGI request
|
|
1302
|
+
|
|
1303
|
+
# Custom names
|
|
1304
|
+
just greet-name Alice
|
|
1305
|
+
just connect-greet-name Bob
|
|
1306
|
+
```
|
|
1307
|
+
|
|
1308
|
+
For more development commands and options, see the [Justfile](Justfile) or run `just --list`.
|
|
1250
1309
|
|
|
1251
1310
|
## TODO
|
|
1252
1311
|
- [x] Streaming Support
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
pydantic_rpc/__init__.py,sha256=c5e0b5d4ed6de3e3c7c1ada5389e704ce149ea56ca0930392db6abdf222b4e86,871
|
|
2
|
-
pydantic_rpc/core.py,sha256=
|
|
3
|
-
pydantic_rpc/decorators.py,sha256=
|
|
2
|
+
pydantic_rpc/core.py,sha256=450c03111224052a3e2b85567ffd16a0ff6dd4d6f02bcfa23c09f6d39cf30861,114578
|
|
3
|
+
pydantic_rpc/decorators.py,sha256=94d87825e034b6303df46dd74e6c8c7ed5c88133aa504d61249a83dbf82f1de3,6219
|
|
4
4
|
pydantic_rpc/mcp/__init__.py,sha256=f05ad62cc38db5c5972e16870c484523c58c5ac650d8454706f1ce4539cc7a52,123
|
|
5
5
|
pydantic_rpc/mcp/converter.py,sha256=b60dcaf82e6bff6be4b2ab8b1e9f2d16e09cb98d8f00182a4f6f73d1a78848a4,4158
|
|
6
6
|
pydantic_rpc/mcp/exporter.py,sha256=20833662f9a973ed9e1a705b9b59aaa2533de6c514ebd87ef66664a430a0d04f,10239
|
|
7
7
|
pydantic_rpc/options.py,sha256=6094036184a500b92715fd91d31ecc501460a56de47dcf6024c6335ae8e5d6e3,4561
|
|
8
8
|
pydantic_rpc/py.typed,sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,0
|
|
9
9
|
pydantic_rpc/tls.py,sha256=0eb290cc09c8ebedc71e7a97c01736d9675a314f70ebd80a2ce73e3f1689d359,3567
|
|
10
|
-
pydantic_rpc-0.
|
|
11
|
-
pydantic_rpc-0.
|
|
12
|
-
pydantic_rpc-0.
|
|
10
|
+
pydantic_rpc-0.12.0.dist-info/WHEEL,sha256=ab6157bc637547491fb4567cd7ddf26b04d63382916ca16c29a5c8e94c9c9ef7,79
|
|
11
|
+
pydantic_rpc-0.12.0.dist-info/METADATA,sha256=daa91c220e04adae53cb3e654d249f2feaca8e461f84e9d03f70fad2b48e403b,36156
|
|
12
|
+
pydantic_rpc-0.12.0.dist-info/RECORD,,
|
|
File without changes
|