pydantic-rpc 0.6.1__tar.gz → 0.8.0__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.6.1/README.md → pydantic_rpc-0.8.0/PKG-INFO +360 -10
- pydantic_rpc-0.6.1/PKG-INFO → pydantic_rpc-0.8.0/README.md +342 -25
- {pydantic_rpc-0.6.1 → pydantic_rpc-0.8.0}/pyproject.toml +10 -7
- {pydantic_rpc-0.6.1 → pydantic_rpc-0.8.0}/src/pydantic_rpc/__init__.py +10 -0
- pydantic_rpc-0.8.0/src/pydantic_rpc/core.py +2815 -0
- pydantic_rpc-0.8.0/src/pydantic_rpc/mcp/__init__.py +5 -0
- pydantic_rpc-0.8.0/src/pydantic_rpc/mcp/converter.py +115 -0
- pydantic_rpc-0.8.0/src/pydantic_rpc/mcp/exporter.py +283 -0
- pydantic_rpc-0.6.1/.gitignore +0 -13
- pydantic_rpc-0.6.1/.python-version +0 -1
- pydantic_rpc-0.6.1/LICENSE +0 -21
- pydantic_rpc-0.6.1/examples/README.md +0 -152
- pydantic_rpc-0.6.1/examples/agent_aio_grpc.py +0 -65
- pydantic_rpc-0.6.1/examples/agent_connecpy.py +0 -56
- pydantic_rpc-0.6.1/examples/asyncio_greeting.py +0 -22
- pydantic_rpc-0.6.1/examples/barservice.proto +0 -17
- pydantic_rpc-0.6.1/examples/barservice_pb2.py +0 -37
- pydantic_rpc-0.6.1/examples/barservice_pb2.pyi +0 -18
- pydantic_rpc-0.6.1/examples/barservice_pb2_grpc.py +0 -106
- pydantic_rpc-0.6.1/examples/foobar.py +0 -76
- pydantic_rpc-0.6.1/examples/foobar_client.py +0 -20
- pydantic_rpc-0.6.1/examples/fooservice.proto +0 -21
- pydantic_rpc-0.6.1/examples/fooservice_pb2.py +0 -45
- pydantic_rpc-0.6.1/examples/fooservice_pb2.pyi +0 -56
- pydantic_rpc-0.6.1/examples/fooservice_pb2_grpc.py +0 -106
- pydantic_rpc-0.6.1/examples/google/protobuf/duration.proto +0 -115
- pydantic_rpc-0.6.1/examples/google/protobuf/timestamp.proto +0 -144
- pydantic_rpc-0.6.1/examples/greeter.proto +0 -35
- pydantic_rpc-0.6.1/examples/greeter_client.py +0 -14
- pydantic_rpc-0.6.1/examples/greeter_connecpy.py +0 -111
- pydantic_rpc-0.6.1/examples/greeter_connecpy_client.py +0 -41
- pydantic_rpc-0.6.1/examples/greeter_pb2.py +0 -37
- pydantic_rpc-0.6.1/examples/greeter_pb2.pyi +0 -17
- pydantic_rpc-0.6.1/examples/greeter_pb2_grpc.py +0 -119
- pydantic_rpc-0.6.1/examples/greeter_sonora_client.py +0 -8
- pydantic_rpc-0.6.1/examples/greeting.py +0 -45
- pydantic_rpc-0.6.1/examples/greeting_asgi.py +0 -55
- pydantic_rpc-0.6.1/examples/greeting_connecpy.py +0 -44
- pydantic_rpc-0.6.1/examples/greeting_using_exsiting_pb2_modules.py +0 -23
- pydantic_rpc-0.6.1/examples/greeting_wsgi.py +0 -63
- pydantic_rpc-0.6.1/examples/olympicsagent.proto +0 -40
- pydantic_rpc-0.6.1/examples/olympicsagent_pb2.py +0 -41
- pydantic_rpc-0.6.1/examples/olympicsagent_pb2.pyi +0 -37
- pydantic_rpc-0.6.1/examples/olympicsagent_pb2_grpc.py +0 -155
- pydantic_rpc-0.6.1/examples/olympicslocationagent.proto +0 -24
- pydantic_rpc-0.6.1/examples/olympicslocationagent_connecpy.py +0 -113
- pydantic_rpc-0.6.1/examples/olympicslocationagent_pb2.py +0 -39
- pydantic_rpc-0.6.1/examples/olympicslocationagent_pb2.pyi +0 -21
- pydantic_rpc-0.6.1/examples/olympicslocationagent_pb2_grpc.py +0 -125
- pydantic_rpc-0.6.1/src/pydantic_rpc/core.py +0 -1455
- pydantic_rpc-0.6.1/tests/asyncechoservice.proto +0 -33
- pydantic_rpc-0.6.1/tests/asyncechoservice_connecpy.py +0 -109
- pydantic_rpc-0.6.1/tests/asyncechoservice_pb2.py +0 -37
- pydantic_rpc-0.6.1/tests/asyncechoservice_pb2.pyi +0 -17
- pydantic_rpc-0.6.1/tests/asyncechoservice_pb2_grpc.py +0 -121
- pydantic_rpc-0.6.1/tests/echoservice.proto +0 -33
- pydantic_rpc-0.6.1/tests/echoservice_connecpy.py +0 -109
- pydantic_rpc-0.6.1/tests/echoservice_pb2.py +0 -37
- pydantic_rpc-0.6.1/tests/echoservice_pb2.pyi +0 -17
- pydantic_rpc-0.6.1/tests/echoservice_pb2_grpc.py +0 -119
- pydantic_rpc-0.6.1/tests/test_apps.py +0 -190
- pydantic_rpc-0.6.1/uv.lock +0 -1636
- {pydantic_rpc-0.6.1 → pydantic_rpc-0.8.0}/src/pydantic_rpc/py.typed +0 -0
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pydantic-rpc
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
|
|
5
|
+
Author: Yasushi Itoh
|
|
6
|
+
Requires-Dist: annotated-types>=0.5.0
|
|
7
|
+
Requires-Dist: pydantic>=2.1.1
|
|
8
|
+
Requires-Dist: grpcio>=1.56.2
|
|
9
|
+
Requires-Dist: grpcio-tools>=1.56.2
|
|
10
|
+
Requires-Dist: grpcio-reflection>=1.56.2
|
|
11
|
+
Requires-Dist: grpcio-health-checking>=1.56.2
|
|
12
|
+
Requires-Dist: sonora>=0.2.3
|
|
13
|
+
Requires-Dist: connecpy==2.0.0
|
|
14
|
+
Requires-Dist: mcp>=1.9.4
|
|
15
|
+
Requires-Dist: starlette>=0.27.0
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
1
19
|
# 🚀 PydanticRPC
|
|
2
20
|
|
|
3
21
|
**PydanticRPC** is a Python library that enables you to rapidly expose [Pydantic](https://docs.pydantic.dev/) models via [gRPC](https://grpc.io/)/[Connect RPC](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.
|
|
@@ -110,6 +128,7 @@ app.mount(OlympicsLocationAgent())
|
|
|
110
128
|
- **For Connect-RPC:**
|
|
111
129
|
- 🌐 **Connecpy Support:** Partially supports Connect-RPC via `Connecpy`.
|
|
112
130
|
- 🛠️ **Pre-generated Protobuf Files and Code:** Pre-generate proto files and corresponding code via the CLI. By setting the environment variable (PYDANTIC_RPC_SKIP_GENERATION), you can skip runtime generation.
|
|
131
|
+
- 🤖 **MCP (Model Context Protocol) Support:** Expose your services as tools for AI assistants using the official MCP SDK, supporting both stdio and HTTP/SSE transports.
|
|
113
132
|
|
|
114
133
|
## 📦 Installation
|
|
115
134
|
|
|
@@ -163,12 +182,18 @@ class Greeter:
|
|
|
163
182
|
return HelloReply(message=f"Hello, {request.name}!")
|
|
164
183
|
|
|
165
184
|
|
|
185
|
+
async def main():
|
|
186
|
+
# You can specify a custom port (default is 50051)
|
|
187
|
+
server = AsyncIOServer(port=50052)
|
|
188
|
+
await server.run(Greeter())
|
|
189
|
+
|
|
190
|
+
|
|
166
191
|
if __name__ == "__main__":
|
|
167
|
-
|
|
168
|
-
loop = asyncio.get_event_loop()
|
|
169
|
-
loop.run_until_complete(server.run(Greeter()))
|
|
192
|
+
asyncio.run(main())
|
|
170
193
|
```
|
|
171
194
|
|
|
195
|
+
The AsyncIOServer automatically handles graceful shutdown on SIGTERM and SIGINT signals.
|
|
196
|
+
|
|
172
197
|
### 🌐 ASGI Application Example
|
|
173
198
|
|
|
174
199
|
```python
|
|
@@ -248,7 +273,7 @@ This will launch a Connecpy-based ASGI application that uses the same Pydantic m
|
|
|
248
273
|
> - Please follow the instruction described in https://go.dev/doc/install.
|
|
249
274
|
> 2. Install `protoc-gen-connecpy`:
|
|
250
275
|
> ```bash
|
|
251
|
-
> go install github.com/connecpy/protoc-gen-connecpy@latest
|
|
276
|
+
> go install github.com/i2y/connecpy/v2/protoc-gen-connecpy@latest
|
|
252
277
|
> ```
|
|
253
278
|
|
|
254
279
|
## ♻️ Skipping Protobuf Generation
|
|
@@ -260,6 +285,21 @@ export PYDANTIC_RPC_SKIP_GENERATION=true
|
|
|
260
285
|
|
|
261
286
|
When this variable is set to "true", PydanticRPC will load existing pre-generated modules rather than generating them on the fly.
|
|
262
287
|
|
|
288
|
+
## 🪧 Setting Protobuf and Connecpy/gRPC generation directory
|
|
289
|
+
By default your files will be generated in the current working directory where you ran the code from, but you can set a custom specific directory by setting the environment variable below:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
export PYDANTIC_RPC_PROTO_PATH=/your/path
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## ⚠️ Reserved Fields
|
|
296
|
+
|
|
297
|
+
You can also set an environment variable to reserve a set number of fields for proto generation, for backward and forward compatibility.
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
export PYDANTIC_RPC_RESERVED_FIELDS=1
|
|
301
|
+
```
|
|
302
|
+
|
|
263
303
|
## 💎 Advanced Features
|
|
264
304
|
|
|
265
305
|
### 🌊 Response Streaming
|
|
@@ -633,6 +673,203 @@ buf: * (#2) Call complete
|
|
|
633
673
|
%
|
|
634
674
|
```
|
|
635
675
|
|
|
676
|
+
### 🪶 Empty Messages
|
|
677
|
+
|
|
678
|
+
Empty request/response messages are automatically mapped to `google.protobuf.Empty`:
|
|
679
|
+
|
|
680
|
+
```python
|
|
681
|
+
from pydantic_rpc import AsyncIOServer, Message
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
class EmptyRequest(Message):
|
|
685
|
+
pass # Automatically uses google.protobuf.Empty
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
class GreetingResponse(Message):
|
|
689
|
+
message: str
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
class GreetingService:
|
|
693
|
+
async def say_hello(self, request: EmptyRequest) -> GreetingResponse:
|
|
694
|
+
return GreetingResponse(message="Hello!")
|
|
695
|
+
|
|
696
|
+
async def get_default_greeting(self) -> GreetingResponse:
|
|
697
|
+
# Method with no request parameter (implicitly empty)
|
|
698
|
+
return GreetingResponse(message="Hello, World!")
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
### 🎨 Custom Serialization
|
|
702
|
+
|
|
703
|
+
Pydantic's serialization decorators are fully supported:
|
|
704
|
+
|
|
705
|
+
```python
|
|
706
|
+
from typing import Any
|
|
707
|
+
from pydantic import field_serializer, model_serializer
|
|
708
|
+
from pydantic_rpc import Message
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
class UserMessage(Message):
|
|
712
|
+
name: str
|
|
713
|
+
age: int
|
|
714
|
+
|
|
715
|
+
@field_serializer('name')
|
|
716
|
+
def serialize_name(self, name: str) -> str:
|
|
717
|
+
"""Always uppercase the name when serializing."""
|
|
718
|
+
return name.upper()
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
class ComplexMessage(Message):
|
|
722
|
+
value: int
|
|
723
|
+
multiplier: int
|
|
724
|
+
|
|
725
|
+
@model_serializer
|
|
726
|
+
def serialize_model(self) -> dict[str, Any]:
|
|
727
|
+
"""Custom serialization with computed fields."""
|
|
728
|
+
return {
|
|
729
|
+
'value': self.value,
|
|
730
|
+
'multiplier': self.multiplier,
|
|
731
|
+
'result': self.value * self.multiplier # Computed field
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
The serializers are automatically applied when converting between Pydantic models and protobuf messages.
|
|
736
|
+
|
|
737
|
+
#### ⚠️ Limitations and Considerations
|
|
738
|
+
|
|
739
|
+
**1. Nested Message serializers are now supported (v0.8.0+)**
|
|
740
|
+
```python
|
|
741
|
+
class Address(Message):
|
|
742
|
+
city: str
|
|
743
|
+
|
|
744
|
+
@field_serializer("city")
|
|
745
|
+
def serialize_city(self, city: str) -> str:
|
|
746
|
+
return city.upper()
|
|
747
|
+
|
|
748
|
+
class User(Message):
|
|
749
|
+
name: str
|
|
750
|
+
address: Address # ← Address's serializers ARE applied with DEEP strategy
|
|
751
|
+
|
|
752
|
+
@field_serializer("name")
|
|
753
|
+
def serialize_name(self, name: str) -> str:
|
|
754
|
+
return name.upper() # ← This IS applied
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
**Serializer Strategy Control:**
|
|
758
|
+
You can control how nested serializers are applied via environment variable:
|
|
759
|
+
```bash
|
|
760
|
+
# Apply serializers at all nesting levels (default)
|
|
761
|
+
export PYDANTIC_RPC_SERIALIZER_STRATEGY=deep
|
|
762
|
+
|
|
763
|
+
# Apply only top-level serializers
|
|
764
|
+
export PYDANTIC_RPC_SERIALIZER_STRATEGY=shallow
|
|
765
|
+
|
|
766
|
+
# Disable all serializers
|
|
767
|
+
export PYDANTIC_RPC_SERIALIZER_STRATEGY=none
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
**Performance Impact:**
|
|
771
|
+
- DEEP strategy: ~4% overhead for simple nested structures
|
|
772
|
+
- SHALLOW strategy: ~2% overhead (only top-level)
|
|
773
|
+
- NONE strategy: No overhead (serializers disabled)
|
|
774
|
+
|
|
775
|
+
**2. New fields added by serializers are ignored**
|
|
776
|
+
```python
|
|
777
|
+
class ComplexMessage(Message):
|
|
778
|
+
value: int
|
|
779
|
+
multiplier: int
|
|
780
|
+
|
|
781
|
+
@model_serializer
|
|
782
|
+
def serialize_model(self) -> dict[str, Any]:
|
|
783
|
+
return {
|
|
784
|
+
"value": self.value,
|
|
785
|
+
"multiplier": self.multiplier,
|
|
786
|
+
"result": self.value * self.multiplier # ← Won't appear in protobuf
|
|
787
|
+
}
|
|
788
|
+
```
|
|
789
|
+
**Problem**: The `result` field doesn't exist in the Message definition, so it's not in the protobuf schema.
|
|
790
|
+
|
|
791
|
+
**3. Type must remain consistent**
|
|
792
|
+
```python
|
|
793
|
+
class BadExample(Message):
|
|
794
|
+
number: int
|
|
795
|
+
|
|
796
|
+
@field_serializer("number")
|
|
797
|
+
def serialize_number(self, number: int) -> str: # ❌ int → str
|
|
798
|
+
return str(number) # This will cause issues
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
**4. Union/Optional fields have limited support**
|
|
802
|
+
```python
|
|
803
|
+
class UnionExample(Message):
|
|
804
|
+
data: str | int | None # Union type
|
|
805
|
+
|
|
806
|
+
@field_serializer("data")
|
|
807
|
+
def serialize_data(self, data: str | int | None) -> str | int | None:
|
|
808
|
+
# Serializer may not be applied to Union types
|
|
809
|
+
return data
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
**5. Errors fail silently with fallback**
|
|
813
|
+
```python
|
|
814
|
+
class RiskyMessage(Message):
|
|
815
|
+
value: int
|
|
816
|
+
|
|
817
|
+
@field_serializer("value")
|
|
818
|
+
def serialize_value(self, value: int) -> int:
|
|
819
|
+
if value == 0:
|
|
820
|
+
raise ValueError("Cannot serialize zero")
|
|
821
|
+
return value * 2
|
|
822
|
+
|
|
823
|
+
# If error occurs, original value is used (silent fallback)
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
**6. Circular references are handled gracefully**
|
|
827
|
+
```python
|
|
828
|
+
class Node(Message):
|
|
829
|
+
value: str
|
|
830
|
+
child: "Node | None" = None
|
|
831
|
+
|
|
832
|
+
@field_serializer("value")
|
|
833
|
+
def serialize_value(self, v: str) -> str:
|
|
834
|
+
return v.upper()
|
|
835
|
+
|
|
836
|
+
# Circular references are detected and prevented
|
|
837
|
+
node1 = Node(value="first")
|
|
838
|
+
node2 = Node(value="second")
|
|
839
|
+
node1.child = node2
|
|
840
|
+
node2.child = node1 # Circular reference
|
|
841
|
+
|
|
842
|
+
# When converting to protobuf:
|
|
843
|
+
# - Circular references are detected
|
|
844
|
+
# - Empty proto is returned for repeated objects
|
|
845
|
+
# - No infinite recursion occurs
|
|
846
|
+
# Note: Pydantic's model_dump() will fail on circular refs,
|
|
847
|
+
# so serializers won't be applied in this case
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
**✅ Recommended Usage:**
|
|
851
|
+
```python
|
|
852
|
+
class GoodMessage(Message):
|
|
853
|
+
# Use with primitive types
|
|
854
|
+
name: str
|
|
855
|
+
age: int
|
|
856
|
+
|
|
857
|
+
@field_serializer("name")
|
|
858
|
+
def normalize_name(self, name: str) -> str:
|
|
859
|
+
return name.strip().title() # Normalization
|
|
860
|
+
|
|
861
|
+
@field_serializer("age")
|
|
862
|
+
def clamp_age(self, age: int) -> int:
|
|
863
|
+
return max(0, min(age, 150)) # Range limiting
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
**Best Practices:**
|
|
867
|
+
- Use serializers primarily for primitive types (str, int, float, bool)
|
|
868
|
+
- Keep type consistency (int → int, str → str)
|
|
869
|
+
- Avoid complex transformations or side effects
|
|
870
|
+
- Test error cases thoroughly
|
|
871
|
+
- Be aware that errors fail silently
|
|
872
|
+
|
|
636
873
|
### 🔗 Multiple Services with Custom Interceptors
|
|
637
874
|
|
|
638
875
|
PydanticRPC supports defining and running multiple services in a single server:
|
|
@@ -718,6 +955,74 @@ if __name__ == "__main__":
|
|
|
718
955
|
|
|
719
956
|
TODO
|
|
720
957
|
|
|
958
|
+
### 🤖 MCP (Model Context Protocol) Support
|
|
959
|
+
|
|
960
|
+
PydanticRPC can expose your services as MCP tools for AI assistants using FastMCP. This enables seamless integration with any MCP-compatible client.
|
|
961
|
+
|
|
962
|
+
#### Stdio Mode Example
|
|
963
|
+
|
|
964
|
+
```python
|
|
965
|
+
from pydantic_rpc import Message
|
|
966
|
+
from pydantic_rpc.mcp import MCPExporter
|
|
967
|
+
|
|
968
|
+
class CalculateRequest(Message):
|
|
969
|
+
expression: str
|
|
970
|
+
|
|
971
|
+
class CalculateResponse(Message):
|
|
972
|
+
result: float
|
|
973
|
+
|
|
974
|
+
class MathService:
|
|
975
|
+
def calculate(self, req: CalculateRequest) -> CalculateResponse:
|
|
976
|
+
result = eval(req.expression, {"__builtins__": {}}, {})
|
|
977
|
+
return CalculateResponse(result=float(result))
|
|
978
|
+
|
|
979
|
+
# Run as MCP stdio server
|
|
980
|
+
if __name__ == "__main__":
|
|
981
|
+
service = MathService()
|
|
982
|
+
mcp = MCPExporter(service)
|
|
983
|
+
mcp.run_stdio()
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
#### Configuring MCP Clients
|
|
987
|
+
|
|
988
|
+
Any MCP-compatible client can connect to your service. For example, to configure Claude Desktop:
|
|
989
|
+
|
|
990
|
+
```json
|
|
991
|
+
{
|
|
992
|
+
"mcpServers": {
|
|
993
|
+
"my-math-service": {
|
|
994
|
+
"command": "python",
|
|
995
|
+
"args": ["/path/to/math_mcp_server.py"]
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
#### HTTP/ASGI Mode Example
|
|
1002
|
+
|
|
1003
|
+
MCP can also be mounted to existing ASGI applications:
|
|
1004
|
+
|
|
1005
|
+
```python
|
|
1006
|
+
from pydantic_rpc import ConnecpyASGIApp
|
|
1007
|
+
from pydantic_rpc.mcp import MCPExporter
|
|
1008
|
+
|
|
1009
|
+
# Create Connect-RPC ASGI app
|
|
1010
|
+
app = ConnecpyASGIApp()
|
|
1011
|
+
app.mount(MathService())
|
|
1012
|
+
|
|
1013
|
+
# Add MCP support via HTTP/SSE
|
|
1014
|
+
mcp = MCPExporter(MathService())
|
|
1015
|
+
mcp.mount_to_asgi(app, path="/mcp")
|
|
1016
|
+
|
|
1017
|
+
# Run with uvicorn
|
|
1018
|
+
import uvicorn
|
|
1019
|
+
uvicorn.run(app, host="127.0.0.1", port=8000)
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
MCP endpoints will be available at:
|
|
1023
|
+
- SSE: `GET http://localhost:8000/mcp/sse`
|
|
1024
|
+
- Messages: `POST http://localhost:8000/mcp/messages/`
|
|
1025
|
+
|
|
721
1026
|
### 🗄️ Protobuf file and code (Python files) generation using CLI
|
|
722
1027
|
|
|
723
1028
|
You can genereate protobuf files and code for a given module and a specified class using `pydantic-rpc` CLI command:
|
|
@@ -747,16 +1052,61 @@ Using this generated proto file and tools as `protoc`, `buf` and `BSR`, you coul
|
|
|
747
1052
|
| subclass of pydantic.BaseModel | message |
|
|
748
1053
|
|
|
749
1054
|
|
|
1055
|
+
## ⚠️ Known Limitations
|
|
1056
|
+
|
|
1057
|
+
### Union Types with Collections
|
|
1058
|
+
|
|
1059
|
+
Due to protobuf's `oneof` restrictions, you cannot use `Union` types that contain `repeated` (list/tuple) or `map` (dict) fields directly. This is a limitation of the protobuf specification itself.
|
|
1060
|
+
|
|
1061
|
+
**❌ Not Supported:**
|
|
1062
|
+
```python
|
|
1063
|
+
from typing import Union, List, Dict
|
|
1064
|
+
from pydantic_rpc import Message
|
|
1065
|
+
|
|
1066
|
+
# These will fail during proto compilation
|
|
1067
|
+
class MyMessage(Message):
|
|
1068
|
+
# Union with list - NOT SUPPORTED
|
|
1069
|
+
field1: Union[List[int], str]
|
|
1070
|
+
|
|
1071
|
+
# Union with dict - NOT SUPPORTED
|
|
1072
|
+
field2: Union[Dict[str, int], int]
|
|
1073
|
+
|
|
1074
|
+
# Union with nested collections - NOT SUPPORTED
|
|
1075
|
+
field3: Union[List[Dict[str, int]], str]
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
**✅ Workaround - Use Message Wrappers:**
|
|
1079
|
+
```python
|
|
1080
|
+
from typing import Union, List, Dict
|
|
1081
|
+
from pydantic_rpc import Message
|
|
1082
|
+
|
|
1083
|
+
# Wrap collections in Message types
|
|
1084
|
+
class IntList(Message):
|
|
1085
|
+
values: List[int]
|
|
1086
|
+
|
|
1087
|
+
class StringIntMap(Message):
|
|
1088
|
+
values: Dict[str, int]
|
|
1089
|
+
|
|
1090
|
+
class MyMessage(Message):
|
|
1091
|
+
# Now these work!
|
|
1092
|
+
field1: Union[IntList, str]
|
|
1093
|
+
field2: Union[StringIntMap, int]
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
This approach works because protobuf allows message types within `oneof` fields, and the collections are contained within those messages.
|
|
1097
|
+
|
|
1098
|
+
|
|
750
1099
|
## TODO
|
|
751
|
-
- [
|
|
1100
|
+
- [x] Streaming Support
|
|
752
1101
|
- [x] unary-stream
|
|
753
|
-
- [
|
|
754
|
-
- [
|
|
755
|
-
- [
|
|
756
|
-
- [
|
|
1102
|
+
- [x] stream-unary
|
|
1103
|
+
- [x] stream-stream
|
|
1104
|
+
- [x] Empty Message Support (automatic google.protobuf.Empty)
|
|
1105
|
+
- [x] Pydantic Serializer Support (@model_serializer, @field_serializer)
|
|
757
1106
|
- [ ] Custom Health Check Support
|
|
1107
|
+
- [x] MCP (Model Context Protocol) Support via official MCP SDK
|
|
758
1108
|
- [ ] Add more examples
|
|
759
|
-
- [
|
|
1109
|
+
- [x] Add tests
|
|
760
1110
|
|
|
761
1111
|
## 📜 License
|
|
762
1112
|
|