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

Bug#862734: marked as done (unblock: python-vertica/0.7.1-1)



Your message dated Wed, 24 May 2017 22:00:54 +0200
with message-id <20170524200051.GA9486@ugent.be>
and subject line Re: unblock: python-vertica/0.7.1-1
has caused the Debian Bug report #862734,
regarding unblock: python-vertica/0.7.1-1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
862734: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=862734
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
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-----

--- End Message ---
--- Begin Message ---
Hi,

On Tue, May 16, 2017 at 12:46:54PM +0200, Jean Baptiste Favre wrote:
> 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.

Updating to new upstream versions with a number of random changes isn't
in line with the freeze policy. Sorry.

https://release.debian.org/stretch/freeze_policy.html

Cheers,

Ivo

--- End Message ---

Reply to: