diff --git a/gcloud/bigtable/happybase/connection.py b/gcloud/bigtable/happybase/connection.py index 2181782e0278..1dc07271391d 100644 --- a/gcloud/bigtable/happybase/connection.py +++ b/gcloud/bigtable/happybase/connection.py @@ -20,6 +20,7 @@ import six from gcloud.bigtable.client import Client +from gcloud.bigtable.happybase.table import Table # Constants reproduced here for HappyBase compatibility, though values @@ -202,3 +203,34 @@ def close(self): def __del__(self): if self._cluster is not None: self.close() + + def _table_name(self, name): + """Construct a table name by optionally adding a table name prefix. + + :type name: str + :param name: The name to have a prefix added to it. + + :rtype: str + :returns: The prefixed name, if the current connection has a table + prefix set. + """ + if self.table_prefix is None: + return name + + return self.table_prefix + self.table_prefix_separator + name + + def table(self, name, use_prefix=True): + """Table factory. + + :type name: str + :param name: The name of the table to be created. + + :type use_prefix: bool + :param use_prefix: Whether to use the table prefix (if any). + + :rtype: `Table ` + :returns: Table instance owned by this connection. + """ + if use_prefix: + name = self._table_name(name) + return Table(name, self) diff --git a/gcloud/bigtable/happybase/table.py b/gcloud/bigtable/happybase/table.py new file mode 100644 index 000000000000..d1f82eaad4c9 --- /dev/null +++ b/gcloud/bigtable/happybase/table.py @@ -0,0 +1,93 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google Cloud Bigtable HappyBase table module.""" + + +from gcloud.bigtable.table import Table as _LowLevelTable + + +def make_row(cell_map, include_timestamp): + """Make a row dict for a Thrift cell mapping. + + .. note:: + + This method is only provided for HappyBase compatibility, but does not + actually work. + + :type cell_map: dict + :param cell_map: Dictionary with ``fam:col`` strings as keys and ``TCell`` + instances as values. + + :type include_timestamp: bool + :param include_timestamp: Flag to indicate if cell timestamps should be + included with the output. + + :raises: :class:`NotImplementedError ` + always + """ + raise NotImplementedError('The Cloud Bigtable API output is not the same ' + 'as the output from the Thrift server, so this ' + 'helper can not be implemented.', 'Called with', + cell_map, include_timestamp) + + +def make_ordered_row(sorted_columns, include_timestamp): + """Make a row dict for sorted Thrift column results from scans. + + .. note:: + + This method is only provided for HappyBase compatibility, but does not + actually work. + + :type sorted_columns: list + :param sorted_columns: List of ``TColumn`` instances from Thrift. + + :type include_timestamp: bool + :param include_timestamp: Flag to indicate if cell timestamps should be + included with the output. + + :raises: :class:`NotImplementedError ` + always + """ + raise NotImplementedError('The Cloud Bigtable API output is not the same ' + 'as the output from the Thrift server, so this ' + 'helper can not be implemented.', 'Called with', + sorted_columns, include_timestamp) + + +class Table(object): + """Representation of Cloud Bigtable table. + + Used for adding data and + + :type name: str + :param name: The name of the table. + + :type connection: :class:`.Connection` + :param connection: The connection which has access to the table. + """ + + def __init__(self, name, connection): + self.name = name + # This remains as legacy for HappyBase, but only the cluster + # from the connection is needed. + self.connection = connection + self._low_level_table = None + if self.connection is not None: + self._low_level_table = _LowLevelTable(self.name, + self.connection._cluster) + + def __repr__(self): + return '' % (self.name,) diff --git a/gcloud/bigtable/happybase/test_connection.py b/gcloud/bigtable/happybase/test_connection.py index 218c848b5a21..14a8ea4b25aa 100644 --- a/gcloud/bigtable/happybase/test_connection.py +++ b/gcloud/bigtable/happybase/test_connection.py @@ -212,6 +212,72 @@ def test___del__no_cluster(self): connection.__del__() self.assertEqual(cluster._client.stop_calls, 0) + def test__table_name_with_prefix_set(self): + table_prefix = 'table-prefix' + table_prefix_separator = '<>' + cluster = _Cluster() + + connection = self._makeOne( + autoconnect=False, + table_prefix=table_prefix, + table_prefix_separator=table_prefix_separator, + cluster=cluster) + + name = 'some-name' + prefixed = connection._table_name(name) + self.assertEqual(prefixed, + table_prefix + table_prefix_separator + name) + + def test__table_name_with_no_prefix_set(self): + cluster = _Cluster() + connection = self._makeOne(autoconnect=False, + cluster=cluster) + + name = 'some-name' + prefixed = connection._table_name(name) + self.assertEqual(prefixed, name) + + def test_table_factory(self): + from gcloud.bigtable.happybase.table import Table + + cluster = _Cluster() # Avoid implicit environ check. + connection = self._makeOne(autoconnect=False, cluster=cluster) + + name = 'table-name' + table = connection.table(name) + + self.assertTrue(isinstance(table, Table)) + self.assertEqual(table.name, name) + self.assertEqual(table.connection, connection) + + def _table_factory_prefix_helper(self, use_prefix=True): + from gcloud.bigtable.happybase.table import Table + + cluster = _Cluster() # Avoid implicit environ check. + table_prefix = 'table-prefix' + table_prefix_separator = '<>' + connection = self._makeOne( + autoconnect=False, table_prefix=table_prefix, + table_prefix_separator=table_prefix_separator, + cluster=cluster) + + name = 'table-name' + table = connection.table(name, use_prefix=use_prefix) + + self.assertTrue(isinstance(table, Table)) + prefixed_name = table_prefix + table_prefix_separator + name + if use_prefix: + self.assertEqual(table.name, prefixed_name) + else: + self.assertEqual(table.name, name) + self.assertEqual(table.connection, connection) + + def test_table_factory_with_prefix(self): + self._table_factory_prefix_helper(use_prefix=True) + + def test_table_factory_with_ignored_prefix(self): + self._table_factory_prefix_helper(use_prefix=False) + class _Client(object): diff --git a/gcloud/bigtable/happybase/test_table.py b/gcloud/bigtable/happybase/test_table.py new file mode 100644 index 000000000000..29f33469e4ce --- /dev/null +++ b/gcloud/bigtable/happybase/test_table.py @@ -0,0 +1,97 @@ +# Copyright 2016 Google Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest2 + + +class Test_make_row(unittest2.TestCase): + + def _callFUT(self, *args, **kwargs): + from gcloud.bigtable.happybase.table import make_row + return make_row(*args, **kwargs) + + def test_it(self): + with self.assertRaises(NotImplementedError): + self._callFUT({}, False) + + +class Test_make_ordered_row(unittest2.TestCase): + + def _callFUT(self, *args, **kwargs): + from gcloud.bigtable.happybase.table import make_ordered_row + return make_ordered_row(*args, **kwargs) + + def test_it(self): + with self.assertRaises(NotImplementedError): + self._callFUT([], False) + + +class TestTable(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud.bigtable.happybase.table import Table + return Table + + def _makeOne(self, *args, **kwargs): + return self._getTargetClass()(*args, **kwargs) + + def test_constructor(self): + from gcloud._testing import _Monkey + from gcloud.bigtable.happybase import table as MUT + + name = 'table-name' + cluster = object() + connection = _Connection(cluster) + tables_constructed = [] + + def make_low_level_table(*args, **kwargs): + result = _MockLowLevelTable(*args, **kwargs) + tables_constructed.append(result) + return result + + with _Monkey(MUT, _LowLevelTable=make_low_level_table): + table = self._makeOne(name, connection) + self.assertEqual(table.name, name) + self.assertEqual(table.connection, connection) + + table_instance, = tables_constructed + self.assertEqual(table._low_level_table, table_instance) + self.assertEqual(table_instance.args, (name, cluster)) + self.assertEqual(table_instance.kwargs, {}) + + def test_constructor_null_connection(self): + name = 'table-name' + connection = None + table = self._makeOne(name, connection) + self.assertEqual(table.name, name) + self.assertEqual(table.connection, connection) + self.assertEqual(table._low_level_table, None) + + def test___repr__(self): + name = 'table-name' + table = self._makeOne(name, None) + self.assertEqual(repr(table), '') + + +class _Connection(object): + + def __init__(self, cluster): + self._cluster = cluster + + +class _MockLowLevelTable(object): + + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs diff --git a/scripts/verify_included_modules.py b/scripts/verify_included_modules.py index 8626f970f217..8c328f7f3188 100644 --- a/scripts/verify_included_modules.py +++ b/scripts/verify_included_modules.py @@ -34,6 +34,7 @@ 'gcloud.bigtable.cluster', 'gcloud.bigtable.column_family', 'gcloud.bigtable.happybase.connection', + 'gcloud.bigtable.happybase.table', 'gcloud.bigtable.row', 'gcloud.bigtable.row_data', 'gcloud.bigtable.table',