Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ If you do Kerberos development, you need to install additional [dependencies](RE
We appreciate any and all [contributions to the test suite](#tests)! These tests use a Python module: [pytest](https://docs.pytest.org/en/latest/). You might want to check out the pytest documentation for more details.

There are two types of tests: unit tests and integration tests. Unit tests do simple unit testing of individual classes and functions, which do not require database connection. Integration tests need to connect to a Vertica database to run stuffs, so you must have access to a Vertica database. We recommend using a non-production database, because some tests need the superuser permission to manipulate global settings and potentially break that database. Heres one way to go about it:
- Download docker kitematic: https://kitematic.com/
- Spin up a vertica container (e.g. sumitchawla/vertica)
- Spin up a vertica docker container (e.g. [vertica/vertica-ce](https://hub.docker.com/r/vertica/vertica-ce))

Spin up your Vertica database for integration tests and then config test settings:
* Here are default settings:
Expand Down Expand Up @@ -126,20 +125,20 @@ Examples of running tests:
tox

# Run tests on specified python versions with `tox -e ENV,ENV`
tox -e py27,py35
tox -e py37,py38

# Run specific tests by filename (e.g.) `test_notice.py`
tox -- vertica_python/tests/unit_tests/test_notice.py

# Run all unit tests on the python version 3.6:
tox -e py36 -- -m unit_tests
# Run all unit tests on the python version 3.9:
tox -e py39 -- -m unit_tests

# Run all integration tests on the python version 3.4 with verbose result outputs:
tox -e py34 -- -v -m integration_tests
# Run all integration tests on the python version 3.10 with verbose result outputs:
tox -e py310 -- -v -m integration_tests

# Run an individual test on specified python versions.
# e.g.: Run the test `test_error_message` under `test_notice.py` on the python versions 2.7 and 3.5
tox -e py27,py35 -- vertica_python/tests/unit_tests/test_notice.py::NoticeTestCase::test_error_message
# e.g.: Run the test `test_error_message` under `test_notice.py` on the python versions 3.8 and 3.9
tox -e py38,py39 -- vertica_python/tests/unit_tests/test_notice.py::NoticeTestCase::test_error_message
```

The arguments after the `--` will be substituted everywhere where you specify `{posargs}` in your test *commands* of
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

Please check out [release notes](https://github.com/vertica/vertica-python/releases) to learn about the latest improvements.

vertica-python has been tested with Vertica 12.0.2 and Python 2.7/3.7/3.8/3.9/3.10/3.11. Feel free to submit issues and/or pull requests (Read up on our [contributing guidelines](#contributing-guidelines)).
vertica-python has been tested with Vertica 12.0.2 and Python 3.7/3.8/3.9/3.10/3.11. Feel free to submit issues and/or pull requests (Read up on our [contributing guidelines](#contributing-guidelines)).


## Installation
Expand Down
8 changes: 2 additions & 6 deletions vertica_python/tests/common/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,7 @@
import unittest
import inspect
import getpass
import six
if six.PY2:
from ConfigParser import ConfigParser
elif six.PY3:
from configparser import ConfigParser
from configparser import ConfigParser

from ...compat import as_text, as_str, as_bytes
from ...vertica.log import VerticaLogging
Expand Down Expand Up @@ -148,7 +144,7 @@ def assertBytesEqual(self, first, second, msg=None):
self.assertEqual(first=first_bytes, second=second_bytes, msg=msg)

def assertResultEqual(self, value, result, msg=None):
if isinstance(value, six.string_types):
if isinstance(value, str):
self.assertTextEqual(first=value, second=result, msg=msg)
else:
self.assertEqual(first=value, second=result, msg=msg)
Expand Down
32 changes: 13 additions & 19 deletions vertica_python/vertica/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,11 @@
import random

# noinspection PyCompatibility,PyUnresolvedReferences
from six import raise_from, string_types, integer_types, PY2

if PY2:
from urlparse import urlparse, parse_qs
else:
from urllib.parse import urlparse, parse_qs

from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any, Dict, Literal, Optional, Type, Union
from typing_extensions import Self
from urllib.parse import urlparse, parse_qs
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Any, Dict, Literal, Optional, Type, Union
from typing_extensions import Self

import vertica_python
from .. import errors
Expand Down Expand Up @@ -165,7 +159,7 @@ def __init__(self, host, port, backup_nodes, logger):
# a host name or IP address string (using default port) or
# a (host, port) tuple
for node in backup_nodes:
if isinstance(node, string_types):
if isinstance(node, str):
self._append(node, DEFAULT_PORT)
elif isinstance(node, tuple) and len(node) == 2:
self._append(node[0], node[1])
Expand All @@ -177,16 +171,16 @@ def __init__(self, host, port, backup_nodes, logger):
self._logger.debug('Address list: {0}'.format(list(self.address_deque)))

def _append(self, host, port):
if not isinstance(host, string_types):
if not isinstance(host, str):
err_msg = 'Host must be a string: invalid value: {0}'.format(host)
self._logger.error(err_msg)
raise TypeError(err_msg)

if not isinstance(port, (string_types, integer_types)):
if not isinstance(port, (str, int)):
err_msg = 'Port must be an integer or a string: invalid value: {0}'.format(port)
self._logger.error(err_msg)
raise TypeError(err_msg)
elif isinstance(port, string_types):
elif isinstance(port, str):
try:
port = int(port)
except ValueError as e:
Expand Down Expand Up @@ -541,9 +535,9 @@ def enable_ssl(self, raw_socket, ssl_options):
else:
raw_socket = ssl.wrap_socket(raw_socket)
except ssl.CertificateError as e:
raise_from(errors.ConnectionError(str(e)), e)
raise errors.ConnectionError(str(e))
except ssl.SSLError as e:
raise_from(errors.ConnectionError(str(e)), e)
raise errors.ConnectionError(str(e))
else:
err_msg = "SSL requested but not supported by server"
self._logger.error(err_msg)
Expand Down Expand Up @@ -611,7 +605,7 @@ def write(self, message, vsocket=None):
self.close_socket()
self._logger.error(str(e))
if isinstance(e, IOError):
raise_from(errors.ConnectionError(str(e)), e)
raise errors.ConnectionError(str(e))
else:
raise

Expand Down Expand Up @@ -695,7 +689,7 @@ def read_message(self):
self.close_socket()
# noinspection PyTypeChecker
self._logger.error(e)
raise_from(errors.ConnectionError(str(e)), e)
raise errors.ConnectionError(str(e))
if not self.is_asynchronous_message(message):
break
return message
Expand Down
40 changes: 15 additions & 25 deletions vertica_python/vertica/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
import sys
import traceback
from decimal import Decimal
from io import IOBase
from io import IOBase, BytesIO, StringIO
from tempfile import NamedTemporaryFile, SpooledTemporaryFile, TemporaryFile
from uuid import UUID
from collections import OrderedDict
Expand All @@ -55,20 +55,13 @@
except ImportError:
_TemporaryFileWrapper = None

import six
# noinspection PyUnresolvedReferences,PyCompatibility
from six import binary_type, text_type, string_types, integer_types, BytesIO, StringIO
from six.moves import zip

if six.PY3:
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import IO, Any, AnyStr, Callable, Dict, Generator, List, Literal, Optional, Sequence, Tuple, Type, TypeVar, Union
from typing_extensions import Self
from .connection import Connection
from logging import Logger
T = TypeVar('T')
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import IO, Any, AnyStr, Callable, Dict, Generator, List, Literal, Optional, Sequence, Tuple, Type, TypeVar, Union
from typing_extensions import Self
from .connection import Connection
from logging import Logger
T = TypeVar('T')

from .. import errors, os_utils
from ..compat import as_text
Expand Down Expand Up @@ -126,9 +119,6 @@
]
if inspect.isclass(type_)
)
if six.PY2:
# noinspection PyUnresolvedReferences
file_type = file_type + (file,)


RE_NAME_BASE = u"[0-9a-zA-Z_][\\w\\d\\$_]*"
Expand Down Expand Up @@ -442,9 +432,9 @@ def copy(self, sql, data, **kwargs):

self.flush_to_query_ready()

if isinstance(data, binary_type):
if isinstance(data, bytes):
stream = BytesIO(data)
elif isinstance(data, text_type):
elif isinstance(data, str):
stream = StringIO(data)
elif isinstance(data, file_type) or callable(getattr(data, 'read', None)):
stream = data
Expand Down Expand Up @@ -570,7 +560,7 @@ def object_to_string(self, py_obj, is_copy_data):
if type(py_obj) in self._sql_literal_adapters and not is_copy_data:
adapter = self._sql_literal_adapters[type(py_obj)]
result = adapter(py_obj)
if not isinstance(result, (string_types, bytes)):
if not isinstance(result, (str, bytes)):
raise TypeError("Unexpected return type of {} adapter: {}, expected a string type."
.format(type(py_obj), type(result)))
return as_text(result)
Expand All @@ -579,9 +569,9 @@ def object_to_string(self, py_obj, is_copy_data):
return '' if is_copy_data else 'NULL'
elif isinstance(py_obj, bool):
return str(py_obj)
elif isinstance(py_obj, (string_types, bytes)):
elif isinstance(py_obj, (str, bytes)):
return self.format_quote(as_text(py_obj), is_copy_data)
elif isinstance(py_obj, (integer_types, float, Decimal)):
elif isinstance(py_obj, (int, float, Decimal)):
return str(py_obj)
elif isinstance(py_obj, tuple): # tuple and namedtuple
elements = [None] * len(py_obj)
Expand All @@ -605,8 +595,8 @@ def format_operation_with_parameters(self, operation, parameters, is_copy_data=F
operation = as_text(operation)

if isinstance(parameters, dict):
for key, param in six.iteritems(parameters):
if not isinstance(key, string_types):
for key, param in parameters.items():
if not isinstance(key, str):
key = str(key)
key = as_text(key)

Expand Down
31 changes: 4 additions & 27 deletions vertica_python/vertica/deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@
from dateutil import tz
from dateutil.relativedelta import relativedelta
from decimal import Context, Decimal
from six import PY2
from struct import unpack
from uuid import UUID
if PY2:
from binascii import hexlify

from .. import errors
from ..compat import as_str, as_bytes
Expand Down Expand Up @@ -91,21 +88,6 @@ def load_float8_binary(val, ctx):
"""
return unpack("!d", val)[0]

def _int_from_bytes(val):
"""
(Python 2) Convert big-endian signed integer bytes to int.
"""
b = bytearray(val)
if len(b) == 0:
return 0
sign_set = b[0] & 0x80
b[0] &= 0x7f # skip sign bit for negative number
n = int(hexlify(b), 16)
if sign_set: # if sign bit is set, 2's complement
offset = 2**(8 * len(b) - 1)
return n - offset
return n

def load_numeric_binary(val, ctx):
"""
Parses binary representation of a NUMERIC type.
Expand All @@ -115,10 +97,7 @@ def load_numeric_binary(val, ctx):
"""
# N-byte signed integer represents the unscaled value of the numeric
# N is roughly (precision//19+1)*8
if PY2:
unscaledVal = _int_from_bytes(val)
else:
unscaledVal = int.from_bytes(val, byteorder='big', signed=True)
unscaledVal = int.from_bytes(val, byteorder='big', signed=True)
precision = ctx['column'].precision
scale = ctx['column'].scale
# The numeric value is (unscaledVal * 10^(-scale))
Expand All @@ -129,7 +108,7 @@ def load_varchar_text(val, ctx):
Parses text/binary representation of a CHAR / VARCHAR / LONG VARCHAR type.
:param val: bytes
:param ctx: dict
:return: (PY2) unicode / (PY3) str
:return: str
"""
return val.decode('utf-8', ctx['unicode_error'])

Expand Down Expand Up @@ -452,13 +431,11 @@ def load_varbinary_text(s, ctx):
if c == b'\\':
c2 = s[i+1: i+2]
if c2 == b'\\': # escaped \
if PY2: c = str(c)
i += 2
else: # A \xxx octal string
c = chr(int(str(s[i+1: i+4]), 8)) if PY2 else bytes([int(s[i+1: i+4], 8)])
c = bytes([int(s[i+1: i+4], 8)])
i += 4
else:
if PY2: c = str(c)
i += 1
buf.append(c)
return b''.join(buf)
Expand Down Expand Up @@ -548,7 +525,7 @@ def parse_json_element(element, ctx):
# "-Infinity", "Infinity", "NaN"
if type_code == VerticaType.FLOAT8:
return float(element)
# element type: (PY2) unicode / (PY3) str
# element type: str
if type_code in (VerticaType.DATE, VerticaType.TIME, VerticaType.TIMETZ,
VerticaType.TIMESTAMP, VerticaType.TIMESTAMPTZ,
VerticaType.INTERVAL, VerticaType.INTERVALYM,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Authentication(BackendMessage):
CHANGE_PASSWORD = 9
PASSWORD_CHANGED = 10 # client doesn't do password changing, this should never be seen
PASSWORD_GRACE = 11
OAUTH = 12
HASH = 65536
HASH_MD5 = 65536 + 5
HASH_SHA512 = 65536 + 512
Expand Down
2 changes: 0 additions & 2 deletions vertica_python/vertica/messages/backend_messages/data_row.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@

from struct import unpack_from

from six.moves import range

from ..message import BackendMessage


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@

from __future__ import print_function, division, absolute_import

import six
from struct import unpack_from

from ...mixins.notice_response_attr import _NoticeResponseAttrMixin
Expand All @@ -54,7 +53,7 @@ def __init__(self, data):
def error_message(self):
return ', '.join([
"{0}: {1}".format(name, value)
for (name, value) in six.iteritems(self.values)
for (name, value) in self.values.items()
])

def __str__(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@

from struct import unpack, unpack_from, calcsize

from six.moves import range

from ..message import BackendMessage
from ...column import Column
from ....datatypes import getTypeName, getComplexElementType
Expand Down
3 changes: 1 addition & 2 deletions vertica_python/vertica/messages/frontend_messages/bind.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
from __future__ import print_function, division, absolute_import

from struct import pack
from six import string_types

from ..message import BulkFrontendMessage
from ....datatypes import VerticaType
Expand Down Expand Up @@ -97,7 +96,7 @@ def read_bytes(self):
# Convert input to string
if oid == VerticaType.BOOL:
val = '1' if str(val).lower() in ('t', 'true', 'y', 'yes', '1') else '0'
elif not isinstance(val, (string_types, bytes)):
elif not isinstance(val, (str, bytes)):
val = str(val)
# Encode string as UTF8 bytes
val = val.encode('utf-8') if not isinstance(val, bytes) else val
Expand Down
Loading