pydantic-rpc 0.14.0__tar.gz → 0.15.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.14.0 → pydantic_rpc-0.15.0}/PKG-INFO +1 -1
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.0}/pyproject.toml +1 -1
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.0}/src/pydantic_rpc/__init__.py +2 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.0}/src/pydantic_rpc/core.py +190 -45
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.0}/src/pydantic_rpc/decorators.py +47 -13
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.0}/README.md +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.0}/src/pydantic_rpc/mcp/__init__.py +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.0}/src/pydantic_rpc/mcp/converter.py +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.0}/src/pydantic_rpc/mcp/exporter.py +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.0}/src/pydantic_rpc/options.py +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.0}/src/pydantic_rpc/py.typed +0 -0
- {pydantic_rpc-0.14.0 → pydantic_rpc-0.15.0}/src/pydantic_rpc/tls.py +0 -0
|
@@ -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,11 @@ 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(code=Errors.INTERNAL, message=str(e))
|
|
937
1068
|
else: # size_of_parameters == 2
|
|
938
1069
|
|
|
939
1070
|
async def stub_method(
|
|
@@ -949,9 +1080,11 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
949
1080
|
resp_obj, output_item_type, pb2_module
|
|
950
1081
|
)
|
|
951
1082
|
except ValidationError as e:
|
|
952
|
-
await
|
|
1083
|
+
await handle_validation_error_async(
|
|
1084
|
+
e, method, context, None, is_grpc=False
|
|
1085
|
+
)
|
|
953
1086
|
except Exception as e:
|
|
954
|
-
|
|
1087
|
+
raise ConnectError(code=Errors.INTERNAL, message=str(e))
|
|
955
1088
|
|
|
956
1089
|
return stub_method
|
|
957
1090
|
else:
|
|
@@ -973,9 +1106,11 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
973
1106
|
resp_obj, response_type, pb2_module
|
|
974
1107
|
)
|
|
975
1108
|
except ValidationError as e:
|
|
976
|
-
await
|
|
1109
|
+
await handle_validation_error_async(
|
|
1110
|
+
e, method, context, None, is_grpc=False
|
|
1111
|
+
)
|
|
977
1112
|
except Exception as e:
|
|
978
|
-
|
|
1113
|
+
raise ConnectError(code=Errors.INTERNAL, message=str(e))
|
|
979
1114
|
else: # size_of_parameters == 2
|
|
980
1115
|
|
|
981
1116
|
async def stub_method(
|
|
@@ -993,9 +1128,11 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
993
1128
|
resp_obj, response_type, pb2_module
|
|
994
1129
|
)
|
|
995
1130
|
except ValidationError as e:
|
|
996
|
-
await
|
|
1131
|
+
await handle_validation_error_async(
|
|
1132
|
+
e, method, context, None, is_grpc=False
|
|
1133
|
+
)
|
|
997
1134
|
except Exception as e:
|
|
998
|
-
|
|
1135
|
+
raise ConnectError(code=Errors.INTERNAL, message=str(e))
|
|
999
1136
|
|
|
1000
1137
|
return stub_method
|
|
1001
1138
|
else:
|
|
@@ -1024,9 +1161,11 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
1024
1161
|
resp_obj, output_item_type, pb2_module
|
|
1025
1162
|
)
|
|
1026
1163
|
except ValidationError as e:
|
|
1027
|
-
await
|
|
1164
|
+
await handle_validation_error_async(
|
|
1165
|
+
e, method, context, request, is_grpc=False
|
|
1166
|
+
)
|
|
1028
1167
|
except Exception as e:
|
|
1029
|
-
|
|
1168
|
+
raise ConnectError(code=Errors.INTERNAL, message=str(e))
|
|
1030
1169
|
else: # size_of_parameters == 2
|
|
1031
1170
|
|
|
1032
1171
|
async def stub_method(
|
|
@@ -1045,9 +1184,11 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
1045
1184
|
resp_obj, output_item_type, pb2_module
|
|
1046
1185
|
)
|
|
1047
1186
|
except ValidationError as e:
|
|
1048
|
-
await
|
|
1187
|
+
await handle_validation_error_async(
|
|
1188
|
+
e, method, context, request, is_grpc=False
|
|
1189
|
+
)
|
|
1049
1190
|
except Exception as e:
|
|
1050
|
-
|
|
1191
|
+
raise ConnectError(code=Errors.INTERNAL, message=str(e))
|
|
1051
1192
|
|
|
1052
1193
|
return stub_method
|
|
1053
1194
|
else:
|
|
@@ -1074,9 +1215,11 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
1074
1215
|
resp_obj, response_type, pb2_module
|
|
1075
1216
|
)
|
|
1076
1217
|
except ValidationError as e:
|
|
1077
|
-
await
|
|
1218
|
+
await handle_validation_error_async(
|
|
1219
|
+
e, method, context, request, is_grpc=False
|
|
1220
|
+
)
|
|
1078
1221
|
except Exception as e:
|
|
1079
|
-
|
|
1222
|
+
raise ConnectError(code=Errors.INTERNAL, message=str(e))
|
|
1080
1223
|
else: # size_of_parameters == 2
|
|
1081
1224
|
|
|
1082
1225
|
async def stub_method(
|
|
@@ -1099,9 +1242,11 @@ def connect_obj_with_stub_async_connect_python(
|
|
|
1099
1242
|
resp_obj, response_type, pb2_module
|
|
1100
1243
|
)
|
|
1101
1244
|
except ValidationError as e:
|
|
1102
|
-
await
|
|
1245
|
+
await handle_validation_error_async(
|
|
1246
|
+
e, method, context, request, is_grpc=False
|
|
1247
|
+
)
|
|
1103
1248
|
except Exception as e:
|
|
1104
|
-
|
|
1249
|
+
raise ConnectError(code=Errors.INTERNAL, message=str(e))
|
|
1105
1250
|
|
|
1106
1251
|
return stub_method
|
|
1107
1252
|
|
|
@@ -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
|