Skip to content

Commit d085294

Browse files
authored
Merge pull request #1931 from tseaver/bigtable-v2-happybase
Convert `gcloud.bigtable.happybase` to V2 patterns
2 parents 0c625c5 + d18a316 commit d085294

12 files changed

Lines changed: 279 additions & 258 deletions

File tree

gcloud/bigtable/happybase/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@
8484
* ``protocol``
8585
* In order to make
8686
:class:`Connection <gcloud.bigtable.happybase.connection.Connection>`
87-
compatible with Cloud Bigtable, we add a ``cluster`` keyword argument to
87+
compatible with Cloud Bigtable, we add a ``instance`` keyword argument to
8888
allow users to pass in their own
89-
:class:`Cluster <gcloud.bigtable.cluster.Cluster>` (which they can
89+
:class:`Instance <gcloud.bigtable.instance.Instance>` (which they can
9090
construct beforehand).
9191
9292
For example:
@@ -95,11 +95,11 @@
9595
9696
from gcloud.bigtable.client import Client
9797
client = Client(project=PROJECT_ID, admin=True)
98-
cluster = client.cluster(zone, cluster_id)
99-
cluster.reload()
98+
instance = client.instance(instance_id, location_id)
99+
instance.reload()
100100
101101
from gcloud.bigtable.happybase import Connection
102-
connection = Connection(cluster=cluster)
102+
connection = Connection(instance=instance)
103103
104104
* Any uses of the ``wal`` (Write Ahead Log) argument will result in a
105105
warning as well. This includes uses in:

gcloud/bigtable/happybase/connection.py

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -54,57 +54,57 @@
5454
'of enabled / disabled tables.')
5555

5656

57-
def _get_cluster(timeout=None):
58-
"""Gets cluster for the default project.
57+
def _get_instance(timeout=None):
58+
"""Gets instance for the default project.
5959
6060
Creates a client with the inferred credentials and project ID from
6161
the local environment. Then uses
62-
:meth:`.bigtable.client.Client.list_clusters` to
63-
get the unique cluster owned by the project.
62+
:meth:`.bigtable.client.Client.list_instances` to
63+
get the unique instance owned by the project.
6464
65-
If the request fails for any reason, or if there isn't exactly one cluster
65+
If the request fails for any reason, or if there isn't exactly one instance
6666
owned by the project, then this function will fail.
6767
6868
:type timeout: int
6969
:param timeout: (Optional) The socket timeout in milliseconds.
7070
71-
:rtype: :class:`gcloud.bigtable.cluster.Cluster`
72-
:returns: The unique cluster owned by the project inferred from
71+
:rtype: :class:`gcloud.bigtable.instance.Instance`
72+
:returns: The unique instance owned by the project inferred from
7373
the environment.
7474
:raises: :class:`ValueError <exceptions.ValueError>` if there is a failed
75-
zone or any number of clusters other than one.
75+
location or any number of instances other than one.
7676
"""
7777
client_kwargs = {'admin': True}
7878
if timeout is not None:
7979
client_kwargs['timeout_seconds'] = timeout / 1000.0
8080
client = Client(**client_kwargs)
8181
try:
8282
client.start()
83-
clusters, failed_zones = client.list_clusters()
83+
instances, failed_locations = client.list_instances()
8484
finally:
8585
client.stop()
8686

87-
if len(failed_zones) != 0:
88-
raise ValueError('Determining cluster via ListClusters encountered '
89-
'failed zones.')
90-
if len(clusters) == 0:
91-
raise ValueError('This client doesn\'t have access to any clusters.')
92-
if len(clusters) > 1:
93-
raise ValueError('This client has access to more than one cluster. '
94-
'Please directly pass the cluster you\'d '
87+
if len(failed_locations) != 0:
88+
raise ValueError('Determining instance via ListInstances encountered '
89+
'failed locations.')
90+
if len(instances) == 0:
91+
raise ValueError('This client doesn\'t have access to any instances.')
92+
if len(instances) > 1:
93+
raise ValueError('This client has access to more than one instance. '
94+
'Please directly pass the instance you\'d '
9595
'like to use.')
96-
return clusters[0]
96+
return instances[0]
9797

9898

9999
class Connection(object):
100100
"""Connection to Cloud Bigtable backend.
101101
102102
.. note::
103103
104-
If you pass a ``cluster``, it will be :meth:`.Cluster.copy`-ed before
104+
If you pass a ``instance``, it will be :meth:`.Instance.copy`-ed before
105105
being stored on the new connection. This also copies the
106106
:class:`Client <gcloud.bigtable.client.Client>` that created the
107-
:class:`Cluster <gcloud.bigtable.cluster.Cluster>` instance and the
107+
:class:`Instance <gcloud.bigtable.instance.Instance>` instance and the
108108
:class:`Credentials <oauth2client.client.Credentials>` stored on the
109109
client.
110110
@@ -127,27 +127,27 @@ class Connection(object):
127127
:param table_prefix_separator: (Optional) Separator used with
128128
``table_prefix``. Defaults to ``_``.
129129
130-
:type cluster: :class:`Cluster <gcloud.bigtable.cluster.Cluster>`
131-
:param cluster: (Optional) A Cloud Bigtable cluster. The instance also
130+
:type instance: :class:`Instance <gcloud.bigtable.instance.Instance>`
131+
:param instance: (Optional) A Cloud Bigtable instance. The instance also
132132
owns a client for making gRPC requests to the Cloud
133133
Bigtable API. If not passed in, defaults to creating client
134134
with ``admin=True`` and using the ``timeout`` here for the
135135
``timeout_seconds`` argument to the
136136
:class:`Client <gcloud.bigtable.client.Client>`
137137
constructor. The credentials for the client
138138
will be the implicit ones loaded from the environment.
139-
Then that client is used to retrieve all the clusters
139+
Then that client is used to retrieve all the instances
140140
owned by the client's project.
141141
142142
:type kwargs: dict
143143
:param kwargs: Remaining keyword arguments. Provided for HappyBase
144144
compatibility.
145145
"""
146146

147-
_cluster = None
147+
_instance = None
148148

149149
def __init__(self, timeout=None, autoconnect=True, table_prefix=None,
150-
table_prefix_separator='_', cluster=None, **kwargs):
150+
table_prefix_separator='_', instance=None, **kwargs):
151151
self._handle_legacy_args(kwargs)
152152
if table_prefix is not None:
153153
if not isinstance(table_prefix, six.string_types):
@@ -162,13 +162,13 @@ def __init__(self, timeout=None, autoconnect=True, table_prefix=None,
162162
self.table_prefix = table_prefix
163163
self.table_prefix_separator = table_prefix_separator
164164

165-
if cluster is None:
166-
self._cluster = _get_cluster(timeout=timeout)
165+
if instance is None:
166+
self._instance = _get_instance(timeout=timeout)
167167
else:
168168
if timeout is not None:
169169
raise ValueError('Timeout cannot be used when an existing '
170-
'cluster is passed')
171-
self._cluster = cluster.copy()
170+
'instance is passed')
171+
self._instance = instance.copy()
172172

173173
if autoconnect:
174174
self.open()
@@ -203,23 +203,23 @@ def open(self):
203203
204204
This method opens the underlying HTTP/2 gRPC connection using a
205205
:class:`Client <gcloud.bigtable.client.Client>` bound to the
206-
:class:`Cluster <gcloud.bigtable.cluster.Cluster>` owned by
206+
:class:`Instance <gcloud.bigtable.instance.Instance>` owned by
207207
this connection.
208208
"""
209-
self._cluster._client.start()
209+
self._instance._client.start()
210210

211211
def close(self):
212212
"""Close the underlying transport to Cloud Bigtable.
213213
214214
This method closes the underlying HTTP/2 gRPC connection using a
215215
:class:`Client <gcloud.bigtable.client.Client>` bound to the
216-
:class:`Cluster <gcloud.bigtable.cluster.Cluster>` owned by
216+
:class:`Instance <gcloud.bigtable.instance.Instance>` owned by
217217
this connection.
218218
"""
219-
self._cluster._client.stop()
219+
self._instance._client.stop()
220220

221221
def __del__(self):
222-
if self._cluster is not None:
222+
if self._instance is not None:
223223
self.close()
224224

225225
def _table_name(self, name):
@@ -258,7 +258,7 @@ def tables(self):
258258
259259
.. note::
260260
261-
This lists every table in the cluster owned by this connection,
261+
This lists every table in the instance owned by this connection,
262262
**not** every table that a given user may have access to.
263263
264264
.. note::
@@ -269,7 +269,7 @@ def tables(self):
269269
:rtype: list
270270
:returns: List of string table names.
271271
"""
272-
low_level_table_instances = self._cluster.list_tables()
272+
low_level_table_instances = self._instance.list_tables()
273273
table_names = [table_instance.table_id
274274
for table_instance in low_level_table_instances]
275275

@@ -345,7 +345,7 @@ def create_table(self, name, families):
345345

346346
# Create table instance and then make API calls.
347347
name = self._table_name(name)
348-
low_level_table = _LowLevelTable(name, self._cluster)
348+
low_level_table = _LowLevelTable(name, self._instance)
349349
try:
350350
low_level_table.create()
351351
except face.NetworkError as network_err:
@@ -376,7 +376,7 @@ def delete_table(self, name, disable=False):
376376
_WARN(_DISABLE_DELETE_MSG)
377377

378378
name = self._table_name(name)
379-
_LowLevelTable(name, self._cluster).delete()
379+
_LowLevelTable(name, self._instance).delete()
380380

381381
def enable_table(self, name):
382382
"""Enable the specified table.

gcloud/bigtable/happybase/pool.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import six
2222

2323
from gcloud.bigtable.happybase.connection import Connection
24-
from gcloud.bigtable.happybase.connection import _get_cluster
24+
from gcloud.bigtable.happybase.connection import _get_instance
2525

2626

2727
_MIN_POOL_SIZE = 1
@@ -45,7 +45,7 @@ class ConnectionPool(object):
4545
:class:`Connection <.happybase.connection.Connection>` constructor
4646
**except** for ``autoconnect``. This is because the ``open`` /
4747
``closed`` status of a connection is managed by the pool. In addition,
48-
if ``cluster`` is not passed, the default / inferred cluster is
48+
if ``instance`` is not passed, the default / inferred instance is
4949
determined by the pool and then passed to each
5050
:class:`Connection <.happybase.connection.Connection>` that is created.
5151
@@ -75,8 +75,8 @@ def __init__(self, size, **kwargs):
7575

7676
connection_kwargs = kwargs
7777
connection_kwargs['autoconnect'] = False
78-
if 'cluster' not in connection_kwargs:
79-
connection_kwargs['cluster'] = _get_cluster(
78+
if 'instance' not in connection_kwargs:
79+
connection_kwargs['instance'] = _get_instance(
8080
timeout=kwargs.get('timeout'))
8181

8282
for _ in six.moves.range(size):

gcloud/bigtable/happybase/table.py

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,13 @@ class Table(object):
109109

110110
def __init__(self, name, connection):
111111
self.name = name
112-
# This remains as legacy for HappyBase, but only the cluster
112+
# This remains as legacy for HappyBase, but only the instance
113113
# from the connection is needed.
114114
self.connection = connection
115115
self._low_level_table = None
116116
if self.connection is not None:
117117
self._low_level_table = _LowLevelTable(self.name,
118-
self.connection._cluster)
118+
self.connection._instance)
119119

120120
def __repr__(self):
121121
return '<table.Table name=%r>' % (self.name,)
@@ -378,42 +378,8 @@ def scan(self, row_start=None, row_stop=None, row_prefix=None,
378378
:class:`TypeError <exceptions.TypeError>` if a string
379379
``filter`` is used.
380380
"""
381-
filter_ = kwargs.pop('filter', None)
382-
legacy_args = []
383-
for kw_name in ('batch_size', 'scan_batching', 'sorted_columns'):
384-
if kw_name in kwargs:
385-
legacy_args.append(kw_name)
386-
kwargs.pop(kw_name)
387-
if legacy_args:
388-
legacy_args = ', '.join(legacy_args)
389-
message = ('The HappyBase legacy arguments %s were used. These '
390-
'arguments are unused by gcloud.' % (legacy_args,))
391-
_WARN(message)
392-
if kwargs:
393-
raise TypeError('Received unexpected arguments', kwargs.keys())
394-
395-
if limit is not None and limit < 1:
396-
raise ValueError('limit must be positive')
397-
if row_prefix is not None:
398-
if row_start is not None or row_stop is not None:
399-
raise ValueError('row_prefix cannot be combined with '
400-
'row_start or row_stop')
401-
row_start = row_prefix
402-
row_stop = _string_successor(row_prefix)
403-
404-
filters = []
405-
if isinstance(filter_, six.string_types):
406-
raise TypeError('Specifying filters as a string is not supported '
407-
'by Cloud Bigtable. Use a '
408-
'gcloud.bigtable.row.RowFilter instead.')
409-
elif filter_ is not None:
410-
filters.append(filter_)
411-
412-
if columns is not None:
413-
filters.append(_columns_filter_helper(columns))
414-
# versions == 1 since we only want the latest.
415-
filter_chain = _filter_chain_helper(versions=1, timestamp=timestamp,
416-
filters=filters)
381+
row_start, row_stop, filter_chain = _scan_filter_helper(
382+
row_start, row_stop, row_prefix, columns, timestamp, limit, kwargs)
417383

418384
partial_rows_data = self._low_level_table.read_rows(
419385
start_key=row_start, end_key=row_stop,
@@ -424,11 +390,12 @@ def scan(self, row_start=None, row_stop=None, row_prefix=None,
424390
while True:
425391
try:
426392
partial_rows_data.consume_next()
427-
row_key, curr_row_data = rows_dict.popitem()
428-
# NOTE: We expect len(rows_dict) == 0, but don't check it.
429-
curr_row_dict = _partial_row_to_dict(
430-
curr_row_data, include_timestamp=include_timestamp)
431-
yield (row_key, curr_row_dict)
393+
for row_key in sorted(rows_dict):
394+
curr_row_data = rows_dict.pop(row_key)
395+
# NOTE: We expect len(rows_dict) == 0, but don't check it.
396+
curr_row_dict = _partial_row_to_dict(
397+
curr_row_data, include_timestamp=include_timestamp)
398+
yield (row_key, curr_row_dict)
432399
except StopIteration:
433400
break
434401

@@ -911,6 +878,49 @@ def _filter_chain_helper(column=None, versions=None, timestamp=None,
911878
return RowFilterChain(filters=filters)
912879

913880

881+
def _scan_filter_helper(row_start, row_stop, row_prefix, columns,
882+
timestamp, limit, kwargs):
883+
"""Helper for :meth:`scan`: build up a filter chain."""
884+
filter_ = kwargs.pop('filter', None)
885+
legacy_args = []
886+
for kw_name in ('batch_size', 'scan_batching', 'sorted_columns'):
887+
if kw_name in kwargs:
888+
legacy_args.append(kw_name)
889+
kwargs.pop(kw_name)
890+
if legacy_args:
891+
legacy_args = ', '.join(legacy_args)
892+
message = ('The HappyBase legacy arguments %s were used. These '
893+
'arguments are unused by gcloud.' % (legacy_args,))
894+
_WARN(message)
895+
if kwargs:
896+
raise TypeError('Received unexpected arguments', kwargs.keys())
897+
898+
if limit is not None and limit < 1:
899+
raise ValueError('limit must be positive')
900+
if row_prefix is not None:
901+
if row_start is not None or row_stop is not None:
902+
raise ValueError('row_prefix cannot be combined with '
903+
'row_start or row_stop')
904+
row_start = row_prefix
905+
row_stop = _string_successor(row_prefix)
906+
907+
filters = []
908+
if isinstance(filter_, six.string_types):
909+
raise TypeError('Specifying filters as a string is not supported '
910+
'by Cloud Bigtable. Use a '
911+
'gcloud.bigtable.row.RowFilter instead.')
912+
elif filter_ is not None:
913+
filters.append(filter_)
914+
915+
if columns is not None:
916+
filters.append(_columns_filter_helper(columns))
917+
918+
# versions == 1 since we only want the latest.
919+
filter_ = _filter_chain_helper(versions=1, timestamp=timestamp,
920+
filters=filters)
921+
return row_start, row_stop, filter_
922+
923+
914924
def _columns_filter_helper(columns):
915925
"""Creates a union filter for a list of columns.
916926

0 commit comments

Comments
 (0)