diff --git a/gcloud/bigtable/column_family.py b/gcloud/bigtable/column_family.py index b10f1b12d28f..e6b5daae4955 100644 --- a/gcloud/bigtable/column_family.py +++ b/gcloud/bigtable/column_family.py @@ -210,6 +210,24 @@ def __init__(self, column_family_id, table, gc_rule=None): self._table = table self.gc_rule = gc_rule + @property + def name(self): + """Column family name used in requests. + + .. note:: + + This property will not change if ``column_family_id`` does not, but + the return value is not cached. + + The table name is of the form + + ``"projects/../zones/../clusters/../tables/../columnFamilies/.."`` + + :rtype: str + :returns: The column family name. + """ + return self._table.name + '/columnFamilies/' + self.column_family_id + def __eq__(self, other): if not isinstance(other, self.__class__): return False diff --git a/gcloud/bigtable/table.py b/gcloud/bigtable/table.py index 53930649a5a3..fc418542f756 100644 --- a/gcloud/bigtable/table.py +++ b/gcloud/bigtable/table.py @@ -19,6 +19,7 @@ bigtable_table_service_messages_pb2 as messages_pb2) from gcloud.bigtable._generated import ( bigtable_service_messages_pb2 as data_messages_pb2) +from gcloud.bigtable.column_family import _gc_rule_from_pb from gcloud.bigtable.column_family import ColumnFamily from gcloud.bigtable.row import Row @@ -73,17 +74,21 @@ def name(self): """ return self._cluster.name + '/tables/' + self.table_id - def column_family(self, column_family_id): + def column_family(self, column_family_id, gc_rule=None): """Factory to create a column family associated with this table. :type column_family_id: str :param column_family_id: The ID of the column family. Must be of the form ``[_a-zA-Z0-9][-_.a-zA-Z0-9]*``. + :type gc_rule: :class:`.column_family.GarbageCollectionRule` + :param gc_rule: (Optional) The garbage collection settings for this + column family. + :rtype: :class:`.column_family.ColumnFamily` :returns: A column family owned by this table. """ - return ColumnFamily(column_family_id, self) + return ColumnFamily(column_family_id, self, gc_rule=gc_rule) def row(self, row_key): """Factory to create a row associated with this table. @@ -179,6 +184,34 @@ def delete(self): # We expect a `._generated.empty_pb2.Empty` client._table_stub.DeleteTable(request_pb, client.timeout_seconds) + def list_column_families(self): + """List the column families owned by this table. + + :rtype: dictionary with string as keys and + :class:`.column_family.ColumnFamily` as values + :returns: List of column families attached to this table. + :raises: :class:`ValueError ` if the column + family name from the response does not agree with the computed + name from the column family ID. + """ + request_pb = messages_pb2.GetTableRequest(name=self.name) + client = self._cluster._client + # We expect a `._generated.bigtable_table_data_pb2.Table` + table_pb = client._table_stub.GetTable(request_pb, + client.timeout_seconds) + + result = {} + for column_family_id, value_pb in table_pb.column_families.items(): + gc_rule = _gc_rule_from_pb(value_pb.gc_rule) + column_family = self.column_family(column_family_id, + gc_rule=gc_rule) + if column_family.name != value_pb.name: + raise ValueError('Column family name %s does not agree with ' + 'name from request: %s.' % ( + column_family.name, value_pb.name)) + result[column_family_id] = column_family + return result + def sample_row_keys(self): """Read a sample of row keys in the table. diff --git a/gcloud/bigtable/test_column_family.py b/gcloud/bigtable/test_column_family.py index 4cad0d1210b2..f97d4640a75a 100644 --- a/gcloud/bigtable/test_column_family.py +++ b/gcloud/bigtable/test_column_family.py @@ -355,6 +355,15 @@ def test_constructor(self): self.assertTrue(column_family._table is table) self.assertTrue(column_family.gc_rule is gc_rule) + def test_name_property(self): + column_family_id = u'column-family-id' + table_name = 'table_name' + table = _Table(table_name) + column_family = self._makeOne(column_family_id, table) + + expected_name = table_name + '/columnFamilies/' + column_family_id + self.assertEqual(column_family.name, expected_name) + def test___eq__(self): column_family_id = 'column_family_id' table = object() @@ -460,3 +469,9 @@ def WhichOneof(cls, name): self.assertEqual(MockProto.names, []) self.assertRaises(ValueError, self._callFUT, MockProto) self.assertEqual(MockProto.names, ['rule']) + + +class _Table(object): + + def __init__(self, name): + self.name = name diff --git a/gcloud/bigtable/test_table.py b/gcloud/bigtable/test_table.py index f2b088fb7c09..b0232a3142b6 100644 --- a/gcloud/bigtable/test_table.py +++ b/gcloud/bigtable/test_table.py @@ -46,12 +46,14 @@ def test_column_family_factory(self): from gcloud.bigtable.column_family import ColumnFamily table_id = 'table-id' + gc_rule = object() table = self._makeOne(table_id, None) column_family_id = 'column_family_id' - column_family = table.column_family(column_family_id) + column_family = table.column_family(column_family_id, gc_rule=gc_rule) self.assertTrue(isinstance(column_family, ColumnFamily)) self.assertEqual(column_family.column_family_id, column_family_id) + self.assertTrue(column_family.gc_rule is gc_rule) self.assertEqual(column_family._table, table) def test_row_factory(self): @@ -188,6 +190,65 @@ def test_rename(self): {}, )]) + def _list_column_families_helper(self, column_family_name=None): + from gcloud.bigtable._generated import ( + bigtable_table_data_pb2 as data_pb2) + from gcloud.bigtable._generated import ( + bigtable_table_service_messages_pb2 as messages_pb2) + from gcloud.bigtable._testing import _FakeStub + + project_id = 'project-id' + zone = 'zone' + cluster_id = 'cluster-id' + table_id = 'table-id' + timeout_seconds = 502 + cluster_name = ('projects/' + project_id + '/zones/' + zone + + '/clusters/' + cluster_id) + + client = _Client(timeout_seconds=timeout_seconds) + cluster = _Cluster(cluster_name, client=client) + table = self._makeOne(table_id, cluster) + + # Create request_pb + table_name = cluster_name + '/tables/' + table_id + request_pb = messages_pb2.GetTableRequest(name=table_name) + + # Create response_pb + column_family_id = 'foo' + if column_family_name is None: + column_family_name = (table_name + '/columnFamilies/' + + column_family_id) + column_family = data_pb2.ColumnFamily(name=column_family_name) + response_pb = data_pb2.Table( + column_families={column_family_id: column_family}, + ) + + # Patch the stub used by the API method. + client._table_stub = stub = _FakeStub(response_pb) + + # Create expected_result. + expected_result = { + column_family_id: table.column_family(column_family_id), + } + + # Perform the method and check the result. + result = table.list_column_families() + self.assertEqual(result, expected_result) + self.assertEqual(stub.method_calls, [( + 'GetTable', + (request_pb, timeout_seconds), + {}, + )]) + + def test_list_column_families(self): + self._list_column_families_helper() + + def test_list_column_families_failure(self): + column_family_name = 'not-the-right-format' + with self.assertRaises(ValueError): + self._list_column_families_helper( + column_family_name=column_family_name) + def test_delete(self): from gcloud.bigtable._generated import ( bigtable_table_service_messages_pb2 as messages_pb2)