Hello community, here is the log from the commit of package python-happybase for openSUSE:Factory checked in at 2019-09-27 14:45:54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-happybase (Old) and /work/SRC/openSUSE:Factory/.python-happybase.new.2352 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-happybase" Fri Sep 27 14:45:54 2019 rev:14 rq:730343 version:1.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-happybase/python-happybase.changes 2018-12-24 11:38:00.465632305 +0100 +++ /work/SRC/openSUSE:Factory/.python-happybase.new.2352/python-happybase.changes 2019-09-27 14:45:57.253153911 +0200 @@ -1,0 +2,6 @@ +Thu Sep 12 09:02:50 UTC 2019 - Tomáš Chvátal <tchva...@suse.com> + +- Update to 1.2.0: + * Switch from thriftpy to its successor thriftpy2, which supports Python 3.7. + +------------------------------------------------------------------- Old: ---- happybase-1.1.0.tar.gz New: ---- 1.2.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-happybase.spec ++++++ --- /var/tmp/diff_new_pack.x2z9ev/_old 2019-09-27 14:45:57.813152454 +0200 +++ /var/tmp/diff_new_pack.x2z9ev/_new 2019-09-27 14:45:57.821152434 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-happybase # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,19 +18,21 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-happybase -Version: 1.1.0 +Version: 1.2.0 Release: 0 Summary: A Python library to interact with Apache HBase License: MIT Group: Development/Languages/Python URL: https://github.com/wbolster/happybase -Source: https://files.pythonhosted.org/packages/source/h/happybase/happybase-%{version}.tar.gz +Source: https://github.com/wbolster/happybase/archive/%{version}.tar.gz +BuildRequires: %{python_module nose} BuildRequires: %{python_module setuptools} -BuildRequires: %{python_module thriftpy} +BuildRequires: %{python_module thriftpy2} +BuildRequires: fdupes BuildRequires: python-rpm-macros BuildRequires: python3-Sphinx Requires: python-six -Requires: python-thriftpy >= 0.3.8 +Requires: python-thriftpy2 >= 0.4 BuildArch: noarch %python_subpackages @@ -56,10 +58,12 @@ %install %python_install -rm docs/build/html/.doctrees/environment.pickle +%python_expand %fdupes %{buildroot}%{$python_sitelib} +rm -r docs/build/html/.[a-z]* %check -%python_exec setup.py test +# the api tests need running thrift server +%python_expand nosetests-%{$python_bin_suffix} -v tests/test_util.py %files %{python_files} %license LICENSE.rst ++++++ happybase-1.1.0.tar.gz -> 1.2.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/.gitignore new/happybase-1.2.0/.gitignore --- old/happybase-1.1.0/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/happybase-1.2.0/.gitignore 2019-05-14 16:16:06.000000000 +0200 @@ -0,0 +1,8 @@ +*.py[co] +*.egg-info/ +.coverage +.tox/ +build/ +coverage/ +dist/ +doc/build/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/NEWS.rst new/happybase-1.2.0/NEWS.rst --- old/happybase-1.1.0/NEWS.rst 2017-04-03 23:08:55.000000000 +0200 +++ new/happybase-1.2.0/NEWS.rst 2019-05-14 16:16:06.000000000 +0200 @@ -4,6 +4,17 @@ .. py:currentmodule:: happybase +HappyBase 1.2.0 +--------------- + +Release date: 2019-05-14 + +* Switch from ``thriftpy`` to its successor ``thriftpy2``, + which supports Python 3.7. + (`issue #221 <https://github.com/wbolster/happybase/issues/221>`_, + `pr 222 <https://github.com/wbolster/happybase/pull/222>`_, + + HappyBase 1.1.0 --------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/PKG-INFO new/happybase-1.2.0/PKG-INFO --- old/happybase-1.1.0/PKG-INFO 2017-04-03 23:12:57.000000000 +0200 +++ new/happybase-1.2.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,38 +0,0 @@ -Metadata-Version: 1.1 -Name: happybase -Version: 1.1.0 -Summary: A developer-friendly Python library to interact with Apache HBase -Home-page: https://github.com/wbolster/happybase -Author: Wouter Bolsterlee -Author-email: u...@xs4all.nl -License: MIT -Description: HappyBase - ========= - - **HappyBase** is a developer-friendly Python_ library to interact with Apache - HBase_. - - * `Documentation <https://happybase.readthedocs.io/>`_ (Read the Docs) - * `Downloads <http://pypi.python.org/pypi/happybase/>`_ (PyPI) - * `Source code <https://github.com/wbolster/happybase>`_ (Github) - - .. _Python: http://python.org/ - .. _HBase: http://hbase.apache.org/ - - .. If you're reading this from the README.rst file in a source tree, - you can generate the HTML documentation by running "make doc" and browsing - to doc/build/html/index.html to see the result. - - - .. image:: https://d2weczhvl823v0.cloudfront.net/wbolster/happybase/trend.png - :alt: Bitdeli badge - :target: https://bitdeli.com/free - -Platform: UNKNOWN -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 3 -Classifier: Topic :: Database -Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/README.rst new/happybase-1.2.0/README.rst --- old/happybase-1.1.0/README.rst 2016-07-27 19:16:20.000000000 +0200 +++ new/happybase-1.2.0/README.rst 2019-05-14 16:16:06.000000000 +0200 @@ -14,8 +14,3 @@ .. If you're reading this from the README.rst file in a source tree, you can generate the HTML documentation by running "make doc" and browsing to doc/build/html/index.html to see the result. - - -.. image:: https://d2weczhvl823v0.cloudfront.net/wbolster/happybase/trend.png - :alt: Bitdeli badge - :target: https://bitdeli.com/free diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/doc/user.rst new/happybase-1.2.0/doc/user.rst --- old/happybase-1.1.0/doc/user.rst 2016-08-01 22:27:35.000000000 +0200 +++ new/happybase-1.2.0/doc/user.rst 2019-05-14 16:16:06.000000000 +0200 @@ -435,7 +435,7 @@ with table.batch(batch_size=1000) as b: for i in range(1200): # this put() will result in two mutations (two cells) - b.put(b'row-%04d'.format(i), { + b.put(b'row-%04d' % i, { b'cf1:col1': b'v1', b'cf1:col2': b'v2', }) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/happybase/__init__.py new/happybase-1.2.0/happybase/__init__.py --- old/happybase-1.1.0/happybase/__init__.py 2016-08-01 22:43:10.000000000 +0200 +++ new/happybase-1.2.0/happybase/__init__.py 2019-05-14 16:16:06.000000000 +0200 @@ -4,7 +4,7 @@ """ import pkg_resources as _pkg_resources -import thriftpy as _thriftpy +import thriftpy2 as _thriftpy _thriftpy.load( _pkg_resources.resource_filename('happybase', 'Hbase.thrift'), 'Hbase_thrift') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/happybase/_version.py new/happybase-1.2.0/happybase/_version.py --- old/happybase-1.1.0/happybase/_version.py 2017-04-03 23:01:23.000000000 +0200 +++ new/happybase-1.2.0/happybase/_version.py 2019-05-14 16:16:06.000000000 +0200 @@ -5,4 +5,4 @@ setup.py. """ -__version__ = '1.1.0' +__version__ = '1.2.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/happybase/connection.py new/happybase-1.2.0/happybase/connection.py --- old/happybase-1.1.0/happybase/connection.py 2017-04-03 23:01:23.000000000 +0200 +++ new/happybase-1.2.0/happybase/connection.py 2019-05-14 16:16:06.000000000 +0200 @@ -7,9 +7,9 @@ import logging import six -from thriftpy.thrift import TClient -from thriftpy.transport import TBufferedTransport, TFramedTransport, TSocket -from thriftpy.protocol import TBinaryProtocol, TCompactProtocol +from thriftpy2.thrift import TClient +from thriftpy2.transport import TBufferedTransport, TFramedTransport, TSocket +from thriftpy2.protocol import TBinaryProtocol, TCompactProtocol from Hbase_thrift import Hbase, ColumnDescriptor @@ -53,7 +53,7 @@ The optional `table_prefix` and `table_prefix_separator` arguments specify a prefix and a separator string to be prepended to all table names, e.g. when :py:meth:`Connection.table` is invoked. For - example, if `table_prefix` is ``myproject``, all tables tables will + example, if `table_prefix` is ``myproject``, all tables will have names like ``myproject_XYZ``. The optional `compat` argument sets the compatibility level for diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/happybase/pool.py new/happybase-1.2.0/happybase/pool.py --- old/happybase-1.1.0/happybase/pool.py 2016-08-01 22:27:35.000000000 +0200 +++ new/happybase-1.2.0/happybase/pool.py 2019-05-14 16:16:06.000000000 +0200 @@ -9,7 +9,7 @@ from six.moves import queue, range -from thriftpy.thrift import TException +from thriftpy2.thrift import TException from .connection import Connection diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/happybase/table.py new/happybase-1.2.0/happybase/table.py --- old/happybase-1.1.0/happybase/table.py 2017-04-03 23:01:23.000000000 +0200 +++ new/happybase-1.2.0/happybase/table.py 2019-05-14 16:16:06.000000000 +0200 @@ -285,7 +285,7 @@ * The `reverse` argument is only available when using HBase 0.98 (or up). - .. versionadded:: TODO + .. versionadded:: 1.1.0 `reverse` argument .. versionadded:: 0.8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/happybase.egg-info/PKG-INFO new/happybase-1.2.0/happybase.egg-info/PKG-INFO --- old/happybase-1.1.0/happybase.egg-info/PKG-INFO 2017-04-03 23:12:56.000000000 +0200 +++ new/happybase-1.2.0/happybase.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,38 +0,0 @@ -Metadata-Version: 1.1 -Name: happybase -Version: 1.1.0 -Summary: A developer-friendly Python library to interact with Apache HBase -Home-page: https://github.com/wbolster/happybase -Author: Wouter Bolsterlee -Author-email: u...@xs4all.nl -License: MIT -Description: HappyBase - ========= - - **HappyBase** is a developer-friendly Python_ library to interact with Apache - HBase_. - - * `Documentation <https://happybase.readthedocs.io/>`_ (Read the Docs) - * `Downloads <http://pypi.python.org/pypi/happybase/>`_ (PyPI) - * `Source code <https://github.com/wbolster/happybase>`_ (Github) - - .. _Python: http://python.org/ - .. _HBase: http://hbase.apache.org/ - - .. If you're reading this from the README.rst file in a source tree, - you can generate the HTML documentation by running "make doc" and browsing - to doc/build/html/index.html to see the result. - - - .. image:: https://d2weczhvl823v0.cloudfront.net/wbolster/happybase/trend.png - :alt: Bitdeli badge - :target: https://bitdeli.com/free - -Platform: UNKNOWN -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 3 -Classifier: Topic :: Database -Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/happybase.egg-info/SOURCES.txt new/happybase-1.2.0/happybase.egg-info/SOURCES.txt --- old/happybase-1.1.0/happybase.egg-info/SOURCES.txt 2017-04-03 23:12:57.000000000 +0200 +++ new/happybase-1.2.0/happybase.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,32 +0,0 @@ -LICENSE.rst -MANIFEST.in -Makefile -NEWS.rst -README.rst -TODO.rst -requirements.txt -setup.cfg -setup.py -doc/api.rst -doc/conf.py -doc/development.rst -doc/faq.rst -doc/index.rst -doc/installation.rst -doc/license.rst -doc/news.rst -doc/todo.rst -doc/user.rst -happybase/Hbase.thrift -happybase/__init__.py -happybase/_version.py -happybase/batch.py -happybase/connection.py -happybase/pool.py -happybase/table.py -happybase/util.py -happybase.egg-info/PKG-INFO -happybase.egg-info/SOURCES.txt -happybase.egg-info/dependency_links.txt -happybase.egg-info/requires.txt -happybase.egg-info/top_level.txt \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/happybase.egg-info/dependency_links.txt new/happybase-1.2.0/happybase.egg-info/dependency_links.txt --- old/happybase-1.1.0/happybase.egg-info/dependency_links.txt 2017-04-03 23:12:56.000000000 +0200 +++ new/happybase-1.2.0/happybase.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/happybase.egg-info/requires.txt new/happybase-1.2.0/happybase.egg-info/requires.txt --- old/happybase-1.1.0/happybase.egg-info/requires.txt 2017-04-03 23:12:56.000000000 +0200 +++ new/happybase-1.2.0/happybase.egg-info/requires.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -six -thriftpy>=0.3.8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/happybase.egg-info/top_level.txt new/happybase-1.2.0/happybase.egg-info/top_level.txt --- old/happybase-1.1.0/happybase.egg-info/top_level.txt 2017-04-03 23:12:56.000000000 +0200 +++ new/happybase-1.2.0/happybase.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -happybase diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/requirements.txt new/happybase-1.2.0/requirements.txt --- old/happybase-1.1.0/requirements.txt 2016-08-01 22:27:35.000000000 +0200 +++ new/happybase-1.2.0/requirements.txt 2019-05-14 16:16:06.000000000 +0200 @@ -1,2 +1,2 @@ six -thriftpy>=0.3.8 +thriftpy2>=0.4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/setup.cfg new/happybase-1.2.0/setup.cfg --- old/happybase-1.1.0/setup.cfg 2017-04-03 23:12:57.000000000 +0200 +++ new/happybase-1.2.0/setup.cfg 2019-05-14 16:16:06.000000000 +0200 @@ -3,7 +3,7 @@ verbosity = 2 with-coverage = 1 cover-erase = 1 -cover-package = happybase.connection,happybase.table,happybase.batch,happybase.pool,happybase.util,tests +cover-package=happybase.connection,happybase.table,happybase.batch,happybase.pool,happybase.util,tests cover-tests = 1 cover-html = 1 cover-html-dir = coverage/ @@ -13,10 +13,4 @@ build-dir = doc/build/ [wheel] -universal = 1 - -[egg_info] -tag_build = -tag_date = 0 -tag_svn_revision = 0 - +universal = 1 \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/setup.py new/happybase-1.2.0/setup.py --- old/happybase-1.1.0/setup.py 2016-08-01 22:27:35.000000000 +0200 +++ new/happybase-1.2.0/setup.py 2019-05-14 16:16:06.000000000 +0200 @@ -34,7 +34,7 @@ include_package_data=True, license="MIT", classifiers=( - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/test-requirements.txt new/happybase-1.2.0/test-requirements.txt --- old/happybase-1.1.0/test-requirements.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/happybase-1.2.0/test-requirements.txt 2019-05-14 16:16:06.000000000 +0200 @@ -0,0 +1,4 @@ +-r requirements.txt +coverage +nose +sphinx diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/tests/test_api.py new/happybase-1.2.0/tests/test_api.py --- old/happybase-1.1.0/tests/test_api.py 1970-01-01 01:00:00.000000000 +0100 +++ new/happybase-1.2.0/tests/test_api.py 2019-05-14 16:16:06.000000000 +0200 @@ -0,0 +1,606 @@ +""" +HappyBase tests. +""" + +import collections +import os +import random +import threading + +import six +from six.moves import range + +from nose.tools import ( + assert_dict_equal, + assert_equal, + assert_false, + assert_in, + assert_is_instance, + assert_is_not_none, + assert_list_equal, + assert_not_in, + assert_raises, + assert_true, +) + +from happybase import Connection, ConnectionPool, NoConnectionsAvailable + +HAPPYBASE_HOST = os.environ.get('HAPPYBASE_HOST') +HAPPYBASE_PORT = os.environ.get('HAPPYBASE_PORT') +HAPPYBASE_COMPAT = os.environ.get('HAPPYBASE_COMPAT', '0.98') +HAPPYBASE_TRANSPORT = os.environ.get('HAPPYBASE_TRANSPORT', 'buffered') +KEEP_TABLE = ('HAPPYBASE_NO_CLEANUP' in os.environ) + +TABLE_PREFIX = b'happybase_tests_tmp' +TEST_TABLE_NAME = b'test1' + +connection_kwargs = dict( + host=HAPPYBASE_HOST, + port=HAPPYBASE_PORT, + table_prefix=TABLE_PREFIX, + compat=HAPPYBASE_COMPAT, + transport=HAPPYBASE_TRANSPORT, +) + + +# Yuck, globals +connection = table = None + + +def maybe_delete_table(): + if KEEP_TABLE: + return + + if TEST_TABLE_NAME in connection.tables(): + print("Test table already exists; removing it...") + connection.delete_table(TEST_TABLE_NAME, disable=True) + + +def setup_module(): + global connection, table + connection = Connection(**connection_kwargs) + + assert_is_not_none(connection) + + maybe_delete_table() + cfs = { + 'cf1': {}, + 'cf2': None, + 'cf3': {'max_versions': 1}, + } + connection.create_table(TEST_TABLE_NAME, families=cfs) + + table = connection.table(TEST_TABLE_NAME) + assert_is_not_none(table) + + +def teardown_module(): + if not KEEP_TABLE: + connection.delete_table(TEST_TABLE_NAME, disable=True) + connection.close() + + +def test_connection_compat(): + with assert_raises(ValueError): + Connection(compat='0.1.invalid.version') + + +def test_timeout_arg(): + Connection( + timeout=5000, + autoconnect=False) + + +def test_enabling(): + assert_true(connection.is_table_enabled(TEST_TABLE_NAME)) + connection.disable_table(TEST_TABLE_NAME) + assert_false(connection.is_table_enabled(TEST_TABLE_NAME)) + connection.enable_table(TEST_TABLE_NAME) + assert_true(connection.is_table_enabled(TEST_TABLE_NAME)) + + +def test_compaction(): + connection.compact_table(TEST_TABLE_NAME) + connection.compact_table(TEST_TABLE_NAME, major=True) + + +def test_prefix(): + assert_equal(TABLE_PREFIX + b'_', connection._table_name('')) + assert_equal(TABLE_PREFIX + b'_foo', connection._table_name('foo')) + + assert_equal(connection.table('foobar').name, TABLE_PREFIX + b'_foobar') + assert_equal(connection.table('foobar', use_prefix=False).name, b'foobar') + + c = Connection(autoconnect=False) + assert_equal(b'foo', c._table_name('foo')) + + with assert_raises(TypeError): + Connection(autoconnect=False, table_prefix=123) + + with assert_raises(TypeError): + Connection(autoconnect=False, table_prefix_separator=2.1) + + +def test_stringify(): + str(connection) + repr(connection) + str(table) + repr(table) + + +def test_table_listing(): + names = connection.tables() + assert_is_instance(names, list) + assert_in(TEST_TABLE_NAME, names) + + +def test_table_regions(): + regions = table.regions() + assert_is_instance(regions, list) + + +def test_invalid_table_create(): + with assert_raises(ValueError): + connection.create_table('sometable', families={}) + with assert_raises(TypeError): + connection.create_table('sometable', families=0) + with assert_raises(TypeError): + connection.create_table('sometable', families=[]) + + +def test_families(): + families = table.families() + for name, fdesc in six.iteritems(families): + assert_is_instance(name, bytes) + assert_is_instance(fdesc, dict) + assert_in('name', fdesc) + assert_is_instance(fdesc['name'], six.binary_type) + assert_in('max_versions', fdesc) + + +def test_put(): + table.put(b'r1', {b'cf1:c1': b'v1', b'cf1:c2': b'v2', b'cf2:c3': b'v3'}) + table.put(b'r1', {b'cf1:c4': b'v2'}, timestamp=2345678) + table.put(b'r1', {b'cf1:c4': b'v2'}, timestamp=1369168852994) + + +def test_atomic_counters(): + row = b'row-with-counter' + column = 'cf1:counter' + + assert_equal(0, table.counter_get(row, column)) + + assert_equal(10, table.counter_inc(row, column, 10)) + assert_equal(10, table.counter_get(row, column)) + + table.counter_set(row, column, 0) + assert_equal(1, table.counter_inc(row, column)) + assert_equal(4, table.counter_inc(row, column, 3)) + assert_equal(4, table.counter_get(row, column)) + + table.counter_set(row, column, 3) + assert_equal(3, table.counter_get(row, column)) + assert_equal(8, table.counter_inc(row, column, 5)) + assert_equal(6, table.counter_inc(row, column, -2)) + assert_equal(5, table.counter_dec(row, column)) + assert_equal(3, table.counter_dec(row, column, 2)) + assert_equal(10, table.counter_dec(row, column, -7)) + + +def test_batch(): + with assert_raises(TypeError): + table.batch(timestamp='invalid') + + b = table.batch() + b.put(b'row1', {b'cf1:col1': b'value1', + b'cf1:col2': b'value2'}) + b.put(b'row2', {b'cf1:col1': b'value1', + b'cf1:col2': b'value2', + b'cf1:col3': b'value3'}) + b.delete(b'row1', [b'cf1:col4']) + b.delete(b'another-row') + b.send() + + b = table.batch(timestamp=1234567) + b.put(b'row1', {b'cf1:col5': b'value5'}) + b.send() + + with assert_raises(ValueError): + b = table.batch(batch_size=0) + + with assert_raises(TypeError): + b = table.batch(transaction=True, batch_size=10) + + +def test_batch_context_managers(): + with table.batch() as b: + b.put(b'row4', {b'cf1:col3': b'value3'}) + b.put(b'row5', {b'cf1:col4': b'value4'}) + b.put(b'row', {b'cf1:col1': b'value1'}) + b.delete(b'row', [b'cf1:col4']) + b.put(b'row', {b'cf1:col2': b'value2'}) + + with table.batch(timestamp=87654321) as b: + b.put(b'row', {b'cf1:c3': b'somevalue', + b'cf1:c5': b'anothervalue'}) + b.delete(b'row', [b'cf1:c3']) + + with assert_raises(ValueError): + with table.batch(transaction=True) as b: + b.put(b'fooz', {b'cf1:bar': b'baz'}) + raise ValueError + assert_dict_equal({}, table.row(b'fooz', [b'cf1:bar'])) + + with assert_raises(ValueError): + with table.batch(transaction=False) as b: + b.put(b'fooz', {b'cf1:bar': b'baz'}) + raise ValueError + assert_dict_equal({b'cf1:bar': b'baz'}, table.row(b'fooz', [b'cf1:bar'])) + + with table.batch(batch_size=5) as b: + for i in range(10): + b.put(('row-batch1-%03d' % i).encode('ascii'), + {b'cf1:': str(i).encode('ascii')}) + + with table.batch(batch_size=20) as b: + for i in range(95): + b.put(('row-batch2-%03d' % i).encode('ascii'), + {b'cf1:': str(i).encode('ascii')}) + assert_equal(95, len(list(table.scan(row_prefix=b'row-batch2-')))) + + with table.batch(batch_size=20) as b: + for i in range(95): + b.delete(('row-batch2-%03d' % i).encode('ascii')) + assert_equal(0, len(list(table.scan(row_prefix=b'row-batch2-')))) + + +def test_row(): + row = table.row + put = table.put + row_key = b'row-test' + + with assert_raises(TypeError): + row(row_key, 123) + + with assert_raises(TypeError): + row(row_key, timestamp='invalid') + + put(row_key, {b'cf1:col1': b'v1old'}, timestamp=1234) + put(row_key, {b'cf1:col1': b'v1new'}, timestamp=3456) + put(row_key, {b'cf1:col2': b'v2', + b'cf2:col1': b'v3'}) + put(row_key, {b'cf2:col2': b'v4'}, timestamp=1234) + + exp = {b'cf1:col1': b'v1new', + b'cf1:col2': b'v2', + b'cf2:col1': b'v3', + b'cf2:col2': b'v4'} + assert_dict_equal(exp, row(row_key)) + + exp = {b'cf1:col1': b'v1new', + b'cf1:col2': b'v2'} + assert_dict_equal(exp, row(row_key, [b'cf1'])) + + exp = {b'cf1:col1': b'v1new', + b'cf2:col2': b'v4'} + assert_dict_equal(exp, row(row_key, [b'cf1:col1', b'cf2:col2'])) + + exp = {b'cf1:col1': b'v1old', + b'cf2:col2': b'v4'} + assert_dict_equal(exp, row(row_key, timestamp=2345)) + + assert_dict_equal({}, row(row_key, timestamp=123)) + + res = row(row_key, include_timestamp=True) + assert_equal(len(res), 4) + assert_equal(b'v1new', res[b'cf1:col1'][0]) + assert_is_instance(res[b'cf1:col1'][1], int) + + +def test_rows(): + row_keys = [b'rows-row1', b'rows-row2', b'rows-row3'] + data_old = {b'cf1:col1': b'v1old', b'cf1:col2': b'v2old'} + data_new = {b'cf1:col1': b'v1new', b'cf1:col2': b'v2new'} + + with assert_raises(TypeError): + table.rows(row_keys, object()) + + with assert_raises(TypeError): + table.rows(row_keys, timestamp='invalid') + + for row_key in row_keys: + table.put(row_key, data_old, timestamp=4000) + + for row_key in row_keys: + table.put(row_key, data_new) + + assert_dict_equal({}, table.rows([])) + + rows = dict(table.rows(row_keys)) + for row_key in row_keys: + assert_in(row_key, rows) + assert_dict_equal(data_new, rows[row_key]) + + rows = dict(table.rows(row_keys, timestamp=5000)) + for row_key in row_keys: + assert_in(row_key, rows) + assert_dict_equal(data_old, rows[row_key]) + + +def test_cells(): + row_key = b'cell-test' + col = b'cf1:col1' + + table.put(row_key, {col: b'old'}, timestamp=1234) + table.put(row_key, {col: b'new'}) + + with assert_raises(TypeError): + table.cells(row_key, col, versions='invalid') + + with assert_raises(TypeError): + table.cells(row_key, col, versions=3, timestamp='invalid') + + with assert_raises(ValueError): + table.cells(row_key, col, versions=0) + + results = table.cells(row_key, col, versions=1) + assert_equal(len(results), 1) + assert_equal(b'new', results[0]) + + results = table.cells(row_key, col) + assert_equal(len(results), 2) + assert_equal(b'new', results[0]) + assert_equal(b'old', results[1]) + + results = table.cells(row_key, col, timestamp=2345, include_timestamp=True) + assert_equal(len(results), 1) + assert_equal(b'old', results[0][0]) + assert_equal(1234, results[0][1]) + + +def test_scan(): + with assert_raises(TypeError): + list(table.scan(row_prefix='foobar', row_start='xyz')) + + if connection.compat == '0.90': + with assert_raises(NotImplementedError): + list(table.scan(filter='foo')) + + with assert_raises(ValueError): + list(table.scan(limit=0)) + + with table.batch() as b: + for i in range(2000): + b.put(('row-scan-a%05d' % i).encode('ascii'), + {b'cf1:col1': b'v1', + b'cf1:col2': b'v2', + b'cf2:col1': b'v1', + b'cf2:col2': b'v2'}) + b.put(('row-scan-b%05d' % i).encode('ascii'), + {b'cf1:col1': b'v1', + b'cf1:col2': b'v2'}) + + def calc_len(scanner): + d = collections.deque(maxlen=1) + d.extend(enumerate(scanner, 1)) + if d: + return d[0][0] + return 0 + + scanner = table.scan(row_start=b'row-scan-a00012', + row_stop=b'row-scan-a00022') + assert_equal(10, calc_len(scanner)) + + scanner = table.scan(row_start=b'xyz') + assert_equal(0, calc_len(scanner)) + + scanner = table.scan(row_start=b'xyz', row_stop=b'zyx') + assert_equal(0, calc_len(scanner)) + + scanner = table.scan(row_start=b'row-scan-', row_stop=b'row-scan-a999', + columns=[b'cf1:col1', b'cf2:col2']) + row_key, row = next(scanner) + assert_equal(row_key, b'row-scan-a00000') + assert_dict_equal(row, {b'cf1:col1': b'v1', + b'cf2:col2': b'v2'}) + assert_equal(2000 - 1, calc_len(scanner)) + + scanner = table.scan(row_prefix=b'row-scan-a', batch_size=499, limit=1000) + assert_equal(1000, calc_len(scanner)) + + scanner = table.scan(row_prefix=b'row-scan-b', batch_size=1, limit=10) + assert_equal(10, calc_len(scanner)) + + scanner = table.scan(row_prefix=b'row-scan-b', batch_size=5, limit=10) + assert_equal(10, calc_len(scanner)) + + scanner = table.scan(timestamp=123) + assert_equal(0, calc_len(scanner)) + + scanner = table.scan(row_prefix=b'row', timestamp=123) + assert_equal(0, calc_len(scanner)) + + scanner = table.scan(batch_size=20) + next(scanner) + next(scanner) + scanner.close() + with assert_raises(StopIteration): + next(scanner) + + +def test_scan_sorting(): + if connection.compat < '0.96': + return # not supported + + input_row = {} + for i in range(100): + input_row[('cf1:col-%03d' % i).encode('ascii')] = b'' + input_key = b'row-scan-sorted' + table.put(input_key, input_row) + + scan = table.scan(row_start=input_key, sorted_columns=True) + key, row = next(scan) + assert_equal(key, input_key) + assert_list_equal( + sorted(input_row.items()), + list(row.items())) + + +def test_scan_reverse(): + + if connection.compat < '0.98': + with assert_raises(NotImplementedError): + list(table.scan(reverse=True)) + return + + with table.batch() as b: + for i in range(2000): + b.put(('row-scan-reverse-%04d' % i).encode('ascii'), + {b'cf1:col1': b'v1', + b'cf1:col2': b'v2'}) + + scan = table.scan(row_prefix=b'row-scan-reverse', reverse=True) + assert_equal(2000, len(list(scan))) + + scan = table.scan(limit=10, reverse=True) + assert_equal(10, len(list(scan))) + + scan = table.scan(row_start=b'row-scan-reverse-1999', + row_stop=b'row-scan-reverse-0000', reverse=True) + key, data = next(scan) + assert_equal(b'row-scan-reverse-1999', key) + + key, data = list(scan)[-1] + assert_equal(b'row-scan-reverse-0001', key) + + +def test_scan_filter_and_batch_size(): + # See issue #54 and #56 + filter = b"SingleColumnValueFilter ('cf1', 'qual1', =, 'binary:val1')" + for k, v in table.scan(filter=filter): + print(v) + + +def test_delete(): + row_key = b'row-test-delete' + data = {b'cf1:col1': b'v1', + b'cf1:col2': b'v2', + b'cf1:col3': b'v3'} + table.put(row_key, {b'cf1:col2': b'v2old'}, timestamp=1234) + table.put(row_key, data) + + table.delete(row_key, [b'cf1:col2'], timestamp=2345) + assert_equal(1, len(table.cells(row_key, b'cf1:col2', versions=2))) + assert_dict_equal(data, table.row(row_key)) + + table.delete(row_key, [b'cf1:col1']) + res = table.row(row_key) + assert_not_in(b'cf1:col1', res) + assert_in(b'cf1:col2', res) + assert_in(b'cf1:col3', res) + + table.delete(row_key, timestamp=12345) + res = table.row(row_key) + assert_in(b'cf1:col2', res) + assert_in(b'cf1:col3', res) + + table.delete(row_key) + assert_dict_equal({}, table.row(row_key)) + + +def test_connection_pool_construction(): + with assert_raises(TypeError): + ConnectionPool(size='abc') + + with assert_raises(ValueError): + ConnectionPool(size=0) + + +def test_connection_pool(): + + from thriftpy2.thrift import TException + + def run(): + name = threading.current_thread().name + print("Thread %s starting" % name) + + def inner_function(): + # Nested connection requests must return the same connection + with pool.connection() as another_connection: + assert connection is another_connection + + # Fake an exception once in a while + if random.random() < .25: + print("Introducing random failure") + connection.transport.close() + raise TException("Fake transport exception") + + for i in range(50): + with pool.connection() as connection: + connection.tables() + + try: + inner_function() + except TException: + # This error should have been picked up by the + # connection pool, and the connection should have + # been replaced by a fresh one + pass + + connection.tables() + + print("Thread %s done" % name) + + N_THREADS = 10 + + pool = ConnectionPool(size=3, **connection_kwargs) + threads = [threading.Thread(target=run) for i in range(N_THREADS)] + + for t in threads: + t.start() + + while threads: + for t in threads: + t.join(timeout=.1) + + # filter out finished threads + threads = [t for t in threads if t.is_alive()] + print("%d threads still alive" % len(threads)) + + +def test_pool_exhaustion(): + pool = ConnectionPool(size=1, **connection_kwargs) + + def run(): + with assert_raises(NoConnectionsAvailable): + with pool.connection(timeout=.1) as connection: + connection.tables() + + with pool.connection(): + # At this point the only connection is assigned to this thread, + # so another thread cannot obtain a connection at this point. + + t = threading.Thread(target=run) + t.start() + t.join() + + +if __name__ == '__main__': + import logging + import sys + + # Dump stacktraces using 'kill -USR1', useful for debugging hanging + # programs and multi threading issues. + try: + import faulthandler + except ImportError: + pass + else: + import signal + faulthandler.register(signal.SIGUSR1) + + logging.basicConfig(level=logging.DEBUG) + + method_name = 'test_%s' % sys.argv[1] + method = globals()[method_name] + method() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/tests/test_util.py new/happybase-1.2.0/tests/test_util.py --- old/happybase-1.1.0/tests/test_util.py 1970-01-01 01:00:00.000000000 +0100 +++ new/happybase-1.2.0/tests/test_util.py 2019-05-14 16:16:06.000000000 +0200 @@ -0,0 +1,56 @@ +""" +HappyBase utility tests. +""" + +from codecs import decode, encode + +from nose.tools import assert_equal, assert_less + +import happybase.util as util + + +def test_camel_case_to_pep8(): + def check(lower_cc, upper_cc, correct): + + x1 = util.camel_case_to_pep8(lower_cc) + x2 = util.camel_case_to_pep8(upper_cc) + assert_equal(correct, x1) + assert_equal(correct, x2) + + y1 = util.pep8_to_camel_case(x1, True) + y2 = util.pep8_to_camel_case(x2, False) + assert_equal(upper_cc, y1) + assert_equal(lower_cc, y2) + + examples = [('foo', 'Foo', 'foo'), + ('fooBar', 'FooBar', 'foo_bar'), + ('fooBarBaz', 'FooBarBaz', 'foo_bar_baz'), + ('fOO', 'FOO', 'f_o_o')] + + for a, b, c in examples: + yield check, a, b, c + + +def test_bytes_increment(): + def check(s_hex, expected): + s = decode(s_hex, 'hex') + v = util.bytes_increment(s) + v_hex = encode(v, 'hex') + assert_equal(expected, v_hex) + assert_less(s, v) + + test_values = [ + (b'00', b'01'), + (b'01', b'02'), + (b'fe', b'ff'), + (b'1234', b'1235'), + (b'12fe', b'12ff'), + (b'12ff', b'13'), + (b'424242ff', b'424243'), + (b'4242ffff', b'4243'), + ] + + assert util.bytes_increment(b'\xff\xff\xff') is None + + for s, expected in test_values: + yield check, s, expected diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/happybase-1.1.0/tox.ini new/happybase-1.2.0/tox.ini --- old/happybase-1.1.0/tox.ini 1970-01-01 01:00:00.000000000 +0100 +++ new/happybase-1.2.0/tox.ini 2019-05-14 16:16:06.000000000 +0200 @@ -0,0 +1,8 @@ +[tox] +envlist = py27,py34,py35 + +[testenv] +deps= + nose + coverage +commands=nosetests