pydantic-rpc 0.2.0__py3-none-any.whl → 0.2.2__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 +75 -37
- {pydantic_rpc-0.2.0.dist-info → pydantic_rpc-0.2.2.dist-info}/METADATA +37 -3
- pydantic_rpc-0.2.2.dist-info/RECORD +7 -0
- pydantic_rpc-0.2.0.dist-info/RECORD +0 -7
- {pydantic_rpc-0.2.0.dist-info → pydantic_rpc-0.2.2.dist-info}/WHEEL +0 -0
- {pydantic_rpc-0.2.0.dist-info → pydantic_rpc-0.2.2.dist-info}/licenses/LICENSE +0 -0
pydantic_rpc/core.py
CHANGED
|
@@ -24,10 +24,11 @@ from grpc_health.v1 import health_pb2, health_pb2_grpc
|
|
|
24
24
|
from grpc_health.v1.health import HealthServicer
|
|
25
25
|
from grpc_reflection.v1alpha import reflection
|
|
26
26
|
from grpc_tools import protoc
|
|
27
|
-
from pydantic import BaseModel
|
|
27
|
+
from pydantic import BaseModel, ValidationError
|
|
28
28
|
from sonora.wsgi import grpcWSGI
|
|
29
29
|
from sonora.asgi import grpcASGI
|
|
30
30
|
from connecpy.asgi import ConnecpyASGIApp as ConnecpyASGI
|
|
31
|
+
from connecpy.errors import Errors
|
|
31
32
|
|
|
32
33
|
# Protobuf Python modules for Timestamp, Duration (requires protobuf / grpcio)
|
|
33
34
|
from google.protobuf import timestamp_pb2, duration_pb2
|
|
@@ -198,25 +199,35 @@ def connect_obj_with_stub(pb2_grpc_module, pb2_module, service_obj: object) -> t
|
|
|
198
199
|
case 1:
|
|
199
200
|
|
|
200
201
|
def stub_method1(self, request, context, method=method):
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
try:
|
|
203
|
+
# Convert request to Python object
|
|
204
|
+
arg = converter(request)
|
|
205
|
+
# Invoke the actual method
|
|
206
|
+
resp_obj = method(arg)
|
|
207
|
+
# Convert the returned Python Message to a protobuf message
|
|
208
|
+
return convert_python_message_to_proto(
|
|
209
|
+
resp_obj, response_type, pb2_module
|
|
210
|
+
)
|
|
211
|
+
except ValidationError as e:
|
|
212
|
+
return context.abort(grpc.StatusCode.INVALID_ARGUMENT, str(e))
|
|
213
|
+
except Exception as e:
|
|
214
|
+
return context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
209
215
|
|
|
210
216
|
return stub_method1
|
|
211
217
|
|
|
212
218
|
case 2:
|
|
213
219
|
|
|
214
220
|
def stub_method2(self, request, context, method=method):
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
try:
|
|
222
|
+
arg = converter(request)
|
|
223
|
+
resp_obj = method(arg, context)
|
|
224
|
+
return convert_python_message_to_proto(
|
|
225
|
+
resp_obj, response_type, pb2_module
|
|
226
|
+
)
|
|
227
|
+
except ValidationError as e:
|
|
228
|
+
return context.abort(grpc.StatusCode.INVALID_ARGUMENT, str(e))
|
|
229
|
+
except Exception as e:
|
|
230
|
+
return context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
220
231
|
|
|
221
232
|
return stub_method2
|
|
222
233
|
|
|
@@ -255,22 +266,32 @@ def connect_obj_with_stub_async(pb2_grpc_module, pb2_module, obj: object) -> typ
|
|
|
255
266
|
case 1:
|
|
256
267
|
|
|
257
268
|
async def stub_method1(self, request, context, method=method):
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
269
|
+
try:
|
|
270
|
+
arg = converter(request)
|
|
271
|
+
resp_obj = await method(arg)
|
|
272
|
+
return convert_python_message_to_proto(
|
|
273
|
+
resp_obj, response_type, pb2_module
|
|
274
|
+
)
|
|
275
|
+
except ValidationError as e:
|
|
276
|
+
await context.abort(grpc.StatusCode.INVALID_ARGUMENT, str(e))
|
|
277
|
+
except Exception as e:
|
|
278
|
+
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
263
279
|
|
|
264
280
|
return stub_method1
|
|
265
281
|
|
|
266
282
|
case 2:
|
|
267
283
|
|
|
268
284
|
async def stub_method2(self, request, context, method=method):
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
285
|
+
try:
|
|
286
|
+
arg = converter(request)
|
|
287
|
+
resp_obj = await method(arg, context)
|
|
288
|
+
return convert_python_message_to_proto(
|
|
289
|
+
resp_obj, response_type, pb2_module
|
|
290
|
+
)
|
|
291
|
+
except ValidationError as e:
|
|
292
|
+
await context.abort(grpc.StatusCode.INVALID_ARGUMENT, str(e))
|
|
293
|
+
except Exception as e:
|
|
294
|
+
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
274
295
|
|
|
275
296
|
return stub_method2
|
|
276
297
|
|
|
@@ -302,7 +323,6 @@ def connect_obj_with_stub_async_connecpy(
|
|
|
302
323
|
|
|
303
324
|
def implement_stub_method(method):
|
|
304
325
|
sig = inspect.signature(method)
|
|
305
|
-
print(type(method))
|
|
306
326
|
arg_type = get_request_arg_type(sig)
|
|
307
327
|
converter = generate_message_converter(arg_type)
|
|
308
328
|
response_type = sig.return_annotation
|
|
@@ -312,22 +332,32 @@ def connect_obj_with_stub_async_connecpy(
|
|
|
312
332
|
case 1:
|
|
313
333
|
|
|
314
334
|
async def stub_method1(self, request, context, method=method):
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
335
|
+
try:
|
|
336
|
+
arg = converter(request)
|
|
337
|
+
resp_obj = await method(arg)
|
|
338
|
+
return convert_python_message_to_proto(
|
|
339
|
+
resp_obj, response_type, pb2_module
|
|
340
|
+
)
|
|
341
|
+
except ValidationError as e:
|
|
342
|
+
await context.abort(Errors.InvalidArgument, str(e))
|
|
343
|
+
except Exception as e:
|
|
344
|
+
await context.abort(Errors.Internal, str(e))
|
|
320
345
|
|
|
321
346
|
return stub_method1
|
|
322
347
|
|
|
323
348
|
case 2:
|
|
324
349
|
|
|
325
350
|
async def stub_method2(self, request, context, method=method):
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
351
|
+
try:
|
|
352
|
+
arg = converter(request)
|
|
353
|
+
resp_obj = await method(arg, context)
|
|
354
|
+
return convert_python_message_to_proto(
|
|
355
|
+
resp_obj, response_type, pb2_module
|
|
356
|
+
)
|
|
357
|
+
except ValidationError as e:
|
|
358
|
+
await context.abort(Errors.InvalidArgument, str(e))
|
|
359
|
+
except Exception as e:
|
|
360
|
+
await context.abort(Errors.Internal, str(e))
|
|
331
361
|
|
|
332
362
|
return stub_method2
|
|
333
363
|
|
|
@@ -865,7 +895,11 @@ def generate_and_compile_proto(obj: object, package_name: str = ""):
|
|
|
865
895
|
pb2_grpc_module = importlib.import_module(
|
|
866
896
|
f"{obj.__class__.__name__.lower()}_pb2_grpc"
|
|
867
897
|
)
|
|
868
|
-
|
|
898
|
+
|
|
899
|
+
if pb2_grpc_module is not None and pb2_module is not None:
|
|
900
|
+
return pb2_grpc_module, pb2_module
|
|
901
|
+
|
|
902
|
+
# If the modules are not found, generate and compile the proto files.
|
|
869
903
|
|
|
870
904
|
klass = obj.__class__
|
|
871
905
|
proto_file = generate_proto(obj, package_name)
|
|
@@ -892,7 +926,11 @@ def generate_and_compile_proto_using_connecpy(obj: object, package_name: str = "
|
|
|
892
926
|
connecpy_module = importlib.import_module(
|
|
893
927
|
f"{obj.__class__.__name__.lower()}_connecpy"
|
|
894
928
|
)
|
|
895
|
-
|
|
929
|
+
|
|
930
|
+
if connecpy_module is not None and pb2_module is not None:
|
|
931
|
+
return connecpy_module, pb2_module
|
|
932
|
+
|
|
933
|
+
# If the modules are not found, generate and compile the proto files.
|
|
896
934
|
|
|
897
935
|
klass = obj.__class__
|
|
898
936
|
proto_file = generate_proto(obj, package_name)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pydantic-rpc
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
|
|
5
5
|
Author: Yasushi Itoh
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -14,10 +14,10 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
|
|
15
15
|
# 🚀 PydanticRPC
|
|
16
16
|
|
|
17
|
-
**PydanticRPC** is a Python library that enables you to rapidly expose Pydantic models via gRPC/ConnectRPC 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.
|
|
17
|
+
**PydanticRPC** is a Python library that enables you to rapidly expose [Pydantic](https://docs.pydantic.dev/) models via [gRPC](https://grpc.io/)/[ConnectRPC](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.
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
Below is an example of a simple gRPC service that exposes a PydanticAI agent:
|
|
20
|
+
Below is an example of a simple gRPC service that exposes a [PydanticAI](https://ai.pydantic.dev/) agent:
|
|
21
21
|
|
|
22
22
|
```python
|
|
23
23
|
import asyncio
|
|
@@ -54,6 +54,40 @@ if __name__ == "__main__":
|
|
|
54
54
|
loop.run_until_complete(s.run(OlympicsLocationAgent()))
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
And here is an example of a simple ConnectRPC service that exposes the same agent as an ASGI application:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import asyncio
|
|
61
|
+
|
|
62
|
+
from pydantic_ai import Agent
|
|
63
|
+
from pydantic_rpc import ConnecpyASGIApp, Message
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CityLocation(Message):
|
|
67
|
+
city: str
|
|
68
|
+
country: str
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Olympics(Message):
|
|
72
|
+
year: int
|
|
73
|
+
|
|
74
|
+
def prompt(self):
|
|
75
|
+
return f"Where were the Olympics held in {self.year}?"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class OlympicsLocationAgent:
|
|
79
|
+
def __init__(self):
|
|
80
|
+
self._agent = Agent("ollama:llama3.2", result_type=CityLocation)
|
|
81
|
+
|
|
82
|
+
async def ask(self, req: Olympics) -> CityLocation:
|
|
83
|
+
result = await self._agent.run(req.prompt())
|
|
84
|
+
return result.data
|
|
85
|
+
|
|
86
|
+
app = ConnecpyASGIApp()
|
|
87
|
+
app.mount(OlympicsLocationAgent())
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
|
|
57
91
|
|
|
58
92
|
## 💡 Key Features
|
|
59
93
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
pydantic_rpc/__init__.py,sha256=AWYjSmYQcMqsqGmGK4k-pQQhX6RBBgkTvNcQtCtsctU,113
|
|
2
|
+
pydantic_rpc/core.py,sha256=awYIYiWOtvRsgom6xeC5Ipaa1DoBjT2IJSPwQ9h2Sv4,44434
|
|
3
|
+
pydantic_rpc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pydantic_rpc-0.2.2.dist-info/METADATA,sha256=-Rx8bKZe9Grea0Qk6KDY3yIL6s1UlRbgjdYFO23XNOE,8933
|
|
5
|
+
pydantic_rpc-0.2.2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
6
|
+
pydantic_rpc-0.2.2.dist-info/licenses/LICENSE,sha256=Y6jkAm2VqPqoGIGQ-mEQCecNfteQ2LwdpYhC5XiH_cA,1069
|
|
7
|
+
pydantic_rpc-0.2.2.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
pydantic_rpc/__init__.py,sha256=AWYjSmYQcMqsqGmGK4k-pQQhX6RBBgkTvNcQtCtsctU,113
|
|
2
|
-
pydantic_rpc/core.py,sha256=IynEU8bReuJM--MvOnw3zlDpneJFROoJNk35pM4fgvI,42286
|
|
3
|
-
pydantic_rpc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
pydantic_rpc-0.2.0.dist-info/METADATA,sha256=RBvdwlbQS1Q020U8Tpp0T-CPswc1xYAlsurXM414Awo,8081
|
|
5
|
-
pydantic_rpc-0.2.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
6
|
-
pydantic_rpc-0.2.0.dist-info/licenses/LICENSE,sha256=Y6jkAm2VqPqoGIGQ-mEQCecNfteQ2LwdpYhC5XiH_cA,1069
|
|
7
|
-
pydantic_rpc-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|