Skip to content

Commit 09a22fe

Browse files
committed
feat: Rerouted DirectRow.commit to use MutateRow
1 parent af49a62 commit 09a22fe

File tree

3 files changed

+134
-29
lines changed

3 files changed

+134
-29
lines changed

google/cloud/bigtable/row.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414

1515
"""User-friendly container for Google Cloud Bigtable Row."""
1616

17+
from google.api_core.exceptions import GoogleAPICallError
18+
1719
from google.cloud._helpers import _datetime_from_microseconds # type: ignore
1820
from google.cloud._helpers import _microseconds_from_datetime # type: ignore
1921
from google.cloud._helpers import _to_bytes # type: ignore
2022

2123
from google.cloud.bigtable.data import mutations
2224
from google.cloud.bigtable.data import read_modify_write_rules as rmw_rules
2325

26+
from google.rpc import code_pb2
27+
from google.rpc import status_pb2
28+
2429

2530
MAX_MUTATIONS = 100000
2631
"""The maximum number of mutations that a row can accumulate."""
@@ -459,14 +464,21 @@ def commit(self):
459464
:rtype: :class:`~google.rpc.status_pb2.Status`
460465
:returns: A response status (`google.rpc.status_pb2.Status`)
461466
representing success or failure of the row committed.
462-
:raises: :exc:`~.table.TooManyMutationsError` if the number of
463-
mutations is greater than 100,000.
464467
"""
465-
response = self._table.mutate_rows([self])
466-
467-
self.clear()
468-
469-
return response[0]
468+
try:
469+
self._table._table_impl.mutate_row(self.row_key, self._get_mutations())
470+
return status_pb2.Status()
471+
except GoogleAPICallError as e:
472+
# If the RPC call returns an error, extract the error into a status object, if possible.
473+
return status_pb2.Status(
474+
code=e.grpc_status_code.value[0]
475+
if e.grpc_status_code is not None
476+
else code_pb2.UNKNOWN,
477+
message=e.message,
478+
details=e.details,
479+
)
480+
finally:
481+
self.clear()
470482

471483
def clear(self):
472484
"""Removes all currently accumulated mutations on the current row.

tests/system/v2_client/test_data_api.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,37 @@ def test_table_read_rows_filter_millis(data_table):
219219
row_data.consume_all()
220220

221221

222+
def test_table_direct_row_commit(data_table, rows_to_delete):
223+
from google.rpc import code_pb2
224+
225+
row = data_table.direct_row(ROW_KEY)
226+
227+
# Test set cell
228+
row.set_cell(COLUMN_FAMILY_ID1, COL_NAME1, CELL_VAL1)
229+
row.set_cell(COLUMN_FAMILY_ID1, COL_NAME2, CELL_VAL1)
230+
status = row.commit()
231+
rows_to_delete.append(row)
232+
assert status.code == code_pb2.Code.OK
233+
row_data = data_table.read_row(ROW_KEY)
234+
assert row_data.cells[COLUMN_FAMILY_ID1][COL_NAME1][0].value == CELL_VAL1
235+
assert row_data.cells[COLUMN_FAMILY_ID1][COL_NAME2][0].value == CELL_VAL1
236+
237+
# Test delete cell
238+
row.delete_cell(COLUMN_FAMILY_ID1, COL_NAME1)
239+
status = row.commit()
240+
assert status.code == code_pb2.Code.OK
241+
row_data = data_table.read_row(ROW_KEY)
242+
assert COL_NAME1 not in row_data.cells[COLUMN_FAMILY_ID1]
243+
assert row_data.cells[COLUMN_FAMILY_ID1][COL_NAME2][0].value == CELL_VAL1
244+
245+
# Test delete row
246+
row.delete()
247+
status = row.commit()
248+
assert status.code == code_pb2.Code.OK
249+
row_data = data_table.read_row(ROW_KEY)
250+
assert row_data is None
251+
252+
222253
def test_table_mutate_rows(data_table, rows_to_delete):
223254
row1 = data_table.direct_row(ROW_KEY)
224255
row1.set_cell(COLUMN_FAMILY_ID1, COL_NAME1, CELL_VAL1)
@@ -1028,7 +1059,6 @@ def test_table_sample_row_keys(data_table, skip_on_emulator):
10281059

10291060

10301061
def test_table_direct_row_input_errors(data_table, rows_to_delete):
1031-
from google.api_core.exceptions import InvalidArgument
10321062
from google.cloud.bigtable.row import MAX_MUTATIONS
10331063

10341064
row = data_table.direct_row(ROW_KEY)
@@ -1054,19 +1084,17 @@ def test_table_direct_row_input_errors(data_table, rows_to_delete):
10541084
with pytest.raises(TypeError):
10551085
row.set_cell(COLUMN_FAMILY_ID1, COL_NAME1, FLOAT_CELL_VAL)
10561086

1057-
# Can't have more than MAX_MUTATIONS mutations, but only enforced after
1058-
# a row.commit
1087+
# Can't have more than MAX_MUTATIONS mutations, enforced on server side now.
10591088
row.clear()
10601089
for _ in range(0, MAX_MUTATIONS + 1):
10611090
row.set_cell(COLUMN_FAMILY_ID1, COL_NAME1, CELL_VAL1)
10621091

1063-
with pytest.raises(ValueError):
1064-
row.commit()
1092+
resp = row.commit()
1093+
assert resp.code == StatusCode.INVALID_ARGUMENT.value[0]
10651094

1066-
# Not having any mutations gives a server error (InvalidArgument), not
1067-
# enforced on the client side.
1095+
# Not having any mutations gives a ValueError enforced on the client side.
10681096
row.clear()
1069-
with pytest.raises(InvalidArgument):
1097+
with pytest.raises(ValueError):
10701098
row.commit()
10711099

10721100

tests/unit/v2_client/test_row.py

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -368,44 +368,110 @@ def test_direct_row_delete_cells_with_string_columns():
368368

369369

370370
def test_direct_row_commit():
371+
from google.cloud.bigtable_v2.services.bigtable import BigtableClient
372+
from google.rpc import code_pb2, status_pb2
373+
371374
project_id = "project-id"
372375
row_key = b"row_key"
373376
table_name = "projects/more-stuff"
377+
app_profile_id = "app_profile_id"
374378
column_family_id = "column_family_id"
375379
column = b"column"
376380

377381
credentials = _make_credentials()
378382
client = _make_client(project=project_id, credentials=credentials, admin=True)
379-
table = _Table(table_name, client=client)
383+
table = _Table(table_name, client=client, app_profile_id=app_profile_id)
380384
row = _make_direct_row(row_key, table)
381385
value = b"bytes-value"
382386

387+
# Set mock
388+
api = mock.create_autospec(BigtableClient)
389+
response_pb = _MutateRowResponsePB()
390+
api.mutate_row.side_effect = [response_pb]
391+
client.table_data_client
392+
client._table_data_client._gapic_client = api
393+
383394
# Perform the method and check the result.
384395
row.set_cell(column_family_id, column, value)
385-
row.commit()
386-
assert table.mutated_rows == [row]
396+
response = row.commit()
397+
assert row._mutations == []
398+
assert response == status_pb2.Status(code=code_pb2.OK)
399+
call_args = api.mutate_row.call_args
400+
assert app_profile_id == call_args.app_profile_id[0]
387401

388402

389403
def test_direct_row_commit_with_exception():
390-
from google.rpc import status_pb2
404+
from google.api_core.exceptions import InternalServerError
405+
from google.cloud.bigtable_v2.services.bigtable import BigtableClient
406+
from google.rpc import code_pb2, status_pb2
391407

392408
project_id = "project-id"
393409
row_key = b"row_key"
394410
table_name = "projects/more-stuff"
411+
app_profile_id = "app_profile_id"
395412
column_family_id = "column_family_id"
396413
column = b"column"
397414

398415
credentials = _make_credentials()
399416
client = _make_client(project=project_id, credentials=credentials, admin=True)
400-
table = _Table(table_name, client=client)
417+
table = _Table(table_name, client=client, app_profile_id=app_profile_id)
401418
row = _make_direct_row(row_key, table)
402419
value = b"bytes-value"
403420

421+
# Set mock
422+
api = mock.create_autospec(BigtableClient)
423+
exception_message = "Boom!"
424+
exception = InternalServerError(exception_message)
425+
api.mutate_row.side_effect = [exception]
426+
client.table_data_client
427+
client._table_data_client._gapic_client = api
428+
404429
# Perform the method and check the result.
405430
row.set_cell(column_family_id, column, value)
406431
result = row.commit()
407-
expected = status_pb2.Status(code=0)
408-
assert result == expected
432+
assert row._mutations == []
433+
assert result == status_pb2.Status(
434+
code=code_pb2.Code.INTERNAL, message=exception_message
435+
)
436+
call_args = api.mutate_row.call_args
437+
assert app_profile_id == call_args.app_profile_id[0]
438+
439+
440+
def test_direct_row_commit_with_unknown_exception():
441+
from google.api_core.exceptions import GoogleAPICallError
442+
from google.cloud.bigtable_v2.services.bigtable import BigtableClient
443+
from google.rpc import code_pb2, status_pb2
444+
445+
project_id = "project-id"
446+
row_key = b"row_key"
447+
table_name = "projects/more-stuff"
448+
app_profile_id = "app_profile_id"
449+
column_family_id = "column_family_id"
450+
column = b"column"
451+
452+
credentials = _make_credentials()
453+
client = _make_client(project=project_id, credentials=credentials, admin=True)
454+
table = _Table(table_name, client=client, app_profile_id=app_profile_id)
455+
row = _make_direct_row(row_key, table)
456+
value = b"bytes-value"
457+
458+
# Set mock
459+
api = mock.create_autospec(BigtableClient)
460+
exception_message = "Boom!"
461+
exception = GoogleAPICallError(message=exception_message)
462+
api.mutate_row.side_effect = [exception]
463+
client.table_data_client
464+
client._table_data_client._gapic_client = api
465+
466+
# Perform the method and check the result.
467+
row.set_cell(column_family_id, column, value)
468+
result = row.commit()
469+
assert row._mutations == []
470+
assert result == status_pb2.Status(
471+
code=code_pb2.Code.UNKNOWN, message=exception_message
472+
)
473+
call_args = api.mutate_row.call_args
474+
assert app_profile_id == call_args.app_profile_id[0]
409475

410476

411477
def _make_conditional_row(*args, **kwargs):
@@ -729,6 +795,12 @@ def test__parse_rmw_row_response():
729795
assert expected_output == _parse_rmw_row_response(sample_input)
730796

731797

798+
def _MutateRowResponsePB():
799+
from google.cloud.bigtable_v2.types import bigtable as messages_v2_pb2
800+
801+
return messages_v2_pb2.MutateRowResponse()
802+
803+
732804
def _CheckAndMutateRowResponsePB(*args, **kw):
733805
from google.cloud.bigtable_v2.types import bigtable as messages_v2_pb2
734806

@@ -778,16 +850,9 @@ def __init__(self, name, client=None, app_profile_id=None):
778850
self._instance = _Instance(client)
779851
self._app_profile_id = app_profile_id
780852
self.client = client
781-
self.mutated_rows = []
782853

783854
self._table_impl = self._instance._client._veneer_data_client.get_table(
784855
_INSTANCE_ID,
785856
self.name,
786857
app_profile_id=self._app_profile_id,
787858
)
788-
789-
def mutate_rows(self, rows):
790-
from google.rpc import status_pb2
791-
792-
self.mutated_rows.extend(rows)
793-
return [status_pb2.Status(code=0)]

0 commit comments

Comments
 (0)