pydantic-rpc 0.3.1__tar.gz → 0.4.1__tar.gz
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-0.3.1 → pydantic_rpc-0.4.1}/PKG-INFO +33 -41
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/README.md +29 -38
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/README.md +152 -144
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/agent_aio_grpc.py +17 -29
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/agent_connecpy.py +53 -50
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/barservice_pb2.py +9 -2
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/barservice_pb2.pyi +2 -2
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/barservice_pb2_grpc.py +26 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/fooservice_pb2.py +13 -6
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/fooservice_pb2.pyi +4 -4
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/fooservice_pb2_grpc.py +26 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_connecpy_client.py +2 -1
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_pb2.py +9 -2
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_pb2.pyi +2 -2
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_pb2_grpc.py +26 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_sonora_client.py +8 -8
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeting_using_exsiting_pb2_modules.py +23 -22
- pydantic_rpc-0.4.1/examples/olympicsagent.proto +40 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicsagent_pb2.py +9 -2
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicsagent_pb2.pyi +4 -4
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicsagent_pb2_grpc.py +30 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicslocationagent.proto +6 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicslocationagent_pb2.py +9 -2
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicslocationagent_pb2.pyi +2 -2
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/pyproject.toml +6 -5
- pydantic_rpc-0.4.1/src/pydantic_rpc/__init__.py +17 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/src/pydantic_rpc/core.py +1343 -1308
- pydantic_rpc-0.4.1/uv.lock +1586 -0
- pydantic_rpc-0.3.1/examples/olympicsagent.proto +0 -144
- pydantic_rpc-0.3.1/requirements-dev.lock +0 -223
- pydantic_rpc-0.3.1/requirements.lock +0 -97
- pydantic_rpc-0.3.1/src/pydantic_rpc/__init__.py +0 -8
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/.gitignore +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/.python-version +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/LICENSE +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/asyncio_greeting.py +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/barservice.proto +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/foobar.py +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/foobar_client.py +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/fooservice.proto +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/google/protobuf/duration.proto +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/google/protobuf/timestamp.proto +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter.proto +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_client.py +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_connecpy.py +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeting.py +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeting_asgi.py +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeting_connecpy.py +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeting_wsgi.py +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicslocationagent_connecpy.py +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicslocationagent_pb2_grpc.py +0 -0
- {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/src/pydantic_rpc/py.typed +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pydantic-rpc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
|
|
5
5
|
Author: Yasushi Itoh
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Requires-Python: >=3.11
|
|
7
|
-
Requires-Dist: connecpy>=1.2.
|
|
8
|
+
Requires-Dist: connecpy>=1.2.1
|
|
8
9
|
Requires-Dist: grpcio-health-checking>=1.56.2
|
|
9
10
|
Requires-Dist: grpcio-reflection>=1.56.2
|
|
10
11
|
Requires-Dist: grpcio-tools>=1.56.2
|
|
@@ -94,6 +95,7 @@ app.mount(OlympicsLocationAgent())
|
|
|
94
95
|
- 🔄 **Automatic Protobuf Generation:** Automatically creates protobuf files matching the method signatures of your Python objects.
|
|
95
96
|
- ⚙️ **Dynamic Code Generation:** Generates server and client stubs using `grpcio-tools`.
|
|
96
97
|
- ✅ **Pydantic Integration:** Uses `pydantic` for robust type validation and serialization.
|
|
98
|
+
- 📄 **Pprotobuf File Export:** Exports the generated protobuf files for use in other languages.
|
|
97
99
|
- **For gRPC:**
|
|
98
100
|
- 💚 **Health Checking:** Built-in support for gRPC health checks using `grpc_health.v1`.
|
|
99
101
|
- 🔎 **Server Reflection:** Built-in support for gRPC server reflection.
|
|
@@ -228,7 +230,7 @@ app.mount(Greeter())
|
|
|
228
230
|
PydanticRPC also partially supports Connect-RPC via connecpy. Check out “greeting_connecpy.py” for an example:
|
|
229
231
|
|
|
230
232
|
```bash
|
|
231
|
-
|
|
233
|
+
uv run greeting_connecpy.py
|
|
232
234
|
```
|
|
233
235
|
|
|
234
236
|
This will launch a Connecpy-based ASGI application that uses the same Pydantic models to serve Connect-RPC requests.
|
|
@@ -261,57 +263,38 @@ Please see the sample code below:
|
|
|
261
263
|
|
|
262
264
|
```python
|
|
263
265
|
import asyncio
|
|
264
|
-
from typing import AsyncIterator
|
|
266
|
+
from typing import Annotated, AsyncIterator
|
|
265
267
|
|
|
266
|
-
from pydantic import
|
|
268
|
+
from pydantic import Field
|
|
267
269
|
from pydantic_ai import Agent
|
|
268
270
|
from pydantic_rpc import AsyncIOServer, Message
|
|
269
271
|
|
|
270
272
|
|
|
271
273
|
# `Message` is just a pydantic BaseModel alias
|
|
272
274
|
class CityLocation(Message):
|
|
273
|
-
city: str
|
|
274
|
-
country:
|
|
275
|
+
city: Annotated[str, Field(description="The city where the Olympics were held")]
|
|
276
|
+
country: Annotated[
|
|
277
|
+
str, Field(description="The country where the Olympics were held")
|
|
278
|
+
]
|
|
275
279
|
|
|
276
280
|
|
|
277
281
|
class OlympicsQuery(Message):
|
|
278
|
-
year: int
|
|
282
|
+
year: Annotated[int, Field(description="The year of the Olympics", ge=1896)]
|
|
279
283
|
|
|
280
284
|
def prompt(self):
|
|
281
285
|
return f"Where were the Olympics held in {self.year}?"
|
|
282
286
|
|
|
283
|
-
@field_validator("year")
|
|
284
|
-
def validate_year(cls, value):
|
|
285
|
-
if value < 1896:
|
|
286
|
-
raise ValueError("The first modern Olympics was held in 1896.")
|
|
287
|
-
|
|
288
|
-
return value
|
|
289
|
-
|
|
290
287
|
|
|
291
288
|
class OlympicsDurationQuery(Message):
|
|
292
|
-
start: int
|
|
293
|
-
end: int
|
|
289
|
+
start: Annotated[int, Field(description="The start year of the Olympics", ge=1896)]
|
|
290
|
+
end: Annotated[int, Field(description="The end year of the Olympics", ge=1896)]
|
|
294
291
|
|
|
295
292
|
def prompt(self):
|
|
296
293
|
return f"From {self.start} to {self.end}, how many Olympics were held? Please provide the list of countries and cities."
|
|
297
294
|
|
|
298
|
-
@field_validator("start")
|
|
299
|
-
def validate_start(cls, value):
|
|
300
|
-
if value < 1896:
|
|
301
|
-
raise ValueError("The first modern Olympics was held in 1896.")
|
|
302
|
-
|
|
303
|
-
return value
|
|
304
|
-
|
|
305
|
-
@field_validator("end")
|
|
306
|
-
def validate_end(cls, value):
|
|
307
|
-
if value < 1896:
|
|
308
|
-
raise ValueError("The first modern Olympics was held in 1896.")
|
|
309
|
-
|
|
310
|
-
return value
|
|
311
|
-
|
|
312
295
|
|
|
313
296
|
class StreamingResult(Message):
|
|
314
|
-
answer: str
|
|
297
|
+
answer: Annotated[str, Field(description="The answer to the query")]
|
|
315
298
|
|
|
316
299
|
|
|
317
300
|
class OlympicsAgent:
|
|
@@ -433,18 +416,27 @@ Using this generated proto file and tools as `protoc`, `buf` and `BSR`, you coul
|
|
|
433
416
|
|
|
434
417
|
## 📖 Data Type Mapping
|
|
435
418
|
|
|
436
|
-
| Python Type
|
|
437
|
-
|
|
438
|
-
| str
|
|
439
|
-
|
|
|
440
|
-
|
|
|
441
|
-
|
|
|
442
|
-
|
|
|
443
|
-
|
|
|
419
|
+
| Python Type | Protobuf Type |
|
|
420
|
+
|--------------------------------|---------------------------|
|
|
421
|
+
| str | string |
|
|
422
|
+
| bytes | bytes |
|
|
423
|
+
| bool | bool |
|
|
424
|
+
| int | int32 |
|
|
425
|
+
| float | float, double |
|
|
426
|
+
| list[T], tuple[T] | repeated T |
|
|
427
|
+
| dict[K, V] | map<K, V> |
|
|
428
|
+
| datetime.datetime | google.protobuf.Timestamp |
|
|
429
|
+
| datetime.timedelta | google.protobuf.Duration |
|
|
430
|
+
| typing.Union[A, B] | oneof A, B |
|
|
431
|
+
| subclass of enum.Enum | enum |
|
|
432
|
+
| subclass of pydantic.BaseModel | message |
|
|
444
433
|
|
|
445
434
|
|
|
446
435
|
## TODO
|
|
447
436
|
- [ ] Streaming Support
|
|
437
|
+
- [x] unary-stream
|
|
438
|
+
- [ ] stream-unary
|
|
439
|
+
- [ ] stream-stream
|
|
448
440
|
- [ ] Betterproto Support
|
|
449
441
|
- [ ] Sonora-connect Support
|
|
450
442
|
- [ ] Custom Health Check Support
|
|
@@ -80,6 +80,7 @@ app.mount(OlympicsLocationAgent())
|
|
|
80
80
|
- 🔄 **Automatic Protobuf Generation:** Automatically creates protobuf files matching the method signatures of your Python objects.
|
|
81
81
|
- ⚙️ **Dynamic Code Generation:** Generates server and client stubs using `grpcio-tools`.
|
|
82
82
|
- ✅ **Pydantic Integration:** Uses `pydantic` for robust type validation and serialization.
|
|
83
|
+
- 📄 **Pprotobuf File Export:** Exports the generated protobuf files for use in other languages.
|
|
83
84
|
- **For gRPC:**
|
|
84
85
|
- 💚 **Health Checking:** Built-in support for gRPC health checks using `grpc_health.v1`.
|
|
85
86
|
- 🔎 **Server Reflection:** Built-in support for gRPC server reflection.
|
|
@@ -214,7 +215,7 @@ app.mount(Greeter())
|
|
|
214
215
|
PydanticRPC also partially supports Connect-RPC via connecpy. Check out “greeting_connecpy.py” for an example:
|
|
215
216
|
|
|
216
217
|
```bash
|
|
217
|
-
|
|
218
|
+
uv run greeting_connecpy.py
|
|
218
219
|
```
|
|
219
220
|
|
|
220
221
|
This will launch a Connecpy-based ASGI application that uses the same Pydantic models to serve Connect-RPC requests.
|
|
@@ -247,57 +248,38 @@ Please see the sample code below:
|
|
|
247
248
|
|
|
248
249
|
```python
|
|
249
250
|
import asyncio
|
|
250
|
-
from typing import AsyncIterator
|
|
251
|
+
from typing import Annotated, AsyncIterator
|
|
251
252
|
|
|
252
|
-
from pydantic import
|
|
253
|
+
from pydantic import Field
|
|
253
254
|
from pydantic_ai import Agent
|
|
254
255
|
from pydantic_rpc import AsyncIOServer, Message
|
|
255
256
|
|
|
256
257
|
|
|
257
258
|
# `Message` is just a pydantic BaseModel alias
|
|
258
259
|
class CityLocation(Message):
|
|
259
|
-
city: str
|
|
260
|
-
country:
|
|
260
|
+
city: Annotated[str, Field(description="The city where the Olympics were held")]
|
|
261
|
+
country: Annotated[
|
|
262
|
+
str, Field(description="The country where the Olympics were held")
|
|
263
|
+
]
|
|
261
264
|
|
|
262
265
|
|
|
263
266
|
class OlympicsQuery(Message):
|
|
264
|
-
year: int
|
|
267
|
+
year: Annotated[int, Field(description="The year of the Olympics", ge=1896)]
|
|
265
268
|
|
|
266
269
|
def prompt(self):
|
|
267
270
|
return f"Where were the Olympics held in {self.year}?"
|
|
268
271
|
|
|
269
|
-
@field_validator("year")
|
|
270
|
-
def validate_year(cls, value):
|
|
271
|
-
if value < 1896:
|
|
272
|
-
raise ValueError("The first modern Olympics was held in 1896.")
|
|
273
|
-
|
|
274
|
-
return value
|
|
275
|
-
|
|
276
272
|
|
|
277
273
|
class OlympicsDurationQuery(Message):
|
|
278
|
-
start: int
|
|
279
|
-
end: int
|
|
274
|
+
start: Annotated[int, Field(description="The start year of the Olympics", ge=1896)]
|
|
275
|
+
end: Annotated[int, Field(description="The end year of the Olympics", ge=1896)]
|
|
280
276
|
|
|
281
277
|
def prompt(self):
|
|
282
278
|
return f"From {self.start} to {self.end}, how many Olympics were held? Please provide the list of countries and cities."
|
|
283
279
|
|
|
284
|
-
@field_validator("start")
|
|
285
|
-
def validate_start(cls, value):
|
|
286
|
-
if value < 1896:
|
|
287
|
-
raise ValueError("The first modern Olympics was held in 1896.")
|
|
288
|
-
|
|
289
|
-
return value
|
|
290
|
-
|
|
291
|
-
@field_validator("end")
|
|
292
|
-
def validate_end(cls, value):
|
|
293
|
-
if value < 1896:
|
|
294
|
-
raise ValueError("The first modern Olympics was held in 1896.")
|
|
295
|
-
|
|
296
|
-
return value
|
|
297
|
-
|
|
298
280
|
|
|
299
281
|
class StreamingResult(Message):
|
|
300
|
-
answer: str
|
|
282
|
+
answer: Annotated[str, Field(description="The answer to the query")]
|
|
301
283
|
|
|
302
284
|
|
|
303
285
|
class OlympicsAgent:
|
|
@@ -419,18 +401,27 @@ Using this generated proto file and tools as `protoc`, `buf` and `BSR`, you coul
|
|
|
419
401
|
|
|
420
402
|
## 📖 Data Type Mapping
|
|
421
403
|
|
|
422
|
-
| Python Type
|
|
423
|
-
|
|
424
|
-
| str
|
|
425
|
-
|
|
|
426
|
-
|
|
|
427
|
-
|
|
|
428
|
-
|
|
|
429
|
-
|
|
|
404
|
+
| Python Type | Protobuf Type |
|
|
405
|
+
|--------------------------------|---------------------------|
|
|
406
|
+
| str | string |
|
|
407
|
+
| bytes | bytes |
|
|
408
|
+
| bool | bool |
|
|
409
|
+
| int | int32 |
|
|
410
|
+
| float | float, double |
|
|
411
|
+
| list[T], tuple[T] | repeated T |
|
|
412
|
+
| dict[K, V] | map<K, V> |
|
|
413
|
+
| datetime.datetime | google.protobuf.Timestamp |
|
|
414
|
+
| datetime.timedelta | google.protobuf.Duration |
|
|
415
|
+
| typing.Union[A, B] | oneof A, B |
|
|
416
|
+
| subclass of enum.Enum | enum |
|
|
417
|
+
| subclass of pydantic.BaseModel | message |
|
|
430
418
|
|
|
431
419
|
|
|
432
420
|
## TODO
|
|
433
421
|
- [ ] Streaming Support
|
|
422
|
+
- [x] unary-stream
|
|
423
|
+
- [ ] stream-unary
|
|
424
|
+
- [ ] stream-stream
|
|
434
425
|
- [ ] Betterproto Support
|
|
435
426
|
- [ ] Sonora-connect Support
|
|
436
427
|
- [ ] Custom Health Check Support
|
|
@@ -1,144 +1,152 @@
|
|
|
1
|
-
# 📚 Examples
|
|
2
|
-
|
|
3
|
-
## 📝 Prerequisites
|
|
4
|
-
|
|
5
|
-
Ensure you have [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
|
|
1
|
+
# 📚 Examples
|
|
2
|
+
|
|
3
|
+
## 📝 Prerequisites
|
|
4
|
+
|
|
5
|
+
Ensure you have [UV](https://docs.astral.sh/uv/) installed on your system. If not, you can install it using the following command:
|
|
6
|
+
|
|
7
|
+
### Linux/macOS
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Windows
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 🔧 Setup
|
|
20
|
+
|
|
21
|
+
1. **Clone the Repository:**
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
git clone https://github.com/i2y/pydantic-rpc.git
|
|
25
|
+
cd pydantic-rpc/examples
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
2. **Install Dependencies with Rye:**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv sync
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 🖥️ gRPC Server Example
|
|
35
|
+
|
|
36
|
+
### 🔧 Server (`greeting.py`)
|
|
37
|
+
|
|
38
|
+
A simple gRPC server.
|
|
39
|
+
|
|
40
|
+
**Usage:**
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv run greeting.py
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 🔗 Client (`greeter_client.py`)
|
|
47
|
+
|
|
48
|
+
A gRPC client to interact with the server.
|
|
49
|
+
|
|
50
|
+
**Usage:**
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv run greeter_client.py
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## ⚡ Asyncio gRPC Server Example
|
|
57
|
+
|
|
58
|
+
### 🔧 Asyncio Server (`asyncio_greeting.py`)
|
|
59
|
+
|
|
60
|
+
An asyncio gRPC server using `AsyncIOServer`.
|
|
61
|
+
|
|
62
|
+
**Usage:**
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv run asyncio_greeting.py
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 🌐 ASGI Integration (gRPC-Web)
|
|
69
|
+
|
|
70
|
+
### 🌐 ASGI Application (`greeting_asgi.py`)
|
|
71
|
+
|
|
72
|
+
Integrate **PydanticRPC** (gRPC-Web) with an ASGI-compatible framework.
|
|
73
|
+
|
|
74
|
+
**Usage:**
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv run hypercorn -bind :3000 greeting_asgi:app
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 🔗 Client (`greeter_sonora_client.py`)
|
|
81
|
+
A gRPC-Web client to interact with the server.
|
|
82
|
+
|
|
83
|
+
**Usage:**
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
uv run greeter_sonora_client.py
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
## 🌐 WSGI Integration
|
|
91
|
+
|
|
92
|
+
### 🌐 WSGI Application (`greeting_wsgi.py`)
|
|
93
|
+
|
|
94
|
+
Integrate **PydanticRPC** (gRPC-Web) with a WSGI-compatible framework.
|
|
95
|
+
|
|
96
|
+
**Usage:**
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
uv run greeting_wsgi.py
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 🔗 Client (`greeter_sonora_client.py`)
|
|
103
|
+
A gRPC-Web client to interact with the server.
|
|
104
|
+
|
|
105
|
+
**Usage:**
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv run greeter_sonora_client.py
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## 🛡️ Custom Interceptor and Running Multiple Services Exxample
|
|
113
|
+
|
|
114
|
+
### 🔧 Server (`foobar.py`)
|
|
115
|
+
A simple gRPC server with custom interceptor and running multiple services.
|
|
116
|
+
|
|
117
|
+
**Usage:**
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uv run foobar.py
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 🔗 Client (`foobar_client.py`)
|
|
124
|
+
A gRPC client to interact with the server.
|
|
125
|
+
|
|
126
|
+
**Usage:**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
uv run foobar_client.py
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 🤝 Connecpy (Connect-RPC) Example
|
|
133
|
+
|
|
134
|
+
### 🔧 Server (`greeting_connecpy.py`)
|
|
135
|
+
|
|
136
|
+
A Connect-RPC ASGI application using PydanticRPC + connecpy.
|
|
137
|
+
|
|
138
|
+
**Usage:**
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
uv run hypercorn --bind :3000 greeting_connecpy:app
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 🔗 Client (`greeter_client_connecpy.py`)
|
|
145
|
+
|
|
146
|
+
A Connect-RPC client to interact with the server.
|
|
147
|
+
|
|
148
|
+
**Usage:**
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
uv run greeter_client_connecpy.py
|
|
152
|
+
```
|
|
@@ -1,59 +1,47 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import AsyncIterator
|
|
2
|
+
from typing import Annotated, AsyncIterator
|
|
3
3
|
|
|
4
|
-
from pydantic import
|
|
4
|
+
from pydantic import Field
|
|
5
5
|
from pydantic_ai import Agent
|
|
6
6
|
from pydantic_rpc import AsyncIOServer, Message
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
# `Message` is just a pydantic BaseModel alias
|
|
10
10
|
class CityLocation(Message):
|
|
11
|
-
city: str
|
|
12
|
-
country:
|
|
11
|
+
city: Annotated[str, Field(description="The city where the Olympics were held")]
|
|
12
|
+
country: Annotated[
|
|
13
|
+
str, Field(description="The country where the Olympics were held")
|
|
14
|
+
]
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class OlympicsQuery(Message):
|
|
16
|
-
year: int
|
|
18
|
+
year: Annotated[int, Field(description="The year of the Olympics", ge=1896)]
|
|
17
19
|
|
|
18
20
|
def prompt(self):
|
|
19
21
|
return f"Where were the Olympics held in {self.year}?"
|
|
20
22
|
|
|
21
|
-
@field_validator("year")
|
|
22
|
-
def validate_year(cls, value):
|
|
23
|
-
if value < 1896:
|
|
24
|
-
raise ValueError("The first modern Olympics was held in 1896.")
|
|
25
|
-
|
|
26
|
-
return value
|
|
27
|
-
|
|
28
23
|
|
|
29
24
|
class OlympicsDurationQuery(Message):
|
|
30
|
-
start: int
|
|
31
|
-
end: int
|
|
25
|
+
start: Annotated[int, Field(description="The start year of the Olympics", ge=1896)]
|
|
26
|
+
end: Annotated[int, Field(description="The end year of the Olympics", ge=1896)]
|
|
32
27
|
|
|
33
28
|
def prompt(self):
|
|
34
29
|
return f"From {self.start} to {self.end}, how many Olympics were held? Please provide the list of countries and cities."
|
|
35
30
|
|
|
36
|
-
@field_validator("start")
|
|
37
|
-
def validate_start(cls, value):
|
|
38
|
-
if value < 1896:
|
|
39
|
-
raise ValueError("The first modern Olympics was held in 1896.")
|
|
40
|
-
|
|
41
|
-
return value
|
|
42
|
-
|
|
43
|
-
@field_validator("end")
|
|
44
|
-
def validate_end(cls, value):
|
|
45
|
-
if value < 1896:
|
|
46
|
-
raise ValueError("The first modern Olympics was held in 1896.")
|
|
47
|
-
|
|
48
|
-
return value
|
|
49
|
-
|
|
50
31
|
|
|
51
32
|
class StreamingResult(Message):
|
|
52
|
-
answer: str
|
|
33
|
+
answer: Annotated[str, Field(description="The answer to the query")]
|
|
53
34
|
|
|
54
35
|
|
|
55
36
|
class OlympicsAgent:
|
|
56
37
|
def __init__(self):
|
|
38
|
+
# # if pydantic_ai >= 0.0.21
|
|
39
|
+
# ollama_model = OpenAIModel(
|
|
40
|
+
# model_name="llama3.2",
|
|
41
|
+
# base_url="http://localhost:11434/v1",
|
|
42
|
+
# api_key="",
|
|
43
|
+
# )
|
|
44
|
+
# self._agent = Agent(ollama_model)
|
|
57
45
|
self._agent = Agent("ollama:llama3.2")
|
|
58
46
|
|
|
59
47
|
async def ask(self, req: OlympicsQuery) -> CityLocation:
|