pydantic-rpc 0.14.0__tar.gz → 0.15.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.
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/PKG-INFO +5 -1
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/pyproject.toml +7 -1
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/src/pydantic_rpc/__init__.py +2 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/src/pydantic_rpc/core.py +208 -46
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/src/pydantic_rpc/decorators.py +47 -13
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/README.md +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/src/pydantic_rpc/mcp/__init__.py +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/src/pydantic_rpc/mcp/converter.py +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/src/pydantic_rpc/mcp/exporter.py +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/src/pydantic_rpc/options.py +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/src/pydantic_rpc/py.typed +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.1}/src/pydantic_rpc/tls.py +0 -0
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pydantic-rpc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.1
|
|
4
4
|
Summary: A Python library for building gRPC/ConnectRPC services with Pydantic models.
|
|
5
5
|
Author: Yasushi Itoh
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
6
10
|
Requires-Dist: annotated-types==0.7.0
|
|
7
11
|
Requires-Dist: pydantic>=2.1.1
|
|
8
12
|
Requires-Dist: grpcio>=1.56.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pydantic-rpc"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.15.1"
|
|
4
4
|
description = "A Python library for building gRPC/ConnectRPC services with Pydantic models."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Yasushi Itoh" }
|
|
@@ -19,6 +19,12 @@ dependencies = [
|
|
|
19
19
|
]
|
|
20
20
|
readme = "README.md"
|
|
21
21
|
requires-python = ">= 3.11"
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
]
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
[build-system]
|
|
@@ -15,6 +15,7 @@ from .decorators import (
|
|
|
15
15
|
has_http_option,
|
|
16
16
|
error_handler,
|
|
17
17
|
get_error_handlers,
|
|
18
|
+
invoke_error_handler,
|
|
18
19
|
)
|
|
19
20
|
from .tls import (
|
|
20
21
|
GrpcTLSConfig,
|
|
@@ -35,6 +36,7 @@ __all__ = [
|
|
|
35
36
|
"has_http_option",
|
|
36
37
|
"error_handler",
|
|
37
38
|
"get_error_handlers",
|
|
39
|
+
"invoke_error_handler",
|
|
38
40
|
"GrpcTLSConfig",
|
|
39
41
|
"extract_peer_identity",
|
|
40
42
|
"extract_peer_certificate_chain",
|
|
@@ -14,6 +14,8 @@ import types
|
|
|
14
14
|
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence
|
|
15
15
|
from concurrent import futures
|
|
16
16
|
from connectrpc.code import Code as Errors
|
|
17
|
+
from connectrpc.errors import ConnectError
|
|
18
|
+
|
|
17
19
|
# Protobuf Python modules for Timestamp, Duration (requires protobuf / grpcio)
|
|
18
20
|
from google.protobuf import duration_pb2, timestamp_pb2, empty_pb2
|
|
19
21
|
from grpc import ServicerContext
|
|
@@ -32,12 +34,17 @@ from typing import (
|
|
|
32
34
|
get_origin,
|
|
33
35
|
cast,
|
|
34
36
|
TypeGuard,
|
|
37
|
+
Union,
|
|
38
|
+
Tuple,
|
|
35
39
|
)
|
|
36
|
-
from typing import Union
|
|
37
|
-
from typing import Union, Sequence, Tuple
|
|
38
40
|
from concurrent.futures import Executor
|
|
39
41
|
|
|
40
|
-
from .decorators import
|
|
42
|
+
from .decorators import (
|
|
43
|
+
get_method_options,
|
|
44
|
+
has_http_option,
|
|
45
|
+
get_error_handlers,
|
|
46
|
+
invoke_error_handler,
|
|
47
|
+
)
|
|
41
48
|
from .tls import GrpcTLSConfig
|
|
42
49
|
|
|
43
50
|
###############################################################################
|
|
@@ -309,6 +316,116 @@ def generate_message_converter(
|
|
|
309
316
|
return converter
|
|
310
317
|
|
|
311
318
|
|
|
319
|
+
def handle_validation_error_sync(
|
|
320
|
+
exc: ValidationError,
|
|
321
|
+
method: Callable,
|
|
322
|
+
context: Any,
|
|
323
|
+
request: Any = None,
|
|
324
|
+
is_grpc: bool = True,
|
|
325
|
+
) -> Any:
|
|
326
|
+
"""
|
|
327
|
+
Handle ValidationError with custom error handlers or default behavior (sync version).
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
exc: The ValidationError that was raised
|
|
331
|
+
method: The RPC method being called
|
|
332
|
+
context: The gRPC or Connect context
|
|
333
|
+
request: Optional raw request data
|
|
334
|
+
is_grpc: True for gRPC, False for Connect RPC
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
Result of context.abort() call
|
|
338
|
+
"""
|
|
339
|
+
error_handlers = get_error_handlers(method)
|
|
340
|
+
|
|
341
|
+
if error_handlers:
|
|
342
|
+
# Check if there's a handler for ValidationError
|
|
343
|
+
for handler_config in error_handlers:
|
|
344
|
+
if isinstance(exc, handler_config["exception_type"]):
|
|
345
|
+
if handler_config["handler"]:
|
|
346
|
+
# Custom handler function
|
|
347
|
+
try:
|
|
348
|
+
msg, _details = invoke_error_handler(
|
|
349
|
+
handler_config["handler"], exc, request
|
|
350
|
+
)
|
|
351
|
+
except Exception:
|
|
352
|
+
# Handler failed, fall back to default
|
|
353
|
+
msg = str(exc)
|
|
354
|
+
else:
|
|
355
|
+
# No custom handler, use default message
|
|
356
|
+
msg = str(exc)
|
|
357
|
+
|
|
358
|
+
# Use the configured status code
|
|
359
|
+
if is_grpc:
|
|
360
|
+
status_code = handler_config["status_code"]
|
|
361
|
+
return context.abort(status_code, msg)
|
|
362
|
+
else:
|
|
363
|
+
status_code = handler_config["connect_code"]
|
|
364
|
+
raise ConnectError(code=status_code, message=msg)
|
|
365
|
+
|
|
366
|
+
# No handler found, use default behavior
|
|
367
|
+
if is_grpc:
|
|
368
|
+
return context.abort(grpc.StatusCode.INVALID_ARGUMENT, str(exc))
|
|
369
|
+
else:
|
|
370
|
+
raise ConnectError(code=Errors.INVALID_ARGUMENT, message=str(exc))
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
async def handle_validation_error_async(
|
|
374
|
+
exc: ValidationError,
|
|
375
|
+
method: Callable,
|
|
376
|
+
context: Any,
|
|
377
|
+
request: Any = None,
|
|
378
|
+
is_grpc: bool = True,
|
|
379
|
+
) -> Any:
|
|
380
|
+
"""
|
|
381
|
+
Handle ValidationError with custom error handlers or default behavior (async version).
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
exc: The ValidationError that was raised
|
|
385
|
+
method: The RPC method being called
|
|
386
|
+
context: The gRPC or Connect context
|
|
387
|
+
request: Optional raw request data
|
|
388
|
+
is_grpc: True for gRPC, False for Connect RPC
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
Result of context.abort() call
|
|
392
|
+
"""
|
|
393
|
+
error_handlers = get_error_handlers(method)
|
|
394
|
+
|
|
395
|
+
if error_handlers:
|
|
396
|
+
# Check if there's a handler for ValidationError
|
|
397
|
+
for handler_config in error_handlers:
|
|
398
|
+
if isinstance(exc, handler_config["exception_type"]):
|
|
399
|
+
# Found a matching handler
|
|
400
|
+
if handler_config["handler"]:
|
|
401
|
+
# Custom handler function
|
|
402
|
+
try:
|
|
403
|
+
msg, _details = invoke_error_handler(
|
|
404
|
+
handler_config["handler"], exc, request
|
|
405
|
+
)
|
|
406
|
+
except Exception:
|
|
407
|
+
# Handler failed, fall back to default
|
|
408
|
+
msg = str(exc)
|
|
409
|
+
else:
|
|
410
|
+
# No custom handler, use default message
|
|
411
|
+
msg = str(exc)
|
|
412
|
+
|
|
413
|
+
# Use the configured status code
|
|
414
|
+
if is_grpc:
|
|
415
|
+
status_code = handler_config["status_code"]
|
|
416
|
+
await context.abort(status_code, msg)
|
|
417
|
+
return
|
|
418
|
+
else:
|
|
419
|
+
status_code = handler_config["connect_code"]
|
|
420
|
+
raise ConnectError(code=status_code, message=msg)
|
|
421
|
+
|
|
422
|
+
# No handler found, use default behavior
|
|
423
|
+
if is_grpc:
|
|
424
|
+
await context.abort(grpc.StatusCode.INVALID_ARGUMENT, str(exc))
|
|
425
|
+
else:
|
|
426
|
+
raise ConnectError(code=Errors.INVALID_ARGUMENT, message=str(exc))
|
|
427
|
+
|
|
428
|
+
|
|
312
429
|
def python_value_to_proto_value(field_type: type[Any], value: Any) -> Any:
|
|
313
430
|
"""
|
|
314
431
|
Converts Python values to protobuf values.
|
|
@@ -394,7 +511,9 @@ def connect_obj_with_stub(
|
|
|
394
511
|
resp_obj, response_type, pb2_module
|
|
395
512
|
)
|
|
396
513
|
except ValidationError as e:
|
|
397
|
-
return
|
|
514
|
+
return handle_validation_error_sync(
|
|
515
|
+
e, original, context, request, is_grpc=True
|
|
516
|
+
)
|
|
398
517
|
except Exception as e:
|
|
399
518
|
return context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
400
519
|
|
|
@@ -431,7 +550,9 @@ def connect_obj_with_stub(
|
|
|
431
550
|
resp_obj, response_type, pb2_module
|
|
432
551
|
)
|
|
433
552
|
except ValidationError as e:
|
|
434
|
-
return
|
|
553
|
+
return handle_validation_error_sync(
|
|
554
|
+
e, original, context, request, is_grpc=True
|
|
555
|
+
)
|
|
435
556
|
except Exception as e:
|
|
436
557
|
return context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
437
558
|
|
|
@@ -513,8 +634,8 @@ def connect_obj_with_stub_async(
|
|
|
513
634
|
resp_obj, output_item_type, pb2_module
|
|
514
635
|
)
|
|
515
636
|
except ValidationError as e:
|
|
516
|
-
await
|
|
517
|
-
|
|
637
|
+
await handle_validation_error_async(
|
|
638
|
+
e, method, context, None, is_grpc=True
|
|
518
639
|
)
|
|
519
640
|
except Exception as e:
|
|
520
641
|
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
@@ -534,8 +655,8 @@ def connect_obj_with_stub_async(
|
|
|
534
655
|
resp_obj, output_item_type, pb2_module
|
|
535
656
|
)
|
|
536
657
|
except ValidationError as e:
|
|
537
|
-
await
|
|
538
|
-
|
|
658
|
+
await handle_validation_error_async(
|
|
659
|
+
e, method, context, None, is_grpc=True
|
|
539
660
|
)
|
|
540
661
|
except Exception as e:
|
|
541
662
|
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
@@ -559,8 +680,8 @@ def connect_obj_with_stub_async(
|
|
|
559
680
|
resp_obj, response_type, pb2_module
|
|
560
681
|
)
|
|
561
682
|
except ValidationError as e:
|
|
562
|
-
await
|
|
563
|
-
|
|
683
|
+
await handle_validation_error_async(
|
|
684
|
+
e, method, context, None, is_grpc=True
|
|
564
685
|
)
|
|
565
686
|
except Exception as e:
|
|
566
687
|
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
@@ -580,8 +701,8 @@ def connect_obj_with_stub_async(
|
|
|
580
701
|
resp_obj, response_type, pb2_module
|
|
581
702
|
)
|
|
582
703
|
except ValidationError as e:
|
|
583
|
-
await
|
|
584
|
-
|
|
704
|
+
await handle_validation_error_async(
|
|
705
|
+
e, method, context, None, is_grpc=True
|
|
585
706
|
)
|
|
586
707
|
except Exception as e:
|
|
587
708
|
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
@@ -611,8 +732,8 @@ def connect_obj_with_stub_async(
|
|
|
611
732
|
resp_obj, output_item_type, pb2_module
|
|
612
733
|
)
|
|
613
734
|
except ValidationError as e:
|
|
614
|
-
await
|
|
615
|
-
|
|
735
|
+
await handle_validation_error_async(
|
|
736
|
+
e, method, context, request, is_grpc=True
|
|
616
737
|
)
|
|
617
738
|
except Exception as e:
|
|
618
739
|
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
@@ -632,8 +753,8 @@ def connect_obj_with_stub_async(
|
|
|
632
753
|
resp_obj, output_item_type, pb2_module
|
|
633
754
|
)
|
|
634
755
|
except ValidationError as e:
|
|
635
|
-
await
|
|
636
|
-
|
|
756
|
+
await handle_validation_error_async(
|
|
757
|
+
e, method, context, request, is_grpc=True
|
|
637
758
|
)
|
|
638
759
|
except Exception as e:
|
|
639
760
|
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
@@ -674,8 +795,8 @@ def connect_obj_with_stub_async(
|
|
|
674
795
|
resp_obj, response_type, pb2_module
|
|
675
796
|
)
|
|
676
797
|
except ValidationError as e:
|
|
677
|
-
await
|
|
678
|
-
|
|
798
|
+
await handle_validation_error_async(
|
|
799
|
+
e, method, context, request, is_grpc=True
|
|
679
800
|
)
|
|
680
801
|
except Exception as e:
|
|
681
802
|
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
@@ -709,8 +830,8 @@ def connect_obj_with_stub_async(
|
|
|
709
830
|
resp_obj, response_type, pb2_module
|
|
710
831
|
)
|
|
711
832
|
except ValidationError as e:
|
|
712
|
-
await
|
|
713
|
-
|
|
833
|
+
await handle_validation_error_async(
|
|
834
|
+
e, method, context, request, is_grpc=True
|
|
714
835
|
)
|
|
715
836
|
except Exception as e:
|
|
716
837
|
await context.abort(grpc.StatusCode.INTERNAL, str(e))
|
|
@@ -769,9 +890,11 @@ def connect_obj_with_stub_connect_python(
|
|
|
769
890
|
resp_obj, response_type, pb2_module
|
|
770
891
|
)
|
|
771
892
|
except ValidationError as e:
|
|
772
|
-
return
|
|
893
|
+
return handle_validation_error_sync(
|
|
894
|
+
e, method, context, request, is_grpc=False
|
|
895
|
+
)
|
|
773
896
|
except Exception as e:
|
|
774
|
-
|
|
897
|
+
raise ConnectError(code=Errors.INTERNAL, message=str(e))
|
|
775
898
|
|
|
776
899
|
return stub_method0
|
|
777
900
|
|
|
@@ -798,9 +921,11 @@ def connect_obj_with_stub_connect_python(
|
|
|
798
921
|
resp_obj, response_type, pb2_module
|
|
799
922
|
)
|
|
800
923
|
except ValidationError as e:
|
|
801
|
-
return
|
|
924
|
+
return handle_validation_error_sync(
|
|
925
|
+
e, method, context, request, is_grpc=False
|
|
926
|
+
)
|
|
802
927
|
except Exception as e:
|
|
803
|
-
|
|
928
|
+
raise ConnectError(code=Errors.INTERNAL, message=str(e))
|
|
804
929
|
|
|
805
930
|
return stub_method1
|
|
806
931
|
|
|
@@ -827,9 +952,11 @@ def connect_obj_with_stub_connect_python(
|
|
|
827
952
|
resp_obj, response_type, pb2_module
|
|
828
953
|
)
|
|
829
954
|
except ValidationError as e:
|
|
830
|
-
return
|
|
955
|
+
return handle_validation_error_sync(
|
|
956
|
+
e, method, context, request, is_grpc=False
|
|
957
|
+
)
|
|
831
958
|
except Exception as e:
|
|
832
|
-
|
|
959
|
+
raise ConnectError(code=Errors.INTERNAL, message=str(e))
|
|
833
960
|
|
|
834
961
|
return stub_method2
|
|
835
962
|
|
|
@@ -889,9 +1016,11 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
889
1016
|
resp_obj, response_type, pb2_module
|
|
890
1017
|
)
|
|
891
1018
|
except ValidationError as e:
|
|
892
|
-
await
|
|
1019
|
+
await handle_validation_error_async(
|
|
1020
|
+
e, method, context, request, is_grpc=False
|
|
1021
|
+
)
|
|
893
1022
|
except Exception as e:
|
|
894
|
-
|
|
1023
|
+
raise ConnectError(code=Errors.INTERNAL, message=str(e))
|
|
895
1024
|
|
|
896
1025
|
return stub_method0
|
|
897
1026
|
|
|
@@ -931,9 +1060,13 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
931
1060
|
resp_obj, output_item_type, pb2_module
|
|
932
1061
|
)
|
|
933
1062
|
except ValidationError as e:
|
|
934
|
-
await
|
|
1063
|
+
await handle_validation_error_async(
|
|
1064
|
+
e, method, context, None, is_grpc=False
|
|
1065
|
+
)
|
|
935
1066
|
except Exception as e:
|
|
936
|
-
|
|
1067
|
+
raise ConnectError(
|
|
1068
|
+
code=Errors.INTERNAL, message=str(e)
|
|
1069
|
+
)
|
|
937
1070
|
else: # size_of_parameters == 2
|
|
938
1071
|
|
|
939
1072
|
async def stub_method(
|
|
@@ -949,9 +1082,13 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
949
1082
|
resp_obj, output_item_type, pb2_module
|
|
950
1083
|
)
|
|
951
1084
|
except ValidationError as e:
|
|
952
|
-
await
|
|
1085
|
+
await handle_validation_error_async(
|
|
1086
|
+
e, method, context, None, is_grpc=False
|
|
1087
|
+
)
|
|
953
1088
|
except Exception as e:
|
|
954
|
-
|
|
1089
|
+
raise ConnectError(
|
|
1090
|
+
code=Errors.INTERNAL, message=str(e)
|
|
1091
|
+
)
|
|
955
1092
|
|
|
956
1093
|
return stub_method
|
|
957
1094
|
else:
|
|
@@ -973,9 +1110,13 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
973
1110
|
resp_obj, response_type, pb2_module
|
|
974
1111
|
)
|
|
975
1112
|
except ValidationError as e:
|
|
976
|
-
await
|
|
1113
|
+
await handle_validation_error_async(
|
|
1114
|
+
e, method, context, None, is_grpc=False
|
|
1115
|
+
)
|
|
977
1116
|
except Exception as e:
|
|
978
|
-
|
|
1117
|
+
raise ConnectError(
|
|
1118
|
+
code=Errors.INTERNAL, message=str(e)
|
|
1119
|
+
)
|
|
979
1120
|
else: # size_of_parameters == 2
|
|
980
1121
|
|
|
981
1122
|
async def stub_method(
|
|
@@ -993,9 +1134,13 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
993
1134
|
resp_obj, response_type, pb2_module
|
|
994
1135
|
)
|
|
995
1136
|
except ValidationError as e:
|
|
996
|
-
await
|
|
1137
|
+
await handle_validation_error_async(
|
|
1138
|
+
e, method, context, None, is_grpc=False
|
|
1139
|
+
)
|
|
997
1140
|
except Exception as e:
|
|
998
|
-
|
|
1141
|
+
raise ConnectError(
|
|
1142
|
+
code=Errors.INTERNAL, message=str(e)
|
|
1143
|
+
)
|
|
999
1144
|
|
|
1000
1145
|
return stub_method
|
|
1001
1146
|
else:
|
|
@@ -1024,9 +1169,13 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
1024
1169
|
resp_obj, output_item_type, pb2_module
|
|
1025
1170
|
)
|
|
1026
1171
|
except ValidationError as e:
|
|
1027
|
-
await
|
|
1172
|
+
await handle_validation_error_async(
|
|
1173
|
+
e, method, context, request, is_grpc=False
|
|
1174
|
+
)
|
|
1028
1175
|
except Exception as e:
|
|
1029
|
-
|
|
1176
|
+
raise ConnectError(
|
|
1177
|
+
code=Errors.INTERNAL, message=str(e)
|
|
1178
|
+
)
|
|
1030
1179
|
else: # size_of_parameters == 2
|
|
1031
1180
|
|
|
1032
1181
|
async def stub_method(
|
|
@@ -1045,9 +1194,13 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
1045
1194
|
resp_obj, output_item_type, pb2_module
|
|
1046
1195
|
)
|
|
1047
1196
|
except ValidationError as e:
|
|
1048
|
-
await
|
|
1197
|
+
await handle_validation_error_async(
|
|
1198
|
+
e, method, context, request, is_grpc=False
|
|
1199
|
+
)
|
|
1049
1200
|
except Exception as e:
|
|
1050
|
-
|
|
1201
|
+
raise ConnectError(
|
|
1202
|
+
code=Errors.INTERNAL, message=str(e)
|
|
1203
|
+
)
|
|
1051
1204
|
|
|
1052
1205
|
return stub_method
|
|
1053
1206
|
else:
|
|
@@ -1074,9 +1227,13 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
1074
1227
|
resp_obj, response_type, pb2_module
|
|
1075
1228
|
)
|
|
1076
1229
|
except ValidationError as e:
|
|
1077
|
-
await
|
|
1230
|
+
await handle_validation_error_async(
|
|
1231
|
+
e, method, context, request, is_grpc=False
|
|
1232
|
+
)
|
|
1078
1233
|
except Exception as e:
|
|
1079
|
-
|
|
1234
|
+
raise ConnectError(
|
|
1235
|
+
code=Errors.INTERNAL, message=str(e)
|
|
1236
|
+
)
|
|
1080
1237
|
else: # size_of_parameters == 2
|
|
1081
1238
|
|
|
1082
1239
|
async def stub_method(
|
|
@@ -1099,9 +1256,13 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
1099
1256
|
resp_obj, response_type, pb2_module
|
|
1100
1257
|
)
|
|
1101
1258
|
except ValidationError as e:
|
|
1102
|
-
await
|
|
1259
|
+
await handle_validation_error_async(
|
|
1260
|
+
e, method, context, request, is_grpc=False
|
|
1261
|
+
)
|
|
1103
1262
|
except Exception as e:
|
|
1104
|
-
|
|
1263
|
+
raise ConnectError(
|
|
1264
|
+
code=Errors.INTERNAL, message=str(e)
|
|
1265
|
+
)
|
|
1105
1266
|
|
|
1106
1267
|
return stub_method
|
|
1107
1268
|
|
|
@@ -2811,12 +2972,13 @@ class AsyncIOServer:
|
|
|
2811
2972
|
await self._server.start()
|
|
2812
2973
|
|
|
2813
2974
|
shutdown_event = asyncio.Event()
|
|
2975
|
+
loop = asyncio.get_running_loop()
|
|
2814
2976
|
|
|
2815
2977
|
def shutdown(signum: signal.Signals, frame: Any):
|
|
2816
2978
|
_ = signum
|
|
2817
2979
|
_ = frame
|
|
2818
2980
|
print("Received shutdown signal...")
|
|
2819
|
-
shutdown_event.set
|
|
2981
|
+
loop.call_soon_threadsafe(shutdown_event.set)
|
|
2820
2982
|
|
|
2821
2983
|
for s in [signal.SIGTERM, signal.SIGINT]:
|
|
2822
2984
|
_ = signal.signal(s, shutdown) # pyright:ignore[reportArgumentType]
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, Callable, Dict, List, Optional, TypeVar, Type
|
|
4
4
|
from functools import wraps
|
|
5
|
+
import inspect
|
|
5
6
|
import grpc
|
|
6
7
|
from connectrpc.code import Code as ConnectErrors
|
|
7
8
|
|
|
@@ -145,7 +146,10 @@ def error_handler(
|
|
|
145
146
|
exception_type: Type[Exception],
|
|
146
147
|
status_code: Optional[grpc.StatusCode] = None,
|
|
147
148
|
connect_code: Optional[ConnectErrors] = None,
|
|
148
|
-
handler: Optional[
|
|
149
|
+
handler: Optional[
|
|
150
|
+
Callable[[Exception], tuple[str, Any]]
|
|
151
|
+
| Callable[[Exception, Any], tuple[str, Any]]
|
|
152
|
+
] = None,
|
|
149
153
|
) -> Callable[[F], F]:
|
|
150
154
|
"""
|
|
151
155
|
Decorator to add automatic error handling to an RPC method.
|
|
@@ -154,13 +158,22 @@ def error_handler(
|
|
|
154
158
|
exception_type: The type of exception to handle
|
|
155
159
|
status_code: The gRPC status code to return (for gRPC services)
|
|
156
160
|
connect_code: The Connect error code to return (for Connect services)
|
|
157
|
-
handler: Optional custom handler function that returns (message, details)
|
|
161
|
+
handler: Optional custom handler function that returns (message, details).
|
|
162
|
+
Can accept either (exception) or (exception, request_data) as parameters.
|
|
158
163
|
|
|
159
164
|
Example:
|
|
160
165
|
@error_handler(ValidationError, status_code=grpc.StatusCode.INVALID_ARGUMENT)
|
|
161
166
|
@error_handler(KeyError, status_code=grpc.StatusCode.NOT_FOUND)
|
|
162
167
|
async def get_user(self, request: GetUserRequest) -> User:
|
|
163
168
|
...
|
|
169
|
+
|
|
170
|
+
# With custom handler that accesses request data
|
|
171
|
+
def validation_handler(exc: ValidationError, request_data: Any) -> tuple[str, dict]:
|
|
172
|
+
return f"Validation failed for {request_data}", {"errors": exc.errors()}
|
|
173
|
+
|
|
174
|
+
@error_handler(ValidationError, handler=validation_handler)
|
|
175
|
+
async def create_user(self, request: CreateUserRequest) -> User:
|
|
176
|
+
...
|
|
164
177
|
"""
|
|
165
178
|
|
|
166
179
|
def decorator(func: F) -> F:
|
|
@@ -180,18 +193,11 @@ def error_handler(
|
|
|
180
193
|
}
|
|
181
194
|
)
|
|
182
195
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# Preserve the error handlers on the wrapper
|
|
188
|
-
setattr(wrapper, ERROR_HANDLER_ATTR, handlers)
|
|
196
|
+
# Set the error handlers directly on the function
|
|
197
|
+
# No need for a wrapper - we're just storing metadata
|
|
198
|
+
setattr(func, ERROR_HANDLER_ATTR, handlers)
|
|
189
199
|
|
|
190
|
-
#
|
|
191
|
-
if hasattr(func, OPTION_METADATA_ATTR):
|
|
192
|
-
setattr(wrapper, OPTION_METADATA_ATTR, getattr(func, OPTION_METADATA_ATTR))
|
|
193
|
-
|
|
194
|
-
return wrapper # type: ignore
|
|
200
|
+
return func # type: ignore
|
|
195
201
|
|
|
196
202
|
return decorator
|
|
197
203
|
|
|
@@ -207,3 +213,31 @@ def get_error_handlers(method: Callable) -> Optional[List[Dict[str, Any]]]:
|
|
|
207
213
|
List of error handler configurations if present, None otherwise
|
|
208
214
|
"""
|
|
209
215
|
return getattr(method, ERROR_HANDLER_ATTR, None)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def invoke_error_handler(
|
|
219
|
+
handler_func: Callable, exception: Exception, request_data: Any = None
|
|
220
|
+
) -> tuple[str, Any]:
|
|
221
|
+
"""
|
|
222
|
+
Invoke an error handler function with appropriate parameters based on its signature.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
handler_func: The error handler function to invoke
|
|
226
|
+
exception: The exception that was raised
|
|
227
|
+
request_data: Optional request data (raw protobuf request)
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Tuple of (error_message, error_details)
|
|
231
|
+
"""
|
|
232
|
+
sig = inspect.signature(handler_func)
|
|
233
|
+
param_count = len(sig.parameters)
|
|
234
|
+
|
|
235
|
+
if param_count == 1:
|
|
236
|
+
# Handler only accepts exception
|
|
237
|
+
return handler_func(exception)
|
|
238
|
+
elif param_count == 2:
|
|
239
|
+
# Handler accepts exception and request_data
|
|
240
|
+
return handler_func(exception, request_data)
|
|
241
|
+
else:
|
|
242
|
+
# Invalid signature, fall back to exception only
|
|
243
|
+
return handler_func(exception)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|