1616"""
1717from __future__ import annotations
1818
19- from typing import Sequence , List , Tuple , TYPE_CHECKING , Union
19+ from typing import Callable , Sequence , List , Tuple , TYPE_CHECKING , Union
2020import time
2121import enum
2222from collections import namedtuple
3131 import grpc
3232 from google .cloud .bigtable .data ._async .client import _DataApiTargetAsync
3333 from google .cloud .bigtable .data ._sync_autogen .client import _DataApiTarget
34+ from google .cloud .bigtable .data ._metrics .data_model import ActiveOperationMetric
3435
3536"""
3637Helper functions used in various places in the library.
@@ -90,7 +91,6 @@ def _retry_exception_factory(
9091 exc_list : list [Exception ],
9192 reason : RetryFailureReason ,
9293 timeout_val : float | None ,
93- operation : "ActiveOperationMetric" | None = None ,
9494) -> tuple [Exception , Exception | None ]:
9595 """
9696 Build retry error based on exceptions encountered during operation
@@ -118,8 +118,35 @@ def _retry_exception_factory(
118118 # use the retry exception group as the cause of the exception
119119 cause_exc : Exception | None = RetryExceptionGroup (exc_list ) if exc_list else None
120120 source_exc .__cause__ = cause_exc
121- if operation :
121+ return source_exc , cause_exc
122+
123+
124+ def _tracked_exception_factory (
125+ operation : "ActiveOperationMetric" ,
126+ ) -> Callable [[list [Exception ], RetryFailureReason , float | None ], tuple [Exception , Exception | None ]]:
127+ """
128+ wraps and extends _retry_exception_factory to add client-side metrics tracking.
129+
130+ When the rpc raises a terminal error, record any discovered metadata and finalize
131+ the associated operation
132+
133+ Used by streaming rpcs, which can't always be perfectly captured by context managers
134+ for operation termination.
135+
136+ Args:
137+ exc_list: list of exceptions encountered during operation
138+ is_timeout: whether the operation failed due to timeout
139+ timeout_val: the operation timeout value in seconds, for constructing
140+ the error message
141+ operation: the operation to finalize when an exception is built
142+ Returns:
143+ tuple[Exception, Exception|None]:
144+ tuple of the exception to raise, and a cause exception if applicabl
145+ """
146+ def wrapper (exc_list : list [Exception ], reason : RetryFailureReason , timeout_val : float | None ) -> tuple [Exception , Exception | None ]:
147+ source_exc , cause_exc = _retry_exception_factory (exc_list , reason , timeout_val )
122148 try :
149+ # record metadata from failed rpc
123150 if isinstance (source_exc , core_exceptions .GoogleAPICallError ) and source_exc .errors :
124151 rpc_error = source_exc .errors [- 1 ]
125152 metadata = list (rpc_error .trailing_metadata ()) + list (rpc_error .initial_metadata ())
@@ -128,7 +155,8 @@ def _retry_exception_factory(
128155 # ignore errors in metadata collection
129156 pass
130157 operation .end_with_status (source_exc )
131- return source_exc , cause_exc
158+ return source_exc , cause_exc
159+ return wrapper
132160
133161
134162def _get_timeouts (
0 commit comments