Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Release date: *not yet released*
* Add :py:meth:`ConnectionPool.close` and support using the pool as a
context manager
(`issue 229 <https://github.com/python-happybase/happybase/issues/229>`_)
* Add :py:meth:`Table.append`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls add the issue/pr links



HappyBase 1.3.0
Expand Down
32 changes: 31 additions & 1 deletion happybase/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from six import iteritems

from Hbase_thrift import TScan
from Hbase_thrift import TAppend, TScan

from .util import thrift_type_to_dict, bytes_increment, OrderedDict
from .batch import Batch
Expand Down Expand Up @@ -494,6 +494,36 @@ def delete(self, row, columns=None, timestamp=None, wal=True):
with self.batch(timestamp=timestamp, wal=wal) as batch:
batch.delete(row, columns)

def append(self, row, data, include_timestamp=False):
"""Atomically append values to one or more columns in a row.

The `data` argument is a dictionary that maps columns to the values
to append. If a column did not exist, it is created with the given
value.

:param str row: the row key
:param dict data: the values to append
:param bool include_timestamp: whether timestamps are returned

:return: mapping of columns to their values after appending
:rtype: dict
"""
# The server returns the cells without their names, sorted by
# family and qualifier, so send the columns in that same order.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the benefit of sorting? sounds like unnecessary work, no?

columns = sorted(
data, key=lambda c: c.split(b':' if isinstance(c, bytes) else ':'))
cells = self.connection.client.append(TAppend(
table=self.name,
row=row,
columns=columns,
values=[data[column] for column in columns],
))
return {
column: (cell.value, cell.timestamp) if include_timestamp
else cell.value
for column, cell in zip(columns, cells)
}

def batch(self, timestamp=None, batch_size=None, transaction=False,
wal=True):
"""Create a new batch operation for this table.
Expand Down
14 changes: 14 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,20 @@ def test_atomic_counters():
assert 10 == table.counter_dec(row, column, -7)


def test_append():
row = b'row-with-append'

assert {b'cf1:c1': b'a'} == table.append(row, {b'cf1:c1': b'a'})
assert {b'cf1:c1': b'ab', b'cf2:c2': b'x'} == table.append(
row, {b'cf2:c2': b'x', b'cf1:c1': b'b'})
assert {b'cf1:c1': b'ab', b'cf2:c2': b'x'} == table.row(row)

value, timestamp = table.append(
row, {b'cf1:c1': b'c'}, include_timestamp=True)[b'cf1:c1']
assert b'abc' == value
assert isinstance(timestamp, int)


def test_batch():
with pytest.raises(TypeError):
table.batch(timestamp='invalid')
Expand Down
Loading