diff --git a/gcloud/bigtable/happybase/__init__.py b/gcloud/bigtable/happybase/__init__.py index f425b09c2e78..4c5b52f80924 100644 --- a/gcloud/bigtable/happybase/__init__.py +++ b/gcloud/bigtable/happybase/__init__.py @@ -16,4 +16,138 @@ This package is intended to emulate the HappyBase library using Google Cloud Bigtable as the backing store. + +Differences in Public API +------------------------- + +Some concepts from HBase/Thrift do not map directly to the Cloud +Bigtable API. As a result, the following instance methods and functions +could not be implemented: + +* :meth:`.Connection.enable_table` - no concept of enabled/disabled +* :meth:`.Connection.disable_table` - no concept of enabled/disabled +* :meth:`.Connection.is_table_enabled` - no concept of enabled/disabled +* :meth:`.Connection.compact_table` - table storage is opaque to user +* :func:`make_row() ` - helper + needed for Thrift library +* :func:`make_ordered_row() ` + - helper needed for Thrift library +* :meth:`Table.regions() ` + - tables in Cloud Bigtable do not expose internal storage details +* :meth:`Table.counter_set() \ + ` - method can't + be atomic, so we disable it +* The ``__version__`` value for the HappyBase package is :data:`None`. + However, it's worth nothing this implementation was based off HappyBase + 0.9. + +In addition, many of the constants from :mod:`.connection` are specific +to HBase and are defined as :data:`None` in our module: + +* ``COMPAT_MODES`` +* ``THRIFT_TRANSPORTS`` +* ``THRIFT_PROTOCOLS`` +* ``DEFAULT_HOST`` +* ``DEFAULT_PORT`` +* ``DEFAULT_TRANSPORT`` +* ``DEFAULT_COMPAT`` +* ``DEFAULT_PROTOCOL`` + +Two of these ``DEFAULT_HOST`` and ``DEFAULT_PORT``, are even imported in +the main :mod:`happybase ` package. + +Finally, we do not provide the ``util`` module. Though it is public in the +HappyBase library, it provides no core functionality. + +API Behavior Changes +-------------------- + +* Since there is no concept of an enabled / disabled table, calling + :meth:`.Connection.delete_table` with ``disable=True`` can't be supported. + Using that argument will result in a warning. +* The :class:`.Connection` constructor **disables** the use of several + arguments and will print a warning if any of them are passed in as keyword + arguments. The arguments are: + + * ``host`` + * ``port`` + * ``compat`` + * ``transport`` + * ``protocol`` +* In order to make :class:`.Connection` compatible with Cloud Bigtable, we + add a ``cluster`` keyword argument to allow user's to pass in their own + :class:`.Cluster` (which they can construct beforehand). + + For example: + + .. code:: python + + from gcloud.bigtable.client import Client + client = Client(project=PROJECT_ID, admin=True) + cluster = client.cluster(zone, cluster_id) + cluster.reload() + + from gcloud.bigtable.happybase import Connection + connection = Connection(cluster=cluster) + +* Any uses of the ``wal`` (Write Ahead Log) argument will result in a + warning as well. This includes uses in: + + * :class:`.Batch` constructor + * :meth:`.Batch.put` + * :meth:`.Batch.delete` + * :meth:`Table.put() ` + * :meth:`Table.delete() ` + * :meth:`Table.batch() ` factory +* When calling :meth:`.Connection.create_table`, the majority of HBase column + family options cannot be used. Among + + * ``max_versions`` + * ``compression`` + * ``in_memory`` + * ``bloom_filter_type`` + * ``bloom_filter_vector_size`` + * ``bloom_filter_nb_hashes`` + * ``block_cache_enabled`` + * ``time_to_live`` + + Only ``max_versions`` and ``time_to_live`` are availabe in Cloud Bigtable + (as + :class:`MaxVersionsGCRule ` + and + `MaxAgeGCRule `). + + In addition to using a dictionary for specifying column family options, + we also accept instances of :class:`.GarbageCollectionRule` or subclasses. +* :meth:`Table.scan() ` no longer + accepts the following arguments (which will result in a warning): + + * ``batch_size`` + * ``scan_batching`` + * ``sorted_columns`` + +* Using a HBase filter string in + :meth:`Table.scan() ` is + not possible with Cloud Bigtable and will result in a + :class:`TypeError `. However, the method now accepts + instances of :class:`.RowFilter` and subclasses. +* :meth:`.Batch.delete` (and hence + :meth:`Table.delete() `) + will fail with a :class:`ValueError ` when either a + row or column family delete is attempted with a ``timestamp``. This is + because the Cloud Bigtable API uses the ``DeleteFromFamily`` and + ``DeleteFromRow`` mutations for these deletes, and neither of these + mutations support a timestamp. """ + +from gcloud.bigtable.happybase.batch import Batch +from gcloud.bigtable.happybase.connection import Connection +from gcloud.bigtable.happybase.connection import DEFAULT_HOST +from gcloud.bigtable.happybase.connection import DEFAULT_PORT +from gcloud.bigtable.happybase.pool import ConnectionPool +from gcloud.bigtable.happybase.pool import NoConnectionsAvailable +from gcloud.bigtable.happybase.table import Table + + +# Values from HappyBase that we don't reproduce / are not relevant. +__version__ = None diff --git a/gcloud/bigtable/happybase/connection.py b/gcloud/bigtable/happybase/connection.py index 77332fda3c07..b3abad5da841 100644 --- a/gcloud/bigtable/happybase/connection.py +++ b/gcloud/bigtable/happybase/connection.py @@ -41,6 +41,9 @@ _LEGACY_ARGS = frozenset(('host', 'port', 'compat', 'transport', 'protocol')) _WARN = warnings.warn +_DISABLE_DELETE_MSG = ('The disable argument should not be used in ' + 'delete_table(). Cloud Bigtable has no concept ' + 'of enabled / disabled tables.') def _get_cluster(timeout=None): @@ -349,14 +352,9 @@ def delete_table(self, name, disable=False): is provided for compatibility with HappyBase, but is not relevant for Cloud Bigtable since it has no concept of enabled / disabled tables. - - :raises: :class:`ValueError ` - if ``disable=True``. """ if disable: - raise ValueError('The disable argument should not be used in ' - 'delete_table(). Cloud Bigtable has no concept ' - 'of enabled / disabled tables.') + _WARN(_DISABLE_DELETE_MSG) name = self._table_name(name) _LowLevelTable(name, self._cluster).delete() diff --git a/gcloud/bigtable/happybase/test_connection.py b/gcloud/bigtable/happybase/test_connection.py index e503a1e25a87..a07e9d86b7a2 100644 --- a/gcloud/bigtable/happybase/test_connection.py +++ b/gcloud/bigtable/happybase/test_connection.py @@ -395,7 +395,7 @@ def test_create_table_bad_value(self): with self.assertRaises(ValueError): connection.create_table(name, families) - def test_delete_table(self): + def _delete_table_helper(self, disable=False): from gcloud._testing import _Monkey from gcloud.bigtable.happybase import connection as MUT @@ -411,7 +411,7 @@ def make_table(*args, **kwargs): name = 'table-name' with _Monkey(MUT, _LowLevelTable=make_table): - connection.delete_table(name) + connection.delete_table(name, disable=disable) # Just one table would have been created. table_instance, = tables_created @@ -419,12 +419,22 @@ def make_table(*args, **kwargs): self.assertEqual(table_instance.kwargs, {}) self.assertEqual(table_instance.delete_calls, 1) + def test_delete_table(self): + self._delete_table_helper() + def test_delete_table_disable(self): - cluster = _Cluster() # Avoid implicit environ check. - connection = self._makeOne(autoconnect=False, cluster=cluster) - name = 'table-name' - with self.assertRaises(ValueError): - connection.delete_table(name, disable=True) + from gcloud._testing import _Monkey + from gcloud.bigtable.happybase import connection as MUT + + warned = [] + + def mock_warn(msg): + warned.append(msg) + + with _Monkey(MUT, _WARN=mock_warn): + self._delete_table_helper(disable=True) + + self.assertEqual(warned, [MUT._DISABLE_DELETE_MSG]) def test_enable_table(self): cluster = _Cluster() # Avoid implicit environ check.