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.
Files changed (52) hide show
  1. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/PKG-INFO +33 -41
  2. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/README.md +29 -38
  3. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/README.md +152 -144
  4. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/agent_aio_grpc.py +17 -29
  5. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/agent_connecpy.py +53 -50
  6. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/barservice_pb2.py +9 -2
  7. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/barservice_pb2.pyi +2 -2
  8. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/barservice_pb2_grpc.py +26 -0
  9. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/fooservice_pb2.py +13 -6
  10. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/fooservice_pb2.pyi +4 -4
  11. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/fooservice_pb2_grpc.py +26 -0
  12. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_connecpy_client.py +2 -1
  13. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_pb2.py +9 -2
  14. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_pb2.pyi +2 -2
  15. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_pb2_grpc.py +26 -0
  16. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_sonora_client.py +8 -8
  17. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeting_using_exsiting_pb2_modules.py +23 -22
  18. pydantic_rpc-0.4.1/examples/olympicsagent.proto +40 -0
  19. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicsagent_pb2.py +9 -2
  20. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicsagent_pb2.pyi +4 -4
  21. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicsagent_pb2_grpc.py +30 -0
  22. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicslocationagent.proto +6 -0
  23. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicslocationagent_pb2.py +9 -2
  24. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicslocationagent_pb2.pyi +2 -2
  25. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/pyproject.toml +6 -5
  26. pydantic_rpc-0.4.1/src/pydantic_rpc/__init__.py +17 -0
  27. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/src/pydantic_rpc/core.py +1343 -1308
  28. pydantic_rpc-0.4.1/uv.lock +1586 -0
  29. pydantic_rpc-0.3.1/examples/olympicsagent.proto +0 -144
  30. pydantic_rpc-0.3.1/requirements-dev.lock +0 -223
  31. pydantic_rpc-0.3.1/requirements.lock +0 -97
  32. pydantic_rpc-0.3.1/src/pydantic_rpc/__init__.py +0 -8
  33. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/.gitignore +0 -0
  34. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/.python-version +0 -0
  35. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/LICENSE +0 -0
  36. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/asyncio_greeting.py +0 -0
  37. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/barservice.proto +0 -0
  38. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/foobar.py +0 -0
  39. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/foobar_client.py +0 -0
  40. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/fooservice.proto +0 -0
  41. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/google/protobuf/duration.proto +0 -0
  42. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/google/protobuf/timestamp.proto +0 -0
  43. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter.proto +0 -0
  44. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_client.py +0 -0
  45. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeter_connecpy.py +0 -0
  46. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeting.py +0 -0
  47. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeting_asgi.py +0 -0
  48. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeting_connecpy.py +0 -0
  49. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/greeting_wsgi.py +0 -0
  50. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicslocationagent_connecpy.py +0 -0
  51. {pydantic_rpc-0.3.1 → pydantic_rpc-0.4.1}/examples/olympicslocationagent_pb2_grpc.py +0 -0
  52. {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.3
1
+ Metadata-Version: 2.4
2
2
  Name: pydantic-rpc
3
- Version: 0.3.1
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.0
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
- rye run python greeting_connecpy.py
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 field_validator
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: str
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 | Protobuf Type |
437
- |--------------------|-----------------|
438
- | str | string |
439
- | bool | bool |
440
- | int | int32, int64 |
441
- | float | float, double |
442
- | list[T], tuple[T] | repeated T |
443
- | dict[K, V] | map<K, V> |
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
- rye run python greeting_connecpy.py
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 field_validator
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: str
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 | Protobuf Type |
423
- |--------------------|-----------------|
424
- | str | string |
425
- | bool | bool |
426
- | int | int32, int64 |
427
- | float | float, double |
428
- | list[T], tuple[T] | repeated T |
429
- | dict[K, V] | map<K, V> |
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 [Rye](https://rye-up.com/) installed on your system. If not, you can install it using the following command:
6
-
7
- ```bash
8
- curl -sSL https://rye-up.com/install.sh | bash
9
- ```
10
-
11
- ## 🔧 Setup
12
-
13
- 1. **Clone the Repository:**
14
-
15
- ```bash
16
- git clone https://github.com/i2y/pydantic-rpc.git
17
- cd pydantic-rpc/examples
18
- ```
19
-
20
- 2. **Install Dependencies with Rye:**
21
-
22
- ```bash
23
- rye sync
24
- ```
25
-
26
- ## 🖥️ gRPC Server Example
27
-
28
- ### 🔧 Server (`greeting.py`)
29
-
30
- A simple gRPC server.
31
-
32
- **Usage:**
33
-
34
- ```bash
35
- rye run python greeting.py
36
- ```
37
-
38
- ### 🔗 Client (`greeter_client.py`)
39
-
40
- A gRPC client to interact with the server.
41
-
42
- **Usage:**
43
-
44
- ```bash
45
- rye run python greeter_client.py
46
- ```
47
-
48
- ## Asyncio gRPC Server Example
49
-
50
- ### 🔧 Asyncio Server (`asyncio_greeting.py`)
51
-
52
- An asyncio gRPC server using `AsyncIOServer`.
53
-
54
- **Usage:**
55
-
56
- ```bash
57
- rye run python asyncio_greeting.py
58
- ```
59
-
60
- ## 🌐 ASGI Integration (gRPC-Web)
61
-
62
- ### 🌐 ASGI Application (`greeting_asgi.py`)
63
-
64
- Integrate **PydanticRPC** (gRPC-Web) with an ASGI-compatible framework.
65
-
66
- **Usage:**
67
-
68
- ```bash
69
- rye run hypercorn -bind :3000 greeting_asgi:app
70
- ```
71
-
72
- ### 🔗 Client (`greeter_sonora_client.py`)
73
- A gRPC-Web client to interact with the server.
74
-
75
- **Usage:**
76
-
77
- ```bash
78
- rye run python greeter_sonora_client.py
79
- ```
80
-
81
-
82
- ## 🌐 WSGI Integration
83
-
84
- ### 🌐 WSGI Application (`greeting_wsgi.py`)
85
-
86
- Integrate **PydanticRPC** (gRPC-Web) with a WSGI-compatible framework.
87
-
88
- **Usage:**
89
-
90
- ```bash
91
- rye run python greeting_wsgi.py
92
- ```
93
-
94
- ### 🔗 Client (`greeter_sonora_client.py`)
95
- A gRPC-Web client to interact with the server.
96
-
97
- **Usage:**
98
-
99
- ```bash
100
- rye run python greeter_sonora_client.py
101
- ```
102
-
103
-
104
- ## 🛡️ Custom Interceptor and Running Multiple Services Exxample
105
-
106
- ### 🔧 Server (`foobar.py`)
107
- A simple gRPC server with custom interceptor and running multiple services.
108
-
109
- **Usage:**
110
-
111
- ```bash
112
- rye run python foobar.py
113
- ```
114
-
115
- ### 🔗 Client (`foobar_client.py`)
116
- A gRPC client to interact with the server.
117
-
118
- **Usage:**
119
-
120
- ```bash
121
- rye run python foobar_client.py
122
- ```
123
-
124
- ## 🤝 Connecpy (Connect-RPC) Example
125
-
126
- ### 🔧 Server (`greeting_connecpy.py`)
127
-
128
- A Connect-RPC ASGI application using PydanticRPC + connecpy.
129
-
130
- **Usage:**
131
-
132
- ```bash
133
- rye run hypercorn --bind :3000 greeting_connecpy:app
134
- ```
135
-
136
- ### 🔗 Client (`greeter_client_connecpy.py`)
137
-
138
- A Connect-RPC client to interact with the server.
139
-
140
- **Usage:**
141
-
142
- ```bash
143
- rye run python greeter_client_connecpy.py
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 field_validator
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: str
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: