Skip to content

Commit a77e7fa

Browse files
committed
Merge pull request #1511 from dhermes/happybase-table-families
Implementing HappyBase Table.families().
2 parents 252d532 + e84e403 commit a77e7fa

File tree

2 files changed

+199
-0
lines changed

2 files changed

+199
-0
lines changed

gcloud/bigtable/happybase/table.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,18 @@
1515
"""Google Cloud Bigtable HappyBase table module."""
1616

1717

18+
import six
19+
20+
from gcloud._helpers import _total_seconds
21+
from gcloud.bigtable.column_family import GCRuleIntersection
22+
from gcloud.bigtable.column_family import MaxAgeGCRule
23+
from gcloud.bigtable.column_family import MaxVersionsGCRule
1824
from gcloud.bigtable.table import Table as _LowLevelTable
1925

2026

27+
_SIMPLE_GC_RULES = (MaxAgeGCRule, MaxVersionsGCRule)
28+
29+
2130
def make_row(cell_map, include_timestamp):
2231
"""Make a row dict for a Thrift cell mapping.
2332
@@ -92,6 +101,19 @@ def __init__(self, name, connection):
92101
def __repr__(self):
93102
return '<table.Table name=%r>' % (self.name,)
94103

104+
def families(self):
105+
"""Retrieve the column families for this table.
106+
107+
:rtype: dict
108+
:returns: Mapping from column family name to garbage collection rule
109+
for a column family.
110+
"""
111+
column_family_map = self._low_level_table.list_column_families()
112+
result = {}
113+
for col_fam, col_fam_obj in six.iteritems(column_family_map):
114+
result[col_fam] = _gc_rule_to_dict(col_fam_obj.gc_rule)
115+
return result
116+
95117
def regions(self):
96118
"""Retrieve the regions for this table.
97119
@@ -104,3 +126,48 @@ def regions(self):
104126
"""
105127
raise NotImplementedError('The Cloud Bigtable API does not have a '
106128
'concept of splitting a table into regions.')
129+
130+
131+
def _gc_rule_to_dict(gc_rule):
132+
"""Converts garbage collection rule to dictionary if possible.
133+
134+
This is in place to support dictionary values as was done
135+
in HappyBase, which has somewhat different garbage collection rule
136+
settings for column families.
137+
138+
Only does this if the garbage collection rule is:
139+
140+
* :class:`.MaxAgeGCRule`
141+
* :class:`.MaxVersionsGCRule`
142+
* Composite :class:`.GCRuleIntersection` with two rules, one each
143+
of type :class:`.MaxAgeGCRule` and :class:`.MaxVersionsGCRule`
144+
145+
Otherwise, just returns the input without change.
146+
147+
:type gc_rule: :data:`NoneType <types.NoneType>`,
148+
:class:`.GarbageCollectionRule`
149+
:param gc_rule: A garbage collection rule to convert to a dictionary
150+
(if possible).
151+
152+
:rtype: dict or :class:`.GarbageCollectionRule`
153+
:returns: The converted garbage collection rule.
154+
"""
155+
result = gc_rule
156+
if gc_rule is None:
157+
result = {}
158+
elif isinstance(gc_rule, MaxAgeGCRule):
159+
result = {'time_to_live': _total_seconds(gc_rule.max_age)}
160+
elif isinstance(gc_rule, MaxVersionsGCRule):
161+
result = {'max_versions': gc_rule.max_num_versions}
162+
elif isinstance(gc_rule, GCRuleIntersection):
163+
if len(gc_rule.rules) == 2:
164+
rule1, rule2 = gc_rule.rules
165+
if (isinstance(rule1, _SIMPLE_GC_RULES) and
166+
isinstance(rule2, _SIMPLE_GC_RULES)):
167+
rule1 = _gc_rule_to_dict(rule1)
168+
rule2 = _gc_rule_to_dict(rule2)
169+
key1, = rule1.keys()
170+
key2, = rule2.keys()
171+
if key1 != key2:
172+
result = {key1: rule1[key1], key2: rule2[key2]}
173+
return result

gcloud/bigtable/happybase/test_table.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,36 @@ def test_constructor_null_connection(self):
7878
self.assertEqual(table.connection, connection)
7979
self.assertEqual(table._low_level_table, None)
8080

81+
def test_families(self):
82+
from gcloud._testing import _Monkey
83+
from gcloud.bigtable.happybase import table as MUT
84+
85+
name = 'table-name'
86+
connection = None
87+
table = self._makeOne(name, connection)
88+
table._low_level_table = _MockLowLevelTable()
89+
90+
# Mock the column families to be returned.
91+
col_fam_name = 'fam'
92+
gc_rule = object()
93+
col_fam = _MockLowLevelColumnFamily(col_fam_name, gc_rule=gc_rule)
94+
col_fams = {col_fam_name: col_fam}
95+
table._low_level_table.column_families = col_fams
96+
97+
to_dict_result = object()
98+
to_dict_calls = []
99+
100+
def mock_gc_rule_to_dict(gc_rule):
101+
to_dict_calls.append(gc_rule)
102+
return to_dict_result
103+
104+
with _Monkey(MUT, _gc_rule_to_dict=mock_gc_rule_to_dict):
105+
result = table.families()
106+
107+
self.assertEqual(result, {col_fam_name: to_dict_result})
108+
self.assertEqual(table._low_level_table.list_column_families_calls, 1)
109+
self.assertEqual(to_dict_calls, [gc_rule])
110+
81111
def test___repr__(self):
82112
name = 'table-name'
83113
table = self._makeOne(name, None)
@@ -92,14 +122,116 @@ def test_regions(self):
92122
table.regions()
93123

94124

125+
class Test__gc_rule_to_dict(unittest2.TestCase):
126+
127+
def _callFUT(self, *args, **kwargs):
128+
from gcloud.bigtable.happybase.table import _gc_rule_to_dict
129+
return _gc_rule_to_dict(*args, **kwargs)
130+
131+
def test_with_null(self):
132+
gc_rule = None
133+
result = self._callFUT(gc_rule)
134+
self.assertEqual(result, {})
135+
136+
def test_with_max_versions(self):
137+
from gcloud.bigtable.column_family import MaxVersionsGCRule
138+
139+
max_versions = 2
140+
gc_rule = MaxVersionsGCRule(max_versions)
141+
result = self._callFUT(gc_rule)
142+
expected_result = {'max_versions': max_versions}
143+
self.assertEqual(result, expected_result)
144+
145+
def test_with_max_age(self):
146+
import datetime
147+
from gcloud.bigtable.column_family import MaxAgeGCRule
148+
149+
time_to_live = 101
150+
max_age = datetime.timedelta(seconds=time_to_live)
151+
gc_rule = MaxAgeGCRule(max_age)
152+
result = self._callFUT(gc_rule)
153+
expected_result = {'time_to_live': time_to_live}
154+
self.assertEqual(result, expected_result)
155+
156+
def test_with_non_gc_rule(self):
157+
gc_rule = object()
158+
result = self._callFUT(gc_rule)
159+
self.assertTrue(result is gc_rule)
160+
161+
def test_with_gc_rule_union(self):
162+
from gcloud.bigtable.column_family import GCRuleUnion
163+
164+
gc_rule = GCRuleUnion(rules=[])
165+
result = self._callFUT(gc_rule)
166+
self.assertTrue(result is gc_rule)
167+
168+
def test_with_intersection_other_than_two(self):
169+
from gcloud.bigtable.column_family import GCRuleIntersection
170+
171+
gc_rule = GCRuleIntersection(rules=[])
172+
result = self._callFUT(gc_rule)
173+
self.assertTrue(result is gc_rule)
174+
175+
def test_with_intersection_two_max_num_versions(self):
176+
from gcloud.bigtable.column_family import GCRuleIntersection
177+
from gcloud.bigtable.column_family import MaxVersionsGCRule
178+
179+
rule1 = MaxVersionsGCRule(1)
180+
rule2 = MaxVersionsGCRule(2)
181+
gc_rule = GCRuleIntersection(rules=[rule1, rule2])
182+
result = self._callFUT(gc_rule)
183+
self.assertTrue(result is gc_rule)
184+
185+
def test_with_intersection_two_rules(self):
186+
import datetime
187+
from gcloud.bigtable.column_family import GCRuleIntersection
188+
from gcloud.bigtable.column_family import MaxAgeGCRule
189+
from gcloud.bigtable.column_family import MaxVersionsGCRule
190+
191+
time_to_live = 101
192+
max_age = datetime.timedelta(seconds=time_to_live)
193+
rule1 = MaxAgeGCRule(max_age)
194+
max_versions = 2
195+
rule2 = MaxVersionsGCRule(max_versions)
196+
gc_rule = GCRuleIntersection(rules=[rule1, rule2])
197+
result = self._callFUT(gc_rule)
198+
expected_result = {
199+
'max_versions': max_versions,
200+
'time_to_live': time_to_live,
201+
}
202+
self.assertEqual(result, expected_result)
203+
204+
def test_with_intersection_two_nested_rules(self):
205+
from gcloud.bigtable.column_family import GCRuleIntersection
206+
207+
rule1 = GCRuleIntersection(rules=[])
208+
rule2 = GCRuleIntersection(rules=[])
209+
gc_rule = GCRuleIntersection(rules=[rule1, rule2])
210+
result = self._callFUT(gc_rule)
211+
self.assertTrue(result is gc_rule)
212+
213+
95214
class _Connection(object):
96215

97216
def __init__(self, cluster):
98217
self._cluster = cluster
99218

100219

220+
class _MockLowLevelColumnFamily(object):
221+
222+
def __init__(self, column_family_id, gc_rule=None):
223+
self.column_family_id = column_family_id
224+
self.gc_rule = gc_rule
225+
226+
101227
class _MockLowLevelTable(object):
102228

103229
def __init__(self, *args, **kwargs):
104230
self.args = args
105231
self.kwargs = kwargs
232+
self.list_column_families_calls = 0
233+
self.column_families = {}
234+
235+
def list_column_families(self):
236+
self.list_column_families_calls += 1
237+
return self.column_families

0 commit comments

Comments
 (0)