3131from google .cloud .bigtable .column_family import _gc_rule_from_pb
3232from google .cloud .bigtable .column_family import ColumnFamily
3333from google .cloud .bigtable .data ._helpers import TABLE_DEFAULT
34- from google .cloud .bigtable .data .exceptions import MutationsExceptionGroup
34+ from google .cloud .bigtable .data .exceptions import (
35+ RetryExceptionGroup ,
36+ MutationsExceptionGroup ,
37+ )
3538from google .cloud .bigtable .data .mutations import RowMutationEntry
3639from google .cloud .bigtable .batcher import MutationsBatcher
3740from google .cloud .bigtable .batcher import FLUSH_COUNT , MAX_MUTATION_SIZE
@@ -740,18 +743,20 @@ def mutate_rows(self, rows, retry=DEFAULT_RETRY, timeout=DEFAULT):
740743 if timeout is DEFAULT :
741744 timeout = self .mutation_timeout
742745
743- # To adhere to the retry strategy of do-nothing being achievable with a deadline
744- # of 0.0, we modify the retryable errors to be empty if such a deadline is passed.
745746 retryable_errors = RETRYABLE_MUTATION_ERRORS
746- operation_timeout = retry .deadline
747747
748748 # The data client cannot take in zero or null values for deadline, so we set it to
749- # the default if that is the case. It shouldn't affect the behavior of the retry
750- # if a 0.0 deadline is set.
751- if not retry .deadline :
749+ # the default if that is the case.
750+ if retry .deadline is None :
752751 operation_timeout = TABLE_DEFAULT .MUTATE_ROWS
753- if retry .deadline == 0.0 :
754- retryable_errors = []
752+
753+ # To adhere to the retry strategy of do-nothing being achievable with a deadline
754+ # of 0.0, we modify the retryable errors to be empty if such a deadline is passed.
755+ elif retry .deadline == 0 :
756+ operation_timeout = TABLE_DEFAULT .MUTATE_ROWS
757+ retryable_errors = []
758+ else :
759+ operation_timeout = retry .deadline
755760
756761 attempt_timeout = timeout
757762 mutation_entries = [
@@ -761,40 +766,50 @@ def mutate_rows(self, rows, retry=DEFAULT_RETRY, timeout=DEFAULT):
761766 mutation_entries
762767 ) # By default, return status OKs for everything
763768
764- operation = self ._table_impl ._get_mutate_rows_operation (
765- mutation_entries ,
766- operation_timeout = operation_timeout ,
767- attempt_timeout = attempt_timeout ,
768- retryable_errors = retryable_errors ,
769- )
770-
771769 try :
772- operation .start ()
773- except MutationsExceptionGroup :
774- # Take the first exception for each error index with a gRPC status code
775- # and set the status of that row entry to that grpc status. if none of the
776- # errors for a given index have gRPC status codes, return an UNKNOWN status
777- # with the first error message of each index.
778- for idx , errors in operation .errors .items ():
779- return_statuses [idx ] = status_pb2 .Status (
780- code = code_pb2 .Code .UNKNOWN ,
781- message = str (errors [0 ]),
782- )
783-
784- for error in errors :
785- if (
786- isinstance (error , GoogleAPICallError )
787- and error .grpc_status_code is not None
788- ):
789- return_statuses [idx ] = status_pb2 .Status (
790- code = error .grpc_status_code .value [0 ],
791- message = error .message ,
792- details = error .details ,
793- )
794- break
770+ self ._table_impl .bulk_mutate_rows (
771+ mutation_entries ,
772+ operation_timeout = operation_timeout ,
773+ attempt_timeout = attempt_timeout ,
774+ retryable_errors = retryable_errors ,
775+ )
776+ except MutationsExceptionGroup as mut_exc_group :
777+ # We exception handle as follows:
778+ #
779+ # 1. Each exception in the error group is a FailedMutationEntryError, and its
780+ # cause is either a singular exception or a RetryExceptionGroup consisting of
781+ # multiple exceptions.
782+ #
783+ # 2. In the case of a singular exception, if the error does not have a gRPC status
784+ # code, we return a status code of UNKNOWN.
785+ #
786+ # 3. In the case of a RetryExceptionGroup, we use terminal exception in the exception
787+ # group and process that.
788+ for error in mut_exc_group .exceptions :
789+ cause = error .__cause__
790+ if isinstance (cause , RetryExceptionGroup ):
791+ return_statuses [error .index ] = self ._get_status (
792+ cause .exceptions [- 1 ]
793+ )
794+ else :
795+ return_statuses [error .index ] = self ._get_status (cause )
795796
796797 return return_statuses
797798
799+ @staticmethod
800+ def _get_status (error ):
801+ if isinstance (error , GoogleAPICallError ) and error .grpc_status_code is not None :
802+ return status_pb2 .Status (
803+ code = error .grpc_status_code .value [0 ],
804+ message = error .message ,
805+ details = error .details ,
806+ )
807+
808+ return status_pb2 .Status (
809+ code = code_pb2 .Code .UNKNOWN ,
810+ message = str (error ),
811+ )
812+
798813 def sample_row_keys (self ):
799814 """Read a sample of row keys in the table.
800815
0 commit comments