[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#862734: unblock: python-vertica/0.7.1-1



Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Please unblock package python-vertica

Dear release team,

python-vertica version 0.7.1 has just been released.
Amongst other changes, it fixes a bug about named parameters support with python3.
Bug details can be found on [1]

All tests now pass for both python 2.7 & 3.5, though I had to explicitly disable them in the package because they require a running Vertica backend.

[1]: https://github.com/uber/vertica-python/issues/112

diff -Nru python-vertica-0.6.8/debian/changelog python-vertica-0.7.1/debian/changelog
- --- python-vertica-0.6.8/debian/changelog	2016-10-24 11:40:15.000000000 +0200
+++ python-vertica-0.7.1/debian/changelog	2017-05-16 09:33:39.000000000 +0200
@@ -1,3 +1,27 @@
+python-vertica (0.7.1-1) unstable; urgency=medium
+
+  * Import python-vertica_0.7.1.orig.tar.gz
+
+ -- Jean Baptiste Favre <debian@jbfavre.org>  Tue, 16 May 2017 09:33:39 +0200
+
+python-vertica (0.6.14-1) unstable; urgency=medium
+
+  * Import python-vertica_0.6.14.orig.tar.gz
+
+ -- Jean Baptiste Favre <debian@jbfavre.org>  Sat, 25 Mar 2017 14:47:34 +0100
+
+python-vertica (0.6.13-1) unstable; urgency=medium
+
+  * Import python-vertica_0.6.13.orig.tar.gz
+
+ -- Jean Baptiste Favre <debian@jbfavre.org>  Sun, 05 Mar 2017 12:35:21 +0100
+
+python-vertica (0.6.12-1) unstable; urgency=medium
+
+  * Import python-vertica_0.6.12.orig.tar.gz
+
+ -- Jean Baptiste Favre <debian@jbfavre.org>  Mon, 13 Feb 2017 09:59:57 +0100
+
 python-vertica (0.6.8-2) unstable; urgency=medium
 
   * Update compat version
diff -Nru python-vertica-0.6.8/debian/.git-dpm python-vertica-0.7.1/debian/.git-dpm
- --- python-vertica-0.6.8/debian/.git-dpm	2016-10-24 11:37:58.000000000 +0200
+++ python-vertica-0.7.1/debian/.git-dpm	2017-05-16 09:32:26.000000000 +0200
@@ -1,11 +1,11 @@
 # see git-dpm(1) from git-dpm package
- -9058172d19195ba5de74a6ad69d64d07c2629682
- -9058172d19195ba5de74a6ad69d64d07c2629682
- -9058172d19195ba5de74a6ad69d64d07c2629682
- -9058172d19195ba5de74a6ad69d64d07c2629682
- -python-vertica_0.6.8.orig.tar.gz
- -ff6d554fc104801836b55d9291194ff3b8e61c35
- -24542
+8a1cd3b55ec2c7acd314c271aacd37f880bad859
+8a1cd3b55ec2c7acd314c271aacd37f880bad859
+8a1cd3b55ec2c7acd314c271aacd37f880bad859
+8a1cd3b55ec2c7acd314c271aacd37f880bad859
+python-vertica_0.7.1.orig.tar.gz
+9020980df05fe56565f2c67856707d5d1fd0a55c
+28533
 debianTag="debian/%e%v"
 patchedTag="patched/%e%v"
 upstreamTag="upstream/%e%u"
diff -Nru python-vertica-0.6.8/debian/rules python-vertica-0.7.1/debian/rules
- --- python-vertica-0.6.8/debian/rules	2016-10-24 11:37:58.000000000 +0200
+++ python-vertica-0.7.1/debian/rules	2017-05-16 09:33:39.000000000 +0200
@@ -6,3 +6,5 @@
 
 %:
 	dh $@ --with python2,python3 --buildsystem=pybuild
+
+override_dh_auto_test:
diff -Nru python-vertica-0.6.8/.gitignore python-vertica-0.7.1/.gitignore
- --- python-vertica-0.6.8/.gitignore	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/.gitignore	2017-05-14 00:40:53.000000000 +0200
@@ -40,7 +40,7 @@
 .vagrant
 
 # pycharm
- -/.idea/
+.idea
 
 # default virtual environment
 /env/
diff -Nru python-vertica-0.6.8/README.md python-vertica-0.7.1/README.md
- --- python-vertica-0.6.8/README.md	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/README.md	2017-05-14 00:40:53.000000000 +0200
@@ -24,6 +24,10 @@
 
     pip install --pre pytz
 
+If you're using pip >= 1.4 and you don't already have python-dateutil installed:
+
+    pip install --pre python-dateutil
+
 To install vertica-python with pip:
 
     pip install vertica-python
@@ -40,20 +44,24 @@
 
 ## Run unit tests
 
- -To run the tests, you must have access to a Vertica database. You can
- -spin one up with Vagrant that uses the default credentials using
- -`vagrant up`. If you want to run it against an existing database
- -instead; you can set the environment variables seen in
- -`tests/test_commons.py`.
+To run the tests, you must have access to a Vertica database. Heres one way to go about it:
+
+Download docker kitematic:
+https://kitematic.com/
+
+Spin up a vertica container (i use sumitchawla/vertica)
 
- -Assuming you have [tox](http://tox.readthedocs.io/) installed, all you
- -have to do is run `tox`. It will run the unit tests using both python 2 and 3.
+Edit the port number in `tests/test_commons.py` to match the container.
 
- -If you run into an error like:
- -```ERROR: InterpreterNotFound: python3.4```
+Install tox:
+http://tox.readthedocs.io
 
- -Edit the envlist property of tox.ini to use the version of python you have installed (eg py35)
+Edit `tox.ini` envlist property to list the version(s) of python you have installed
 
+Run tox:
+```bash
+tox
+```
 
 ## Usage
 
@@ -73,7 +81,9 @@
              # default throw error on invalid UTF-8 results
              'unicode_error': 'strict',
              # SSL is disabled by default
- -             'ssl': False}
+             'ssl': False,
+             'connection_timeout': 5
+             # connection timeout is not enabled by default}
 
 # simple connection, with manual close
 connection = vertica_python.connect(**conn_info)
diff -Nru python-vertica-0.6.8/setup.py python-vertica-0.7.1/setup.py
- --- python-vertica-0.6.8/setup.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/setup.py	2017-05-14 00:40:53.000000000 +0200
@@ -10,7 +10,7 @@
 # version should use the format 'x.x.x' (instead of 'vx.x.x')
 setup(
     name='vertica-python',
- -    version='0.6.8',
+    version='0.7.1',
     description='A native Python client for the Vertica database.',
     author='Justin Berka, Alex Kim',
     author_email='justin.berka@gmail.com, alex.kim@uber.com',
diff -Nru python-vertica-0.6.8/tox.ini python-vertica-0.7.1/tox.ini
- --- python-vertica-0.6.8/tox.ini	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/tox.ini	2017-05-14 00:40:53.000000000 +0200
@@ -1,7 +1,8 @@
 [tox]
- -envlist = py27,py34
+envlist = py27,py34,py35,py36
 
 [testenv]
+passenv = *
 commands =
 	nosetests
 deps =
diff -Nru python-vertica-0.6.8/vertica_python/compat.py python-vertica-0.7.1/vertica_python/compat.py
- --- python-vertica-0.6.8/vertica_python/compat.py	1970-01-01 01:00:00.000000000 +0100
+++ python-vertica-0.7.1/vertica_python/compat.py	2017-05-14 00:40:53.000000000 +0200
@@ -0,0 +1,100 @@
+# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+
+"""Functions for Python 2 vs. 3 compatibility.
+## Conversion routines
+In addition to the functions below, `as_str` converts an object to a `str`.
+@@as_bytes
+@@as_text
+@@as_str_any
+## Types
+The compatibility module also provides the following types:
+* `bytes_or_text_types`
+* `complex_types`
+* `integral_types`
+* `real_types`
+"""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import six as _six
+
+
+def as_bytes(bytes_or_text, encoding='utf-8'):
+    """Converts either bytes or unicode to `bytes`, using utf-8 encoding for text.
+    Args:
+      bytes_or_text: A `bytes`, `str`, or `unicode` object.
+      encoding: A string indicating the charset for encoding unicode.
+    Returns:
+      A `bytes` object.
+    Raises:
+      TypeError: If `bytes_or_text` is not a binary or unicode string.
+    """
+    if isinstance(bytes_or_text, _six.text_type):
+        return bytes_or_text.encode(encoding)
+    elif isinstance(bytes_or_text, bytes):
+        return bytes_or_text
+    else:
+        raise TypeError('Expected binary or unicode string, got %r' %
+                        (bytes_or_text,))
+
+
+def as_text(bytes_or_text, encoding='utf-8'):
+    """Returns the given argument as a unicode string.
+    Args:
+      bytes_or_text: A `bytes`, `str, or `unicode` object.
+      encoding: A string indicating the charset for decoding unicode.
+    Returns:
+      A `unicode` (Python 2) or `str` (Python 3) object.
+    Raises:
+      TypeError: If `bytes_or_text` is not a binary or unicode string.
+    """
+    if isinstance(bytes_or_text, _six.text_type):
+        return bytes_or_text
+    elif isinstance(bytes_or_text, bytes):
+        return bytes_or_text.decode(encoding)
+    else:
+        raise TypeError('Expected binary or unicode string, got %r' % bytes_or_text)
+
+
+# Convert an object to a `str` in both Python 2 and 3.
+if _six.PY2:
+    as_str = as_bytes
+else:
+    as_str = as_text
+
+
+def as_str_any(value):
+    """Converts to `str` as `str(value)`, but use `as_str` for `bytes`.
+    Args:
+      value: A object that can be converted to `str`.
+    Returns:
+      A `str` object.
+    """
+    if isinstance(value, bytes):
+        return as_str(value)
+    else:
+        return str(value)
+
+
+# Either bytes or text.
+bytes_or_text_types = (bytes, _six.text_type)
+
+_allowed_symbols = [
+    'as_str',
+    'bytes_or_text_types',
+]
diff -Nru python-vertica-0.6.8/vertica_python/datatypes.py python-vertica-0.7.1/vertica_python/datatypes.py
- --- python-vertica-0.6.8/vertica_python/datatypes.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/datatypes.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,33 +1,38 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -from datetime import datetime
- -from datetime import timedelta
+from datetime import date, datetime, time
 
 
+# noinspection PyPep8Naming
 def Date(year, month, day):
- -    return datetime.date(year, month, day)
+    return date(year, month, day)
 
 
+# noinspection PyPep8Naming
 def Time(hour, minute, second):
- -    return datetime.time(hour, minute, second)
+    return time(hour, minute, second)
 
 
+# noinspection PyPep8Naming
 def Timestamp(year, month, day, hour, minute, second):
- -    return datetime.datetime(year, month, day, hour, minute, second)
+    return datetime(year, month, day, hour, minute, second)
 
 
+# noinspection PyPep8Naming
 def DateFromTicks(ticks):
- -    d = datetime(1970, 1, 1) + timedelta(seconds=ticks)
+    d = datetime.utcfromtimestamp(ticks)
     return d.date()
 
 
+# noinspection PyPep8Naming
 def TimeFromTicks(ticks):
- -    d = datetime(1970, 1, 1) + timedelta(seconds=ticks)
+    d = datetime.utcfromtimestamp(ticks)
     return d.time()
 
 
+# noinspection PyPep8Naming
 def TimestampFromTicks(ticks):
- -    d = datetime(1970, 1, 1) + timedelta(seconds=ticks)
+    d = datetime.utcfromtimestamp(ticks)
     return d.time()
 
 
@@ -35,9 +40,11 @@
     pass
 
 
+# noinspection PyPep8Naming
 def Binary(string):
     return Bytea(string)
 
+
 # vertica doesnt have a binary or row_id type i think
 STRING = 9
 BINARY = 10000
diff -Nru python-vertica-0.6.8/vertica_python/errors.py python-vertica-0.7.1/vertica_python/errors.py
- --- python-vertica-0.6.8/vertica_python/errors.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/errors.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,15 +1,16 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -import sys
- -if sys.version_info < (3,):
- -    import exceptions
 import re
 
 
+#############################################
+# dbapi errors
+#############################################
 class Error(Exception):
     pass
 
 
+# noinspection PyShadowingBuiltins
 class Warning(Exception):
     pass
 
@@ -75,9 +76,9 @@
     def __init__(self, error_response, sql):
         self.error_response = error_response
         self.sql = sql
- -        super(QueryError, self).__init__("{0}, SQL: {1}".format(
- -            error_response.error_message(), repr(self.one_line_sql()))
- -        )
+        ProgrammingError.__init__(self,
+                                  "{0}, SQL: {1}".format(error_response.error_message(),
+                                                         repr(self.one_line_sql())))
 
     def one_line_sql(self):
         if self.sql:
@@ -137,6 +138,14 @@
     pass
 
 
+class QueryCanceled(QueryError):
+    pass
+
+
+class ConnectionFailure(QueryError):
+    pass
+
+
 QUERY_ERROR_CLASSES = {
     b'55V03': LockFailure,
     b'53000': InsufficientResources,
@@ -148,5 +157,7 @@
     b'22V04': CopyRejected,
     b'42501': PermissionDenied,
     b'22007': InvalidDatetimeFormat,
- -    b'42710': DuplicateObject
+    b'42710': DuplicateObject,
+    b'57014': QueryCanceled,
+    b'08006': ConnectionFailure
 }
diff -Nru python-vertica-0.6.8/vertica_python/__init__.py python-vertica-0.7.1/vertica_python/__init__.py
- --- python-vertica-0.6.8/vertica_python/__init__.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/__init__.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,30 +1,32 @@
+from __future__ import print_function, division, absolute_import
 
+from .vertica.connection import Connection, connect
 
- -from vertica_python.vertica.connection import Connection
- -
+# Importing exceptions for compatibility with dbapi 2.0.
+# See: PEP 249 - Python Database API 2.0
+#      https://www.python.org/dev/peps/pep-0249/#exceptions
+from . import errors
+from .errors import (
+    Error, Warning, DataError, DatabaseError, IntegrityError, InterfaceError,
+    InternalError, NotSupportedError, OperationalError, ProgrammingError)
 
 # Main module for this library.
- -
- -# The version number of this library.
- -version_info = (0, 6, 8)
- -
- -__version__ = '.'.join(map(str, version_info))
- -
 __author__ = 'Uber Technologies, Inc'
 __copyright__ = 'Copyright 2013, Uber Technologies, Inc.'
 __license__ = 'MIT'
 
+__all__ = ['Connection', 'PROTOCOL_VERSION', 'version_info', 'apilevel', 'threadsafety',
+           'paramstyle', 'connect', 'Error', 'Warning', 'DataError', 'DatabaseError',
+           'IntegrityError', 'InterfaceError', 'InternalError', 'NotSupportedError',
+           'OperationalError', 'ProgrammingError']
+
+# The version number of this library.
+version_info = (0, 7, 1)
+__version__ = '.'.join(map(str, version_info))
+
 # The protocol version (3.0.0) implemented in this library.
 PROTOCOL_VERSION = 3 << 16
 
- -
 apilevel = 2.0
- -
- -# Threads may share the module, but not connections!
- -threadsafety = 1
+threadsafety = 1  # Threads may share the module, but not connections!
 paramstyle = 'named'  # WHERE name=:name
- -
- -
- -def connect(**kwargs):
- -    """Opens a new connection to a Vertica database."""
- -    return Connection(kwargs)
diff -Nru python-vertica-0.6.8/vertica_python/tests/base.py python-vertica-0.7.1/vertica_python/tests/base.py
- --- python-vertica-0.6.8/vertica_python/tests/base.py	1970-01-01 01:00:00.000000000 +0100
+++ python-vertica-0.7.1/vertica_python/tests/base.py	2017-05-14 00:40:53.000000000 +0200
@@ -0,0 +1,103 @@
+from __future__ import print_function, division, absolute_import
+
+import os
+import unittest
+
+from six import string_types
+
+from .. import *
+from ..compat import as_text, as_str, as_bytes
+
+DEFAULT_VP_TEST_HOST = '127.0.0.1'
+DEFAULT_VP_TEST_PORT = 5433
+DEFAULT_VP_TEST_USER = 'dbadmin'
+DEFAULT_VP_TEST_PASSWD = ''
+DEFAULT_VP_TEST_DB = 'docker'
+DEFAULT_VP_TEST_TABLE = 'vertica_python_unit_test'
+
+
+class VerticaPythonTestCase(unittest.TestCase):
+    """Base class for tests that query Vertica."""
+
+    @classmethod
+    def setUpClass(cls):
+        cls._host = os.getenv('VP_TEST_HOST', DEFAULT_VP_TEST_HOST)
+        cls._port = int(os.getenv('VP_TEST_PORT', DEFAULT_VP_TEST_PORT))
+        cls._user = os.getenv('VP_TEST_USER', DEFAULT_VP_TEST_USER)
+        cls._password = os.getenv('VP_TEST_PASSWD', DEFAULT_VP_TEST_PASSWD)
+        cls._database = os.getenv('VP_TEST_DB', DEFAULT_VP_TEST_DB)
+        cls._table = os.getenv('VP_TEST_TABLE', DEFAULT_VP_TEST_TABLE)
+
+        cls._conn_info = {
+            'host': cls._host,
+            'port': cls._port,
+            'database': cls._database,
+            'user': cls._user,
+            'password': cls._password,
+        }
+
+    @classmethod
+    def tearDownClass(cls):
+        with cls._connect() as conn:
+            cur = conn.cursor()
+            cur.execute("DROP TABLE IF EXISTS {0}".format(cls._table))
+
+    @classmethod
+    def _connect(cls):
+        """Connects to vertica.
+        
+        :return: a connection to vertica.
+        """
+        return connect(**cls._conn_info)
+
+    def _query_and_fetchall(self, query):
+        """Creates a new connection, executes a query and fetches all the results.
+        
+        :param query: query to execute
+        :return: all fetched results as returned by cursor.fetchall()
+        """
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute(query)
+            results = cur.fetchall()
+
+        return results
+
+    def _query_and_fetchone(self, query):
+        """Creates a new connection, executes a query and fetches one result.
+        
+        :param query: query to execute
+        :return: the first result fetched by cursor.fetchone()
+        """
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute(query)
+            result = cur.fetchone()
+
+        return result
+
+    def assertTextEqual(self, first, second, msg=None):
+        first_text = as_text(first)
+        second_text = as_text(second)
+        self.assertEqual(first=first_text, second=second_text, msg=msg)
+
+    def assertStrEqual(self, first, second, msg=None):
+        first_str = as_str(first)
+        second_str = as_str(second)
+        self.assertEqual(first=first_str, second=second_str, msg=msg)
+
+    def assertBytesEqual(self, first, second, msg=None):
+        first_bytes = as_bytes(first)
+        second_bytes = as_bytes(second)
+        self.assertEqual(first=first_bytes, second=second_bytes, msg=msg)
+
+    def assertResultEqual(self, value, result, msg=None):
+        if isinstance(value, string_types):
+            self.assertTextEqual(first=value, second=result, msg=msg)
+        else:
+            self.assertEqual(first=value, second=result, msg=msg)
+
+    def assertListOfListsEqual(self, list1, list2, msg=None):
+        self.assertEqual(len(list1), len(list2), msg=msg)
+        for l1, l2 in zip(list1, list2):
+            self.assertListEqual(l1, l2, msg=msg)
diff -Nru python-vertica-0.6.8/vertica_python/tests/basic_tests.py python-vertica-0.7.1/vertica_python/tests/basic_tests.py
- --- python-vertica-0.6.8/vertica_python/tests/basic_tests.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/tests/basic_tests.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,411 +0,0 @@
- -import unittest
- -import logging
- -import tempfile
- -
- -from .test_commons import conn_info
- -
- -import vertica_python
- -from vertica_python import errors
- -
- -logger = logging.getLogger('vertica')
- -
- -
- -def init_table(cur):
- -    # clean old table
- -    cur.execute('DROP TABLE IF EXISTS vertica_python_unit_test;')
- -    
- -    # create test table
- -    cur.execute("""CREATE TABLE vertica_python_unit_test (
- -                    a int,
- -                    b varchar(32)
- -                    ) ;
- -                """)
- -
- -
- -class TestVerticaPython(unittest.TestCase):
- -
- -    def test_inline_commit(self):
- -
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -        
- -        cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (1, 'aa'); commit; """)
- -        cur.execute("SELECT a, b from vertica_python_unit_test WHERE a = 1")
- -
- -        # unknown rowcount
- -        assert cur.rowcount == -1
- -
- -        res = cur.fetchall()
- -        assert 1 == len(res)
- -        assert 1 == res[0][0]
- -        assert 'aa' == res[0][1]
- -        assert cur.rowcount == 1
- -
- -    def test_multi_inserts_and_transaction(self):
- -
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -    
- -        conn2 = vertica_python.connect(**conn_info)
- -        cur2 = conn2.cursor()
- -    
- -        # insert data without a commit
- -        cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (2, 'bb') """)
- -    
- -        # verify we can see it from this cursor
- -        cur.execute("SELECT a, b from vertica_python_unit_test WHERE a = 2")
- -        res = cur.fetchall()
- -        assert 1 == len(res)
- -        assert 2 == res[0][0]
- -        assert 'bb' == res[0][1]
- -        
- -        # verify we cant see it from other cursor
- -        cur2.execute("SELECT a, b from vertica_python_unit_test WHERE a = 2")
- -        res = cur2.fetchall()
- -        assert 0 == len(res)
- -    
- -        # insert more data then commit
- -        cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (3, 'cc') """)
- -        cur.execute(""" commit; """)
- -        
- -        # verify we can see it from this cursor
- -        cur.execute("SELECT a, b from vertica_python_unit_test WHERE a = 2 or a = 3")
- -        res = cur.fetchall()
- -        assert 2 == len(res)
- -    
- -        # verify we can see it from other cursor
- -        cur2.execute("SELECT a, b from vertica_python_unit_test WHERE a = 2 or a = 3")
- -        res = cur2.fetchall()
- -        assert 2 == len(res)
- -
- -    def test_conn_commit(self):
- -
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -    
- -        cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (5, 'cc') """)
- -        conn.commit()
- -        cur.execute("SELECT a, b from vertica_python_unit_test WHERE a = 5")
- -        res = cur.fetchall()
- -        assert 1 == len(res)
- -
- -
- -    def test_delete(self):
- -
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -
- -        cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (5, 'cc') """)
- -        conn.commit()
- -
- -        cur.execute(""" DELETE from vertica_python_unit_test WHERE a = 5 """)
- -
- -        # validate delete count
- -        assert cur.rowcount == -1
- -        res = cur.fetchone()
- -        assert 1 == len(res)
- -        assert 1 == res[0]
- -
- -        conn.commit()
- -
- -        cur.execute("SELECT a, b from vertica_python_unit_test WHERE a = 5")
- -        res = cur.fetchall()
- -        assert 0 == len(res)
- -
- -
- -    def test_update(self):
- -
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -    
- -        cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (5, 'cc') """)
- -
- -        # validate insert count
- -        res = cur.fetchone()
- -        assert 1 == len(res)
- -        assert 1 == res[0]
- -
- -        conn.commit()
- -    
- -        cur.execute(""" UPDATE vertica_python_unit_test SET b = 'ff' WHERE a = 5 """)
- -
- -        # validate update count
- -        assert cur.rowcount == -1
- -        res = cur.fetchone()
- -        assert 1 == len(res)
- -        assert 1 == res[0]
- -
- -        conn.commit()
- -    
- -        cur.execute("SELECT a, b from vertica_python_unit_test WHERE a = 5")
- -        res = cur.fetchall()
- -        assert 1 == len(res)
- -        assert 5 == res[0][0]
- -        assert 'ff' == res[0][1]
- -
- -    def test_copy_with_string(self):
- -
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -    
- -        conn2 = vertica_python.connect(**conn_info)
- -        cur2 = conn.cursor()
- -    
- -        cur.copy(""" COPY vertica_python_unit_test (a, b) from stdin DELIMITER ',' """,  "1,foo\n2,bar")
- -        # no commit necessary for copy
- -    
- -        # verify this cursor can see copy data
- -        cur.execute("SELECT a, b from vertica_python_unit_test WHERE a = 1")
- -        res = cur.fetchall()
- -        assert 1 == len(res)
- -        assert 1 == res[0][0]
- -        assert 'foo' == res[0][1]
- -    
- -        # verify other cursor can see copy data
- -        cur2.execute("SELECT a, b from vertica_python_unit_test WHERE a = 2")
- -        res = cur2.fetchall()
- -        assert 1 == len(res)
- -        assert 2 == res[0][0]
- -        assert 'bar' == res[0][1]
- -
- -    def test_copy_with_file(self):
- -
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -    
- -        conn2 = vertica_python.connect(**conn_info)
- -        cur2 = conn.cursor()
- -    
- -        f = tempfile.TemporaryFile()
- -        f.write(b"1,foo\n2,bar")
- -        # move rw pointer to top of file
- -        f.seek(0)
- -        cur.copy(""" COPY vertica_python_unit_test (a, b) from stdin DELIMITER ',' """,  f)
- -        f.close()
- -    
- -        # verify this cursor can see copy data
- -        cur.execute("SELECT a, b from vertica_python_unit_test WHERE a = 1")
- -        res = cur.fetchall()
- -        assert 1 == len(res)
- -        assert 1 == res[0][0]
- -        assert 'foo' == res[0][1]
- -    
- -        # verify other cursor can see copy data
- -        cur2.execute("SELECT a, b from vertica_python_unit_test WHERE a = 2")
- -        res = cur2.fetchall()
- -        assert 1 == len(res)
- -        assert 2 == res[0][0]
- -        assert 'bar' == res[0][1]
- -
- -    def test_with_conn(self):
- -
- -        with vertica_python.connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            init_table(cur)
- -        
- -            cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (1, 'aa'); commit; """)
- -            cur.execute("SELECT a, b from vertica_python_unit_test WHERE a = 1")
- -            res = cur.fetchall()
- -            assert 1 == len(res)
- -
- -    def test_iterator(self):
- -
- -        with vertica_python.connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            init_table(cur)
- -        
- -            cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (1, 'aa') """)
- -            cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (2, 'bb') """)
- -            cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (3, 'cc') """)
- -            conn.commit()
- -        
- -            cur.execute("SELECT a, b from vertica_python_unit_test ORDER BY a ASC")
- -        
- -            i = 0;
- -            for row in cur.iterate():
- -                if i == 0:
- -                    assert 1 == row[0]
- -                    assert 'aa' == row[1]
- -                if i == 1:
- -                    assert 2 == row[0]
- -                    assert 'bb' == row[1]
- -                if i == 2:
- -                    assert 3 == row[0]
- -                    assert 'cc' == row[1]
- -                i = i + 1
- -
- -
- -    def test_mid_iterator_execution(self):
- -
- -        with vertica_python.connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            init_table(cur)
- -        
- -            cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (1, 'aa') """)
- -            cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (2, 'bb') """)
- -            cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (3, 'cc') """)
- -            conn.commit()
- -        
- -            cur.execute("SELECT a, b from vertica_python_unit_test ORDER BY a ASC")
- -        
- -            # don't finish iterating
- -            for row in cur.iterate():
- -                break;
- -
- -            # make new query and verify result
- -            cur.execute(""" SELECT COUNT(*) FROM vertica_python_unit_test """)
- -            res = cur.fetchall()
- -            assert 1 == len(res)
- -            assert 3 == res[0][0]
- -
- -
- -    def test_query_errors(self):
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -        
- -        failed = False;
- -        # create table syntax error
- -        try:
- -            failed = False;
- -            cur.execute("""CREATE TABLE vertica_python_unit_test_fail (
- -                            a int,
- -                            b varchar(32),,,
- -                            ) ;
- -                        """)
- -        except errors.VerticaSyntaxError:
- -            failed = True;
- -        assert True == failed
- -    
- -        # select table not found error
- -        try:
- -            failed = False;
- -            cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (1, 'aa') """)
- -            cur.execute(""" SELECT * from vertica_python_unit_test_fail  """)
- -            #conn.commit()
- -        except errors.QueryError:
- -            failed = True;
- -        assert True == failed
- -
- -        # verify cursor still useable after errors
- -        cur.execute("SELECT a, b from vertica_python_unit_test WHERE a = 1")
- -        res = cur.fetchall()
- -        assert 1 == len(res)
- -        assert 1 == res[0][0]
- -        assert 'aa' == res[0][1]
- -
- -    def test_cursor_close_and_reuse(self):
- -
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -
- -        # insert data
- -        cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (2, 'bb'); commit; """)
- -        #conn.commit()
- -
- -        # query
- -        cur.execute("SELECT a, b from vertica_python_unit_test WHERE a = 2")
- -        res = cur.fetchall()
- -        assert 1 == len(res)
- -
- -        # close and reopen cursor
- -        cur.close()
- -        cur = conn.cursor()
- -
- -        cur.execute("SELECT a, b from vertica_python_unit_test")
- -        res = cur.fetchall()
- -        assert 1 == len(res)
- -
- -    # unit test for #78
- -    def test_copy_with_data_in_buffer(self):
- -
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -
- -        cur.execute("select 1;")
- -        cur.fetchall()
- -
- -        # Current status: CommandComplete
- -
- -        copy_sql = """COPY vertica_python_unit_test (a, b)
- -                     FROM STDIN
- -                     DELIMITER '|'
- -                     NULL AS 'None'"""
- -
- -        data = """1|name1
- -        2|name2"""
- -
- -        cur.copy(copy_sql, data)
- -        cur.execute("select 1;") # will raise QueryError here
- -
- -        conn.close()
- -
- -    # unit test for #74
- -    def test_nextset(self):
- -
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -
- -        cur.execute("select 1; select 2;")
- -        res = cur.fetchall()
- -
- -        assert 1 == len(res)
- -        assert 1 == res[0][0]
- -        assert cur.fetchone() is None
- -
- -        assert cur.nextset() == True
- -
- -        res = cur.fetchall()
- -        assert 1 == len(res)
- -        assert 2 == res[0][0]
- -        assert cur.fetchone() is None
- -
- -        assert cur.nextset() is None
- -
- -    # unit test for #74
- -    def test_nextset_with_delete(self):
- -
- -        conn = vertica_python.connect(**conn_info)
- -        cur = conn.cursor()
- -        init_table(cur)
- -
- -        # insert data
- -        cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (1, 'aa') """)
- -        cur.execute(""" INSERT INTO vertica_python_unit_test (a, b) VALUES (2, 'bb') """)
- -        conn.commit()
- -
- -        cur.execute("""select * from vertica_python_unit_test;
- -                    delete from vertica_python_unit_test;
- -                    select * from vertica_python_unit_test;
- -                    """)
- -
- -        # check first select results
- -        res = cur.fetchall()
- -        assert 2 == len(res)
- -        assert cur.fetchone() is None
- -
- -        # check delete results
- -        assert cur.nextset() == True
- -        res = cur.fetchall()
- -        assert 1 == len(res)
- -        assert 2 == res[0][0]
- -        assert cur.fetchone() is None
- -
- -        # check second select results
- -        assert cur.nextset() == True
- -        res = cur.fetchall()
- -        assert 0 == len(res)
- -        assert cur.fetchone() is None
- -
- -        # no more data sets
- -        assert cur.nextset() is None
diff -Nru python-vertica-0.6.8/vertica_python/tests/column_tests.py python-vertica-0.7.1/vertica_python/tests/column_tests.py
- --- python-vertica-0.6.8/vertica_python/tests/column_tests.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/tests/column_tests.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,21 +0,0 @@
- -from .test_commons import conn_info, VerticaTestCase
- -from .. import connect
- -
- -
- -class ColumnTestCase(VerticaTestCase):
- -    def test_column_names_query(self):
- -        column_0 = 'isocode'
- -        column_1 = 'name'
- -        query = """
- -        select 'US' as {column_0}, 'United States' as {column_1}
- -        union all
- -        select 'CA', 'Canada'
- -        union all
- -        select 'MX', 'Mexico'
- -        """.format(column_0=column_0, column_1=column_1)
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            cur.execute(query)
- -            description = cur.description
- -            assert description[0].name == column_0
- -            assert description[1].name == column_1
diff -Nru python-vertica-0.6.8/vertica_python/tests/date_tests.py python-vertica-0.7.1/vertica_python/tests/date_tests.py
- --- python-vertica-0.6.8/vertica_python/tests/date_tests.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/tests/date_tests.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,73 +0,0 @@
- -from datetime import date, datetime
- -from .test_commons import *
- -from vertica_python import errors
- -from vertica_python.vertica.column import timestamp_parse
- -
- -
- -class DateParsingTestCase(VerticaTestCase):
- -    """
- -    Testing DATE type parsing with focus on AD/BC and lack of support for dates Before Christ.
- -
- -    Note: the 'BC' or 'AD' era indicators in Vertica's date format seem to make Vertica behave as follows:
- -
- -     - Both 'BC' and 'AD' are simply a flags that tell Vertica: include era indicator if the date is Before
- -       Christ
- -     - Dates in AD will never include era indicator
- -    """
- -    def _query_to_date(self, expression, pattern):
- -        return self.query_and_fetchall("SELECT TO_DATE('%(expression)s', '%(pattern)s')" % locals())
- -
- -    def _assert_date(self, expression, pattern, expected):
- -        res = self._query_to_date(expression, pattern)
- -
- -        if len(res) == 0:
- -            self.fail("Expected that query '%(query)s' would return one row with one column. Got nothing." % locals())
- -
- -        elif len(res[0]) == 0:
- -            self.fail("Expected that query '%(query)s' would return one row and one column. Got one row and no column."
- -                      % locals())
- -
- -        self.assertEqual(expected, res[0][0], "Expected date '%s' but got: '%s'" % (str(expected), str(res[0][0])))
- -
- -    def test_after_christ(self):
- -        self._assert_date('2000-01-01 AD', 'YYYY-MM-DD BC', date(2000, 1, 1))
- -        self._assert_date('2000-01-01 AD', 'YYYY-MM-DD AD', date(2000, 1, 1))
- -        self._assert_date('2000-01-01', 'YYYY-MM-DD', date(2000, 1, 1))
- -
- -    def test_before_christ_bc_indicator(self):
- -        try:
- -            res = self._query_to_date('2000-01-01 BC', 'YYYY-MM-DD BC')
- -
- -            self.fail("Expected to see NotSupportedError when Before Christ date is encountered. Got: " + str(res))
- -        except errors.NotSupportedError:
- -            pass
- -
- -    def test_before_christ_ad_indicator(self):
- -        try:
- -            res = self._query_to_date('2000-01-01 BC', 'YYYY-MM-DD AD')
- -
- -            self.fail("Expected to see NotSupportedError when Before Christ date is encountered. Got: " + str(res))
- -        except errors.NotSupportedError:
- -            pass
- -
- -
- -class TimestampParsingTestCase(VerticaTestCase):
- -    """Verify timestamp parsing works properly."""
- -
- -    def test_timestamp_parser(self):
- -        test_timestamp = '1841-05-05 22:07:58'.encode(encoding='utf-8', errors='strict')
- -        parsed_timestamp = timestamp_parse(test_timestamp)
- -        # Assert parser default to strptime
- -        self.assertEqual(datetime(year=1841, month=5, day=5, hour=22, minute=7, second=58), parsed_timestamp)
- -
- -    def test_timestamp_with_year_over_9999(self):
- -        test_timestamp = '44841-05-05 22:07:58'.encode(encoding='utf-8', errors='strict')
- -        parsed_timestamp = timestamp_parse(test_timestamp)
- -        # Assert year was truncated properly
- -        self.assertEqual(datetime(year=9999, month=5, day=5, hour=22, minute=7, second=58), parsed_timestamp)
- -
- -    def test_timestamp_with_year_over_9999_and_ms(self):
- -        test_timestamp = '124841-05-05 22:07:58.000003'.encode(encoding='utf-8', errors='strict')
- -        parsed_timestamp = timestamp_parse(test_timestamp)
- -        # Assert year was truncated properly
- -        self.assertEqual(datetime(year=9999, month=5, day=5, hour=22, minute=7, second=58, microsecond=3), parsed_timestamp)
diff -Nru python-vertica-0.6.8/vertica_python/tests/error_tests.py python-vertica-0.7.1/vertica_python/tests/error_tests.py
- --- python-vertica-0.6.8/vertica_python/tests/error_tests.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/tests/error_tests.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,62 +0,0 @@
- -from .test_commons import conn_info, VerticaTestCase
- -from .. import connect
- -from .. import errors
- -
- -
- -class ErrorTestCase(VerticaTestCase):
- -
- -    def test_missing_schema(self):
- -
- -        query = "SELECT 1 FROM missing_schema.table"
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -
- -            failed = False
- -
- -            try:
- -                cur.execute(query)
- -            except errors.MissingSchema:
- -                failed = True
- -
- -            assert failed is True
- -
- -    def test_missing_relation(self):
- -
- -        query = "SELECT 1 FROM missing_table"
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -
- -            failed = False
- -
- -            try:
- -                cur.execute(query)
- -            except errors.MissingRelation:
- -                failed = True
- -
- -            assert failed is True
- -
- -    def test_duplicate_object(self):
- -
- -        create = "CREATE TABLE test_table (a BOOLEAN)"
- -        drop = "DROP TABLE test_table"
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -
- -            failed = False
- -
- -            cur.execute(create)
- -
- -            try:
- -                cur.execute(create)
- -            except errors.DuplicateObject:
- -                failed = True
- -            finally:
- -                try:
- -                    cur.execute(drop)
- -                except errors.MissingRelation:
- -                    pass
- -
- -            assert failed is True
diff -Nru python-vertica-0.6.8/vertica_python/tests/test_column.py python-vertica-0.7.1/vertica_python/tests/test_column.py
- --- python-vertica-0.6.8/vertica_python/tests/test_column.py	1970-01-01 01:00:00.000000000 +0100
+++ python-vertica-0.7.1/vertica_python/tests/test_column.py	2017-05-14 00:40:53.000000000 +0200
@@ -0,0 +1,18 @@
+from __future__ import print_function, division, absolute_import
+
+from .base import VerticaPythonTestCase
+
+
+class ColumnTestCase(VerticaPythonTestCase):
+    def test_column_names_query(self):
+        columns = ['isocode', 'name']
+
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute("""
+                SELECT 'US' AS {0}, 'United States' AS {1}
+                UNION ALL SELECT 'CA', 'Canada'
+                UNION ALL SELECT 'MX', 'Mexico' """.format(*columns))
+            description = cur.description
+
+        self.assertListEqual([d.name for d in description], columns)
diff -Nru python-vertica-0.6.8/vertica_python/tests/test_commons.py python-vertica-0.7.1/vertica_python/tests/test_commons.py
- --- python-vertica-0.6.8/vertica_python/tests/test_commons.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/tests/test_commons.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,28 +0,0 @@
- -import unittest
- -import os
- -import vertica_python
- -
- -conn_info = {'host': os.getenv('VP_TEST_HOST', '127.0.0.1'),
- -             'port': int(os.getenv('VP_TEST_PORT', 5433)),
- -             'user': os.getenv('VP_TEST_USER', 'dbadmin'),
- -             'password': os.getenv('VP_TEST_PASSWD', ''),
- -             'database': os.getenv('VP_TEST_DB', 'docker')}
- -
- -
- -class VerticaTestCase(unittest.TestCase):
- -    """
- -    Base class for tests that query Vertica.
- -
- -    Implements a couple of functions for mindless repetetive tasks.
- -    """
- -    def query_and_fetchall(self, query):
- -        """
- -        Creates new connection to vertica, executes query, returns all fetched results. Closes connection.
- -        :param query: query to execute
- -        :return: all fetched results as returned by cursor.fetchall()
- -        """
- -        with vertica_python.connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            cur.execute(query)
- -
- -            return cur.fetchall()
diff -Nru python-vertica-0.6.8/vertica_python/tests/test_cursor.py python-vertica-0.7.1/vertica_python/tests/test_cursor.py
- --- python-vertica-0.6.8/vertica_python/tests/test_cursor.py	1970-01-01 01:00:00.000000000 +0100
+++ python-vertica-0.7.1/vertica_python/tests/test_cursor.py	2017-05-14 00:40:53.000000000 +0200
@@ -0,0 +1,353 @@
+from __future__ import print_function, division, absolute_import
+
+import logging
+import tempfile
+
+from .base import VerticaPythonTestCase
+from .. import errors
+
+logger = logging.getLogger('vertica')
+
+
+class CursorTestCase(VerticaPythonTestCase):
+    def setUp(self):
+        self._init_table()
+
+    def tearDown(self):
+        # self._init_table()
+        pass
+
+    def _init_table(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            # clean old table
+            cur.execute("DROP TABLE IF EXISTS {0}".format(self._table))
+
+            # create test table
+            cur.execute("""CREATE TABLE {0} (
+                                a INT,
+                                b VARCHAR(32)
+                           )
+                        """.format(self._table))
+
+    def test_inline_commit(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute(
+                "INSERT INTO {0} (a, b) VALUES (1, 'aa'); COMMIT;".format(self._table))
+            cur.execute("SELECT a, b FROM {0} WHERE a = 1".format(self._table))
+
+            # unknown rowcount
+            self.assertEqual(cur.rowcount, -1)
+
+            res = cur.fetchall()
+            self.assertEqual(cur.rowcount, 1)
+
+            self.assertListOfListsEqual(res, [[1, 'aa']])
+
+    def test_multi_inserts_and_transaction(self):
+        with self._connect() as conn1, self._connect() as conn2:
+            cur1 = conn1.cursor()
+            cur2 = conn2.cursor()
+
+            # insert data without a commit
+            cur1.execute("INSERT INTO {0} (a, b) VALUES (2, 'bb')".format(self._table))
+
+            # verify we can see it from this cursor
+            cur1.execute("SELECT a, b FROM {0} WHERE a = 2".format(self._table))
+            res_from_cur_1_before_commit = cur1.fetchall()
+            self.assertListOfListsEqual(res_from_cur_1_before_commit, [[2, 'bb']])
+
+            # verify we cant see it from other cursor
+            cur2.execute("SELECT a, b FROM {0} WHERE a = 2".format(self._table))
+
+            res_from_cur2_before_commit = cur2.fetchall()
+            self.assertListOfListsEqual(res_from_cur2_before_commit, [])
+
+            # insert more data then commit
+            cur1.execute("INSERT INTO {0} (a, b) VALUES (3, 'cc')".format(self._table))
+            cur1.execute("COMMIT")
+
+            # verify we can see it from this cursor
+            cur1.execute(
+                "SELECT a, b FROM {0} WHERE a = 2 OR a = 3 ORDER BY a".format(self._table))
+            res_from_cur1_after_commit = cur1.fetchall()
+            self.assertListOfListsEqual(res_from_cur1_after_commit, [[2, 'bb'], [3, 'cc']])
+
+            # verify we can see it from other cursor
+            cur2.execute(
+                "SELECT a, b FROM {0} WHERE a = 2 OR a = 3 ORDER BY a".format(self._table))
+            res_from_cur2_after_commit = cur2.fetchall()
+            self.assertListOfListsEqual(res_from_cur2_after_commit, [[2, 'bb'], [3, 'cc']])
+
+    def test_conn_commit(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute("INSERT INTO {0} (a, b) VALUES (5, 'cc')".format(self._table))
+            conn.commit()
+
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute("SELECT a, b FROM {0} WHERE a = 5".format(self._table))
+            res = cur.fetchall()
+
+        self.assertListOfListsEqual(res, [[5, 'cc']])
+
+    def test_delete(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+
+            cur.execute("INSERT INTO {0} (a, b) VALUES (5, 'cc')".format(self._table))
+            self.assertEqual(cur.rowcount, -1)
+            update_res = cur.fetchall()
+            self.assertListOfListsEqual(update_res, [[1]])
+            conn.commit()
+
+            # validate delete count
+            cur.execute("DELETE FROM {0} WHERE a = 5".format(self._table))
+            self.assertEqual(cur.rowcount, -1)
+            delete_res = cur.fetchall()
+            self.assertListOfListsEqual(delete_res, [[1]])
+            conn.commit()
+
+            # validate deleted
+            cur.execute("SELECT a, b FROM {0} WHERE a = 5".format(self._table))
+            res = cur.fetchall()
+            self.assertListOfListsEqual(res, [])
+
+    def test_update(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+
+            cur.execute("INSERT INTO {0} (a, b) VALUES (5, 'cc')".format(self._table))
+            # validate insert count
+            insert_res = cur.fetchall()
+            self.assertListOfListsEqual(insert_res, [[1]], msg='Bad INSERT response')
+            conn.commit()
+
+            cur.execute("UPDATE {0} SET b = 'ff' WHERE a = 5".format(self._table))
+            # validate update count
+            assert cur.rowcount == -1
+            update_res = cur.fetchall()
+            self.assertListOfListsEqual(update_res, [[1]], msg='Bad UPDATE response')
+            conn.commit()
+
+            cur.execute("SELECT a, b FROM {0} WHERE a = 5".format(self._table))
+            res = cur.fetchall()
+            self.assertListOfListsEqual(res, [[5, 'ff']])
+
+    def test_copy_with_string(self):
+        with self._connect() as conn1, self._connect() as conn2:
+            cur1 = conn1.cursor()
+            cur2 = conn2.cursor()
+
+            cur1.copy("COPY {0} (a, b) FROM STDIN DELIMITER ','".format(self._table),
+                      "1,foo\n2,bar")
+            # no commit necessary for copy
+            cur1.execute("SELECT a, b FROM {0} WHERE a = 1".format(self._table))
+            res_from_cur1 = cur1.fetchall()
+            self.assertListOfListsEqual(res_from_cur1, [[1, 'foo']])
+
+            cur2.execute("SELECT a, b FROM {0} WHERE a = 2".format(self._table))
+            res_from_cur2 = cur2.fetchall()
+            self.assertListOfListsEqual(res_from_cur2, [[2, 'bar']])
+
+    def test_copy_with_file(self):
+        f = tempfile.TemporaryFile()
+        f.write(b"1,foo\n2,bar")
+        # move rw pointer to top of file
+        f.seek(0)
+
+        with self._connect() as conn1, self._connect() as conn2:
+            cur1 = conn1.cursor()
+            cur2 = conn2.cursor()
+
+            cur1.copy("COPY {0} (a, b) FROM STDIN DELIMITER ','".format(self._table),
+                      f)
+            # no commit necessary for copy
+            cur1.execute("SELECT a, b FROM {0} WHERE a = 1".format(self._table))
+            res_from_cur1 = cur1.fetchall()
+            self.assertListOfListsEqual(res_from_cur1, [[1, 'foo']])
+
+            cur2.execute("SELECT a, b FROM {0} WHERE a = 2".format(self._table))
+            res_from_cur2 = cur2.fetchall()
+            self.assertListOfListsEqual(res_from_cur2, [[2, 'bar']])
+
+    # unit test for #78
+    def test_copy_with_data_in_buffer(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+
+            cur.execute("SELECT 1;")
+            res = cur.fetchall()
+            self.assertListOfListsEqual(res, [[1]])
+
+            cur.copy("COPY {0} (a, b) FROM STDIN DELIMITER ','".format(self._table),
+                     "1,foo\n2,bar")
+
+            cur.execute("SELECT 1;")
+            res = cur.fetchall()
+            self.assertListOfListsEqual(res, [[1]])
+
+    def test_with_conn(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+
+            cur.execute("INSERT INTO {0} (a, b) VALUES (1, 'aa'); COMMIT;".format(self._table))
+            cur.execute("SELECT a, b FROM {0} WHERE a = 1".format(self._table))
+            res = cur.fetchall()
+            self.assertListOfListsEqual(res, [[1, 'aa']])
+
+    def test_iterator(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            values = [[1, 'aa'], [2, 'bb'], [3, 'cc']]
+
+            for n, s in values:
+                cur.execute("INSERT INTO {0} (a, b) VALUES (:n, :s)".format(self._table),
+                            {'n': n, 's': s})
+            conn.commit()
+
+            cur.execute("SELECT a, b FROM {0} ORDER BY a ASC".format(self._table))
+
+            for val, res in zip(sorted(values), cur.iterate()):
+                self.assertListEqual(res, val)
+
+            remaining = cur.fetchall()
+            self.assertListOfListsEqual(remaining, [])
+
+    def test_mid_iterator_execution(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            values = [[1, 'aa'], [2, 'bb'], [3, 'cc']]
+
+            for n, s in values:
+                cur.execute("INSERT INTO {0} (a, b) VALUES (:n, :s)".format(self._table),
+                            {'n': n, 's': s})
+            conn.commit()
+
+            cur.execute("SELECT a, b FROM {0} ORDER BY a ASC".format(self._table))
+
+            for val, res in zip(sorted(values), cur.iterate()):
+                self.assertListEqual(res, val)
+                break  # stop after one comparison
+
+            # make new query and verify result
+            cur.execute("SELECT COUNT(*) FROM {0}".format(self._table))
+            res = cur.fetchall()
+            self.assertListOfListsEqual(res, [[3]])
+
+    def test_query_errors(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+
+            # create table syntax error
+            with self.assertRaises(errors.VerticaSyntaxError):
+                cur.execute("""CREATE TABLE {0}_fail (
+                                a INT,
+                                b VARCHAR(32),,,
+                                );
+                            """.format(self._table))
+
+            # select table not found error
+            cur.execute("INSERT INTO {0} (a, b) VALUES (1, 'aa'); COMMIT;".format(self._table))
+            with self.assertRaises(errors.QueryError):
+                cur.execute("SELECT * FROM {0}_fail".format(self._table))
+
+            # verify cursor still usable after errors
+            cur.execute("SELECT a, b FROM {0} WHERE a = 1".format(self._table))
+            res = cur.fetchall()
+            self.assertListOfListsEqual(res, [[1, 'aa']])
+
+    def test_cursor_close_and_reuse(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+
+            # insert data
+            cur.execute("INSERT INTO {0} (a, b) VALUES (2, 'bb'); COMMIT;".format(self._table))
+
+            # (query -> close -> reopen) * 3 times
+            for _ in range(3):
+                cur.execute("SELECT a, b FROM {0} WHERE a = 2".format(self._table))
+                res = cur.fetchall()
+                self.assertListOfListsEqual(res, [[2, 'bb']])
+
+                # close and reopen cursor
+                cur.close()
+                cur = conn.cursor()
+
+    # unit test for #74
+    def test_nextset(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+
+            cur.execute("SELECT 1; SELECT 2;")
+
+            res1 = cur.fetchall()
+            self.assertListOfListsEqual(res1, [[1]])
+            self.assertIsNone(cur.fetchone())
+            self.assertTrue(cur.nextset())
+
+            res2 = cur.fetchall()
+            self.assertListOfListsEqual(res2, [[2]])
+            self.assertIsNone(cur.fetchone())
+            self.assertFalse(cur.nextset())
+
+    # unit test for #74
+    def test_nextset_with_delete(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+
+            # insert data
+            cur.execute("INSERT INTO {0} (a, b) VALUES (1, 'aa')".format(self._table))
+            cur.execute("INSERT INTO {0} (a, b) VALUES (2, 'bb')".format(self._table))
+            conn.commit()
+
+            cur.execute("""
+                          SELECT * FROM {0} ORDER BY a ASC;
+                          DELETE FROM {0};
+                          SELECT * FROM {0} ORDER BY a ASC;
+                        """.format(self._table))
+
+            # check first select results
+            res1 = cur.fetchall()
+            self.assertListOfListsEqual(res1, [[1, 'aa'], [2, 'bb']])
+            self.assertIsNone(cur.fetchone())
+            self.assertTrue(cur.nextset())
+
+            # check delete results
+            res2 = cur.fetchall()
+            self.assertListOfListsEqual(res2, [[2]])
+            self.assertIsNone(cur.fetchone())
+            self.assertTrue(cur.nextset())
+
+            # check second select results
+            res3 = cur.fetchall()
+            self.assertListOfListsEqual(res3, [])
+            self.assertIsNone(cur.fetchone())
+            self.assertFalse(cur.nextset())
+
+    # unit test for #124
+    def test_nextset_with_error(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+
+            cur.execute("SELECT 1; SELECT a; SELECT 2")
+
+            # verify data from first query
+            res1 = cur.fetchall()
+            self.assertListOfListsEqual(res1, [[1]])
+            self.assertIsNone(cur.fetchone())
+
+            # second statement results in a query error
+            with self.assertRaises(errors.MissingColumn):
+                cur.nextset()
+
+    # unit test for #144
+    def test_empty_query(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+
+            cur.execute("")
+            res = cur.fetchall()
+            self.assertListOfListsEqual(res, [])
diff -Nru python-vertica-0.6.8/vertica_python/tests/test_datatypes.py python-vertica-0.7.1/vertica_python/tests/test_datatypes.py
- --- python-vertica-0.6.8/vertica_python/tests/test_datatypes.py	1970-01-01 01:00:00.000000000 +0100
+++ python-vertica-0.7.1/vertica_python/tests/test_datatypes.py	2017-05-14 00:40:53.000000000 +0200
@@ -0,0 +1,25 @@
+from __future__ import print_function, division, absolute_import
+
+from decimal import Decimal
+
+from .base import VerticaPythonTestCase
+
+
+class TypeTestCase(VerticaPythonTestCase):
+    def test_decimal_query(self):
+        value = Decimal(0.42)
+        query = "SELECT {0}::numeric".format(value)
+        res = self._query_and_fetchone(query)
+        self.assertAlmostEqual(res[0], value)
+
+    def test_boolean_query__true(self):
+        value = True
+        query = "SELECT {0}::boolean".format(value)
+        res = self._query_and_fetchone(query)
+        self.assertEqual(res[0], value)
+
+    def test_boolean_query__false(self):
+        value = False
+        query = "SELECT {0}::boolean".format(value)
+        res = self._query_and_fetchone(query)
+        self.assertEqual(res[0], value)
diff -Nru python-vertica-0.6.8/vertica_python/tests/test_dates.py python-vertica-0.7.1/vertica_python/tests/test_dates.py
- --- python-vertica-0.6.8/vertica_python/tests/test_dates.py	1970-01-01 01:00:00.000000000 +0100
+++ python-vertica-0.7.1/vertica_python/tests/test_dates.py	2017-05-14 00:40:53.000000000 +0200
@@ -0,0 +1,238 @@
+from __future__ import print_function, division, absolute_import
+
+from collections import namedtuple
+from datetime import date, datetime
+
+from .base import VerticaPythonTestCase
+from .. import errors
+from ..vertica.column import timestamp_parse
+
+DateTestingCase = namedtuple("DateTestingCase", ["string", "template", "date"])
+TimestampTestingCase = namedtuple("TimestampTestingCase", ["string", "timestamp"])
+
+
+class DateParsingTestCase(VerticaPythonTestCase):
+    """Testing DATE type parsing with focus on 'AD'/'BC'.
+
+    Note: the 'BC' or 'AD' era indicators in Vertica's date format seem to make Vertica behave as
+    follows:
+        1. Both 'BC' and 'AD' are simply a flags that tell Vertica: include era indicator if the
+        date is Before Christ
+        2. Dates in 'AD' will never include era indicator
+    """
+
+    def _test_dates(self, test_cases, msg=None):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            for tc in test_cases:
+                cur.execute("SELECT TO_DATE('{0}', '{1}')".format(tc.string, tc.template))
+                res = cur.fetchall()
+                self.assertListOfListsEqual(res, [[tc.date]], msg=msg)
+
+    def _test_not_supported(self, test_cases, msg=None):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            for tc in test_cases:
+                with self.assertRaises(errors.NotSupportedError, msg=msg):
+                    cur.execute("SELECT TO_DATE('{0}', '{1}')".format(tc.string, tc.template))
+                    res = cur.fetchall()
+                    self.assertListOfListsEqual(res, [[tc.date]])
+
+    def test_no_to_no(self):
+        test_cases = [
+            DateTestingCase('1985-10-25', 'YYYY-MM-DD', date(1985, 10, 25)),
+            DateTestingCase('1955-11-12', 'YYYY-MM-DD', date(1955, 11, 12)),
+            DateTestingCase('1885-01-01', 'YYYY-MM-DD', date(1885, 1, 1)),
+            DateTestingCase('2015-10-21', 'YYYY-MM-DD', date(2015, 10, 21)),
+        ]
+        self._test_dates(test_cases=test_cases, msg='no indicator -> no indicator')
+
+    def test_ad_to_no(self):
+        test_cases = [
+            DateTestingCase('1985-10-25 AD', 'YYYY-MM-DD', date(1985, 10, 25)),
+            DateTestingCase('1955-11-12 AD', 'YYYY-MM-DD', date(1955, 11, 12)),
+            DateTestingCase('1885-01-01 AD', 'YYYY-MM-DD', date(1885, 1, 1)),
+            DateTestingCase('2015-10-21 AD', 'YYYY-MM-DD', date(2015, 10, 21)),
+        ]
+        self._test_dates(test_cases=test_cases, msg='AD indicator -> no indicator')
+
+    def test_bc_to_no(self):
+        test_cases = [
+            DateTestingCase('1985-10-25 BC', 'YYYY-MM-DD', date(1985, 10, 25)),
+            DateTestingCase('1955-11-12 BC', 'YYYY-MM-DD', date(1955, 11, 12)),
+            DateTestingCase('1885-01-01 BC', 'YYYY-MM-DD', date(1885, 1, 1)),
+            DateTestingCase('2015-10-21 BC', 'YYYY-MM-DD', date(2015, 10, 21)),
+        ]
+        self._test_dates(test_cases=test_cases, msg='BC indicator -> no indicator')
+
+    def test_no_to_ad(self):
+        test_cases = [
+            DateTestingCase('1985-10-25', 'YYYY-MM-DD AD', date(1985, 10, 25)),
+            DateTestingCase('1955-11-12', 'YYYY-MM-DD AD', date(1955, 11, 12)),
+            DateTestingCase('1885-01-01', 'YYYY-MM-DD AD', date(1885, 1, 1)),
+            DateTestingCase('2015-10-21', 'YYYY-MM-DD AD', date(2015, 10, 21)),
+        ]
+        self._test_dates(test_cases=test_cases, msg='no indicator -> AD indicator')
+
+    def test_ad_to_ad(self):
+        test_cases = [
+            DateTestingCase('1985-10-25 AD', 'YYYY-MM-DD AD', date(1985, 10, 25)),
+            DateTestingCase('1955-11-12 AD', 'YYYY-MM-DD AD', date(1955, 11, 12)),
+            DateTestingCase('1885-01-01 AD', 'YYYY-MM-DD AD', date(1885, 1, 1)),
+            DateTestingCase('2015-10-21 AD', 'YYYY-MM-DD AD', date(2015, 10, 21)),
+        ]
+        self._test_dates(test_cases=test_cases, msg='AD indicator -> AD indicator')
+
+    def test_bc_to_ad(self):
+        test_cases = [
+            DateTestingCase('1985-10-25 BC', 'YYYY-MM-DD AD', date(1985, 10, 25)),
+            DateTestingCase('1955-11-12 BC', 'YYYY-MM-DD AD', date(1955, 11, 12)),
+            DateTestingCase('1885-01-01 BC', 'YYYY-MM-DD AD', date(1885, 1, 1)),
+            DateTestingCase('2015-10-21 BC', 'YYYY-MM-DD AD', date(2015, 10, 21)),
+        ]
+        self._test_not_supported(test_cases=test_cases, msg='BC indicator -> AD indicator')
+
+    def test_no_to_bc(self):
+        test_cases = [
+            DateTestingCase('1985-10-25', 'YYYY-MM-DD BC', date(1985, 10, 25)),
+            DateTestingCase('1955-11-12', 'YYYY-MM-DD BC', date(1955, 11, 12)),
+            DateTestingCase('1885-01-01', 'YYYY-MM-DD BC', date(1885, 1, 1)),
+            DateTestingCase('2015-10-21', 'YYYY-MM-DD BC', date(2015, 10, 21)),
+        ]
+        self._test_dates(test_cases=test_cases, msg='no indicator -> BC indicator')
+
+    def test_ad_to_bc(self):
+        test_cases = [
+            DateTestingCase('1985-10-25 AD', 'YYYY-MM-DD BC', date(1985, 10, 25)),
+            DateTestingCase('1955-11-12 AD', 'YYYY-MM-DD BC', date(1955, 11, 12)),
+            DateTestingCase('1885-01-01 AD', 'YYYY-MM-DD BC', date(1885, 1, 1)),
+            DateTestingCase('2015-10-21 AD', 'YYYY-MM-DD BC', date(2015, 10, 21)),
+        ]
+        self._test_dates(test_cases=test_cases, msg='AD indicator -> BC indicator')
+
+    def test_bc_to_bc(self):
+        test_cases = [
+            DateTestingCase('1985-10-25 BC', 'YYYY-MM-DD BC', date(1985, 10, 25)),
+            DateTestingCase('1955-11-12 BC', 'YYYY-MM-DD BC', date(1955, 11, 12)),
+            DateTestingCase('1885-01-01 BC', 'YYYY-MM-DD BC', date(1885, 1, 1)),
+            DateTestingCase('2015-10-21 BC', 'YYYY-MM-DD BC', date(2015, 10, 21)),
+        ]
+        self._test_not_supported(test_cases=test_cases, msg='BC indicator -> BC indicator')
+
+
+class TimestampParsingTestCase(VerticaPythonTestCase):
+    def _test_timestamps(self, test_cases, msg=None):
+        for tc in test_cases:
+            self.assertEqual(timestamp_parse(tc.string), tc.timestamp, msg=msg)
+
+    def test_timestamp_second_resolution(self):
+        test_cases = [  # back to the future dates
+            TimestampTestingCase(
+                '1985-10-26 01:25:01',
+                datetime(year=1985, month=10, day=26, hour=1, minute=25, second=1)
+            ),
+            TimestampTestingCase(
+                '1955-11-12 22:55:02',
+                datetime(year=1955, month=11, day=12, hour=22, minute=55, second=2)
+            ),
+            TimestampTestingCase(
+                '2015-10-21 11:12:03',
+                datetime(year=2015, month=10, day=21, hour=11, minute=12, second=3)
+            ),
+            TimestampTestingCase(
+                '1885-01-01 01:02:04',
+                datetime(year=1885, month=1, day=1, hour=1, minute=2, second=4)
+            ),
+            TimestampTestingCase(
+                '1885-09-02 02:03:05',
+                datetime(year=1885, month=9, day=2, hour=2, minute=3, second=5)
+            ),
+        ]
+        self._test_timestamps(test_cases=test_cases, msg='timestamp second resolution')
+
+    def test_timestamp_microsecond_resolution(self):
+        test_cases = [  # back to the future dates
+            TimestampTestingCase(
+                '1985-10-26 01:25:01.1',
+                datetime(year=1985, month=10, day=26, hour=1, minute=25, second=1,
+                         microsecond=100000)
+            ),
+            TimestampTestingCase(
+                '1955-11-12 22:55:02.01',
+                datetime(year=1955, month=11, day=12, hour=22, minute=55, second=2,
+                         microsecond=10000)
+            ),
+            TimestampTestingCase(
+                '2015-10-21 11:12:03.001',
+                datetime(year=2015, month=10, day=21, hour=11, minute=12, second=3,
+                         microsecond=1000)
+            ),
+            TimestampTestingCase(
+                '1885-01-01 01:02:04.000001',
+                datetime(year=1885, month=1, day=1, hour=1, minute=2, second=4,
+                         microsecond=1)
+            ),
+            TimestampTestingCase(
+                '1885-09-02 02:03:05.002343',
+                datetime(year=1885, month=9, day=2, hour=2, minute=3, second=5,
+                         microsecond=2343)
+            ),
+        ]
+        self._test_timestamps(test_cases=test_cases, msg='timestamp microsecond resolution')
+
+    def test_timestamp_year_over_9999_second_resolution(self):
+        """Asserts that years over 9999 are truncated to 9999"""
+        test_cases = [
+            TimestampTestingCase(
+                '19850-10-26 01:25:01',
+                datetime(year=9999, month=10, day=26, hour=1, minute=25, second=1)
+            ),
+            TimestampTestingCase(
+                '10000-11-12 22:55:02',
+                datetime(year=9999, month=11, day=12, hour=22, minute=55, second=2)
+            ),
+            TimestampTestingCase(
+                '9999-10-21 11:12:03',
+                datetime(year=9999, month=10, day=21, hour=11, minute=12, second=3)
+            ),
+            TimestampTestingCase(
+                '18850-01-01 01:02:04',
+                datetime(year=9999, month=1, day=1, hour=1, minute=2, second=4)
+            ),
+            TimestampTestingCase(
+                '18850-09-02 02:03:05',
+                datetime(year=9999, month=9, day=2, hour=2, minute=3, second=5)
+            ),
+        ]
+        self._test_timestamps(test_cases=test_cases, msg='timestamp past 9999 second resolution')
+
+    def test_timestamp_year_over_9999_microsecond_resolution(self):
+        test_cases = [
+            TimestampTestingCase(
+                '19850-10-26 01:25:01.1',
+                datetime(year=9999, month=10, day=26, hour=1, minute=25, second=1,
+                         microsecond=100000)
+            ),
+            TimestampTestingCase(
+                '10000-11-12 22:55:02.01',
+                datetime(year=9999, month=11, day=12, hour=22, minute=55, second=2,
+                         microsecond=10000)
+            ),
+            TimestampTestingCase(
+                '9999-10-21 11:12:03.001',
+                datetime(year=9999, month=10, day=21, hour=11, minute=12, second=3,
+                         microsecond=1000)
+            ),
+            TimestampTestingCase(
+                '18850-01-01 01:02:04.000001',
+                datetime(year=9999, month=1, day=1, hour=1, minute=2, second=4,
+                         microsecond=1)
+            ),
+            TimestampTestingCase(
+                '18850-09-02 02:03:05.002343',
+                datetime(year=9999, month=9, day=2, hour=2, minute=3, second=5,
+                         microsecond=2343)
+            ),
+        ]
+        self._test_timestamps(test_cases=test_cases,
+                              msg='timestamp past 9999 microsecond resolution')
diff -Nru python-vertica-0.6.8/vertica_python/tests/test_errors.py python-vertica-0.7.1/vertica_python/tests/test_errors.py
- --- python-vertica-0.6.8/vertica_python/tests/test_errors.py	1970-01-01 01:00:00.000000000 +0100
+++ python-vertica-0.7.1/vertica_python/tests/test_errors.py	2017-05-14 00:40:53.000000000 +0200
@@ -0,0 +1,31 @@
+from __future__ import print_function, division, absolute_import
+
+from .base import VerticaPythonTestCase
+
+from .. import errors
+
+
+class ErrorTestCase(VerticaPythonTestCase):
+    def setUp(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute("DROP TABLE IF EXISTS {0}".format(self._table))
+
+    def test_missing_schema(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            with self.assertRaises(errors.MissingSchema):
+                cur.execute("SELECT 1 FROM missing_schema.table")
+
+    def test_missing_relation(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            with self.assertRaises(errors.MissingRelation):
+                cur.execute("SELECT 1 FROM missing_table")
+
+    def test_duplicate_object(self):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute("CREATE TABLE {0} (a BOOLEAN)".format(self._table))
+            with self.assertRaises(errors.DuplicateObject):
+                cur.execute("CREATE TABLE {0} (a BOOLEAN)".format(self._table))
diff -Nru python-vertica-0.6.8/vertica_python/tests/test_timezones.py python-vertica-0.7.1/vertica_python/tests/test_timezones.py
- --- python-vertica-0.6.8/vertica_python/tests/test_timezones.py	1970-01-01 01:00:00.000000000 +0100
+++ python-vertica-0.7.1/vertica_python/tests/test_timezones.py	2017-05-14 00:40:53.000000000 +0200
@@ -0,0 +1,52 @@
+from __future__ import print_function, division, absolute_import
+
+from collections import namedtuple
+from datetime import datetime
+import pytz
+
+from .base import VerticaPythonTestCase
+
+TimeZoneTestingCase = namedtuple("TimeZoneTestingCase", ["string", "template", "timestamp"])
+
+
+class TimeZoneTestCase(VerticaPythonTestCase):
+    def _test_ts(self, test_cases):
+        with self._connect() as conn:
+            cur = conn.cursor()
+            for tc in test_cases:
+                cur.execute("SELECT TO_TIMESTAMP('{0}', '{1}')".format(tc.string, tc.template))
+                res = cur.fetchone()
+                self.assertEqual(tc.timestamp.toordinal(), res[0].toordinal())
+
+    def test_simple_ts_query(self):
+        template = 'YYYY-MM-DD HH:MI:SS.MS'
+        test_cases = [
+            TimeZoneTestingCase(
+                string='2016-05-15 13:15:17.789', template=template,
+                timestamp=datetime(year=2016, month=5, day=15, hour=13, minute=15, second=17,
+                                   microsecond=789000)
+            ),
+        ]
+        self._test_ts(test_cases=test_cases)
+
+    def test_simple_ts_with_tz_query(self):
+        template = 'YYYY-MM-DD HH:MI:SS.MS TZ'
+        test_cases = [
+            TimeZoneTestingCase(
+                string='2016-05-15 13:15:17.789 UTC', template=template,
+                timestamp=datetime(year=2016, month=5, day=15, hour=13, minute=15, second=17,
+                                   microsecond=789000, tzinfo=pytz.utc)
+            ),
+        ]
+        self._test_ts(test_cases=test_cases)
+
+    def test_simple_ts_with_offset_query(self):
+        template = 'YYYY-MM-DD HH:MI:SS.MS+00'
+        test_cases = [
+            TimeZoneTestingCase(
+                string='2016-05-15 13:15:17.789 UTC', template=template,
+                timestamp=datetime(year=2016, month=5, day=15, hour=13, minute=15, second=17,
+                                   microsecond=789000, tzinfo=pytz.utc)
+            ),
+        ]
+        self._test_ts(test_cases=test_cases)
diff -Nru python-vertica-0.6.8/vertica_python/tests/test_unicode.py python-vertica-0.7.1/vertica_python/tests/test_unicode.py
- --- python-vertica-0.6.8/vertica_python/tests/test_unicode.py	1970-01-01 01:00:00.000000000 +0100
+++ python-vertica-0.7.1/vertica_python/tests/test_unicode.py	2017-05-14 00:40:53.000000000 +0200
@@ -0,0 +1,65 @@
+from __future__ import print_function, division, absolute_import
+
+from .base import VerticaPythonTestCase
+
+
+class UnicodeTestCase(VerticaPythonTestCase):
+    def test_unicode_query(self):
+        value = u'\u16a0'
+        query = u"SELECT '{0}'".format(value)
+
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute(query)
+            res = cur.fetchone()
+
+        self.assertResultEqual(value, res[0])
+
+    def test_unicode_list_parameter(self):
+        values = [u'\u00f1', 'foo', 3]
+        query = u"SELECT {0}".format(", ".join(["%s"] * len(values)))
+
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute(query, tuple(values))
+            results = cur.fetchone()
+
+        for val, res in zip(values, results):
+            self.assertResultEqual(val, res)
+
+    def test_unicode_named_parameter_binding(self):
+        values = [u'\u16b1', 'foo', 3]
+        keys = [u'\u16a0', 'foo', 3]
+
+        query = u"SELECT {0}".format(", ".join([u":{0}".format(key) for key in keys]))
+
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute(query, dict(zip(keys, values)))
+            results = cur.fetchone()
+
+        for val, res in zip(values, results):
+            self.assertResultEqual(val, res)
+
+    def test_string_query(self):
+        value = u'test'
+        query = u"SELECT '{0}'".format(value)
+
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute(query)
+            res = cur.fetchone()
+
+        self.assertEqual(value, res[0])
+
+    def test_string_named_parameter_binding(self):
+        key = u'test'
+        value = u'value'
+        query = u"SELECT :{0}".format(key)
+
+        with self._connect() as conn:
+            cur = conn.cursor()
+            cur.execute(query, {key: value})
+            res = cur.fetchone()
+
+        self.assertResultEqual(value, res[0])
diff -Nru python-vertica-0.6.8/vertica_python/tests/timezone_tests.py python-vertica-0.7.1/vertica_python/tests/timezone_tests.py
- --- python-vertica-0.6.8/vertica_python/tests/timezone_tests.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/tests/timezone_tests.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,48 +0,0 @@
- -from datetime import date, datetime
- -import pytz
- -from .test_commons import conn_info, VerticaTestCase
- -from .. import connect
- -
- -class TimeZoneTestCase(VerticaTestCase):
- -
- -    def test_simple_ts_query(self):
- -        query = """
- -        select
- -          to_timestamp('2016-05-15 13:15:17.789', 'YYYY-MM-DD HH:MI:SS.MS')
- -        ;
- -        """
- -        value = datetime(year=2016, month=5, day=15, hour=13, minute=15, second=17, microsecond=789000)
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            cur.execute(query)
- -            res = cur.fetchall()
- -            assert res[0][0] == value
- -
- -    def test_simple_ts_with_tz_query(self):
- -        query = """
- -        select
- -          to_timestamp_tz('2016-05-15 13:15:17.789 UTC', 'YYYY-MM-DD HH:MI:SS.MS TZ')
- -        ;
- -        """
- -        value = datetime(year=2016, month=5, day=15, hour=13, minute=15, second=17, microsecond=789000, tzinfo=pytz.utc)
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            cur.execute(query)
- -            res = cur.fetchall()
- -            assert res[0][0] == value
- -
- -    def test_simple_ts_with_offset_query(self):
- -        query = """
- -        select
- -          timestamp '2016-05-15 13:15:17.789+00'
- -        ;
- -        """
- -        value = datetime(year=2016, month=5, day=15, hour=13, minute=15, second=17, microsecond=789000, tzinfo=pytz.utc)
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            cur.execute(query)
- -            res = cur.fetchall()
- -            assert res[0][0] == value
diff -Nru python-vertica-0.6.8/vertica_python/tests/type_tests.py python-vertica-0.7.1/vertica_python/tests/type_tests.py
- --- python-vertica-0.6.8/vertica_python/tests/type_tests.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/tests/type_tests.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,15 +0,0 @@
- -from .test_commons import conn_info, VerticaTestCase
- -from .. import connect
- -from decimal import Decimal
- -
- -
- -class TypeTestCase(VerticaTestCase):
- -    def test_decimal_query(self):
- -        value = Decimal(0.42)
- -        query = "SELECT {}::numeric".format(value)
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            cur.execute(query)
- -            res = cur.fetchone()
- -            self.assertAlmostEqual(res[0], value)
diff -Nru python-vertica-0.6.8/vertica_python/tests/unicode_tests.py python-vertica-0.7.1/vertica_python/tests/unicode_tests.py
- --- python-vertica-0.6.8/vertica_python/tests/unicode_tests.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/tests/unicode_tests.py	1970-01-01 01:00:00.000000000 +0100
@@ -1,75 +0,0 @@
- -from .test_commons import conn_info, VerticaTestCase
- -from .. import connect
- -
- -
- -class UnicodeTestCase(VerticaTestCase):
- -    def test_unicode_query(self):
- -        value = u'\u16a0'
- -        query = u"SELECT '{}'".format(value)
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            cur.execute(query)
- -            res = cur.fetchone()
- -
- -            assert res[0] == value
- -
- -    # this test is broken on python3: see issue #112
- -    def test_unicode_list_parameter(self):
- -        v1 = u'\u00f1'
- -        v2 = 'foo'
- -        v3 = 3
- -        query = u"SELECT %s, %s, %s"
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            cur.execute(query, (v1, v2, v3))
- -            res = cur.fetchone()
- -
- -            assert res[0] == v1
- -            assert res[1] == v2
- -            assert res[2] == v3
- -
- -    # this test is broken on python3: see issue #112
- -    def test_unicode_named_parameter_binding(self):
- -        k1 = u'\u16a0'
- -        k2 = 'foo'
- -        k3 = 3
- -
- -        v1 = u'\u16b1'
- -        v2 = 'foo'
- -        v3 = 3
- -
- -        query = u"SELECT :{}, :{}, :{}".format(k1, k2, k3)
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            cur.execute(query, {k1: v1, k2: v2, k3: v3})
- -            res = cur.fetchone()
- -
- -            assert res[0] == v1
- -            assert res[1] == v2
- -            assert res[2] == v3
- -
- -    def test_string_query(self):
- -        value = u'test'
- -        query = u"SELECT '{}'".format(value)
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            cur.execute(query)
- -            res = cur.fetchone()
- -
- -            assert res[0] == value
- -
- -    def test_string_named_parameter_binding(self):
- -        key = u'test'
- -        value = u'value'
- -        query = u"SELECT :{}".format(key)
- -
- -        with connect(**conn_info) as conn:
- -            cur = conn.cursor()
- -            cur.execute(query, {key: value})
- -            res = cur.fetchone()
- -
- -            assert res[0] == value
diff -Nru python-vertica-0.6.8/vertica_python/vertica/column.py python-vertica-0.7.1/vertica_python/vertica/column.py
- --- python-vertica-0.6.8/vertica_python/vertica/column.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/column.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,18 +1,21 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -from collections import namedtuple
 import re
+from collections import namedtuple
+from datetime import date, datetime
+from decimal import Decimal
 
+import pytz
+# noinspection PyCompatibility,PyUnresolvedReferences
 from builtins import str
- -from decimal import Decimal
- -from datetime import date
- -from datetime import datetime
 from dateutil import parser
- -from vertica_python import errors
 
- -import pytz
+from .. import errors
+from ..compat import as_str, as_text
 
- -years_re = re.compile(r'^([0-9]+)-')
+YEARS_RE = re.compile(r"^([0-9]+)-")
+
+UTF_8 = 'utf-8'
 
 
 # these methods are bad...
@@ -34,12 +37,12 @@
 # timestamptz type stores: 2013-01-01 05:00:00.01+00
 #       select t AT TIMEZONE 'America/New_York' returns: 2012-12-31 19:00:00.01
 def timestamp_parse(s):
- -    s = str(s, 'utf-8')
+    s = as_str(s)
     try:
         dt = _timestamp_parse(s)
     except ValueError:
         # Value error, year might be over 9999
- -        year_match = years_re.match(s)
+        year_match = YEARS_RE.match(s)
         if year_match:
             year = year_match.groups()[0]
             dt = _timestamp_parse_without_year(s[len(year) + 1:])
@@ -63,11 +66,11 @@
 
 
 def timestamp_tz_parse(s):
- -    s = str(s, 'utf-8')
- -    # if timezome is simply UTC...
+    s = as_str(s)
+    # if timezone is simply UTC...
     if s.endswith('+00'):
         # remove time zone
- -        ts = timestamp_parse(s[:-3].encode(encoding='utf-8', errors='strict'))
+        ts = timestamp_parse(s[:-3].encode(encoding=UTF_8, errors='strict'))
         ts = ts.replace(tzinfo=pytz.UTC)
         return ts
     # other wise do a real parse (slower)
@@ -81,50 +84,19 @@
     :return: an instance of datetime.date
     :raises NotSupportedError when a date Before Christ is encountered
     """
- -    if s.endswith(b' BC'):
- -        raise errors.NotSupportedError('Dates Before Christ are not supported. Got: ' + str(s, 'utf-8'))
+    s = as_str(s)
+    if s.endswith(' BC'):
+        raise errors.NotSupportedError('Dates Before Christ are not supported. Got: {0}'.format(s))
 
- -    return date(*map(lambda x: int(x), s.split(b'-')))
+    # Value error, year might be over 9999
+    return date(*map(lambda x: min(int(x), 9999), s.split('-')))
 
- -ColumnTuple = namedtuple(
- -    'Column',
- -    ['name', 'type_code', 'display_size', 'internal_size',
- -     'precision', 'scale', 'null_ok']
- -)
 
+ColumnTuple = namedtuple('Column', ['name', 'type_code', 'display_size', 'internal_size',
+                                    'precision', 'scale', 'null_ok'])
 
- -class Column(object):
- -
- -    @classmethod
- -    def data_type_conversions(cls, unicode_error=None):
- -        if unicode_error is None:
- -            unicode_error = 'strict'
- -        return [
- -            ('unspecified', None),
- -            ('tuple', None),
- -            ('pos', None),
- -            ('record', None),
- -            ('unknown', None),
- -            ('bool', lambda s: s == 't'),
- -            ('integer', lambda s: int(s)),
- -            ('float', lambda s: float(s)),
- -            ('char', lambda s: str(s, 'utf-8', unicode_error)),
- -            ('varchar', lambda s: str(s, 'utf-8', unicode_error)),
- -            ('date', date_parse),
- -            ('time', None),
- -            ('timestamp', timestamp_parse),
- -            ('timestamp_tz', timestamp_tz_parse),
- -            ('interval', None),
- -            ('time_tz', None),
- -            ('numeric', lambda s: Decimal(str(s, 'utf-8', unicode_error))),
- -            ('bytea', None),
- -            ('rle_tuple', None),
- -        ]
- -
- -    @property
- -    def data_types():
- -        return map(lambda x: x[0], Column.data_type_conversions())
 
+class Column(object):
     def __init__(self, col, unicode_error=None):
         self.name = col['name'].decode()
         self.type_code = col['data_type_oid']
@@ -134,7 +106,7 @@
         self.scale = None
         self.null_ok = None
         self.unicode_error = unicode_error
- -        self.data_type_conversions = Column.data_type_conversions(unicode_error=self.unicode_error)
+        self.data_type_conversions = Column._data_type_conversions(unicode_error=self.unicode_error)
 
         # WORKAROUND: Treat LONGVARCHAR as VARCHAR
         if self.type_code == 115:
@@ -144,20 +116,53 @@
         if self.type_code >= len(self.data_type_conversions):
             self.type_code = 0
 
- -        #self.props = ColumnTuple(col['name'], col['data_type_oid'], None, col['data_type_size'], None, None, None)
- -        self.props = ColumnTuple(self.name, self.type_code, None, col['data_type_size'], None, None, None)
+        # self.props = ColumnTuple(col['name'], col['data_type_oid'], None, col['data_type_size'],
+        #                          None, None, None)
+        self.props = ColumnTuple(self.name, self.type_code, None, col['data_type_size'], None, None,
+                                 None)
 
- -        #self.converter = self.data_type_conversions[col['data_type_oid']][1]
+        # self.converter = self.data_type_conversions[col['data_type_oid']][1]
         self.converter = self.data_type_conversions[self.type_code][1]
 
         # things that are actually sent
- -#        self.name = col['name']
- -#        self.data_type = self.data_type_conversions[col['data_type_oid']][0]
- -#        self.type_modifier = col['type_modifier']
- -#        self.format = 'text' if col['format_code'] == 0 else 'binary'
- -#        self.table_oid = col['table_oid']
- -#        self.attribute_number = col['attribute_number']
- -#        self.size = col['data_type_size']
+        # self.name = col['name']
+        # self.data_type = self.data_type_conversions[col['data_type_oid']][0]
+        # self.type_modifier = col['type_modifier']
+        # self.format = 'text' if col['format_code'] == 0 else 'binary'
+        # self.table_oid = col['table_oid']
+        # self.attribute_number = col['attribute_number']
+        # self.size = col['data_type_size']
+
+    @classmethod
+    def _data_type_conversions(cls, unicode_error=None):
+        if unicode_error is None:
+            unicode_error = 'strict'
+        return [
+            ('unspecified', None),
+            ('tuple', None),
+            ('pos', None),
+            ('record', None),
+            ('unknown', None),
+            ('bool', lambda s: 't' == str(s, encoding=UTF_8, errors=unicode_error)),
+            ('integer', lambda s: int(s)),
+            ('float', lambda s: float(s)),
+            ('char', lambda s: str(s, encoding=UTF_8, errors=unicode_error)),
+            ('varchar', lambda s: str(s, encoding=UTF_8, errors=unicode_error)),
+            ('date', date_parse),
+            ('time', None),
+            ('timestamp', timestamp_parse),
+            ('timestamp_tz', timestamp_tz_parse),
+            ('interval', None),
+            ('time_tz', None),
+            ('numeric',
+             lambda s: Decimal(str(s, encoding=UTF_8, errors=unicode_error))),
+            ('bytea', None),
+            ('rle_tuple', None),
+        ]
+
+    @classmethod
+    def data_types(cls):
+        return tuple([name for name, value in cls._data_type_conversions()])
 
     def convert(self, s):
         if s is None:
@@ -165,13 +170,13 @@
         return self.converter(s) if self.converter is not None else s
 
     def __str__(self):
- -        return self.props.__str__()
+        return as_str(str(self.props))
 
     def __unicode__(self):
- -        return str(self.props.__str__())
+        return as_text(str(self.props))
 
     def __repr__(self):
- -        return self.props.__str__()
+        return as_str(str(self.props))
 
     def __iter__(self):
         for prop in self.props:
diff -Nru python-vertica-0.6.8/vertica_python/vertica/connection.py python-vertica-0.7.1/vertica_python/vertica/connection.py
- --- python-vertica-0.6.8/vertica_python/vertica/connection.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/connection.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,34 +1,41 @@
- -
+from __future__ import print_function, division, absolute_import
 
 import logging
- -import select
 import socket
 import ssl
- -
 from struct import unpack
 
+# noinspection PyCompatibility,PyUnresolvedReferences
 from builtins import str
+from six import raise_from
 
- -import vertica_python.errors as errors
- -import vertica_python.vertica.messages as messages
+from .. import errors
+from ..vertica import messages
+from ..vertica.cursor import Cursor
+from ..vertica.messages.message import BackendMessage, FrontendMessage
+from ..vertica.messages.frontend_messages import CancelRequest
 
- -from vertica_python.vertica.messages.message import BackendMessage
+logger = logging.getLogger('vertica')
 
- -from vertica_python.vertica.cursor import Cursor
- -from vertica_python.errors import SSLNotSupported
- -import collections
+ASCII = 'ascii'
 
- -logger = logging.getLogger('vertica')
+
+def connect(**kwargs):
+    """Opens a new connection to a Vertica database."""
+    return Connection(kwargs)
 
 
 class Connection(object):
     def __init__(self, options=None):
- -        self.reset_values()
+        self.parameters = {}
+        self.session_id = None
+        self.backend_pid = None
+        self.backend_key = None
+        self.transaction_status = None
+        self.socket = None
 
         options = options or {}
- -        self.options = dict(
- -            (key, value) for key, value in options.items() if value is not None
- -        )
+        self.options = {key: value for key, value in options.items() if value is not None}
 
         # we only support one cursor per connection
         self.options.setdefault('unicode_error', None)
@@ -53,16 +60,21 @@
         finally:
             self.close()
 
- -    #
- -    # dbApi methods
- -    #
- -
+    #############################################
+    # dbapi methods
+    #############################################
     def close(self):
         try:
             self.write(messages.Terminate())
         finally:
             self.close_socket()
 
+    def cancel(self):
+        if self.closed():
+            raise errors.ConnectionError('Connection is closed')
+
+        self.write(CancelRequest(backend_pid=self.backend_pid, backend_key=self.backend_key))
+
     def commit(self):
         if self.closed():
             raise errors.ConnectionError('Connection is closed')
@@ -88,9 +100,9 @@
         self._cursor.cursor_type = cursor_type
         return self._cursor
 
- -    #
- -    # Internal
- -    #
+    #############################################
+    # internal
+    #############################################
     def reset_values(self):
         self.parameters = {}
         self.session_id = None
@@ -105,14 +117,17 @@
 
         host = self.options.get('host')
         port = self.options.get('port')
+        connection_timeout = self.options.get('connection_timeout')
         raw_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         raw_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+        if connection_timeout is not None:
+            raw_socket.settimeout(connection_timeout)
         raw_socket.connect((host, port))
 
         ssl_options = self.options.get('ssl')
         if ssl_options is not None and ssl_options is not False:
             from ssl import CertificateError, SSLError
- -            raw_socket.sendall(messages.SslRequest().to_bytes())
+            raw_socket.sendall(messages.SslRequest().get_message())
             response = raw_socket.recv(1)
             if response in ('S', b'S'):
                 try:
@@ -121,11 +136,11 @@
                     else:
                         raw_socket = ssl.wrap_socket(raw_socket)
                 except CertificateError as e:
- -                    raise errors.ConnectionError('SSL: ' + e.message)
+                    raise_from(errors.ConnectionError, e)
                 except SSLError as e:
- -                    raise errors.ConnectionError('SSL: ' + e.reason)
+                    raise_from(errors.ConnectionError, e)
             else:
- -                raise SSLNotSupported("SSL requested but not supported by server")
+                raise errors.SSLNotSupported("SSL requested but not supported by server")
 
         self.socket = raw_socket
         return self.socket
@@ -142,32 +157,27 @@
         return not self.opened()
 
     def write(self, message):
- -
- -        is_stream = hasattr(message, "read_bytes")
- -
- -        if (hasattr(message, 'to_bytes') is False or isinstance(getattr(message, 'to_bytes'), collections.Callable) is False) and not is_stream:
+        if not isinstance(message, FrontendMessage):
             raise TypeError("invalid message: ({0})".format(message))
 
         logger.debug('=> %s', message)
- -        try:
- -
- -            if not is_stream:
- -                self._socket().sendall(message.to_bytes())
- -            else:
- -                # read to end in chunks
- -                while True:
- -                    data = message.read_bytes()
- -                    if len(data) == 0:
- -                        break
 
+        try:
+            for data in message.fetch_message():
+                try:
                     self._socket().sendall(data)
+                except Exception:
+                    logger.error("couldn't send message")
+                    raise
 
         except Exception as e:
             self.close_socket()
             if str(e) == 'unsupported authentication method: 9':
- -                raise errors.ConnectionError('Error during authentication. Your password might be expired.')
+                raise errors.ConnectionError(
+                    'Error during authentication. Your password might be expired.')
             else:
- -                raise errors.ConnectionError(str(e))
+                # noinspection PyTypeChecker
+                raise_from(errors.ConnectionError, e)
 
     def close_socket(self):
         try:
@@ -186,15 +196,14 @@
             size = unpack('!I', self.read_bytes(4))[0]
 
             if size < 4:
- -                raise errors.MessageError(
- -                    "Bad message size: {0}".format(size)
- -                )
- -            message = BackendMessage.factory(type_, self.read_bytes(size - 4))
+                raise errors.MessageError("Bad message size: {0}".format(size))
+            message = BackendMessage.from_type(type_, self.read_bytes(size - 4))
             logger.debug('<= %s', message)
             return message
         except (SystemError, IOError) as e:
             self.close_socket()
- -            raise errors.ConnectionError(str(e))
+            # noinspection PyTypeChecker
+            raise_from(errors.ConnectionError, e)
 
     def process_message(self, message):
         if isinstance(message, messages.ErrorResponse):
@@ -210,10 +219,12 @@
         elif isinstance(message, messages.ReadyForQuery):
             self.transaction_status = message.transaction_status
         elif isinstance(message, messages.CommandComplete):
- -            # TODO: im not ever seeing this actually returned by vertica...
+            # TODO: I'm not ever seeing this actually returned by vertica...
             # if vertica returns a row count, set the rowcount attribute in cursor
- -            #if hasattr(message, 'rows'):
- -            #    self.cursor.rowcount = message.rows
+            # if hasattr(message, 'rows'):
+            #     self.cursor.rowcount = message.rows
+            pass
+        elif isinstance(message, messages.EmptyQueryResponse):
             pass
         elif isinstance(message, messages.CopyInResponse):
             pass
@@ -224,17 +235,13 @@
         self._cursor._message = message
 
     def __str__(self):
- -        safe_options = dict(
- -            (key, value) for key, value in self.options.items() if key != 'password'
- -        )
+        safe_options = {key: value for key, value in self.options.items() if key != 'password'}
+
         s1 = "<Vertica.Connection:{0} parameters={1} backend_pid={2}, ".format(
- -            id(self), self.parameters, self.backend_pid
- -        )
+            id(self), self.parameters, self.backend_pid)
         s2 = "backend_key={0}, transaction_status={1}, socket={2}, options={3}>".format(
- -            self.backend_key, self.transaction_status, self.socket,
- -            safe_options,
- -        )
- -        return s1 + s2
+            self.backend_key, self.transaction_status, self.socket, safe_options)
+        return ''.join([s1, s2])
 
     def read_bytes(self, n):
         results = bytes()
@@ -242,14 +249,14 @@
             bytes_ = self._socket().recv(n - len(results))
             if not bytes_:
                 raise errors.ConnectionError("Connection closed by Vertica")
- -            results = results + bytes_
+            results += bytes_
         return results
 
     def startup_connection(self):
         # This doesn't handle Unicode usernames or passwords
- -        user = self.options['user'].encode('ascii')
- -        database = self.options['database'].encode('ascii')
- -        password = self.options['password'].encode('ascii')
+        user = self.options['user'].encode(ASCII)
+        database = self.options['database'].encode(ASCII)
+        password = self.options['password'].encode(ASCII)
 
         self.write(messages.Startup(user, database))
 
@@ -259,9 +266,9 @@
             if isinstance(message, messages.Authentication):
                 # Password message isn't right format ("incomplete message from client")
                 if message.code != messages.Authentication.OK:
- -                    self.write(messages.Password(password, message.code, {
- -                        'user': user, 'salt': getattr(message, 'salt', None)
- -                    }))
+                    self.write(messages.Password(password, message.code,
+                                                 {'user': user,
+                                                  'salt': getattr(message, 'salt', None)}))
             else:
                 self.process_message(message)
 
diff -Nru python-vertica-0.6.8/vertica_python/vertica/cursor.py python-vertica-0.7.1/vertica_python/vertica/cursor.py
- --- python-vertica-0.6.8/vertica_python/vertica/cursor.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/cursor.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,132 +1,178 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -import re
 import logging
+import re
 
- -from collections import OrderedDict
- -from builtins import str
+try:
+    from collections import OrderedDict  # python 2.7+ / 3
+except ImportError:
+    from ordereddict import OrderedDict  # python 2.6
 
- -import vertica_python.errors as errors
+from io import IOBase
 
- -import vertica_python.vertica.messages as messages
- -from vertica_python.vertica.column import Column
+import six
+# noinspection PyUnresolvedReferences,PyCompatibility
+from builtins import str
+from six import binary_type, text_type, string_types, BytesIO, StringIO
+
+try:
+    from psycopg2.extensions import QuotedString
+except ImportError:
+    class QuotedString(object):
+        def __init__(self, s):
+            raise ImportError("couldn't import psycopg2.extensions.QuotedString")
+
+from .. import errors
+from ..compat import as_text
+from ..vertica import messages
+from ..vertica.column import Column
 
 logger = logging.getLogger('vertica')
 
+UTF_8 = 'utf-8'
+
+if six.PY2:
+    # noinspection PyUnresolvedReferences
+    file_type = (IOBase, file)
+elif six.PY3:
+    file_type = (IOBase,)
+
+
 class Cursor(object):
- -    def __init__(self, connection, cursor_type=None, unicode_error='strict'):
+    # NOTE: this is used in executemany and is here for pandas compatibility
+    _insert_statement = re.compile(
+        u"INSERT\\s+INTO"
+        "\\s+((?P<schema>{id})\\.)?(?P<table>{id})"
+        "\\s*\\(\\s*(?P<variables>({id}(\\s*,\\s*{id})*)?\\s*)\\)"
+        "\\s+VALUES\\s*\\(\\s*(?P<values>.*)\\)".format(id=u"[a-zA-Z_][\\w\\d\\$_]*"), re.U | re.I)
+
+    def __init__(self, connection, cursor_type=None, unicode_error=None):
         self.connection = connection
         self.cursor_type = cursor_type
- -        self.unicode_error = unicode_error
+        self.unicode_error = unicode_error if unicode_error is not None else 'strict'
         self._closed = False
         self._message = None
+        self.operation = None
 
         self.error = None
 
         #
- -        # dbApi properties
+        # dbapi properties
         #
         self.description = None
         self.rowcount = -1
         self.arraysize = 1
 
+    #############################################
+    # supporting `with` statements
+    #############################################
     def __enter__(self):
         return self
 
     def __exit__(self, type_, value, traceback):
         self.close()
 
- -    #
- -    # dbApi methods
- -    #
- -
+    #############################################
+    # dbapi methods
+    #############################################
+    # noinspection PyMethodMayBeStatic
     def callproc(self, procname, parameters=None):
         raise errors.NotSupportedError('Cursor.callproc() is not implemented')
 
     def close(self):
         self._closed = True
 
+    def cancel(self):
+        if self.closed():
+            raise errors.Error('Cursor is closed')
+
+        self.connection.close()
+
     def execute(self, operation, parameters=None):
+        self.operation = as_text(operation)
+
         if self.closed():
             raise errors.Error('Cursor is closed')
 
         self.flush_to_query_ready()
 
         if parameters:
- -            # # optional requirement
- -            import six
- -            from psycopg2.extensions import adapt
- -
- -            if isinstance(parameters, dict):
- -                for key in parameters:
- -                    param = parameters[key]
- -                    # Make sure adapt() behaves properly
- -                    if self.is_stringy(param) and six.PY2:
- -                        param = param.encode('utf8')
- -                    v = adapt(param).getquoted()
- -
- -                    # Using a regex with word boundary to correctly handle params with similar names
- -                    # such as :s and :start
- -                    match_str = u':%s\\b' % str(key)
- -                    operation = re.sub(match_str, v.decode('utf-8'), operation, flags=re.UNICODE)
- -            elif isinstance(parameters, tuple):
- -                tlist = []
- -                for param in parameters:
- -                    if self.is_stringy(param) and six.PY2:
- -                        param = param.encode('utf8')
- -                    v = adapt(param).getquoted()
- -                    tlist.append(v.decode('utf-8'))
- -
- -                operation = operation % tuple(tlist)
- -            else:
- -                raise errors.Error("Argument 'parameters' must be dict or tuple")
+            # TODO: quote = True for backward compatibility. see if should be False.
+            operation = self.format_operation_with_parameters(operation, parameters, quote=True)
 
         self.rowcount = -1
 
         self.connection.write(messages.Query(operation))
 
         # read messages until we hit an Error, DataRow or ReadyForQuery
+        self._message = self.connection.read_message()
         while True:
- -            message = self.connection.read_message()
- -            # save the message because there's no way to undo the read
- -            self._message = message
- -            if isinstance(message, messages.ErrorResponse):
- -                raise errors.QueryError.from_error_response(message, operation)
- -            elif isinstance(message, messages.RowDescription):
- -                self.description = list(map(lambda fd: Column(fd, self.unicode_error), message.fields))
- -            elif isinstance(message, messages.DataRow):
+            if isinstance(self._message, messages.ErrorResponse):
+                raise errors.QueryError.from_error_response(self._message, operation)
+            elif isinstance(self._message, messages.RowDescription):
+                self.description = [Column(fd, self.unicode_error) for fd in self._message.fields]
+            elif isinstance(self._message, messages.DataRow):
+                break
+            elif isinstance(self._message, messages.ReadyForQuery):
                 break
- -            elif isinstance(message, messages.ReadyForQuery):
+            elif isinstance(self._message, messages.CommandComplete):
                 break
             else:
- -                self.connection.process_message(message)
+                self.connection.process_message(self._message)
 
+            self._message = self.connection.read_message()
+
+    def executemany(self, operation, seq_of_parameters):
+        operation = as_text(operation)
+
+        if not isinstance(seq_of_parameters, (list, tuple)):
+            raise TypeError("seq_of_parameters should be list/tuple")
+
+        m = self._insert_statement.match(operation)
+        if m:
+            schema = as_text(m.group('schema'))
+            table = as_text(m.group('table'))
+            variables = as_text(m.group('variables'))
+            values = as_text(m.group('values'))
+            if schema is not None:
+                table = "%s.%s" % (schema, table)
+
+            variables = ",".join([variable.strip() for variable in variables.split(",")])
+
+            values = ",".join([value.strip() for value in values.split(",")])
+            seq_of_values = [self.format_operation_with_parameters(values, parameters)
+                             for parameters in seq_of_parameters]
+            data = "\n".join(seq_of_values)
+
+            copy_statement = (
+                "COPY {table} ({variables}) FROM STDIN DELIMITER ',' ENCLOSED BY '\"' "
+                "ENFORCELENGTH ABORT ON ERROR").format(table=table, variables=variables)
 
- -    def is_stringy(self, s):
- -        try:
- -            # python 2 case
- -            return isinstance(s, basestring)
- -        except NameError:
- -            # python 3 case
- -            return isinstance(s, str)
+            self.copy(copy_statement, data)
+
+        else:
+            raise NotImplementedError(
+                "executemany is implemented for simple INSERT statements only")
 
     def fetchone(self):
- -        if isinstance(self._message, messages.DataRow):
- -            if self.rowcount == -1:
- -                self.rowcount = 1
+        while True:
+            if isinstance(self._message, messages.DataRow):
+                if self.rowcount == -1:
+                    self.rowcount = 1
+                else:
+                    self.rowcount += 1
+                row = self.row_formatter(self._message)
+                # fetch next message
+                self._message = self.connection.read_message()
+                return row
+            elif isinstance(self._message, messages.ReadyForQuery):
+                return None
+            elif isinstance(self._message, messages.CommandComplete):
+                return None
             else:
- -                self.rowcount += 1
+                self.connection.process_message(self._message)
 
- -            row = self.row_formatter(self._message)
- -            # fetch next message
             self._message = self.connection.read_message()
- -            return row
- -        elif isinstance(self._message, messages.ReadyForQuery):
- -            return None
- -        elif isinstance(self._message, messages.CommandComplete):
- -            return None
- -        else:
- -            self.connection.process_message(self._message)
 
     def iterate(self):
         row = self.fetchone()
@@ -155,7 +201,7 @@
         self.flush_to_command_complete()
 
         if self._message is None:
- -            return None
+            return False
         elif isinstance(self._message, messages.CommandComplete):
             # there might be another set, read next message to find out
             self._message = self.connection.read_message()
@@ -164,29 +210,31 @@
                 self._message = self.connection.read_message()
                 return True
             elif isinstance(self._message, messages.ReadyForQuery):
- -                return None
+                return False
+            elif isinstance(self._message, messages.ErrorResponse):
+                raise errors.QueryError.from_error_response(self._message, self.operation)
             else:
- -                raise errors.Error('Unexpected nextset() state after CommandComplete: ' + str(self._message))
+                raise errors.Error(
+                    'Unexpected nextset() state after CommandComplete: {0}'.format(self._message))
         elif isinstance(self._message, messages.ReadyForQuery):
             # no more sets left to be read
- -            return None
+            return False
         else:
- -            raise errors.Error('Unexpected nextset() state: ' + str(self._message))
- -
+            raise errors.Error('Unexpected nextset() state: {0}'.format(self._message))
 
- -    def setinputsizes(self):
+    def setinputsizes(self, sizes):
         pass
 
     def setoutputsize(self, size, column=None):
         pass
 
- -    #
- -    # Non dbApi methods
- -    #
+    #############################################
+    # non-dbapi methods
+    #############################################
     def flush_to_query_ready(self):
- -        # if the last message isnt empty or ReadyForQuery, read all remaining messages
- -        if(self._message is None
- -           or isinstance(self._message, messages.ReadyForQuery)):
+        # if the last message isn't empty or ReadyForQuery, read all remaining messages
+        if self._message is None \
+                or isinstance(self._message, messages.ReadyForQuery):
             return
 
         while True:
@@ -197,10 +245,9 @@
                 break
 
     def flush_to_command_complete(self):
- -        # if the last message isnt empty or CommandComplete, read messages until it is
- -        if(self._message is None
- -           or isinstance(self._message, messages.ReadyForQuery)
- -           or isinstance(self._message, messages.CommandComplete)):
+        # if the last message isn't empty or CommandComplete, read messages until it is
+        if self._message is None or isinstance(self._message, (messages.ReadyForQuery,
+                                                               messages.CommandComplete)):
             return
 
         while True:
@@ -209,20 +256,31 @@
                 self._message = message
                 break
 
- -
- -    # example:
- -    #
- -    # with open("/tmp/file.csv", "rb") as fs:
- -    #   cursor.copy("COPY table(field1,field2) FROM STDIN DELIMITER ',' ENCLOSED BY '\"'", fs, buffer_size=65536)
- -    #
- -
     def copy(self, sql, data, **kwargs):
+        """
+        
+        EXAMPLE:
+        >> with open("/tmp/file.csv", "rb") as fs:
+        >>     cursor.copy("COPY table(field1,field2) FROM STDIN DELIMITER ',' ENCLOSED BY '\"'",
+        >>                 fs, buffer_size=65536)
+
+        """
+        sql = as_text(sql)
 
         if self.closed():
             raise errors.Error('Cursor is closed')
 
         self.flush_to_query_ready()
 
+        if isinstance(data, binary_type):
+            stream = BytesIO(data)
+        elif isinstance(data, text_type):
+            stream = StringIO(data)
+        elif isinstance(data, file_type):
+            stream = data
+        else:
+            raise TypeError("Not valid type of data {0}".format(type(data)))
+
         self.connection.write(messages.Query(sql))
 
         while True:
@@ -232,29 +290,22 @@
                 raise errors.QueryError.from_error_response(message, sql)
 
             self.connection.process_message(message=message)
+
             if isinstance(message, messages.ReadyForQuery):
                 break
             elif isinstance(message, messages.CopyInResponse):
- -
- -                #write stuff
- -                if not hasattr(data, "read"):
- -                    self.connection.write(messages.CopyData(data))
- -                else:
- -                    # treat data as stream
- -                    self.connection.write(messages.CopyStream(data, **kwargs))
- -
+                self.connection.write(messages.CopyStream(stream, **kwargs))
                 self.connection.write(messages.CopyDone())
 
         if self.error is not None:
             raise self.error
 
- -    #
- -    # Internal
- -    #
- -
     def closed(self):
         return self._closed or self.connection.closed()
 
+    #############################################
+    # internal
+    #############################################
     def row_formatter(self, row_data):
         if self.cursor_type is None:
             return self.format_row_as_array(row_data)
@@ -263,7 +314,7 @@
         elif self.cursor_type in (dict, 'dict'):
             return self.format_row_as_dict(row_data)
         else:
- -            raise Exception('Unrecognized cursor_type: %r' % self.cursor_type)
+            raise TypeError('Unrecognized cursor_type: {0}'.format(self.cursor_type))
 
     def format_row_as_dict(self, row_data):
         return OrderedDict(
@@ -274,3 +325,49 @@
     def format_row_as_array(self, row_data):
         return [self.description[idx].convert(value)
                 for idx, value in enumerate(row_data.values)]
+
+    # noinspection PyArgumentList
+    def format_operation_with_parameters(self, operation, parameters, quote=True):
+        operation = as_text(operation)
+
+        if isinstance(parameters, dict):
+            for key, param in six.iteritems(parameters):
+                if not isinstance(key, string_types):
+                    key = str(key)
+                key = as_text(key)
+
+                if isinstance(param, string_types):
+                    param = as_text(param)
+                    if quote:
+                        param = self.format_quote(param)
+                else:
+                    param = str(param)
+                value = as_text(param)
+
+                # Using a regex with word boundary to correctly handle params with similar names
+                # such as :s and :start
+                match_str = u":{0}\\b".format(key)
+                operation = re.sub(match_str, value, operation, flags=re.U)
+
+        elif isinstance(parameters, (tuple, list)):
+            tlist = []
+            for param in parameters:
+                if isinstance(param, string_types):
+                    param = as_text(param)
+                    if quote:
+                        param = self.format_quote(param)
+                else:
+                    param = str(param)
+                value = as_text(param)
+
+                tlist.append(value)
+
+            operation = operation % tuple(tlist)
+        else:
+            raise errors.Error("Argument 'parameters' must be dict or tuple")
+
+        return operation
+
+    def format_quote(self, param):
+        # TODO Make sure adapt() behaves properly
+        return QuotedString(param.encode(UTF_8, self.unicode_error)).getquoted()
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/authentication.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/authentication.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/authentication.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/authentication.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,11 +1,12 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import unpack
 
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class Authentication(BackendMessage):
+    message_id = b'R'
 
     OK = 0
     KERBEROS_V5 = 2
@@ -18,6 +19,7 @@
     SSPI = 9
 
     def __init__(self, data):
+        BackendMessage.__init__(self)
         unpacked = unpack('!I{0}s'.format(len(data) - 4), data)
         self.code = unpacked[0]
         other = unpacked[1::][0]
@@ -27,4 +29,4 @@
             self.auth_data = other
 
 
- -Authentication._message_id(b'R')
+BackendMessage.register(Authentication)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/backend_key_data.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/backend_key_data.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/backend_key_data.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/backend_key_data.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,16 +1,18 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import unpack
 
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class BackendKeyData(BackendMessage):
+    message_id = b'K'
 
     def __init__(self, data):
+        BackendMessage.__init__(self)
         unpacked = unpack('!2I', data)
         self.pid = unpacked[0]
         self.key = unpacked[1]
 
 
- -BackendKeyData._message_id(b'K')
+BackendMessage.register(BackendKeyData)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/bind_complete.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/bind_complete.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/bind_complete.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/bind_complete.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,10 +1,10 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class BindComplete(BackendMessage):
- -    pass
+    message_id = b'2'
 
 
- -BindComplete._message_id(b'2')
+BackendMessage.register(BindComplete)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/close_complete.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/close_complete.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/close_complete.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/close_complete.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,10 +1,10 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class CloseComplete(BackendMessage):
- -    pass
+    message_id = b'3'
 
 
- -CloseComplete._message_id(b'3')
+BackendMessage.register(CloseComplete)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/command_complete.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/command_complete.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/command_complete.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/command_complete.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,16 +1,17 @@
- -
+from __future__ import print_function, division, absolute_import
 
 import re
 
 from struct import unpack
 
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class CommandComplete(BackendMessage):
+    message_id = b'C'
 
     def __init__(self, data):
- -
+        BackendMessage.__init__(self)
         data = unpack('{0}sx'.format(len(data) - 1), data)[0]
 
         if re.match(b"INSERT", data) is not None:
@@ -29,4 +30,4 @@
             self.tag = data
 
 
- -CommandComplete._message_id(b'C')
+BackendMessage.register(CommandComplete)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/copy_in_response.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/copy_in_response.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/copy_in_response.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/copy_in_response.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,16 +1,18 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import unpack
 
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class CopyInResponse(BackendMessage):
+    message_id = b'G'
 
     def __init__(self, data):
- -        values = unpack('!B{0}H'.format((len(data) - 1)//2), data)
+        BackendMessage.__init__(self)
+        values = unpack('!B{0}H'.format((len(data) - 1) // 2), data)
         self.format = values[0]
         self.column_formats = values[2::]
 
 
- -CopyInResponse._message_id(b'G')
+BackendMessage.register(CopyInResponse)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/data_row.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/data_row.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/data_row.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/data_row.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,14 +1,17 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -from builtins import range
 from struct import unpack, unpack_from
 
- -from vertica_python.vertica.messages.message import BackendMessage
+from six.moves import range
+
+from ..message import BackendMessage
 
 
 class DataRow(BackendMessage):
+    message_id = b'D'
 
     def __init__(self, data):
+        BackendMessage.__init__(self)
         self.values = []
         field_count = unpack('!H', data[0:2])[0]
         pos = 2
@@ -26,4 +29,4 @@
             pos += (4 + max(size, 0))
 
 
- -DataRow._message_id(b'D')
+BackendMessage.register(DataRow)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/empty_query_response.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/empty_query_response.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/empty_query_response.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/empty_query_response.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,10 +1,14 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class EmptyQueryResponse(BackendMessage):
- -    pass
+    message_id = b'I'
+
+    def __init__(self, data=None):
+        BackendMessage.__init__(self)
+        self.data = data
 
 
- -EmptyQueryResponse._message_id(b'I')
+BackendMessage.register(EmptyQueryResponse)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/error_response.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/error_response.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/error_response.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/error_response.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,11 +1,11 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 from vertica_python.vertica.messages.backend_messages.notice_response import NoticeResponse
 
 
 class ErrorResponse(NoticeResponse, BackendMessage):
- -    pass
+    message_id = b'E'
 
 
- -ErrorResponse._message_id(b'E')
+BackendMessage.register(ErrorResponse)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/__init__.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/__init__.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/__init__.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/__init__.py	2017-05-14 00:40:53.000000000 +0200
@@ -0,0 +1,25 @@
+from __future__ import print_function, division, absolute_import
+
+from .authentication import Authentication
+from .backend_key_data import BackendKeyData
+from .bind_complete import BindComplete
+from .close_complete import CloseComplete
+from .command_complete import CommandComplete
+from .copy_in_response import CopyInResponse
+from .data_row import DataRow
+from .empty_query_response import EmptyQueryResponse
+from .error_response import ErrorResponse
+from .no_data import NoData
+from .notice_response import NoticeResponse
+from .parameter_description import ParameterDescription
+from .parameter_status import ParameterStatus
+from .parse_complete import ParseComplete
+from .portal_suspended import PortalSuspended
+from .ready_for_query import ReadyForQuery
+from .row_description import RowDescription
+from .unknown import Unknown
+
+__all__ = ['RowDescription', 'ReadyForQuery', 'PortalSuspended', 'ParseComplete', 'ParameterStatus',
+           'NoticeResponse', 'NoData', 'ErrorResponse', 'EmptyQueryResponse', 'DataRow',
+           'CopyInResponse', 'CommandComplete', 'CloseComplete', 'BindComplete', 'Authentication',
+           'BackendKeyData', 'ParameterDescription', 'Unknown']
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/no_data.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/no_data.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/no_data.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/no_data.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,10 +1,10 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class NoData(BackendMessage):
- -    pass
+    message_id = b'n'
 
 
- -NoData._message_id(b'n')
+BackendMessage.register(NoData)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/notice_response.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/notice_response.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/notice_response.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/notice_response.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,11 +1,12 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import unpack_from
 
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class NoticeResponse(BackendMessage):
+    message_id = b'N'
 
     FIELDS_DEFINITIONS = [
         {'type': b'q', 'name': "Internal Query", 'method': 'internal_query'},
@@ -22,13 +23,8 @@
         {'type': b'L', 'name': "Line", 'method': 'line'}
     ]
 
- -    def FIELDS(self):
- -        pairs = []
- -        for field in self.FIELDS_DEFINITIONS:
- -            pairs.append((field['type'], field['name']))
- -        return dict(pairs)
- -
     def __init__(self, data):
+        BackendMessage.__init__(self)
         self.values = {}
 
         pos = 0
@@ -40,7 +36,7 @@
             key = unpacked[0]
             value = unpacked[1]
 
- -            self.values[self.FIELDS()[key]] = value
+            self.values[self.fields()[key]] = value
             pos += (len(value) + 2)
 
         # May want to break out into a function at some point
@@ -48,6 +44,14 @@
             if self.values.get(field_def['name'], None) is not None:
                 setattr(self, field_def['method'], self.values[field_def['name']])
 
+    def fields(self):
+        # was FIELDS before
+        # TODO verify that it doesn't break anything
+        pairs = []
+        for field in self.FIELDS_DEFINITIONS:
+            pairs.append((field['type'], field['name']))
+        return dict(pairs)
+
     def error_message(self):
         ordered = []
         for field in self.FIELDS_DEFINITIONS:
@@ -56,4 +60,4 @@
         return ', '.join(ordered)
 
 
- -NoticeResponse._message_id(b'N')
+BackendMessage.register(NoticeResponse)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/parameter_description.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/parameter_description.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/parameter_description.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/parameter_description.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,17 +1,19 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import unpack, unpack_from
 
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 from vertica_python.vertica.column import Column
 
 
 class ParameterDescription(BackendMessage):
+    message_id = b't'
 
     def __init__(self, data):
+        BackendMessage.__init__(self)
         parameter_count = unpack('!H', data)[0]
         parameter_type_ids = unpack_from("!{0}N".format(parameter_count), data, 2)
- -        self.parameter_types = [Column.data_types[dtid] for dtid in parameter_type_ids]
+        self.parameter_types = [Column.data_types()[dtid] for dtid in parameter_type_ids]
 
 
- -ParameterDescription._message_id(b't')
+BackendMessage.register(ParameterDescription)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/parameter_status.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/parameter_status.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/parameter_status.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/parameter_status.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,17 +1,19 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import unpack
 
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class ParameterStatus(BackendMessage):
+    message_id = b'S'
 
     def __init__(self, data):
+        BackendMessage.__init__(self)
         null_byte = data.find(b'\x00')
         unpacked = unpack('{0}sx{1}sx'.format(null_byte - 1, len(data) - null_byte - 1), data)
         self.name = unpacked[0]
         self.value = unpacked[1]
 
 
- -ParameterStatus._message_id(b'S')
+BackendMessage.register(ParameterStatus)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/parse_complete.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/parse_complete.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/parse_complete.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/parse_complete.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,10 +1,10 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class ParseComplete(BackendMessage):
- -    pass
+    message_id = b'1'
 
 
- -ParseComplete._message_id(b'1')
+BackendMessage.register(ParseComplete)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/portal_suspended.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/portal_suspended.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/portal_suspended.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/portal_suspended.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,10 +1,10 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class PortalSuspended(BackendMessage):
- -    pass
+    message_id = b's'
 
 
- -PortalSuspended._message_id(b's')
+BackendMessage.register(PortalSuspended)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/ready_for_query.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/ready_for_query.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/ready_for_query.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/ready_for_query.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,11 +1,12 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import unpack
 
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class ReadyForQuery(BackendMessage):
+    message_id = b'Z'
 
     STATUSES = {
         b'I': 'no_transaction',
@@ -14,7 +15,8 @@
     }
 
     def __init__(self, data):
+        BackendMessage.__init__(self)
         self.transaction_status = self.STATUSES[unpack('c', data)[0]]
 
 
- -ReadyForQuery._message_id(b'Z')
+BackendMessage.register(ReadyForQuery)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/row_description.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/row_description.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/row_description.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/row_description.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,20 +1,24 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import unpack, unpack_from
 
- -from vertica_python.vertica.messages.message import BackendMessage
+from six.moves import range
+
+from ..message import BackendMessage
 
 
 class RowDescription(BackendMessage):
+    message_id = b'T'
 
     def __init__(self, data):
- -
+        BackendMessage.__init__(self)
         self.fields = []
         field_count = unpack('!H', data[0:2])[0]
         pos = 2
 
         for i in range(field_count):
- -            field_info = unpack_from("!{0}sxIHIHIH".format(data.find(b'\x00', pos) - pos), data, pos)
+            field_info = unpack_from("!{0}sxIHIHIH".format(data.find(b'\x00', pos) - pos), data,
+                                     pos)
             self.fields.append({
                 'name': field_info[0],
                 'table_oid': field_info[1],
@@ -28,4 +32,4 @@
             pos += 19 + len(field_info[0])
 
 
- -RowDescription._message_id(b'T')
+BackendMessage.register(RowDescription)
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/unknown.py python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/unknown.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/backend_messages/unknown.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/backend_messages/unknown.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,10 +1,14 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -from vertica_python.vertica.messages.message import BackendMessage
+from ..message import BackendMessage
 
 
 class Unknown(BackendMessage):
- -
     def __init__(self, message_id, data):
- -        self.message_id = message_id
+        BackendMessage.__init__(self)
+        self._message_id = message_id
         self.data = data
+
+    @property
+    def message_id(self):
+        return self._message_id
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/bind.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/bind.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/bind.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/bind.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,24 +1,29 @@
+from __future__ import print_function, division, absolute_import
+
 from struct import pack
 
- -from vertica_python.vertica.messages.message import FrontendMessage
+from ..message import BulkFrontendMessage
 
 
- -class Bind(FrontendMessage):
+class Bind(BulkFrontendMessage):
+    message_id = b'B'
 
     def __init__(self, portal_name, prepared_statement_name, parameter_values):
- -        self.portal_name = portal_name
- -        self.prepared_statement_name = prepared_statement_name
- -        self.parameter_values = parameter_values
- -
- -    def to_bytes(self):
- -        bytes_ = pack('!{0}sx{1}sxHH'.format(len(self.portal_name), len(self.prepared_statement_name)), self.portal_name, self.prepared_statement_name, 0, len(self.parameter_values))
- -        for val in self.parameter_values.values():
+        BulkFrontendMessage.__init__(self)
+        self._portal_name = portal_name
+        self._prepared_statement_name = prepared_statement_name
+        self._parameter_values = parameter_values
+
+    def read_bytes(self):
+        bytes_ = pack('!{0}sx{1}sxHH'.format(
+            len(self._portal_name), len(self._prepared_statement_name)),
+            self._portal_name, self._prepared_statement_name, 0, len(self._parameter_values))
+
+        for val in self._parameter_values.values():
             if val is None:
                 bytes_ += pack('!I', [-1])
             else:
                 bytes_ += pack('!I{0}s'.format(len(val)), len(val), val)
         bytes_ += pack('!H', [0])
- -        return self.message_string(bytes_)
- -
 
- -Bind._message_id(b'B')
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/cancel_request.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/cancel_request.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/cancel_request.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/cancel_request.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,18 +1,18 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import pack
 
- -from vertica_python.vertica.messages.message import FrontendMessage
+from ..message import BulkFrontendMessage
 
 
- -class CancelRequest(FrontendMessage):
+class CancelRequest(BulkFrontendMessage):
+    message_id = None
 
     def __init__(self, backend_pid, backend_key):
- -        self.backend_pid = backend_pid
- -        self.backend_key = backend_key
- -
- -    def to_bytes(self):
- -        return self.message_string(pack('!3I', 80877102, self.backend_pid, self.backend_key))
- -
- -
- -CancelRequest._message_id(None)
+        BulkFrontendMessage.__init__(self)
+        self._backend_pid = backend_pid
+        self._backend_key = backend_key
+
+    def read_bytes(self):
+        bytes_ = pack('!3I', 80877102, self._backend_pid, self._backend_key)
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/close.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/close.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/close.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/close.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,24 +1,26 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import pack
 
- -from vertica_python.vertica.messages.message import FrontendMessage
+from ..message import BulkFrontendMessage
 
 
- -class Close(FrontendMessage):
+class Close(BulkFrontendMessage):
+    message_id = b'C'
 
     def __init__(self, close_type, close_name):
- -        self.close_name = close_name
+        BulkFrontendMessage.__init__(self)
+
+        self._close_name = close_name
 
         if close_type == 'portal':
- -            self.close_type = 'P'
+            self._close_type = 'P'
         elif close_type == 'prepared_statement':
- -            self.close_type = 'S'
+            self._close_type = 'S'
         else:
- -            raise ValueError("{0} is not a valid close_type.  Must be either portal or prepared_statement".format(close_type))
- -
- -    def to_bytes(self):
- -        return self.message_string(pack('c{0}sx'.format(len(self.close_name)), self.close_type, self.close_name))
- -
+            raise ValueError("{0} is not a valid close_type. "
+                             "Must be either portal or prepared_statement".format(close_type))
 
- -Close._message_id(b'C')
+    def read_bytes(self):
+        bytes_ = pack('c{0}sx'.format(len(self._close_name)), self._close_type, self._close_name)
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/copy_data.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/copy_data.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/copy_data.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/copy_data.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,19 +1,26 @@
+from __future__ import print_function, division, absolute_import
 
+from six import text_type, binary_type
 
- -from struct import pack
+from ..message import BulkFrontendMessage
 
- -from vertica_python.vertica.messages.message import FrontendMessage
+UTF_8 = 'utf-8'
 
 
- -class CopyData(FrontendMessage):
+class CopyData(BulkFrontendMessage):
+    message_id = b'd'
 
- -    def __init__(self, data):
- -        self.data = data
+    def __init__(self, data, unicode_error='strict'):
+        BulkFrontendMessage.__init__(self)
+        self._unicode_error = unicode_error
+        if isinstance(data, text_type):
+            self._data = self._data.encode(encoding=UTF_8, errors=self._unicode_error)
+        elif isinstance(data, binary_type):
+            self._data = data
+        else:
+            raise TypeError("should be string or bytes")
 
- -    def to_bytes(self):
+    def read_bytes(self):
         # to deal with unicode strings
- -        encoded = self.data.encode('utf-8')
- -        return self.message_string(pack('{0}s'.format(len(encoded)), encoded))
- -
- -
- -CopyData._message_id(b'd')
+        bytes_ = self._data
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/copy_done.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/copy_done.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/copy_done.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/copy_done.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,10 +1,8 @@
+from __future__ import print_function, division, absolute_import
 
+from ..message import BulkFrontendMessage
 
- -from vertica_python.vertica.messages.message import FrontendMessage
 
+class CopyDone(BulkFrontendMessage):
+    message_id = b'c'
 
- -class CopyDone(FrontendMessage):
- -    pass
- -
- -
- -CopyDone._message_id(b'c')
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/copy_fail.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/copy_fail.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/copy_fail.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/copy_fail.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,17 +1,17 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import pack
 
- -from vertica_python.vertica.messages.message import FrontendMessage
+from ..message import BulkFrontendMessage
 
 
- -class CopyFail(FrontendMessage):
+class CopyFail(BulkFrontendMessage):
+    message_id = b'f'
 
     def __init__(self, error_message):
- -        self.error_message = error_message
- -
- -    def to_bytes(self):
- -        return self.message_string(pack('{0}sx'.format(len(self.error_message)), self.error_message))
- -
+        BulkFrontendMessage.__init__(self)
+        self._error_message = error_message
 
- -CopyFail._message_id(b'f')
+    def read_bytes(self):
+        bytes_ = pack('{0}sx'.format(len(self._error_message)), self._error_message)
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/copy_stream.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/copy_stream.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/copy_stream.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/copy_stream.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,22 +1,34 @@
+from __future__ import print_function, division, absolute_import
 
+from six import text_type, binary_type
 
- -from struct import pack
+from ..message import StreamFrontendMessage
 
- -from vertica_python.vertica.messages.message import FrontendMessage
+DEFAULT_BUFFER_SIZE = 131072
 
- -class CopyStream(FrontendMessage):
+UTF_8 = 'utf-8'
 
- -    def __init__(self, stream, buffer_size=131072):
- -        self.stream = stream
- -        self.bufsize = buffer_size
 
- -    def read_bytes(self):
+class CopyStream(StreamFrontendMessage):
+    message_id = b'd'
 
- -        data = self.stream.read(self.bufsize)
+    def __init__(self, stream, buffer_size=DEFAULT_BUFFER_SIZE, unicode_error='strict'):
+        StreamFrontendMessage.__init__(self)
+        self._stream = stream
+        self._unicode_error = unicode_error
+        self._buffer_size = buffer_size
 
- -        if len(data) == 0:
- -            return data
+    def stream_bytes(self):
+        while True:
+            chunk = self._stream.read(self._buffer_size)
+            if isinstance(chunk, text_type):
+                bytes_ = chunk.encode(encoding=UTF_8, errors=self._unicode_error)
+            elif isinstance(chunk, binary_type):
+                bytes_ = chunk
+            else:
+                raise TypeError("should be string or bytes")
 
- -        return self.message_string(data)
+            if not chunk:
+                break
 
- -CopyStream._message_id(b'd')
+            yield bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/crypt_windows.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/crypt_windows.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/crypt_windows.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/crypt_windows.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,4 +1,7 @@
 #!/usr/bin/env python
+from __future__ import print_function, division, absolute_import
+
+from six.moves import range
 
 # Initial permutation
 IP = (
@@ -6,7 +9,7 @@
     60, 52, 44, 36, 28, 20, 12, 4,
     62, 54, 46, 38, 30, 22, 14, 6,
     64, 56, 48, 40, 32, 24, 16, 8,
- -    57, 49, 41, 33, 25, 17,  9, 1,
+    57, 49, 41, 33, 25, 17, 9, 1,
     59, 51, 43, 35, 27, 19, 11, 3,
     61, 53, 45, 37, 29, 21, 13, 5,
     63, 55, 47, 39, 31, 23, 15, 7,
@@ -21,31 +24,31 @@
     36, 4, 44, 12, 52, 20, 60, 28,
     35, 3, 43, 11, 51, 19, 59, 27,
     34, 2, 42, 10, 50, 18, 58, 26,
- -    33, 1, 41,  9, 49, 17, 57, 25,
+    33, 1, 41, 9, 49, 17, 57, 25,
 )
 
 # Permuted-choice 1 from the key bits to yield C and D.
 # Note that bits 8,16... are left out: They are intended for a parity check.
 PC1_C = (
- -    57, 49, 41, 33, 25, 17,  9,
+    57, 49, 41, 33, 25, 17, 9,
     1, 58, 50, 42, 34, 26, 18,
- -    10,  2, 59, 51, 43, 35, 27,
- -    19, 11,  3, 60, 52, 44, 36,
+    10, 2, 59, 51, 43, 35, 27,
+    19, 11, 3, 60, 52, 44, 36,
 )
 PC1_D = (
     63, 55, 47, 39, 31, 23, 15,
- -     7, 62, 54, 46, 38, 30, 22,
- -    14,  6, 61, 53, 45, 37, 29,
- -    21, 13,  5, 28, 20, 12,  4,
+    7, 62, 54, 46, 38, 30, 22,
+    14, 6, 61, 53, 45, 37, 29,
+    21, 13, 5, 28, 20, 12, 4,
 )
 
 # Permuted-choice 2, to pick out the bits from the CD array that generate the
 # key schedule.
 PC2_C = (
- -    14, 17, 11, 24,  1,  5,
- -     3, 28, 15,  6, 21, 10,
- -    23, 19, 12,  4, 26,  8,
- -    16,  7, 27, 20, 13,  2,
+    14, 17, 11, 24, 1, 5,
+    3, 28, 15, 6, 21, 10,
+    23, 19, 12, 4, 26, 8,
+    16, 7, 27, 20, 13, 2,
 )
 PC2_D = (
     41, 52, 31, 37, 47, 55,
@@ -64,78 +67,78 @@
 # The E bit-selection table.
 E = [0] * 48
 e2 = (
- -    32,  1,  2,  3,  4,  5,
- -     4,  5,  6,  7,  8,  9,
- -     8,  9, 10, 11, 12, 13,
+    32, 1, 2, 3, 4, 5,
+    4, 5, 6, 7, 8, 9,
+    8, 9, 10, 11, 12, 13,
     12, 13, 14, 15, 16, 17,
     16, 17, 18, 19, 20, 21,
     20, 21, 22, 23, 24, 25,
     24, 25, 26, 27, 28, 29,
- -    28, 29, 30, 31, 32,  1,
+    28, 29, 30, 31, 32, 1,
 )
 
 # S-boxes.
 S = (
     (
- -        14,  4, 13,  1,  2, 15, 11,  8,  3, 10,  6, 12,  5,  9,  0,  7,
- -         0, 15,  7,  4, 14,  2, 13,  1, 10,  6, 12, 11,  9,  5,  3,  8,
- -         4,  1, 14,  8, 13,  6,  2, 11, 15, 12,  9,  7,  3, 10,  5,  0,
- -        15, 12,  8,  2,  4,  9,  1,  7,  5, 11,  3, 14, 10,  0,  6, 13
+        14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
+        0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
+        4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
+        15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13
     ),
     (
- -        15,  1,  8, 14,  6, 11,  3,  4,  9,  7,  2, 13, 12,  0,  5, 10,
- -         3, 13,  4,  7, 15,  2,  8, 14, 12,  0,  1, 10,  6,  9, 11,  5,
- -         0, 14,  7, 11, 10,  4, 13,  1,  5,  8, 12,  6,  9,  3,  2, 15,
- -        13,  8, 10,  1,  3, 15,  4,  2, 11,  6,  7, 12,  0,  5, 14,  9
+        15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
+        3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
+        0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
+        13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9
     ),
     (
- -        10,  0,  9, 14,  6,  3, 15,  5,  1, 13, 12,  7, 11,  4,  2,  8,
- -        13,  7,  0,  9,  3,  4,  6, 10,  2,  8,  5, 14, 12, 11, 15,  1,
- -        13,  6,  4,  9,  8, 15,  3,  0, 11,  1,  2, 12,  5, 10, 14,  7,
- -         1, 10, 13,  0,  6,  9,  8,  7,  4, 15, 14,  3, 11,  5,  2, 12
+        10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
+        13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
+        13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
+        1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12
     ),
     (
- -         7, 13, 14,  3,  0,  6,  9, 10,  1,  2,  8,  5, 11, 12,  4, 15,
- -        13,  8, 11,  5,  6, 15,  0,  3,  4,  7,  2, 12,  1, 10, 14,  9,
- -        10,  6,  9,  0, 12, 11,  7, 13, 15,  1,  3, 14,  5,  2,  8,  4,
- -         3, 15,  0,  6, 10,  1, 13,  8,  9,  4,  5, 11, 12,  7,  2, 14
+        7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
+        13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
+        10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
+        3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14
     ),
     (
- -         2, 12,  4,  1,  7, 10, 11,  6,  8,  5,  3, 15, 13,  0, 14,  9,
- -        14, 11,  2, 12,  4,  7, 13,  1,  5,  0, 15, 10,  3,  9,  8,  6,
- -         4,  2,  1, 11, 10, 13,  7,  8, 15,  9, 12,  5,  6,  3,  0, 14,
- -        11,  8, 12,  7,  1, 14,  2, 13,  6, 15,  0,  9, 10,  4,  5,  3
+        2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
+        14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
+        4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
+        11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3
     ),
     (
- -        12,  1, 10, 15,  9,  2,  6,  8,  0, 13,  3,  4, 14,  7,  5, 11,
- -        10, 15,  4,  2,  7, 12,  9,  5,  6,  1, 13, 14,  0, 11,  3,  8,
- -         9, 14, 15,  5,  2,  8, 12,  3,  7,  0,  4, 10,  1, 13, 11,  6,
- -         4,  3,  2, 12,  9,  5, 15, 10, 11, 14,  1,  7,  6,  0,  8, 13
+        12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
+        10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
+        9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
+        4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13
     ),
     (
- -         4, 11,  2, 14, 15,  0,  8, 13,  3, 12,  9,  7,  5, 10,  6,  1,
- -        13,  0, 11,  7,  4,  9,  1, 10, 14,  3,  5, 12,  2, 15,  8,  6,
- -         1,  4, 11, 13, 12,  3,  7, 14, 10, 15,  6,  8,  0,  5,  9,  2,
- -         6, 11, 13,  8,  1,  4, 10,  7,  9,  5,  0, 15, 14,  2,  3, 12
+        4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
+        13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
+        1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
+        6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12
     ),
     (
- -        13,  2,  8,  4,  6, 15, 11,  1, 10,  9,  3, 14,  5,  0, 12,  7,
- -         1, 15, 13,  8, 10,  3,  7,  4, 12,  5,  6, 11,  0, 14,  9,  2,
- -         7, 11,  4,  1,  9, 12, 14,  2,  0,  6, 10, 13, 15,  3,  5,  8,
- -         2,  1, 14,  7,  4, 10,  8, 13, 15, 12,  9,  0,  3,  5,  6, 11
+        13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
+        1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
+        7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
+        2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
     )
 )
 
 # P is a permutation on the selected combination of the current L and key.
 P = (
- -    16,  7, 20, 21,
+    16, 7, 20, 21,
     29, 12, 28, 17,
- -     1, 15, 23, 26,
- -     5, 18, 31, 10,
- -     2,  8, 24, 14,
- -    32, 27,  3,  9,
- -    19, 13, 30,  6,
- -    22, 11,  4, 25,
+    1, 15, 23, 26,
+    5, 18, 31, 10,
+    2, 8, 24, 14,
+    32, 27, 3, 9,
+    19, 13, 30, 6,
+    22, 11, 4, 25,
 )
 
 # The combination of the key and the input, before selection.
@@ -180,10 +183,11 @@
     for i in range(48):
         E[i] = e2[i]
 
+
 def __encrypt(block):
     global preS
 
- -    left, right = [], []    # block in two halves
+    left, right = [], []  # block in two halves
     f = [0] * 32
 
     # First, permute the bits in the input
@@ -244,6 +248,7 @@
 
     return block
 
+
 def crypt(pw, salt):
     iobuf = []
 
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/describe.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/describe.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/describe.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/describe.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,24 +1,27 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import pack
 
- -from vertica_python.vertica.messages.message import FrontendMessage
+from ..message import BulkFrontendMessage
 
 
- -class Describe(FrontendMessage):
+class Describe(BulkFrontendMessage):
+    message_id = b'D'
 
     def __init__(self, describe_type, describe_name):
- -        self.describe_name = describe_name
+        BulkFrontendMessage.__init__(self)
+
+        self._describe_name = describe_name
 
         if describe_type == 'portal':
- -            self.describe_type = 'P'
+            self._describe_type = 'P'
         elif describe_type == 'prepared_statement':
- -            self.describe_type = 'S'
+            self._describe_type = 'S'
         else:
- -            raise ValueError("{0} is not a valid describe_type.  Must be either portal or prepared_statement".format(describe_type))
- -
- -    def to_bytes(self):
- -        return self.message_string(pack('c{0}sx'.format(len(self.describe_name)), self.describe_type, self.describe_name))
- -
+            raise ValueError("{0} is not a valid describe_type. "
+                             "Must be either portal or prepared_statement".format(describe_type))
 
- -Describe._message_id(b'D')
+    def read_bytes(self):
+        bytes_ = pack('c{0}sx'.format(len(self._describe_name)), self._describe_type,
+                      self._describe_name)
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/execute.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/execute.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/execute.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/execute.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,18 +1,18 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import pack
 
- -from vertica_python.vertica.messages.message import FrontendMessage
+from ..message import BulkFrontendMessage
 
 
- -class Execute(FrontendMessage):
+class Execute(BulkFrontendMessage):
+    message_id = b'E'
 
     def __init__(self, portal_name, max_rows):
- -        self.portal_name = portal_name
- -        self.max_rows = max_rows
- -
- -    def to_bytes(self):
- -        return self.message_string(pack('!{0}sxI'.format(len(self.portal_name)), self.portal_name, self.max_rows))
- -
- -
- -Execute._message_id(b'E')
+        BulkFrontendMessage.__init__(self)
+        self._portal_name = portal_name
+        self._max_rows = max_rows
+
+    def read_bytes(self):
+        bytes_ = pack('!{0}sxI'.format(len(self._portal_name)), self._portal_name, self._max_rows)
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/flush.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/flush.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/flush.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/flush.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,10 +1,7 @@
+from __future__ import print_function, division, absolute_import
 
+from ..message import BulkFrontendMessage
 
- -from vertica_python.vertica.messages.message import FrontendMessage
 
- -
- -class Flush(FrontendMessage):
- -    pass
- -
- -
- -Flush._message_id(b'H')
+class Flush(BulkFrontendMessage):
+    message_id = b'H'
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/__init__.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/__init__.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/__init__.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/__init__.py	2017-05-14 00:40:53.000000000 +0200
@@ -0,0 +1,23 @@
+from __future__ import print_function, division, absolute_import
+
+from .bind import Bind
+from .cancel_request import CancelRequest
+from .close import Close
+from .copy_data import CopyData
+from .copy_stream import CopyStream
+from .copy_done import CopyDone
+from .copy_fail import CopyFail
+from .describe import Describe
+from .execute import Execute
+from .flush import Flush
+from .parse import Parse
+from .password import Password
+from .query import Query
+from .ssl_request import SslRequest
+from .startup import Startup
+from .sync import Sync
+from .terminate import Terminate
+
+__all__ = ['Bind', 'Query', 'CancelRequest', 'Close', 'CopyData', 'CopyDone', 'CopyFail',
+           'CopyStream', 'Describe', 'Execute', 'Flush', 'Parse', 'Terminate', 'Password',
+           'SslRequest', 'Startup', 'Sync']
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/parse.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/parse.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/parse.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/parse.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,23 +1,26 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import pack
 
- -from vertica_python.vertica.messages.message import FrontendMessage
+from ..message import BulkFrontendMessage
 
 
- -class Parse(FrontendMessage):
+class Parse(BulkFrontendMessage):
+    message_id = b'P'
 
     def __init__(self, name, query, param_types):
- -        self.name = name
- -        self.query = query
- -        self.param_types = param_types
+        BulkFrontendMessage.__init__(self)
+
+        self._name = name
+        self._query = query
+        self._param_types = param_types
 
- -    def to_bytes(self):
+    def read_bytes(self):
         params = ""
- -        for param in self.param_types:
+        for param in self._param_types:
             params = params + param
 
- -        return self.message_string(pack('!{0}sx{1}sxH{2}I'.format(len(self.name), len(self.query), len(self.param_types)), self.name, self.query, len(self.param_types), params))
- -
- -
- -Parse._message_id(b'P')
+        bytes_ = pack('!{0}sx{1}sxH{2}I'.format(len(self._name), len(self._query),
+                                                len(self._param_types)),
+                      self._name, self._query, len(self._param_types), params)
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/password.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/password.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/password.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/password.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,59 +1,62 @@
- -
+from __future__ import print_function, division, absolute_import
 
 import os
+import hashlib
+from struct import pack
+
+import six
+
+from ..message import BulkFrontendMessage
+from ..backend_messages.authentication import Authentication
 
 if os.name == 'nt':
     from . import crypt_windows as crypt
 else:
     import crypt
- -import hashlib
 
- -from struct import pack
- -
- -import six
- -from vertica_python.vertica.messages.message import FrontendMessage
- -from vertica_python.vertica.messages.backend_messages.authentication import Authentication
+ASCII = 'ascii'
 
 
- -class Password(FrontendMessage):
+class Password(BulkFrontendMessage):
+    message_id = b'p'
 
     def __init__(self, password, auth_method=None, options=None):
- -        self.password = password
- -        self.options = options or {}
+        BulkFrontendMessage.__init__(self)
+
+        self._password = password
+        self._options = options or {}
         if auth_method is not None:
- -            self.auth_method = auth_method
+            self._auth_method = auth_method
         else:
- -            self.auth_method = Authentication.CLEARTEXT_PASSWORD
+            self._auth_method = Authentication.CLEARTEXT_PASSWORD
 
     def encoded_password(self):
 
- -        if self.auth_method == Authentication.CLEARTEXT_PASSWORD:
- -            return self.password
- -        elif self.auth_method == Authentication.CRYPT_PASSWORD:
- -            return crypt.crypt(self.password, self.options['salt'])
- -        elif self.auth_method == Authentication.MD5_PASSWORD:
+        if self._auth_method == Authentication.CLEARTEXT_PASSWORD:
+            return self._password
+        elif self._auth_method == Authentication.CRYPT_PASSWORD:
+            return crypt.crypt(self._password, self._options['salt'])
+        elif self._auth_method == Authentication.MD5_PASSWORD:
             for key in 'user', 'salt':
                 m = hashlib.md5()
- -                m.update(self.password + self.options[key])
+                m.update(self._password + self._options[key])
                 hexdigest = m.hexdigest()
                 if six.PY3:
                     # In python3 the output of m.hexdigest() is a unicode string,
                     # so has to be converted to bytes before concat'ing with
                     # the password bytes.
- -                    hexdigest  = bytes(hexdigest, 'ascii')
- -                self.password = hexdigest
+                    hexdigest = bytes(hexdigest, ASCII)
+                self._password = hexdigest
 
             prefix = 'md5'
             if six.PY3:
                 # Same workaround for bytes here.
- -                prefix = bytes(prefix, 'ascii')
- -            return prefix + self.password
+                prefix = bytes(prefix, ASCII)
+            return prefix + self._password
         else:
- -            raise ValueError("unsupported authentication method: {0}".format(self.auth_method))
+            raise ValueError("unsupported authentication method: {0}".format(self._auth_method))
 
- -    def to_bytes(self):
+    def read_bytes(self):
         encoded_pw = self.encoded_password()
- -        return self.message_string(pack('{0}sx'.format(len(encoded_pw)), encoded_pw))
- -
- -
- -Password._message_id(b'p')
+        bytes_ = pack('{0}sx'.format(len(encoded_pw)), encoded_pw)
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/query.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/query.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/query.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/query.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,18 +1,20 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import pack
 
- -from vertica_python.vertica.messages.message import FrontendMessage
- -
+from ..message import BulkFrontendMessage
 
- -class Query(FrontendMessage):
+UTF_8 = 'utf-8'
 
- -    def __init__(self, query_string):
- -        self.query_string = query_string
 
- -    def to_bytes(self):
- -        encoded = self.query_string.encode('utf-8')
- -        return self.message_string(pack('{0}sx'.format(len(encoded)), encoded))
+class Query(BulkFrontendMessage):
+    message_id = b'Q'
 
+    def __init__(self, query_string):
+        BulkFrontendMessage.__init__(self)
+        self._query_string = query_string
 
- -Query._message_id(b'Q')
+    def read_bytes(self):
+        encoded = self._query_string.encode(UTF_8)
+        bytes_ = pack('{0}sx'.format(len(encoded)), encoded)
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/ssl_request.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/ssl_request.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/ssl_request.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/ssl_request.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,14 +1,14 @@
- -
+from __future__ import print_function, division, absolute_import
 
 from struct import pack
 
- -from vertica_python.vertica.messages.message import FrontendMessage
- -
- -
- -class SslRequest(FrontendMessage):
+from ..message import BulkFrontendMessage
 
- -    def to_bytes(self):
- -        return self.message_string(pack('!I', 80877103))
 
+class SslRequest(BulkFrontendMessage):
+    message_id = None
+    SSL_REQUEST = 80877103
 
- -SslRequest._message_id(None)
+    def read_bytes(self):
+        bytes_ = pack('!I', self.SSL_REQUEST)
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/startup.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/startup.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/startup.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/startup.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,28 +1,47 @@
+from __future__ import print_function, division, absolute_import
 
- -
+import platform
+import os
+import uuid
 from struct import pack
 
+# noinspection PyUnresolvedReferences,PyCompatibility
+from builtins import str
+
 import vertica_python
- -from vertica_python.vertica.messages.message import FrontendMessage
+from ..message import BulkFrontendMessage
+
+ASCII = 'ascii'
 
 
- -class Startup(FrontendMessage):
+class Startup(BulkFrontendMessage):
+    message_id = None
 
     def __init__(self, user, database, options=None):
- -        self.user = user
- -        self.database = database
- -        self.options = options
- -
- -    def to_bytes(self):
- -        startstr = pack('!I', vertica_python.PROTOCOL_VERSION)
- -        if self.user is not None:
- -            startstr = startstr + pack('4sx{0}sx'.format(len(self.user)), b'user', self.user)
- -        if self.database is not None:
- -            startstr = startstr + pack('8sx{0}sx'.format(len(self.database)), b'database', self.database)
- -        if self.options is not None:
- -            startstr = startstr + pack('7sx{0}sx'.format(len(self.options)), b'options', self.options)
- -        startstr = startstr + pack('x')
- -        return self.message_string(startstr)
+        BulkFrontendMessage.__init__(self)
 
+        self._user = user
+        self._database = database
+        self._options = options
+        self._type = b'vertica-python'
+        self._version = vertica_python.__version__.encode(ASCII)
+        self._platform = platform.platform().encode(ASCII)
+        self._pid = '{0}'.format(os.getpid()).encode(ASCII)
+        self._label = self._type + b'-' + self._version + b'-' + str(uuid.uuid1()).encode(ASCII)
+
+    def read_bytes(self):
+        bytes_ = pack('!I', vertica_python.PROTOCOL_VERSION)
+        if self._user is not None:
+            bytes_ += pack('4sx{0}sx'.format(len(self._user)), b'user', self._user)
+        if self._database is not None:
+            bytes_ += pack('8sx{0}sx'.format(len(self._database)), b'database', self._database)
+        if self._options is not None:
+            bytes_ += pack('7sx{0}sx'.format(len(self._options)), b'options', self._options)
+        bytes_ += pack('12sx{0}sx'.format(len(self._label)), b'client_label', self._label)
+        bytes_ += pack('11sx{0}sx'.format(len(self._type)), b'client_type', self._type)
+        bytes_ += pack('14sx{0}sx'.format(len(self._version)), b'client_version', self._version)
+        bytes_ += pack('9sx{0}sx'.format(len(self._platform)), b'client_os', self._platform)
+        bytes_ += pack('10sx{0}sx'.format(len(self._pid)), b'client_pid', self._pid)
+        bytes_ += pack('x')
 
- -Startup._message_id(None)
+        return bytes_
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/sync.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/sync.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/sync.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/sync.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,10 +1,7 @@
+from __future__ import print_function, division, absolute_import
 
+from ..message import BulkFrontendMessage
 
- -from vertica_python.vertica.messages.message import FrontendMessage
 
- -
- -class Sync(FrontendMessage):
- -    pass
- -
- -
- -Sync._message_id(b'S')
+class Sync(BulkFrontendMessage):
+    message_id = b'S'
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/terminate.py python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/terminate.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/frontend_messages/terminate.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/frontend_messages/terminate.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,10 +1,7 @@
+from __future__ import print_function, division, absolute_import
 
+from ..message import BulkFrontendMessage
 
- -from vertica_python.vertica.messages.message import FrontendMessage
 
- -
- -class Terminate(FrontendMessage):
- -    pass
- -
- -
- -Terminate._message_id(b'X')
+class Terminate(BulkFrontendMessage):
+    message_id = b'X'
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/__init__.py python-vertica-0.7.1/vertica_python/vertica/messages/__init__.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/__init__.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/__init__.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,44 +1,9 @@
- -from .backend_messages.authentication import Authentication
- -from .backend_messages.backend_key_data import BackendKeyData
- -from .backend_messages.bind_complete import BindComplete
- -from .backend_messages.close_complete import CloseComplete
- -from .backend_messages.command_complete import CommandComplete
- -from .backend_messages.copy_in_response import CopyInResponse
- -from .backend_messages.data_row import DataRow
- -from .backend_messages.empty_query_response import EmptyQueryResponse
- -from .backend_messages.error_response import ErrorResponse
- -from .backend_messages.no_data import NoData
- -from .backend_messages.notice_response import NoticeResponse
- -from .backend_messages.parameter_description import ParameterDescription
- -from .backend_messages.parameter_status import ParameterStatus
- -from .backend_messages.parse_complete import ParseComplete
- -from .backend_messages.portal_suspended import PortalSuspended
- -from .backend_messages.ready_for_query import ReadyForQuery
- -from .backend_messages.row_description import RowDescription
- -from .backend_messages.unknown import Unknown
+from __future__ import print_function, division, absolute_import
 
- -from .frontend_messages.bind import Bind
- -from .frontend_messages.cancel_request import CancelRequest
- -from .frontend_messages.close import Close
- -from .frontend_messages.copy_data import CopyData
- -from .frontend_messages.copy_stream import CopyStream
- -from .frontend_messages.copy_done import CopyDone
- -from .frontend_messages.copy_fail import CopyFail
- -from .frontend_messages.describe import Describe
- -from .frontend_messages.execute import Execute
- -from .frontend_messages.flush import Flush
- -from .frontend_messages.parse import Parse
- -from .frontend_messages.password import Password
- -from .frontend_messages.query import Query
- -from .frontend_messages.ssl_request import SslRequest
- -from .frontend_messages.startup import Startup
- -from .frontend_messages.sync import Sync
- -from .frontend_messages.terminate import Terminate
+from ..messages import backend_messages
+from ..messages.backend_messages import *
 
- -__all__ = ["Authentication", "BackendKeyData", "BindComplete", "CloseComplete", "CommandComplete",
- -           "CopyInResponse", "DataRow", "EmptyQueryResponse", "ErrorResponse", "NoData", "NoticeResponse",
- -           "ParameterDescription", "ParameterStatus", "ParseComplete", "PortalSuspended",
- -           "ReadyForQuery", "RowDescription", "Unknown", "Bind", "CancelRequest", "Close", "CopyData", "CopyDone",
- -           "CopyFail", "Describe", "Execute", "Flush", "Parse", "Password", "Query", "SslRequest", "Startup", "Sync",
- -           "Terminate",
- -           ]
+from ..messages import frontend_messages
+from ..messages.frontend_messages import *
+
+__all__ = backend_messages.__all__ + frontend_messages.__all__
diff -Nru python-vertica-0.6.8/vertica_python/vertica/messages/message.py python-vertica-0.7.1/vertica_python/vertica/messages/message.py
- --- python-vertica-0.6.8/vertica_python/vertica/messages/message.py	2016-10-06 23:30:16.000000000 +0200
+++ python-vertica-0.7.1/vertica_python/vertica/messages/message.py	2017-05-14 00:40:53.000000000 +0200
@@ -1,23 +1,22 @@
+from __future__ import print_function, division, absolute_import
 
- -
- -import types
- -
+from abc import ABCMeta
 from struct import pack
 
- -from vertica_python.vertica.messages import *
+from ..messages import *
 
 
 class Message(object):
+    __metaclass__ = ABCMeta
 
- -    @classmethod
- -    def _message_id(cls, message_id):
- -        instance_message_id = message_id
+    def __init__(self):
+        pass
 
- -        def message_id(self):
- -            return instance_message_id
- -        setattr(cls, 'message_id', types.MethodType(message_id, cls))
+    @property
+    def message_id(self):
+        raise NotImplementedError("no default message_id")
 
- -    def message_string(self, msg):
+    def _bytes_to_message(self, msg):
 
         if isinstance(msg, list):
             msg = ''.join(msg)
@@ -28,31 +27,73 @@
             bytesize = len(msg) + 4
 
         message_size = pack('!I', bytesize)
- -        if self.message_id() is not None:
- -            msg_with_size = self.message_id() + message_size + msg
+        if self.message_id is not None:
+            msg_with_size = self.message_id + message_size + msg
         else:
             msg_with_size = message_size + msg
 
         return msg_with_size
 
 
+# noinspection PyAbstractClass
 class BackendMessage(Message):
- -    MessageIdMap = {}
+    __metaclass__ = ABCMeta
+    _message_id_map = {}
 
     @classmethod
- -    def factory(cls, type_, data):
- -        klass = cls.MessageIdMap[type_]
+    def from_type(cls, type_, data):
+        klass = cls._message_id_map.get(type_)
         if klass is not None:
             return klass(data)
         else:
- -            return messages.Unknown(type_, data)
+            return Unknown(type_, data)
 
- -    @classmethod
- -    def _message_id(cls, message_id):
- -        super(BackendMessage, cls)
- -        cls.MessageIdMap[message_id] = cls
+    @staticmethod
+    def register(cls):
+        # TODO replace _message_id() with that
+        assert issubclass(cls, BackendMessage), \
+            "{0} is not subclass of BackendMessage".format(cls.__name__)
+        assert cls.message_id not in BackendMessage._message_id_map, \
+            "can't write the same key twice: {0}".format(cls.message_id)
 
+        BackendMessage._message_id_map[cls.message_id] = cls
 
+
+# noinspection PyAbstractClass
 class FrontendMessage(Message):
- -    def to_bytes(self):
- -        return self.message_string(b'')
+    __metaclass__ = ABCMeta
+
+    def fetch_message(self):
+        """Generator for getting the message's content"""
+        raise NotImplementedError("fetch_bytes has no default implementation")
+
+
+# noinspection PyAbstractClass
+class BulkFrontendMessage(FrontendMessage):
+    __metaclass__ = ABCMeta
+
+    def read_bytes(self):
+        return b''
+
+    def get_message(self):
+        bytes_ = self.read_bytes()
+        return self._bytes_to_message(bytes_)
+
+    def fetch_message(self):
+        yield self.get_message()
+
+
+# noinspection PyAbstractClass
+class StreamFrontendMessage(FrontendMessage):
+    __metaclass__ = ABCMeta
+
+    def stream_bytes(self):
+        raise NotImplementedError("stream_bytes has no default implementation")
+
+    def stream_message(self):
+        for bytes_ in self.stream_bytes():
+            yield self._bytes_to_message(bytes_)
+
+    def fetch_message(self):
+        for message in self.stream_message():
+            yield message

unblock python-vertica/0.7.1-1

- -- System Information:
Debian Release: 9.0
  APT prefers stable-updates
  APT policy: (500, 'stable-updates'), (500, 'oldstable-updates'), (500, 'unstable'), (500, 'testing'), (500, 'stable'), (500, 'oldstable'), (1, 'experimental')
Architecture: amd64
 (x86_64)

Kernel: Linux 4.9.0-3-amd64 (SMP w/4 CPU cores)
Locale: LANG=fr_FR.UTF-8, LC_CTYPE=fr_FR.UTF-8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)

-----BEGIN PGP SIGNATURE-----

iQKTBAEBCgB9FiEEToRbojDLTUSJBphHtN1Tas99hzcFAlka2JhfFIAAAAAALgAo
aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDRF
ODQ1QkEyMzBDQjRENDQ4OTA2OTg0N0I0REQ1MzZBQ0Y3RDg3MzcACgkQtN1Tas99
hzdXPBAA1bIKF4YAvXp1U8ICc6gpFAvA+WqVS8Lmt4EFbDcVpC93d+0XiRns8oD+
dnVK8mIEtMNxIXPJaZGMvze+QWcjeBE88jEhLv2MNT31p82c08f5Zyz9U+jtoLEJ
LN8PJqLE2sJ9TJ8xnpnA599aqBwX4C+my4wy5WCS1tHirZhkz5PlKYoKOeKMFvVi
5ianPDyqkL6H22ZX7cBE0zt5fvBZ1byCPFJTD0r4u6By8T728Mqx6ySthu9WUxuQ
k0GOjGYx5PYmQ1augVQz4eYq891Xb99QCZ4oN4eyUaG0in5T41U60RQ6mSrs2L8v
FpS9wdczkhL098kqaHx+Hz+JS3Wuee9a99DHzmIig3EcVo8kw3d8LRJm6SEcNiRA
kB0WOq6CgPivRXVwwyhQU8TNXTGnytvd8EGtweh628vw+6hfcXLc7N3hKFs/qEpP
1e4U1rXg7Lhrpz9LVIZ9Wh+CqVQzzGNY22YKNaI06b9B+K5iFEF0x8LMrRvo0abD
xgMqoMPLnpzJTwmTE1vvzyxRLmE+T7Ag0oKY2TF0d4y/W+y79EEEryxuqkSUCqa7
UIROctVgb8atGnoyqQ3GGalXSXaw61aIZUlgfkdzQ/fMSJnvyP5ixKwxRRCGmW+f
HEPYHCZ/gfTy06NkfUIZndt2hST18Jcnjz7QGKtZBrsXq7AcHA0=
=2BGZ
-----END PGP SIGNATURE-----


Reply to: