pydantic-rpc 0.2.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/py.typed ADDED
File without changes
@@ -0,0 +1,329 @@
1
+ Metadata-Version: 2.3
2
+ Name: pydantic-rpc
3
+ Version: 0.2.0
4
+ Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
5
+ Author: Yasushi Itoh
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: connecpy>=1.2.0
8
+ Requires-Dist: grpcio-health-checking>=1.56.2
9
+ Requires-Dist: grpcio-reflection>=1.56.2
10
+ Requires-Dist: grpcio-tools>=1.56.2
11
+ Requires-Dist: pydantic>=2.1.1
12
+ Requires-Dist: sonora>=0.2.3
13
+ Description-Content-Type: text/markdown
14
+
15
+ # 🚀 PydanticRPC
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.
18
+
19
+
20
+ Below is an example of a simple gRPC service that exposes a PydanticAI agent:
21
+
22
+ ```python
23
+ import asyncio
24
+
25
+ from pydantic_ai import Agent
26
+ from pydantic_rpc import AsyncIOServer, Message
27
+
28
+
29
+ # `Message` is just an alias for Pydantic's `BaseModel` class.
30
+ class CityLocation(Message):
31
+ city: str
32
+ country: str
33
+
34
+
35
+ class Olympics(Message):
36
+ year: int
37
+
38
+ def prompt(self):
39
+ return f"Where were the Olympics held in {self.year}?"
40
+
41
+
42
+ class OlympicsLocationAgent:
43
+ def __init__(self):
44
+ self._agent = Agent("ollama:llama3.2", result_type=CityLocation)
45
+
46
+ async def ask(self, req: Olympics) -> CityLocation:
47
+ result = await self._agent.run(req.prompt())
48
+ return result.data
49
+
50
+
51
+ if __name__ == "__main__":
52
+ s = AsyncIOServer()
53
+ loop = asyncio.get_event_loop()
54
+ loop.run_until_complete(s.run(OlympicsLocationAgent()))
55
+ ```
56
+
57
+
58
+ ## 💡 Key Features
59
+
60
+ - 🔄 **Automatic Protobuf Generation:** Automatically creates protobuf files matching the method signatures of your Python objects.
61
+ - ⚙️ **Dynamic Code Generation:** Generates server and client stubs using `grpcio-tools`.
62
+ - ✅ **Pydantic Integration:** Uses `pydantic` for robust type validation and serialization.
63
+ - **For gRPC:**
64
+ - 💚 **Health Checking:** Built-in support for gRPC health checks using `grpc_health.v1`.
65
+ - 🔎 **Server Reflection:** Built-in support for gRPC server reflection.
66
+ - ⚡ **Asynchronous Support:** Easily create asynchronous gRPC services with `AsyncIOServer`.
67
+ - **For gRPC-Web:**
68
+ - 🌐 **WSGI/ASGI Support:** Create gRPC-Web services that can run as WSGI or ASGI applications powered by `Sonora`.
69
+ - **For Connect-RPC:**
70
+ - 🌐 **Connecpy Support:** Partially supports Connect-RPC via `Connecpy`.
71
+
72
+ ## 📦 Installation
73
+
74
+ Install PydanticRPC via pip:
75
+
76
+ ```bash
77
+ pip install pydantic-rpc
78
+ ```
79
+
80
+ ## 🚀 Getting Started
81
+
82
+ ### 🔧 Synchronous Service Example
83
+
84
+ ```python
85
+ from pydantic_rpc import Server, Message
86
+
87
+ class HelloRequest(Message):
88
+ name: str
89
+
90
+ class HelloReply(Message):
91
+ message: str
92
+
93
+ class Greeter:
94
+ # Define methods that accepts a request and returns a response.
95
+ def say_hello(self, request: HelloRequest) -> HelloReply:
96
+ return HelloReply(message=f"Hello, {request.name}!")
97
+
98
+ if __name__ == "__main__":
99
+ server = Server()
100
+ server.run(Greeter())
101
+ ```
102
+
103
+ ### ⚙️ Asynchronous Service Example
104
+
105
+ ```python
106
+ import asyncio
107
+
108
+ from pydantic_rpc import AsyncIOServer, Message
109
+
110
+
111
+ class HelloRequest(Message):
112
+ name: str
113
+
114
+
115
+ class HelloReply(Message):
116
+ message: str
117
+
118
+
119
+ class Greeter:
120
+ async def say_hello(self, request: HelloRequest) -> HelloReply:
121
+ return HelloReply(message=f"Hello, {request.name}!")
122
+
123
+
124
+ if __name__ == "__main__":
125
+ server = AsyncIOServer()
126
+ loop = asyncio.get_event_loop()
127
+ loop.run_until_complete(server.run(Greeter()))
128
+ ```
129
+
130
+ ### 🌐 ASGI Application Example
131
+
132
+ ```python
133
+ from pydantic_rpc import ASGIApp, Message
134
+
135
+ class HelloRequest(Message):
136
+ name: str
137
+
138
+ class HelloReply(Message):
139
+ message: str
140
+
141
+ class Greeter:
142
+ def say_hello(self, request: HelloRequest) -> HelloReply:
143
+ return HelloReply(message=f"Hello, {request.name}!")
144
+
145
+
146
+ async def app(scope, receive, send):
147
+ """ASGI application.
148
+
149
+ Args:
150
+ scope (dict): The ASGI scope.
151
+ receive (callable): The receive function.
152
+ send (callable): The send function.
153
+ """
154
+ pass
155
+
156
+ # Please note that `app` is any ASGI application, such as FastAPI or Starlette.
157
+
158
+ app = ASGIApp(app)
159
+ app.mount(Greeter())
160
+ ```
161
+
162
+ ### 🌐 WSGI Application Example
163
+
164
+ ```python
165
+ from pydantic_rpc import WSGIApp, Message
166
+
167
+ class HelloRequest(Message):
168
+ name: str
169
+
170
+ class HelloReply(Message):
171
+ message: str
172
+
173
+ class Greeter:
174
+ def say_hello(self, request: HelloRequest) -> HelloReply:
175
+ return HelloReply(message=f"Hello, {request.name}!")
176
+
177
+ def app(environ, start_response):
178
+ """WSGI application.
179
+
180
+ Args:
181
+ environ (dict): The WSGI environment.
182
+ start_response (callable): The start_response function.
183
+ """
184
+ pass
185
+
186
+ # Please note that `app` is any WSGI application, such as Flask or Django.
187
+
188
+ app = WSGIApp(app)
189
+ app.mount(Greeter())
190
+ ```
191
+
192
+ ### 🏆 Connecpy (Connect-RPC) Example
193
+
194
+ PydanticRPC also partially supports Connect-RPC via connecpy. Check out “greeting_connecpy.py” for an example:
195
+
196
+ ```bash
197
+ rye run python greeting_connecpy.py
198
+ ```
199
+
200
+ This will launch a Connecpy-based ASGI application that uses the same Pydantic models to serve Connect-RPC requests.
201
+
202
+ > [!NOTE]
203
+ > Please install `protoc-gen-connecpy` to run the Connecpy example.
204
+ >
205
+ > 1. Install Go.
206
+ > - Please follow the instruction described in https://go.dev/doc/install.
207
+ > 2. Install `protoc-gen-connecpy`:
208
+ > ```bash
209
+ > go install github.com/connecpy/protoc-gen-connecpy@latest
210
+ > ```
211
+
212
+ ## ♻️ Skipping Protobuf Generation
213
+ By default, PydanticRPC generates .proto files and code at runtime. If you wish to skip the code-generation step (for example, in production environment), set the environment variable below:
214
+
215
+ ```bash
216
+ export PYDANTIC_RPC_SKIP_GENERATION=true
217
+ ```
218
+
219
+ When this variable is set to "true", PydanticRPC will load existing pre-generated modules rather than generating them on the fly.
220
+
221
+ ## 💎 Advanced Features
222
+
223
+ ### 🔗 Multiple Services with Custom Interceptors
224
+
225
+ PydanticRPC supports defining and running multiple services in a single server:
226
+
227
+ ```python
228
+ from datetime import datetime
229
+ import grpc
230
+ from grpc import ServicerContext
231
+
232
+ from pydantic_rpc import Server, Message
233
+
234
+
235
+ class FooRequest(Message):
236
+ name: str
237
+ age: int
238
+ d: dict[str, str]
239
+
240
+
241
+ class FooResponse(Message):
242
+ name: str
243
+ age: int
244
+ d: dict[str, str]
245
+
246
+
247
+ class BarRequest(Message):
248
+ names: list[str]
249
+
250
+
251
+ class BarResponse(Message):
252
+ names: list[str]
253
+
254
+
255
+ class FooService:
256
+ def foo(self, request: FooRequest) -> FooResponse:
257
+ return FooResponse(name=request.name, age=request.age, d=request.d)
258
+
259
+
260
+ class MyMessage(Message):
261
+ name: str
262
+ age: int
263
+ o: int | datetime
264
+
265
+
266
+ class Request(Message):
267
+ name: str
268
+ age: int
269
+ d: dict[str, str]
270
+ m: MyMessage
271
+
272
+
273
+ class Response(Message):
274
+ name: str
275
+ age: int
276
+ d: dict[str, str]
277
+ m: MyMessage | str
278
+
279
+
280
+ class BarService:
281
+ def bar(self, req: BarRequest, ctx: ServicerContext) -> BarResponse:
282
+ return BarResponse(names=req.names)
283
+
284
+
285
+ class CustomInterceptor(grpc.ServerInterceptor):
286
+ def intercept_service(self, continuation, handler_call_details):
287
+ # do something
288
+ print(handler_call_details.method)
289
+ return continuation(handler_call_details)
290
+
291
+
292
+ async def app(scope, receive, send):
293
+ pass
294
+
295
+
296
+ if __name__ == "__main__":
297
+ s = Server(10, CustomInterceptor())
298
+ s.run(
299
+ FooService(),
300
+ BarService(),
301
+ )
302
+ ```
303
+
304
+ ### 🩺 [TODO] Custom Health Check
305
+
306
+ TODO
307
+
308
+ ### 🗄️ Protobuf file generation
309
+
310
+ You can generate protobuf files for a given module and a specified class using `core.py`:
311
+
312
+ ```bash
313
+ python core.py a_module.py aClass
314
+ ```
315
+
316
+ Using this generated proto file and tools as `protoc`, `buf` and `BSR`, you could generate code for any desired language other than Python.
317
+
318
+
319
+ ## TODO
320
+ - [ ] Streaming Support
321
+ - [ ] Betterproto Support
322
+ - [ ] Sonora-connect Support
323
+ - [ ] Custom Health Check Support
324
+ - [ ] Add more examples
325
+ - [ ] Add tests
326
+
327
+ ## 📜 License
328
+
329
+ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,7 @@
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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.26.3
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Yasushi Itoh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.