pydantic-rpc 0.2.1__py3-none-any.whl → 0.3.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/core.py +154 -55
- {pydantic_rpc-0.2.1.dist-info → pydantic_rpc-0.3.0.dist-info}/METADATA +96 -3
- pydantic_rpc-0.3.0.dist-info/RECORD +7 -0
- pydantic_rpc-0.2.1.dist-info/RECORD +0 -7
- {pydantic_rpc-0.2.1.dist-info → pydantic_rpc-0.3.0.dist-info}/WHEEL +0 -0
- {pydantic_rpc-0.2.1.dist-info → pydantic_rpc-0.3.0.dist-info}/licenses/LICENSE +0 -0
pydantic_rpc/core.py
CHANGED
|
@@ -17,17 +17,20 @@ from typing import (
|
|
|
17
17
|
get_origin,
|
|
18
18
|
Union,
|
|
19
19
|
TypeAlias,
|
|
20
|
+
# AsyncIterator, # Add if not already present
|
|
20
21
|
)
|
|
22
|
+
from collections.abc import AsyncIterator
|
|
21
23
|
|
|
22
24
|
import grpc
|
|
23
25
|
from grpc_health.v1 import health_pb2, health_pb2_grpc
|
|
24
26
|
from grpc_health.v1.health import HealthServicer
|
|
25
27
|
from grpc_reflection.v1alpha import reflection
|
|
26
28
|
from grpc_tools import protoc
|
|
27
|
-
from pydantic import BaseModel
|
|
29
|
+
from pydantic import BaseModel, ValidationError
|
|
28
30
|
from sonora.wsgi import grpcWSGI
|
|
29
31
|
from sonora.asgi import grpcASGI
|
|
30
32
|
from connecpy.asgi import ConnecpyASGIApp as ConnecpyASGI
|
|
33
|
+
from connecpy.errors import Errors
|
|
31
34
|
|
|
32
35
|
# Protobuf Python modules for Timestamp, Duration (requires protobuf / grpcio)
|
|
33
36
|
from google.protobuf import timestamp_pb2, duration_pb2
|
|
@@ -104,24 +107,26 @@ def generate_converter(annotation: Type) -> Callable:
|
|
|
104
107
|
|
|
105
108
|
return dur_converter
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
if
|
|
109
|
-
|
|
110
|
+
origin = get_origin(annotation)
|
|
111
|
+
if origin is not None:
|
|
112
|
+
# For seq types
|
|
113
|
+
if origin in (list, tuple):
|
|
114
|
+
item_converter = generate_converter(get_args(annotation)[0])
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
|
|
116
|
+
def seq_converter(value):
|
|
117
|
+
return [item_converter(v) for v in value]
|
|
113
118
|
|
|
114
|
-
|
|
119
|
+
return seq_converter
|
|
115
120
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
# For dict types
|
|
122
|
+
if origin is dict:
|
|
123
|
+
key_converter = generate_converter(get_args(annotation)[0])
|
|
124
|
+
value_converter = generate_converter(get_args(annotation)[1])
|
|
120
125
|
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
def dict_converter(value):
|
|
127
|
+
return {key_converter(k): value_converter(v) for k, v in value.items()}
|
|
123
128
|
|
|
124
|
-
|
|
129
|
+
return dict_converter
|
|
125
130
|
|
|
126
131
|
# For Message classes
|
|
127
132
|
if inspect.isclass(annotation) and issubclass(annotation, Message):
|
|
@@ -198,25 +203,35 @@ def connect_obj_with_stub(pb2_grpc_module, pb2_module, service_obj: object) -> t
|
|
|
198
203
|
case 1:
|
|
199
204
|
|
|
200
205
|
def stub_method1(self, request, context, method=method):
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
try:
|
|
207
|
+
# Convert request to Python object
|
|
208
|
+
arg = converter(request)
|
|
209
|
+
# Invoke the actual method
|
|
210
|
+
resp_obj = method(arg)
|
|
211
|
+
# Convert the returned Python Message to a protobuf message
|
|
212
|
+
return convert_python_message_to_proto(
|
|
213
|
+
resp_obj, response_type, pb2_module
|
|
214
|
+
)
|
|
215
|
+
except ValidationError as e:
|
|
216
|
+
return context.abort(grpc.StatusCode.INVALID_ARGUMENT, str(e))
|
|
217
|
+
except Exception as e:
|
|
218
|
+
return context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
209
219
|
|
|
210
220
|
return stub_method1
|
|
211
221
|
|
|
212
222
|
case 2:
|
|
213
223
|
|
|
214
224
|
def stub_method2(self, request, context, method=method):
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
225
|
+
try:
|
|
226
|
+
arg = converter(request)
|
|
227
|
+
resp_obj = method(arg, context)
|
|
228
|
+
return convert_python_message_to_proto(
|
|
229
|
+
resp_obj, response_type, pb2_module
|
|
230
|
+
)
|
|
231
|
+
except ValidationError as e:
|
|
232
|
+
return context.abort(grpc.StatusCode.INVALID_ARGUMENT, str(e))
|
|
233
|
+
except Exception as e:
|
|
234
|
+
return context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
220
235
|
|
|
221
236
|
return stub_method2
|
|
222
237
|
|
|
@@ -251,26 +266,80 @@ def connect_obj_with_stub_async(pb2_grpc_module, pb2_module, obj: object) -> typ
|
|
|
251
266
|
response_type = sig.return_annotation
|
|
252
267
|
size_of_parameters = len(sig.parameters)
|
|
253
268
|
|
|
269
|
+
if is_stream_type(response_type):
|
|
270
|
+
item_type = get_args(response_type)[0]
|
|
271
|
+
match size_of_parameters:
|
|
272
|
+
case 1:
|
|
273
|
+
|
|
274
|
+
async def stub_method_stream1(
|
|
275
|
+
self, request, context, method=method
|
|
276
|
+
):
|
|
277
|
+
try:
|
|
278
|
+
arg = converter(request)
|
|
279
|
+
async for resp_obj in method(arg):
|
|
280
|
+
yield convert_python_message_to_proto(
|
|
281
|
+
resp_obj, item_type, pb2_module
|
|
282
|
+
)
|
|
283
|
+
except ValidationError as e:
|
|
284
|
+
await context.abort(
|
|
285
|
+
grpc.StatusCode.INVALID_ARGUMENT, str(e)
|
|
286
|
+
)
|
|
287
|
+
except Exception as e:
|
|
288
|
+
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
289
|
+
|
|
290
|
+
return stub_method_stream1
|
|
291
|
+
case 2:
|
|
292
|
+
|
|
293
|
+
async def stub_method_stream2(
|
|
294
|
+
self, request, context, method=method
|
|
295
|
+
):
|
|
296
|
+
try:
|
|
297
|
+
arg = converter(request)
|
|
298
|
+
async for resp_obj in method(arg, context):
|
|
299
|
+
yield convert_python_message_to_proto(
|
|
300
|
+
resp_obj, item_type, pb2_module
|
|
301
|
+
)
|
|
302
|
+
except ValidationError as e:
|
|
303
|
+
await context.abort(
|
|
304
|
+
grpc.StatusCode.INVALID_ARGUMENT, str(e)
|
|
305
|
+
)
|
|
306
|
+
except Exception as e:
|
|
307
|
+
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
308
|
+
|
|
309
|
+
return stub_method_stream2
|
|
310
|
+
case _:
|
|
311
|
+
raise Exception("Method must have exactly one or two parameters")
|
|
312
|
+
|
|
254
313
|
match size_of_parameters:
|
|
255
314
|
case 1:
|
|
256
315
|
|
|
257
316
|
async def stub_method1(self, request, context, method=method):
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
317
|
+
try:
|
|
318
|
+
arg = converter(request)
|
|
319
|
+
resp_obj = await method(arg)
|
|
320
|
+
return convert_python_message_to_proto(
|
|
321
|
+
resp_obj, response_type, pb2_module
|
|
322
|
+
)
|
|
323
|
+
except ValidationError as e:
|
|
324
|
+
await context.abort(grpc.StatusCode.INVALID_ARGUMENT, str(e))
|
|
325
|
+
except Exception as e:
|
|
326
|
+
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
263
327
|
|
|
264
328
|
return stub_method1
|
|
265
329
|
|
|
266
330
|
case 2:
|
|
267
331
|
|
|
268
332
|
async def stub_method2(self, request, context, method=method):
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
333
|
+
try:
|
|
334
|
+
arg = converter(request)
|
|
335
|
+
resp_obj = await method(arg, context)
|
|
336
|
+
return convert_python_message_to_proto(
|
|
337
|
+
resp_obj, response_type, pb2_module
|
|
338
|
+
)
|
|
339
|
+
except ValidationError as e:
|
|
340
|
+
await context.abort(grpc.StatusCode.INVALID_ARGUMENT, str(e))
|
|
341
|
+
except Exception as e:
|
|
342
|
+
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
274
343
|
|
|
275
344
|
return stub_method2
|
|
276
345
|
|
|
@@ -302,7 +371,6 @@ def connect_obj_with_stub_async_connecpy(
|
|
|
302
371
|
|
|
303
372
|
def implement_stub_method(method):
|
|
304
373
|
sig = inspect.signature(method)
|
|
305
|
-
print(type(method))
|
|
306
374
|
arg_type = get_request_arg_type(sig)
|
|
307
375
|
converter = generate_message_converter(arg_type)
|
|
308
376
|
response_type = sig.return_annotation
|
|
@@ -312,22 +380,32 @@ def connect_obj_with_stub_async_connecpy(
|
|
|
312
380
|
case 1:
|
|
313
381
|
|
|
314
382
|
async def stub_method1(self, request, context, method=method):
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
383
|
+
try:
|
|
384
|
+
arg = converter(request)
|
|
385
|
+
resp_obj = await method(arg)
|
|
386
|
+
return convert_python_message_to_proto(
|
|
387
|
+
resp_obj, response_type, pb2_module
|
|
388
|
+
)
|
|
389
|
+
except ValidationError as e:
|
|
390
|
+
await context.abort(Errors.InvalidArgument, str(e))
|
|
391
|
+
except Exception as e:
|
|
392
|
+
await context.abort(Errors.Internal, str(e))
|
|
320
393
|
|
|
321
394
|
return stub_method1
|
|
322
395
|
|
|
323
396
|
case 2:
|
|
324
397
|
|
|
325
398
|
async def stub_method2(self, request, context, method=method):
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
399
|
+
try:
|
|
400
|
+
arg = converter(request)
|
|
401
|
+
resp_obj = await method(arg, context)
|
|
402
|
+
return convert_python_message_to_proto(
|
|
403
|
+
resp_obj, response_type, pb2_module
|
|
404
|
+
)
|
|
405
|
+
except ValidationError as e:
|
|
406
|
+
await context.abort(Errors.InvalidArgument, str(e))
|
|
407
|
+
except Exception as e:
|
|
408
|
+
await context.abort(Errors.Internal, str(e))
|
|
331
409
|
|
|
332
410
|
return stub_method2
|
|
333
411
|
|
|
@@ -388,13 +466,14 @@ def python_value_to_proto(field_type: Type, value, pb2_module):
|
|
|
388
466
|
if inspect.isclass(field_type) and issubclass(field_type, enum.Enum):
|
|
389
467
|
return value.value # proto3 enum is an int
|
|
390
468
|
|
|
391
|
-
|
|
392
|
-
|
|
469
|
+
origin = get_origin(field_type)
|
|
470
|
+
# If seq
|
|
471
|
+
if origin in (list, tuple):
|
|
393
472
|
inner_type = get_args(field_type)[0] # type: ignore
|
|
394
473
|
return [python_value_to_proto(inner_type, v, pb2_module) for v in value]
|
|
395
474
|
|
|
396
475
|
# If dict
|
|
397
|
-
if
|
|
476
|
+
if origin is dict:
|
|
398
477
|
key_type, val_type = get_args(field_type) # type: ignore
|
|
399
478
|
return {
|
|
400
479
|
python_value_to_proto(key_type, k, pb2_module): python_value_to_proto(
|
|
@@ -500,7 +579,7 @@ def protobuf_type_mapping(python_type: Type) -> str | type | None:
|
|
|
500
579
|
return None # Handled separately as oneof
|
|
501
580
|
|
|
502
581
|
if hasattr(python_type, "__origin__"):
|
|
503
|
-
if python_type.__origin__
|
|
582
|
+
if python_type.__origin__ in (list, tuple):
|
|
504
583
|
inner_type = python_type.__args__[0]
|
|
505
584
|
inner_proto_type = protobuf_type_mapping(inner_type)
|
|
506
585
|
if inner_proto_type:
|
|
@@ -629,6 +708,14 @@ def generate_message_definition(
|
|
|
629
708
|
return msg_def, refs
|
|
630
709
|
|
|
631
710
|
|
|
711
|
+
def is_stream_type(annotation: Type) -> bool:
|
|
712
|
+
return get_origin(annotation) is AsyncIterator
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def is_generic_alias(annotation: Type) -> bool:
|
|
716
|
+
return get_origin(annotation) is not None
|
|
717
|
+
|
|
718
|
+
|
|
632
719
|
def generate_proto(obj: object, package_name: str = "") -> str:
|
|
633
720
|
"""
|
|
634
721
|
Generate a .proto definition from a service class.
|
|
@@ -672,6 +759,11 @@ def generate_proto(obj: object, package_name: str = "") -> str:
|
|
|
672
759
|
continue
|
|
673
760
|
done_messages.add(mt)
|
|
674
761
|
|
|
762
|
+
if is_stream_type(mt):
|
|
763
|
+
item_type = get_args(mt)[0]
|
|
764
|
+
message_types.append(item_type)
|
|
765
|
+
continue
|
|
766
|
+
|
|
675
767
|
for _, field_info in mt.model_fields.items():
|
|
676
768
|
t = field_info.annotation
|
|
677
769
|
if is_union_type(t):
|
|
@@ -702,9 +794,16 @@ def generate_proto(obj: object, package_name: str = "") -> str:
|
|
|
702
794
|
if method_docstr:
|
|
703
795
|
for comment_line in comment_out(method_docstr):
|
|
704
796
|
rpc_definitions.append(comment_line)
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
797
|
+
|
|
798
|
+
if is_stream_type(response_type):
|
|
799
|
+
item_type = get_args(response_type)[0]
|
|
800
|
+
rpc_definitions.append(
|
|
801
|
+
f"rpc {method_name} ({request_type.__name__}) returns (stream {item_type.__name__});"
|
|
802
|
+
)
|
|
803
|
+
else:
|
|
804
|
+
rpc_definitions.append(
|
|
805
|
+
f"rpc {method_name} ({request_type.__name__}) returns ({response_type.__name__});"
|
|
806
|
+
)
|
|
708
807
|
|
|
709
808
|
if not package_name:
|
|
710
809
|
if service_name.endswith("Service"):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pydantic-rpc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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,7 +14,7 @@ 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](https://docs.pydantic.dev/) models via [gRPC](https://grpc.io/)/[
|
|
17
|
+
**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.
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
Below is an example of a simple gRPC service that exposes a [PydanticAI](https://ai.pydantic.dev/) agent:
|
|
@@ -54,7 +54,7 @@ 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
|
|
57
|
+
And here is an example of a simple Connect RPC service that exposes the same agent as an ASGI application:
|
|
58
58
|
|
|
59
59
|
```python
|
|
60
60
|
import asyncio
|
|
@@ -254,6 +254,88 @@ When this variable is set to "true", PydanticRPC will load existing pre-generate
|
|
|
254
254
|
|
|
255
255
|
## 💎 Advanced Features
|
|
256
256
|
|
|
257
|
+
### 🌊 Response Streaming
|
|
258
|
+
PydanticRPC supports streaming for responses in asynchronous gRPC and gRPC-Web services only.
|
|
259
|
+
|
|
260
|
+
Please see the sample code below:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
import asyncio
|
|
264
|
+
from typing import AsyncIterator
|
|
265
|
+
|
|
266
|
+
from pydantic import field_validator
|
|
267
|
+
from pydantic_ai import Agent
|
|
268
|
+
from pydantic_rpc import AsyncIOServer, Message
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# `Message` is just a pydantic BaseModel alias
|
|
272
|
+
class CityLocation(Message):
|
|
273
|
+
city: str
|
|
274
|
+
country: str
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class OlympicsQuery(Message):
|
|
278
|
+
year: int
|
|
279
|
+
|
|
280
|
+
def prompt(self):
|
|
281
|
+
return f"Where were the Olympics held in {self.year}?"
|
|
282
|
+
|
|
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
|
+
|
|
291
|
+
class OlympicsDurationQuery(Message):
|
|
292
|
+
start: int
|
|
293
|
+
end: int
|
|
294
|
+
|
|
295
|
+
def prompt(self):
|
|
296
|
+
return f"From {self.start} to {self.end}, how many Olympics were held? Please provide the list of countries and cities."
|
|
297
|
+
|
|
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
|
+
|
|
313
|
+
class StreamingResult(Message):
|
|
314
|
+
answer: str
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class OlympicsAgent:
|
|
318
|
+
def __init__(self):
|
|
319
|
+
self._agent = Agent("ollama:llama3.2")
|
|
320
|
+
|
|
321
|
+
async def ask(self, req: OlympicsQuery) -> CityLocation:
|
|
322
|
+
result = await self._agent.run(req.prompt(), result_type=CityLocation)
|
|
323
|
+
return result.data
|
|
324
|
+
|
|
325
|
+
async def ask_stream(
|
|
326
|
+
self, req: OlympicsDurationQuery
|
|
327
|
+
) -> AsyncIterator[StreamingResult]:
|
|
328
|
+
async with self._agent.run_stream(req.prompt(), result_type=str) as result:
|
|
329
|
+
async for data in result.stream_text(delta=True):
|
|
330
|
+
yield StreamingResult(answer=data)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
if __name__ == "__main__":
|
|
334
|
+
s = AsyncIOServer()
|
|
335
|
+
loop = asyncio.get_event_loop()
|
|
336
|
+
loop.run_until_complete(s.run(OlympicsAgent()))
|
|
337
|
+
```
|
|
338
|
+
|
|
257
339
|
### 🔗 Multiple Services with Custom Interceptors
|
|
258
340
|
|
|
259
341
|
PydanticRPC supports defining and running multiple services in a single server:
|
|
@@ -349,6 +431,17 @@ python core.py a_module.py aClass
|
|
|
349
431
|
|
|
350
432
|
Using this generated proto file and tools as `protoc`, `buf` and `BSR`, you could generate code for any desired language other than Python.
|
|
351
433
|
|
|
434
|
+
## 📖 Data Type Mapping
|
|
435
|
+
|
|
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> |
|
|
444
|
+
|
|
352
445
|
|
|
353
446
|
## TODO
|
|
354
447
|
- [ ] Streaming Support
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
pydantic_rpc/__init__.py,sha256=AWYjSmYQcMqsqGmGK4k-pQQhX6RBBgkTvNcQtCtsctU,113
|
|
2
|
+
pydantic_rpc/core.py,sha256=AA2LISRvwNqqVByFzG_7tToZTnchfLIUhD3jlYcm0S4,47299
|
|
3
|
+
pydantic_rpc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pydantic_rpc-0.3.0.dist-info/METADATA,sha256=kobbXfMf1l64C4yMLiyXBDwxdgLHYFeVp-o0eqYvxno,11412
|
|
5
|
+
pydantic_rpc-0.3.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
6
|
+
pydantic_rpc-0.3.0.dist-info/licenses/LICENSE,sha256=Y6jkAm2VqPqoGIGQ-mEQCecNfteQ2LwdpYhC5XiH_cA,1069
|
|
7
|
+
pydantic_rpc-0.3.0.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
pydantic_rpc/__init__.py,sha256=AWYjSmYQcMqsqGmGK4k-pQQhX6RBBgkTvNcQtCtsctU,113
|
|
2
|
-
pydantic_rpc/core.py,sha256=CUoiQpARAAejnxNv_6foerWvc_vUvXpZOU1LxoR-EiQ,42596
|
|
3
|
-
pydantic_rpc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
pydantic_rpc-0.2.1.dist-info/METADATA,sha256=Td5HvDvxy521IB9rClJcsmtxPHAdPRfxcJHgQ3WSEmU,8933
|
|
5
|
-
pydantic_rpc-0.2.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
6
|
-
pydantic_rpc-0.2.1.dist-info/licenses/LICENSE,sha256=Y6jkAm2VqPqoGIGQ-mEQCecNfteQ2LwdpYhC5XiH_cA,1069
|
|
7
|
-
pydantic_rpc-0.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|