Skip to content

Commit

Permalink
Add HBase storage
Browse files Browse the repository at this point in the history
  • Loading branch information
DonDebonair committed Sep 6, 2018
1 parent 60f58ed commit 63b3934
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 5 deletions.
13 changes: 11 additions & 2 deletions docs/user/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,23 @@ Out of the box, Slack Machine provides 2 options for storage backend:

*Class*: ``machine.storage.backends.redis.RedisStorage``

So if, for example, you want to configure Slack Machine to use Redis as a storage backend, with your Redis
- **HBase**: this backend stores data in `HBase`_. HBase is a columnar store. This backend is for
advanced users only. You should only use it if you already have a HBase cluster running and cannot
use Redis for some reason. This backend requires 2 variables to be set in your ``local_settings.py``:
``HBASE_URL`` and ``HBASE_TABLE``.

*Class*: ``machine.storage.backends.hbase.HBaseStorage``

So if, for example, you want to configure Slack Machine to use Redis as a storage backend, with your Redis
instance running on *localhost* on the default port, you would add this to your ``local_settings.py``:

.. code-block:: python
STORAGE_BACKEND = 'machine.storage.backends.redis.RedisStorage'
REDIS_URL = redis://localhost:6379'
.. _Redis: https://redis.io/

.. _HBase: https://hbase.apache.org/

That's all there is to it!
2 changes: 1 addition & 1 deletion machine/__about__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
__description__ = "A sexy, simple, yet powerful and extendable Slack bot"
__uri__ = "https://github.com/DandyDev/slack-machine"

__version_info__ = (0, 15, 0)
__version_info__ = (0, 16, 0)
__version__ = '.'.join(map(str, __version_info__))

__author__ = "Daan Debie"
Expand Down
61 changes: 61 additions & 0 deletions machine/storage/backends/hbase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from datetime import datetime, timedelta

from happybase import Connection
from machine.storage.backends.base import MachineBaseStorage


def bytes_to_float(byte_arr):
s = byte_arr.decode('utf-8')
return float(s)


def float_to_bytes(i):
return bytes(str(i), 'utf-8')


class HBaseStorage(MachineBaseStorage):

_VAL = b'values:value'
_EXP = b'values:expires_at'
_COLS = [_VAL, _EXP]

def __init__(self, settings):
super().__init__(settings)
hbase_host = settings['HBASE_HOST']
hbase_table = settings['HBASE_TABLE']
self._connection = Connection(hbase_host)
self._table = self._connection.table(hbase_table)

def _get_value(self, key):
row = self._table.row(key, self._COLS)
val = row.get(self._VAL)
if val:
exp = row.get(self._EXP)
if not exp:
return val
elif datetime.fromtimestamp(bytes_to_float(exp)) > datetime.utcnow():
return val
else:
self.delete(key)
return None
return None

def has(self, key):
val = self._get_value(key)
return bool(val)

def get(self, key):
return self._get_value(key)

def set(self, key, value, expires=None):
data = {self._VAL: value}
if expires:
expires_at = datetime.utcnow() + timedelta(seconds=expires)
data[self._EXP] = float_to_bytes(expires_at.timestamp())
self._table.put(key, data)

def delete(self, key):
self._table.delete(key)

def size(self):
return 0
4 changes: 3 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ coverage==4.5.1
pytest-cov==2.6.0
Sphinx==1.7.9
sphinx-autobuild==0.7.1
redis==2.10.6
redis==2.10.6
Cython==0.28.5
happybase==1.1.0
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ def run(self):
install_requires=dependencies,
python_requires='~=3.3',
extras_require={
'redis': ['redis', 'hiredis']
'redis': ['redis', 'hiredis'],
'hbase': ['Cython==0.28.5', 'happybase']
},
classifiers=[
"Development Status :: 3 - Alpha",
Expand Down
46 changes: 46 additions & 0 deletions tests/test_hbase_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import pytest
from happybase import Table

from machine.storage.backends.hbase import bytes_to_float, float_to_bytes, HBaseStorage

_VAL = b'values:value'
_EXP = b'values:expires_at'
_COLS = [_VAL, _EXP]


@pytest.fixture
def table(mocker):
table = mocker.MagicMock(spec=Table)
ConnectionCls = mocker.patch('machine.storage.backends.hbase.Connection', autospec=True)
instance = ConnectionCls.return_value
instance.table.return_value = table
return table


@pytest.fixture
def hbase_storage(table):
return HBaseStorage({'HBASE_HOST': 'foo', 'HBASE_TABLE': 'bar'})


def test_float_conversion():
assert bytes_to_float(float_to_bytes(3.14159265359)) == 3.14159265359


def test_get(table, hbase_storage):
hbase_storage.get('key1')
table.row.assert_called_with('key1', _COLS)


def test_has(table, hbase_storage):
hbase_storage.has('key1')
table.row.assert_called_with('key1', _COLS)


def test_delete(table, hbase_storage):
hbase_storage.delete('key1')
table.delete.assert_called_with('key1')


def test_set(table, hbase_storage):
hbase_storage.set('key1', 'val1')
table.put.assert_called_with('key1', {b'values:value': 'val1'})

0 comments on commit 63b3934

Please sign in to comment.