Skip to content

Commit

Permalink
Merge pull request googleapis#1489 from dhermes/bigtable-happybase-table
Browse files Browse the repository at this point in the history
Adding Bigtable HappyBase Table class.
  • Loading branch information
dhermes committed Feb 18, 2016
2 parents 14263ee + beb897d commit bc718d3
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 0 deletions.
32 changes: 32 additions & 0 deletions gcloud/bigtable/happybase/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 <gcloud.bigtable.happybase.table.Table>`
:returns: Table instance owned by this connection.
"""
if use_prefix:
name = self._table_name(name)
return Table(name, self)
93 changes: 93 additions & 0 deletions gcloud/bigtable/happybase/table.py
Original file line number Diff line number Diff line change
@@ -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 <exceptions.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 <exceptions.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 '<table.Table name=%r>' % (self.name,)
66 changes: 66 additions & 0 deletions gcloud/bigtable/happybase/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down
97 changes: 97 additions & 0 deletions gcloud/bigtable/happybase/test_table.py
Original file line number Diff line number Diff line change
@@ -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), '<table.Table name=\'table-name\'>')


class _Connection(object):

def __init__(self, cluster):
self._cluster = cluster


class _MockLowLevelTable(object):

def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
1 change: 1 addition & 0 deletions scripts/verify_included_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit bc718d3

Please sign in to comment.