pydantic-rpc 0.5.0__py3-none-any.whl → 0.6.1__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 CHANGED
@@ -31,6 +31,7 @@ from sonora.wsgi import grpcWSGI
31
31
  from sonora.asgi import grpcASGI
32
32
  from connecpy.asgi import ConnecpyASGIApp as ConnecpyASGI
33
33
  from connecpy.errors import Errors
34
+ from connecpy.wsgi import ConnecpyWSGIApp as ConnecpyWSGI
34
35
 
35
36
  # Protobuf Python modules for Timestamp, Duration (requires protobuf / grpcio)
36
37
  from google.protobuf import timestamp_pb2, duration_pb2
@@ -356,6 +357,69 @@ def connect_obj_with_stub_async(pb2_grpc_module, pb2_module, obj: object) -> typ
356
357
  return ConcreteServiceClass
357
358
 
358
359
 
360
+ def connect_obj_with_stub_connecpy(connecpy_module, pb2_module, obj: object) -> type:
361
+ """
362
+ Connect a Python service object to a Connecpy stub.
363
+ """
364
+ service_class = obj.__class__
365
+ stub_class_name = service_class.__name__
366
+ stub_class = getattr(connecpy_module, stub_class_name)
367
+
368
+ class ConcreteServiceClass(stub_class):
369
+ pass
370
+
371
+ def implement_stub_method(method):
372
+ sig = inspect.signature(method)
373
+ arg_type = get_request_arg_type(sig)
374
+ converter = generate_message_converter(arg_type)
375
+ response_type = sig.return_annotation
376
+ size_of_parameters = len(sig.parameters)
377
+
378
+ match size_of_parameters:
379
+ case 1:
380
+
381
+ def stub_method1(self, request, context, method=method):
382
+ try:
383
+ arg = converter(request)
384
+ resp_obj = method(arg)
385
+ return convert_python_message_to_proto(
386
+ resp_obj, response_type, pb2_module
387
+ )
388
+ except ValidationError as e:
389
+ return context.abort(Errors.InvalidArgument, str(e))
390
+ except Exception as e:
391
+ return context.abort(Errors.Internal, str(e))
392
+
393
+ return stub_method1
394
+
395
+ case 2:
396
+
397
+ def stub_method2(self, request, context, method=method):
398
+ try:
399
+ arg = converter(request)
400
+ resp_obj = method(arg, context)
401
+ return convert_python_message_to_proto(
402
+ resp_obj, response_type, pb2_module
403
+ )
404
+ except ValidationError as e:
405
+ return context.abort(Errors.InvalidArgument, str(e))
406
+ except Exception as e:
407
+ return context.abort(Errors.Internal, str(e))
408
+
409
+ return stub_method2
410
+
411
+ case _:
412
+ raise Exception("Method must have exactly one or two parameters")
413
+
414
+ for method_name, method in get_rpc_methods(obj):
415
+ if method.__name__.startswith("_"):
416
+ continue
417
+ a_method = implement_stub_method(method)
418
+ setattr(ConcreteServiceClass, method_name, a_method)
419
+
420
+ return ConcreteServiceClass
421
+
422
+
359
423
  def connect_obj_with_stub_async_connecpy(
360
424
  connecpy_module, pb2_module, obj: object
361
425
  ) -> type:
@@ -1330,11 +1394,54 @@ class ConnecpyASGIApp:
1330
1394
  await self._app(scope, receive, send)
1331
1395
 
1332
1396
 
1397
+ class ConnecpyWSGIApp:
1398
+ """
1399
+ A WSGI-compatible application that can serve Connect-RPC via Connecpy's ConnecpyWSGIApp.
1400
+ """
1401
+
1402
+ def __init__(self):
1403
+ self._app = ConnecpyWSGI()
1404
+ self._service_names = []
1405
+ self._package_name = ""
1406
+
1407
+ def mount(self, obj: object, package_name: str = ""):
1408
+ """Generate and compile proto files, then mount the async service implementation."""
1409
+ connecpy_module, pb2_module = generate_and_compile_proto_using_connecpy(
1410
+ obj, package_name
1411
+ )
1412
+ self.mount_using_pb2_modules(connecpy_module, pb2_module, obj)
1413
+
1414
+ def mount_using_pb2_modules(self, connecpy_module, pb2_module, obj: object):
1415
+ """Connect the compiled connecpy and pb2 modules with the async service implementation."""
1416
+ concreteServiceClass = connect_obj_with_stub_connecpy(
1417
+ connecpy_module, pb2_module, obj
1418
+ )
1419
+ service_name = obj.__class__.__name__
1420
+ service_impl = concreteServiceClass()
1421
+ connecpy_server = get_connecpy_server_class(connecpy_module, service_name)
1422
+ self._app.add_service(connecpy_server(service=service_impl))
1423
+ full_service_name = pb2_module.DESCRIPTOR.services_by_name[
1424
+ service_name
1425
+ ].full_name
1426
+ self._service_names.append(full_service_name)
1427
+
1428
+ def mount_objs(self, *objs):
1429
+ """Mount multiple service objects into this WSGI app."""
1430
+ for obj in objs:
1431
+ self.mount(obj, self._package_name)
1432
+
1433
+ def __call__(self, environ, start_response):
1434
+ """WSGI entry point."""
1435
+ return self._app(environ, start_response)
1436
+
1437
+
1333
1438
  def main():
1334
1439
  import argparse
1335
1440
 
1336
1441
  parser = argparse.ArgumentParser(description="Generate and compile proto files.")
1337
- parser.add_argument("py_file", type=str, help="The Python file containing the service class.")
1442
+ parser.add_argument(
1443
+ "py_file", type=str, help="The Python file containing the service class."
1444
+ )
1338
1445
  parser.add_argument("class_name", type=str, help="The name of the service class.")
1339
1446
  args = parser.parse_args()
1340
1447
 
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pydantic-rpc
3
- Version: 0.5.0
3
+ Version: 0.6.1
4
4
  Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
5
5
  Author: Yasushi Itoh
6
6
  License-File: LICENSE
7
7
  Requires-Python: >=3.11
8
- Requires-Dist: connecpy>=1.2.1
8
+ Requires-Dist: connecpy>=1.4.1
9
9
  Requires-Dist: grpcio-health-checking>=1.56.2
10
10
  Requires-Dist: grpcio-reflection>=1.56.2
11
11
  Requires-Dist: grpcio-tools>=1.56.2
@@ -23,7 +23,9 @@ Below is an example of a simple gRPC service that exposes a [PydanticAI](https:/
23
23
  ```python
24
24
  import asyncio
25
25
 
26
+ from openai import AsyncOpenAI
26
27
  from pydantic_ai import Agent
28
+ from pydantic_ai.models.openai import OpenAIModel
27
29
  from pydantic_rpc import AsyncIOServer, Message
28
30
 
29
31
 
@@ -42,7 +44,15 @@ class Olympics(Message):
42
44
 
43
45
  class OlympicsLocationAgent:
44
46
  def __init__(self):
45
- self._agent = Agent("ollama:llama3.2", result_type=CityLocation)
47
+ client = AsyncOpenAI(
48
+ base_url="http://localhost:11434/v1",
49
+ api_key="ollama_api_key",
50
+ )
51
+ ollama_model = OpenAIModel(
52
+ model_name="llama3.2",
53
+ openai_client=client,
54
+ )
55
+ self._agent = Agent(ollama_model)
46
56
 
47
57
  async def ask(self, req: Olympics) -> CityLocation:
48
58
  result = await self._agent.run(req.prompt())
@@ -60,7 +70,9 @@ And here is an example of a simple Connect RPC service that exposes the same age
60
70
  ```python
61
71
  import asyncio
62
72
 
73
+ from openai import AsyncOpenAI
63
74
  from pydantic_ai import Agent
75
+ from pydantic_ai.models.openai import OpenAIModel
64
76
  from pydantic_rpc import ConnecpyASGIApp, Message
65
77
 
66
78
 
@@ -78,7 +90,15 @@ class Olympics(Message):
78
90
 
79
91
  class OlympicsLocationAgent:
80
92
  def __init__(self):
81
- self._agent = Agent("ollama:llama3.2", result_type=CityLocation)
93
+ client = AsyncOpenAI(
94
+ base_url="http://localhost:11434/v1",
95
+ api_key="ollama_api_key",
96
+ )
97
+ ollama_model = OpenAIModel(
98
+ model_name="llama3.2",
99
+ openai_client=client,
100
+ )
101
+ self._agent = Agent(ollama_model, result_type=CityLocation)
82
102
 
83
103
  async def ask(self, req: Olympics) -> CityLocation:
84
104
  result = await self._agent.run(req.prompt())
@@ -104,6 +124,7 @@ app.mount(OlympicsLocationAgent())
104
124
  - 🌐 **WSGI/ASGI Support:** Create gRPC-Web services that can run as WSGI or ASGI applications powered by `Sonora`.
105
125
  - **For Connect-RPC:**
106
126
  - 🌐 **Connecpy Support:** Partially supports Connect-RPC via `Connecpy`.
127
+ - 🛠️ **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.
107
128
 
108
129
  ## 📦 Installation
109
130
 
@@ -257,7 +278,9 @@ When this variable is set to "true", PydanticRPC will load existing pre-generate
257
278
  ## 💎 Advanced Features
258
279
 
259
280
  ### 🌊 Response Streaming
260
- PydanticRPC supports streaming for responses in asynchronous gRPC and gRPC-Web services only.
281
+ PydanticRPC supports streaming responses only for asynchronous gRPC and gRPC-Web services.
282
+ If a service class method’s return type is `typing.AsyncIterator[T]`, the method is considered a streaming method.
283
+
261
284
 
262
285
  Please see the sample code below:
263
286
 
@@ -265,8 +288,10 @@ Please see the sample code below:
265
288
  import asyncio
266
289
  from typing import Annotated, AsyncIterator
267
290
 
291
+ from openai import AsyncOpenAI
268
292
  from pydantic import Field
269
293
  from pydantic_ai import Agent
294
+ from pydantic_ai.models.openai import OpenAIModel
270
295
  from pydantic_rpc import AsyncIOServer, Message
271
296
 
272
297
 
@@ -299,7 +324,15 @@ class StreamingResult(Message):
299
324
 
300
325
  class OlympicsAgent:
301
326
  def __init__(self):
302
- self._agent = Agent("ollama:llama3.2")
327
+ client = AsyncOpenAI(
328
+ base_url='http://localhost:11434/v1',
329
+ api_key='ollama_api_key',
330
+ )
331
+ ollama_model = OpenAIModel(
332
+ model_name='llama3.2',
333
+ openai_client=client,
334
+ )
335
+ self._agent = Agent(ollama_model)
303
336
 
304
337
  async def ask(self, req: OlympicsQuery) -> CityLocation:
305
338
  result = await self._agent.run(req.prompt(), result_type=CityLocation)
@@ -319,6 +352,302 @@ if __name__ == "__main__":
319
352
  loop.run_until_complete(s.run(OlympicsAgent()))
320
353
  ```
321
354
 
355
+ In the example above, the `ask_stream` method returns an `AsyncIterator[StreamingResult]` object, which is considered a streaming method. The `StreamingResult` class is a Pydantic model that defines the response type of the streaming method. You can use any Pydantic model as the response type.
356
+
357
+ Now, you can call the `ask_stream` method of the server described above using your preferred gRPC client tool. The example below uses `buf curl`.
358
+
359
+
360
+ ```console
361
+ % buf curl --data '{"start": 1980, "end": 2024}' -v http://localhost:50051/olympicsagent.v1.OlympicsAgent/AskStream --protocol grpc --http2-prior-knowledge
362
+
363
+ buf: * Using server reflection to resolve "olympicsagent.v1.OlympicsAgent"
364
+ buf: * Dialing (tcp) localhost:50051...
365
+ buf: * Connected to [::1]:50051
366
+ buf: > (#1) POST /grpc.reflection.v1.ServerReflection/ServerReflectionInfo
367
+ buf: > (#1) Accept-Encoding: identity
368
+ buf: > (#1) Content-Type: application/grpc+proto
369
+ buf: > (#1) Grpc-Accept-Encoding: gzip
370
+ buf: > (#1) Grpc-Timeout: 119997m
371
+ buf: > (#1) Te: trailers
372
+ buf: > (#1) User-Agent: grpc-go-connect/1.12.0 (go1.21.4) buf/1.28.1
373
+ buf: > (#1)
374
+ buf: } (#1) [5 bytes data]
375
+ buf: } (#1) [32 bytes data]
376
+ buf: < (#1) HTTP/2.0 200 OK
377
+ buf: < (#1) Content-Type: application/grpc
378
+ buf: < (#1) Grpc-Message: Method not found!
379
+ buf: < (#1) Grpc-Status: 12
380
+ buf: < (#1)
381
+ buf: * (#1) Call complete
382
+ buf: > (#2) POST /grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo
383
+ buf: > (#2) Accept-Encoding: identity
384
+ buf: > (#2) Content-Type: application/grpc+proto
385
+ buf: > (#2) Grpc-Accept-Encoding: gzip
386
+ buf: > (#2) Grpc-Timeout: 119967m
387
+ buf: > (#2) Te: trailers
388
+ buf: > (#2) User-Agent: grpc-go-connect/1.12.0 (go1.21.4) buf/1.28.1
389
+ buf: > (#2)
390
+ buf: } (#2) [5 bytes data]
391
+ buf: } (#2) [32 bytes data]
392
+ buf: < (#2) HTTP/2.0 200 OK
393
+ buf: < (#2) Content-Type: application/grpc
394
+ buf: < (#2) Grpc-Accept-Encoding: identity, deflate, gzip
395
+ buf: < (#2)
396
+ buf: { (#2) [5 bytes data]
397
+ buf: { (#2) [434 bytes data]
398
+ buf: * Server reflection has resolved file "olympicsagent.proto"
399
+ buf: * Invoking RPC olympicsagent.v1.OlympicsAgent.AskStream
400
+ buf: > (#3) POST /olympicsagent.v1.OlympicsAgent/AskStream
401
+ buf: > (#3) Accept-Encoding: identity
402
+ buf: > (#3) Content-Type: application/grpc+proto
403
+ buf: > (#3) Grpc-Accept-Encoding: gzip
404
+ buf: > (#3) Grpc-Timeout: 119947m
405
+ buf: > (#3) Te: trailers
406
+ buf: > (#3) User-Agent: grpc-go-connect/1.12.0 (go1.21.4) buf/1.28.1
407
+ buf: > (#3)
408
+ buf: } (#3) [5 bytes data]
409
+ buf: } (#3) [6 bytes data]
410
+ buf: * (#3) Finished upload
411
+ buf: < (#3) HTTP/2.0 200 OK
412
+ buf: < (#3) Content-Type: application/grpc
413
+ buf: < (#3) Grpc-Accept-Encoding: identity, deflate, gzip
414
+ buf: < (#3)
415
+ buf: { (#3) [5 bytes data]
416
+ buf: { (#3) [25 bytes data]
417
+ {
418
+ "answer": "Here's a list of Summer"
419
+ }
420
+ buf: { (#3) [5 bytes data]
421
+ buf: { (#3) [31 bytes data]
422
+ {
423
+ "answer": " and Winter Olympics from 198"
424
+ }
425
+ buf: { (#3) [5 bytes data]
426
+ buf: { (#3) [29 bytes data]
427
+ {
428
+ "answer": "0 to 2024:\n\nSummer Olympics"
429
+ }
430
+ buf: { (#3) [5 bytes data]
431
+ buf: { (#3) [20 bytes data]
432
+ {
433
+ "answer": ":\n1. 1980 - Moscow"
434
+ }
435
+ buf: { (#3) [5 bytes data]
436
+ buf: { (#3) [20 bytes data]
437
+ {
438
+ "answer": ", Soviet Union\n2. "
439
+ }
440
+ buf: { (#3) [5 bytes data]
441
+ buf: { (#3) [32 bytes data]
442
+ {
443
+ "answer": "1984 - Los Angeles, California"
444
+ }
445
+ buf: { (#3) [5 bytes data]
446
+ buf: { (#3) [15 bytes data]
447
+ {
448
+ "answer": ", USA\n3. 1988"
449
+ }
450
+ buf: { (#3) [5 bytes data]
451
+ buf: { (#3) [26 bytes data]
452
+ {
453
+ "answer": " - Seoul, South Korea\n4."
454
+ }
455
+ buf: { (#3) [5 bytes data]
456
+ buf: { (#3) [27 bytes data]
457
+ {
458
+ "answer": " 1992 - Barcelona, Spain\n"
459
+ }
460
+ buf: { (#3) [5 bytes data]
461
+ buf: { (#3) [20 bytes data]
462
+ {
463
+ "answer": "5. 1996 - Atlanta,"
464
+ }
465
+ buf: { (#3) [5 bytes data]
466
+ buf: { (#3) [22 bytes data]
467
+ {
468
+ "answer": " Georgia, USA\n6. 200"
469
+ }
470
+ buf: { (#3) [5 bytes data]
471
+ buf: { (#3) [26 bytes data]
472
+ {
473
+ "answer": "0 - Sydney, Australia\n7."
474
+ }
475
+ buf: { (#3) [5 bytes data]
476
+ buf: { (#3) [25 bytes data]
477
+ {
478
+ "answer": " 2004 - Athens, Greece\n"
479
+ }
480
+ buf: { (#3) [5 bytes data]
481
+ buf: { (#3) [20 bytes data]
482
+ {
483
+ "answer": "8. 2008 - Beijing,"
484
+ }
485
+ buf: { (#3) [5 bytes data]
486
+ buf: { (#3) [18 bytes data]
487
+ {
488
+ "answer": " China\n9. 2012 -"
489
+ }
490
+ buf: { (#3) [5 bytes data]
491
+ buf: { (#3) [29 bytes data]
492
+ {
493
+ "answer": " London, United Kingdom\n10."
494
+ }
495
+ buf: { (#3) [5 bytes data]
496
+ buf: { (#3) [24 bytes data]
497
+ {
498
+ "answer": " 2016 - Rio de Janeiro"
499
+ }
500
+ buf: { (#3) [5 bytes data]
501
+ buf: { (#3) [18 bytes data]
502
+ {
503
+ "answer": ", Brazil\n11. 202"
504
+ }
505
+ buf: { (#3) [5 bytes data]
506
+ buf: { (#3) [24 bytes data]
507
+ {
508
+ "answer": "0 - Tokyo, Japan (held"
509
+ }
510
+ buf: { (#3) [5 bytes data]
511
+ buf: { (#3) [21 bytes data]
512
+ {
513
+ "answer": " in 2021 due to the"
514
+ }
515
+ buf: { (#3) [5 bytes data]
516
+ buf: { (#3) [26 bytes data]
517
+ {
518
+ "answer": " COVID-19 pandemic)\n12. "
519
+ }
520
+ buf: { (#3) [5 bytes data]
521
+ buf: { (#3) [28 bytes data]
522
+ {
523
+ "answer": "2024 - Paris, France\n\nNote"
524
+ }
525
+ buf: { (#3) [5 bytes data]
526
+ buf: { (#3) [41 bytes data]
527
+ {
528
+ "answer": ": The Olympics were held without a host"
529
+ }
530
+ buf: { (#3) [5 bytes data]
531
+ buf: { (#3) [26 bytes data]
532
+ {
533
+ "answer": " city for one year (2022"
534
+ }
535
+ buf: { (#3) [5 bytes data]
536
+ buf: { (#3) [42 bytes data]
537
+ {
538
+ "answer": ", due to the Russian invasion of Ukraine"
539
+ }
540
+ buf: { (#3) [5 bytes data]
541
+ buf: { (#3) [29 bytes data]
542
+ {
543
+ "answer": ").\n\nWinter Olympics:\n1. 198"
544
+ }
545
+ buf: { (#3) [5 bytes data]
546
+ buf: { (#3) [27 bytes data]
547
+ {
548
+ "answer": "0 - Lake Placid, New York"
549
+ }
550
+ buf: { (#3) [5 bytes data]
551
+ buf: { (#3) [15 bytes data]
552
+ {
553
+ "answer": ", USA\n2. 1984"
554
+ }
555
+ buf: { (#3) [5 bytes data]
556
+ buf: { (#3) [27 bytes data]
557
+ {
558
+ "answer": " - Sarajevo, Yugoslavia ("
559
+ }
560
+ buf: { (#3) [5 bytes data]
561
+ buf: { (#3) [30 bytes data]
562
+ {
563
+ "answer": "now Bosnia and Herzegovina)\n"
564
+ }
565
+ buf: { (#3) [5 bytes data]
566
+ buf: { (#3) [20 bytes data]
567
+ {
568
+ "answer": "3. 1988 - Calgary,"
569
+ }
570
+ buf: { (#3) [5 bytes data]
571
+ buf: { (#3) [25 bytes data]
572
+ {
573
+ "answer": " Alberta, Canada\n4. 199"
574
+ }
575
+ buf: { (#3) [5 bytes data]
576
+ buf: { (#3) [26 bytes data]
577
+ {
578
+ "answer": "2 - Albertville, France\n"
579
+ }
580
+ buf: { (#3) [5 bytes data]
581
+ buf: { (#3) [13 bytes data]
582
+ {
583
+ "answer": "5. 1994 - L"
584
+ }
585
+ buf: { (#3) [5 bytes data]
586
+ buf: { (#3) [24 bytes data]
587
+ {
588
+ "answer": "illehammer, Norway\n6. "
589
+ }
590
+ buf: { (#3) [5 bytes data]
591
+ buf: { (#3) [23 bytes data]
592
+ {
593
+ "answer": "1998 - Nagano, Japan\n"
594
+ }
595
+ buf: { (#3) [5 bytes data]
596
+ buf: { (#3) [16 bytes data]
597
+ {
598
+ "answer": "7. 2002 - Salt"
599
+ }
600
+ buf: { (#3) [5 bytes data]
601
+ buf: { (#3) [24 bytes data]
602
+ {
603
+ "answer": " Lake City, Utah, USA\n"
604
+ }
605
+ buf: { (#3) [5 bytes data]
606
+ buf: { (#3) [18 bytes data]
607
+ {
608
+ "answer": "8. 2006 - Torino"
609
+ }
610
+ buf: { (#3) [5 bytes data]
611
+ buf: { (#3) [17 bytes data]
612
+ {
613
+ "answer": ", Italy\n9. 2010"
614
+ }
615
+ buf: { (#3) [5 bytes data]
616
+ buf: { (#3) [40 bytes data]
617
+ {
618
+ "answer": " - Vancouver, British Columbia, Canada"
619
+ }
620
+ buf: { (#3) [5 bytes data]
621
+ buf: { (#3) [13 bytes data]
622
+ {
623
+ "answer": "\n10. 2014 -"
624
+ }
625
+ buf: { (#3) [5 bytes data]
626
+ buf: { (#3) [20 bytes data]
627
+ {
628
+ "answer": " Sochi, Russia\n11."
629
+ }
630
+ buf: { (#3) [5 bytes data]
631
+ buf: { (#3) [16 bytes data]
632
+ {
633
+ "answer": " 2018 - Pyeong"
634
+ }
635
+ buf: { (#3) [5 bytes data]
636
+ buf: { (#3) [24 bytes data]
637
+ {
638
+ "answer": "chang, South Korea\n12."
639
+ }
640
+ buf: < (#3)
641
+ buf: < (#3) Grpc-Message:
642
+ buf: < (#3) Grpc-Status: 0
643
+ buf: * (#3) Call complete
644
+ buf: < (#2)
645
+ buf: < (#2) Grpc-Message:
646
+ buf: < (#2) Grpc-Status: 0
647
+ buf: * (#2) Call complete
648
+ %
649
+ ```
650
+
322
651
  ### 🔗 Multiple Services with Custom Interceptors
323
652
 
324
653
  PydanticRPC supports defining and running multiple services in a single server:
@@ -404,9 +733,9 @@ if __name__ == "__main__":
404
733
 
405
734
  TODO
406
735
 
407
- ### 🗄️ Protobuf file and code (Python files) generation
736
+ ### 🗄️ Protobuf file and code (Python files) generation using CLI
408
737
 
409
- Youcan genereate protobuf files and code for a given module and a specified class using `pydantic-rpc` CLI command:
738
+ You can genereate protobuf files and code for a given module and a specified class using `pydantic-rpc` CLI command:
410
739
 
411
740
  ```bash
412
741
  pydantic-rpc a_module.py aClassName
@@ -0,0 +1,8 @@
1
+ pydantic_rpc/__init__.py,sha256=oomSVGmh_zddQQaphQt1L2xSVh9dD1LVyaAq1cN1FW4,231
2
+ pydantic_rpc/core.py,sha256=SB9GDZRxsMWQFxphinYQUAqrOx10eUn40fPTc7t1xYs,52107
3
+ pydantic_rpc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ pydantic_rpc-0.6.1.dist-info/METADATA,sha256=Vi8bU0-HjPnhI0ETt9ReRhmMfJxBqPuf9gnAYQjTx4c,20481
5
+ pydantic_rpc-0.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
+ pydantic_rpc-0.6.1.dist-info/entry_points.txt,sha256=LeZJ6UN-fhjKrEGkcmsAAKuA-fIe7MpvzKMPSZfi0NE,56
7
+ pydantic_rpc-0.6.1.dist-info/licenses/LICENSE,sha256=Y6jkAm2VqPqoGIGQ-mEQCecNfteQ2LwdpYhC5XiH_cA,1069
8
+ pydantic_rpc-0.6.1.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- pydantic_rpc/__init__.py,sha256=oomSVGmh_zddQQaphQt1L2xSVh9dD1LVyaAq1cN1FW4,231
2
- pydantic_rpc/core.py,sha256=7RPQEhqzzUIqm6IYLSsKYPIHV1_VV7UvCehPAAkUuJ4,48085
3
- pydantic_rpc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- pydantic_rpc-0.5.0.dist-info/METADATA,sha256=xBMHL0pmboi5BAkREyu-LtlhXJ_drfj3jOwUbOaHlKE,12060
5
- pydantic_rpc-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
- pydantic_rpc-0.5.0.dist-info/entry_points.txt,sha256=LeZJ6UN-fhjKrEGkcmsAAKuA-fIe7MpvzKMPSZfi0NE,56
7
- pydantic_rpc-0.5.0.dist-info/licenses/LICENSE,sha256=Y6jkAm2VqPqoGIGQ-mEQCecNfteQ2LwdpYhC5XiH_cA,1069
8
- pydantic_rpc-0.5.0.dist-info/RECORD,,