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


Reply via email to