Repository: googleapis/google-cloud-python

Issue:
BigTable: single-row transactions are unsupported
#### Environment details

> 1. Specify the API at the beginning of the title (for example, "BigQuery: ...")
   General, Core, and Other are also allowed as types

Bigtable

> 2. OS type and version

Linux Red Hat 4.8.5

> 3. Python version and virtual environment information: `python --version`

Python 2.7.5

> 4. google-cloud-<service> version:  `pip show google-<service>` or `pip freeze`

```
(venv) -bash-4.2$ pip show google google-cloud-bigtable
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support
WARNING: Package(s) not found: google
Name: google-cloud-bigtable
Version: 1.2.0
Summary: Google Cloud Bigtable API client library
Home-page: https://github.com/GoogleCloudPlatform/google-cloud-python
Author: Google LLC
Author-email: googleapis-packages@google.com
License: Apache 2.0
Location: /home/mstarr
Requires: google-cloud-core, grpc-google-iam-v1, google-api-core
Required-by:
```

#### Steps to reproduce

Customize the code example to execute against a Bigtable instance configured with an application profile that has "Allow single-row transactions" enabled.

#### Code example

```python
#!/usr/bin/env python

from google.cloud.bigtable.client import Client
from google.cloud.bigtable.row import ConditionalRow
from google.cloud.bigtable.row_filters import PassAllFilter

PROJECT_ID="<PROJECT_ID>"
INSTANCE_ID="<INSTANCE_ID>"
TABLE_ID="<TABLE_ID>"
APP_PROFILE_ID="<APP_PROFILE_ID>"

client = Client(project=PROJECT_ID)
instance = client.instance(instance_id=INSTANCE_ID)
table = instance.table(table_id=TABLE_ID, app_profile_id=APP_PROFILE_ID)

# throws google.api_core.exceptions.FailedPrecondition: 400 Single-row transactions are not allowed by this app profile
row_cond = ConditionalRow(b'test_row', table, PassAllFilter(True))
row_cond.set_cell('test_column_family', 'test_column_qualifier', 'test_cell_value', timestamp=None, state=False)
row_cond.commit()
```

#### Stack trace

```
(venv) -bash-4.2$ python test-app-profile.py
/home/mstarr/google-case-21627768/venv/lib/python2.7/site-packages/google/auth/_default.py:69: UserWarning: Your application has authenticated using end user credentials from Google Cloud SDK. We recommend that most server applications use service accounts instead. If your application continues to use end user credentials from Cloud SDK, you might receive a "quota exceeded" or "API not enabled" error. For more information about service accounts, see https://cloud.google.com/docs/authentication/
  warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)
Traceback (most recent call last):
  File "test-app-profile.py", line 19, in <module>
    row_cond.commit()
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/cloud/bigtable/row.py", line 586, in commit
    false_mutations=false_mutations,
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/cloud/bigtable_v2/gapic/bigtable_client.py", line 670, in check_and_mutate_row
    request, retry=retry, timeout=timeout, metadata=metadata
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/api_core/gapic_v1/method.py", line 143, in __call__
    return wrapped_func(*args, **kwargs)
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/api_core/retry.py", line 286, in retry_wrapped_func
    on_error=on_error,
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/api_core/retry.py", line 184, in retry_target
    return target()
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/api_core/timeout.py", line 214, in func_with_timeout
    return func(*args, **kwargs)
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/api_core/grpc_helpers.py", line 59, in error_remapped_callable
    six.raise_from(exceptions.from_grpc_error(exc), exc)
  File "/home/mstarr/venv/lib/python2.7/site-packages/six.py", line 740, in raise_from
    raise value
google.api_core.exceptions.FailedPrecondition: 400 Single-row transactions are not allowed by this app profile
```

### Notes

Browsing the latest release [bigtable-1.2.0](https://github.com/googleapis/google-cloud-python/tree/bigtable-1.2.0/bigtable/google/cloud/bigtable), it seems that the issue stems from `app_profile_id` not being passed through to the [gapic client](https://github.com/googleapis/google-cloud-python/blob/6838a4f11162544c0654f3ce99fac5a7c510237a/bigtable/google/cloud/bigtable_v2/gapic/bigtable_client.py#L561-L565) on `commit()`
- ConditionalRow doesn't pass `app_profile_id` on `commit()` [link](https://github.com/googleapis/google-cloud-python/blob/6838a4f11162544c0654f3ce99fac5a7c510237a/bigtable/google/cloud/bigtable/row.py#L581-L587)
- AppendRow doesn't pass `app_profile_id` on `commit()` [link](https://github.com/googleapis/google-cloud-python/blob/6838a4f11162544c0654f3ce99fac5a7c510237a/bigtable/google/cloud/bigtable/row.py#L910-L912)
BigTable: single-row transactions are unsupported
#### Environment details

> 1. Specify the API at the beginning of the title (for example, "BigQuery: ...")
   General, Core, and Other are also allowed as types

Bigtable

> 2. OS type and version

Linux Red Hat 4.8.5

> 3. Python version and virtual environment information: `python --version`

Python 2.7.5

> 4. google-cloud-<service> version:  `pip show google-<service>` or `pip freeze`

```
(venv) -bash-4.2$ pip show google google-cloud-bigtable
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support
WARNING: Package(s) not found: google
Name: google-cloud-bigtable
Version: 1.2.0
Summary: Google Cloud Bigtable API client library
Home-page: https://github.com/GoogleCloudPlatform/google-cloud-python
Author: Google LLC
Author-email: googleapis-packages@google.com
License: Apache 2.0
Location: /home/mstarr
Requires: google-cloud-core, grpc-google-iam-v1, google-api-core
Required-by:
```

#### Steps to reproduce

Customize the code example to execute against a Bigtable instance configured with an application profile that has "Allow single-row transactions" enabled.

#### Code example

```python
#!/usr/bin/env python

from google.cloud.bigtable.client import Client
from google.cloud.bigtable.row import ConditionalRow
from google.cloud.bigtable.row_filters import PassAllFilter

PROJECT_ID="<PROJECT_ID>"
INSTANCE_ID="<INSTANCE_ID>"
TABLE_ID="<TABLE_ID>"
APP_PROFILE_ID="<APP_PROFILE_ID>"

client = Client(project=PROJECT_ID)
instance = client.instance(instance_id=INSTANCE_ID)
table = instance.table(table_id=TABLE_ID, app_profile_id=APP_PROFILE_ID)

# throws google.api_core.exceptions.FailedPrecondition: 400 Single-row transactions are not allowed by this app profile
row_cond = ConditionalRow(b'test_row', table, PassAllFilter(True))
row_cond.set_cell('test_column_family', 'test_column_qualifier', 'test_cell_value', timestamp=None, state=False)
row_cond.commit()
```

#### Stack trace

```
(venv) -bash-4.2$ python test-app-profile.py
/home/mstarr/google-case-21627768/venv/lib/python2.7/site-packages/google/auth/_default.py:69: UserWarning: Your application has authenticated using end user credentials from Google Cloud SDK. We recommend that most server applications use service accounts instead. If your application continues to use end user credentials from Cloud SDK, you might receive a "quota exceeded" or "API not enabled" error. For more information about service accounts, see https://cloud.google.com/docs/authentication/
  warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)
Traceback (most recent call last):
  File "test-app-profile.py", line 19, in <module>
    row_cond.commit()
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/cloud/bigtable/row.py", line 586, in commit
    false_mutations=false_mutations,
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/cloud/bigtable_v2/gapic/bigtable_client.py", line 670, in check_and_mutate_row
    request, retry=retry, timeout=timeout, metadata=metadata
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/api_core/gapic_v1/method.py", line 143, in __call__
    return wrapped_func(*args, **kwargs)
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/api_core/retry.py", line 286, in retry_wrapped_func
    on_error=on_error,
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/api_core/retry.py", line 184, in retry_target
    return target()
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/api_core/timeout.py", line 214, in func_with_timeout
    return func(*args, **kwargs)
  File "/home/mstarr/venv/lib/python2.7/site-packages/google/api_core/grpc_helpers.py", line 59, in error_remapped_callable
    six.raise_from(exceptions.from_grpc_error(exc), exc)
  File "/home/mstarr/venv/lib/python2.7/site-packages/six.py", line 740, in raise_from
    raise value
google.api_core.exceptions.FailedPrecondition: 400 Single-row transactions are not allowed by this app profile
```

### Notes

Browsing the latest release [bigtable-1.2.0](https://github.com/googleapis/google-cloud-python/tree/bigtable-1.2.0/bigtable/google/cloud/bigtable), it seems that the issue stems from `app_profile_id` not being passed through to the [gapic client](https://github.com/googleapis/google-cloud-python/blob/6838a4f11162544c0654f3ce99fac5a7c510237a/bigtable/google/cloud/bigtable_v2/gapic/bigtable_client.py#L561-L565) on `commit()`
- ConditionalRow doesn't pass `app_profile_id` on `commit()` [link](https://github.com/googleapis/google-cloud-python/blob/6838a4f11162544c0654f3ce99fac5a7c510237a/bigtable/google/cloud/bigtable/row.py#L581-L587)
- AppendRow doesn't pass `app_profile_id` on `commit()` [link](https://github.com/googleapis/google-cloud-python/blob/6838a4f11162544c0654f3ce99fac5a7c510237a/bigtable/google/cloud/bigtable/row.py#L910-L912)


Discussion:

Resolving patch:
diff --git a/bigtable/google/cloud/bigtable/row.py b/bigtable/google/cloud/bigtable/row.py
--- a/bigtable/google/cloud/bigtable/row.py
+++ b/bigtable/google/cloud/bigtable/row.py
@@ -582,6 +582,7 @@ def commit(self):
             table_name=self._table.name,
             row_key=self._row_key,
             predicate_filter=self._filter.to_pb(),
+            app_profile_id=self._table._app_profile_id,
             true_mutations=true_mutations,
             false_mutations=false_mutations,
         )
@@ -908,7 +909,10 @@ def commit(self):
 
         data_client = self._table._instance._client.table_data_client
         row_response = data_client.read_modify_write_row(
-            table_name=self._table.name, row_key=self._row_key, rules=self._rule_pb_list
+            table_name=self._table.name,
+            row_key=self._row_key,
+            rules=self._rule_pb_list,
+            app_profile_id=self._table._app_profile_id,
         )
 
         # Reset modifications after commit-ing request.


Test patch:
diff --git a/bigtable/tests/unit/test_row.py b/bigtable/tests/unit/test_row.py
--- a/bigtable/tests/unit/test_row.py
+++ b/bigtable/tests/unit/test_row.py
@@ -409,6 +409,7 @@ def test_commit(self):
         project_id = "project-id"
         row_key = b"row_key"
         table_name = "projects/more-stuff"
+        app_profile_id = "app_profile_id"
         column_family_id1 = u"column_family_id1"
         column_family_id2 = u"column_family_id2"
         column_family_id3 = u"column_family_id3"
@@ -420,7 +421,7 @@ def test_commit(self):
         client = self._make_client(
             project=project_id, credentials=credentials, admin=True
         )
-        table = _Table(table_name, client=client)
+        table = _Table(table_name, client=client, app_profile_id=app_profile_id)
         row_filter = RowSampleFilter(0.33)
         row = self._make_one(row_key, table, filter_=row_filter)
 
@@ -444,6 +445,8 @@ def test_commit(self):
         row.delete_cell(column_family_id2, column2, state=True)
         row.delete_cells(column_family_id3, row.ALL_COLUMNS, state=True)
         result = row.commit()
+        call_args = api.transport.check_and_mutate_row.call_args.args[0]
+        self.assertEqual(app_profile_id, call_args.app_profile_id)
         self.assertEqual(result, expected_result)
         self.assertEqual(row._true_pb_mutations, [])
         self.assertEqual(row._false_pb_mutations, [])
@@ -564,6 +567,7 @@ def test_commit(self):
         project_id = "project-id"
         row_key = b"row_key"
         table_name = "projects/more-stuff"
+        app_profile_id = "app_profile_id"
         column_family_id = u"column_family_id"
         column = b"column"
 
@@ -572,7 +576,7 @@ def test_commit(self):
         client = self._make_client(
             project=project_id, credentials=credentials, admin=True
         )
-        table = _Table(table_name, client=client)
+        table = _Table(table_name, client=client, app_profile_id=app_profile_id)
         row = self._make_one(row_key, table)
 
         # Create request_pb
@@ -593,7 +597,8 @@ def mock_parse_rmw_row_response(row_response):
         with _Monkey(MUT, _parse_rmw_row_response=mock_parse_rmw_row_response):
             row.append_cell_value(column_family_id, column, value)
             result = row.commit()
-
+        call_args = api.transport.read_modify_write_row.call_args.args[0]
+        self.assertEqual(app_profile_id, call_args.app_profile_id)
         self.assertEqual(result, expected_result)
         self.assertEqual(row._rule_pb_list, [])
 
@@ -819,9 +824,10 @@ def __init__(self, client=None):
 
 
 class _Table(object):
-    def __init__(self, name, client=None):
+    def __init__(self, name, client=None, app_profile_id=None):
         self.name = name
         self._instance = _Instance(client)
+        self._app_profile_id = app_profile_id
         self.client = client
         self.mutated_rows = []