Files
ResInsight/GrpcInterface/Python/rips/grpc_retry_interceptor.py
T
Kristian Bendiksen f8d180f48d #9336 Python: Improve error reporting from gRPC to client
Surface gRPC failure context to Python script authors instead of
silently returning sentinel values or raising bare gRPC errors.

- RipsError now carries code/details/location and a from_rpc_error()
  helper. Existing single-arg construction stays valid.
- Stop swallowing grpc.RpcError in case.__grid_count for non-NOT_FOUND
  failures and in instance._check_connection_and_version, so the
  underlying status is propagated.
- pdmobject add_method decorator and _call_pdm_method_* attach
  code/details to the raised RipsError; existing message text is
  preserved so pytest match=... assertions keep working.
- Instance.start_heartbeat / stop_heartbeat / check_alive provide an
  opt-in background ping that flips a sticky lost-connection flag and
  raises a readable RipsError from check_alive() if the server dies.
- Server side: GetPdmObject in RiaGrpcAppService, RiaGrpcCaseService
  and RiaGrpcProjectService no longer return Status::OK with an empty
  reply when the underlying object is missing; they return NOT_FOUND
  / INTERNAL with a descriptive message. Other RiaGrpc*Service files
  audited and already return non-OK with text on error paths.
2026-05-04 20:03:39 +02:00

51 lines
1.8 KiB
Python

import grpc
class RetryOnRpcErrorClientInterceptor(
grpc.UnaryUnaryClientInterceptor, grpc.StreamUnaryClientInterceptor
):
def __init__(
self,
*,
retry_policy,
status_for_retry,
should_abort=None,
):
self.retry_policy = retry_policy
self.status_for_retry = status_for_retry
self.should_abort = should_abort
def _intercept_call(self, continuation, client_call_details, request_or_iterator):
for retry_num in range(self.retry_policy.num_retries()):
response = continuation(client_call_details, request_or_iterator)
if isinstance(response, grpc.RpcError):
# Return if it was last attempt
if retry_num == (self.retry_policy.num_retries() - 1):
return response
# If status code is not in retryable status codes
if (
self.status_for_retry
and response.code() not in self.status_for_retry
):
return response
# Bail out immediately if an external signal (e.g. the
# heartbeat) says the server is gone — otherwise we'd
# spend the full retry budget on a defunct process.
if self.should_abort is not None and self.should_abort():
return response
self.retry_policy.sleep(retry_num)
else:
return response
def intercept_unary_unary(self, continuation, client_call_details, request):
return self._intercept_call(continuation, client_call_details, request)
def intercept_stream_unary(
self, continuation, client_call_details, request_iterator
):
return self._intercept_call(continuation, client_call_details, request_iterator)