diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bafb696a..2e7381f0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', 'pypy3.9'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', 'pypy3.9'] steps: - name: Check out repository @@ -32,7 +32,7 @@ jobs: run: | docker run -d -p 5433:5433 -p 5444:5444 \ --name vertica_docker --network my-network \ - vertica/vertica-ce:23.3.0-0 + vertica/vertica-ce:23.4.0-0 echo "Vertica startup ..." until docker exec vertica_docker test -f /data/vertica/VMart/agent_start.out; do \ echo "..."; \ diff --git a/README.md b/README.md index a81f4618..7713b94d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,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 23.3.0 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)). +vertica-python has been tested with Vertica 23.4.0 and Python 3.7/3.8/3.9/3.10/3.11/3.12. Feel free to submit issues and/or pull requests (Read up on our [contributing guidelines](#contributing-guidelines)). ## Installation @@ -1005,6 +1005,88 @@ print(cur.fetchone()[0]) # Decimal('123.45') ``` +**Example: Vertica complex types** + +The raw bytes data of complex types (ARRAY, MAP, ROW, SET) are in JSON format for both Text & Binary transfer format. + +```python +import json +import numpy as np +import vertica_python +from vertica_python.datatypes import VerticaType + +conn_info = {'host': '127.0.0.1', + 'port': 5433, + 'binary_transfer': False/True, + ... + } + +conn = vertica_python.connect(**conn_info) +cur = conn.cursor() + +#================================================== +cur.execute("SELECT ARRAY[-1.234, 0, 1.66, null, 50]::ARRAY[FLOAT]") +data = cur.fetchone()[0] +print(type(data)) # +print(data) # [-1.234, 0.0, 1.66, None, 50.0] +numpy_data = np.array(data) # This is equal to the query value below +#================================================== +def convert_array(val, ctx): + # val: b'[-1.234,0.0,1.66,null,50.0]' + json_data = json.loads(val) + return np.array(json_data) +# VerticaType.ARRAY1D_FLOAT8 represents one-dimensional array of FLOAT type +cur.register_sqldata_converter(VerticaType.ARRAY1D_FLOAT8, convert_array) +cur.execute("SELECT ARRAY[-1.234, 0, 1.66, null, 50]::ARRAY[FLOAT]") +data = cur.fetchone()[0] +print(type(data)) # +print(data) # [-1.234 0.0 1.66 None 50.0] + +#================================================== +# VerticaType.ARRAY represents multidimensional array or contain ROWs +cur.register_sqldata_converter(VerticaType.ARRAY, convert_array) +cur.execute("SELECT ARRAY[ARRAY[-1, 234, 5],ARRAY[88, 0, 19]]::ARRAY[ARRAY[INT]]") +data = cur.fetchone()[0] +print(type(data)) # +print(data) +#[[ -1 234 5] +# [ 88 0 19]] +``` +```python +import pandas +import vertica_python +from io import BytesIO +from vertica_python.datatypes import VerticaType + +conn_info = {'host': '127.0.0.1', + 'port': 5433, + 'binary_transfer': False/True, + ... + } + +conn = vertica_python.connect(**conn_info) +cur = conn.cursor() + +cur.execute("SELECT ROW(ROW('a','b') as row1, ROW('c','d') as row2)") +data = cur.fetchone()[0] +print(type(data)) # +print(data) # {'row1': {'f0': 'a', 'f1': 'b'}, 'row2': {'f0': 'c', 'f1': 'd'}} + +def convert_row(val, ctx): + # val: b'{"row1":{"f0":"a","f1":"b"},"row2":{"f0":"c","f1":"d"}}' + return pandas.read_json(BytesIO(val), orient='index') + +cur.register_sqldata_converter(VerticaType.ROW, convert_row) +cur.execute("SELECT ROW(ROW('a','b') as row1, ROW('c','d') as row2)") +data = cur.fetchone()[0] +print(type(data)) # +print(data) +# f0 f1 +# row1 a b +# row2 c d +``` + + If you want to learn how default converters for each transfer format and oid works, look at the source code at `vertica_python/vertica/deserializer.py` ### Shortcuts diff --git a/setup.py b/setup.py index d9fb4306..dcf093d9 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ # version should use the format 'x.x.x' (instead of 'vx.x.x') setup( name='vertica-python', - version='1.3.5', + version='1.3.6', description='Official native Python client for the Vertica database.', long_description="vertica-python is the official Vertica database client for the Python programming language. Please check the [project homepage](https://github.com/vertica/vertica-python) for the details.", long_description_content_type='text/markdown', @@ -72,6 +72,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Database", "Topic :: Database :: Database Engines/Servers", "Topic :: Database :: Front-Ends", diff --git a/tox.ini b/tox.ini index 2883cd4e..f3f18cd6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py37,py38,py39,py310,py311 +envlist = py37,py38,py39,py310,py311,py312 [testenv] passenv = * @@ -7,7 +7,7 @@ commands = pytest {posargs} deps = pytest - pytest-timeout==1.4.2 + pytest-timeout parameterized python-dateutil mock diff --git a/vertica_python/__init__.py b/vertica_python/__init__.py index 3b7de259..00b4cada 100644 --- a/vertica_python/__init__.py +++ b/vertica_python/__init__.py @@ -56,7 +56,7 @@ 'OperationalError', 'ProgrammingError'] # The version number of this library. -version_info = (1, 3, 5) +version_info = (1, 3, 6) __version__ = '.'.join(map(str, version_info)) # The protocol version (3.15) implemented in this library. diff --git a/vertica_python/datatypes.py b/vertica_python/datatypes.py index 22582d4d..e7ddfc61 100644 --- a/vertica_python/datatypes.py +++ b/vertica_python/datatypes.py @@ -36,7 +36,7 @@ from __future__ import print_function, division, absolute_import -from datetime import date, datetime, time +from datetime import date, datetime, time, timezone # noinspection PyPep8Naming @@ -56,19 +56,19 @@ def Timestamp(year, month, day, hour, minute, second): # noinspection PyPep8Naming def DateFromTicks(ticks): - d = datetime.utcfromtimestamp(ticks) + d = datetime.fromtimestamp(ticks, timezone.utc) return d.date() # noinspection PyPep8Naming def TimeFromTicks(ticks): - d = datetime.utcfromtimestamp(ticks) + d = datetime.fromtimestamp(ticks, timezone.utc) return d.time() # noinspection PyPep8Naming def TimestampFromTicks(ticks): - d = datetime.utcfromtimestamp(ticks) + d = datetime.fromtimestamp(ticks, timezone.utc) return d.time() diff --git a/vertica_python/vertica/connection.py b/vertica_python/vertica/connection.py index cfd864ea..704090f5 100644 --- a/vertica_python/vertica/connection.py +++ b/vertica_python/vertica/connection.py @@ -548,7 +548,10 @@ def enable_ssl(self, raw_socket, ssl_options): raise errors.ConnectionError(msg) raw_socket = ssl_options.wrap_socket(raw_socket, server_hostname=server_host) else: - raw_socket = ssl.wrap_socket(raw_socket) + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + raw_socket = ssl_context.wrap_socket(raw_socket) except ssl.CertificateError as e: raise errors.ConnectionError(str(e)) except ssl.SSLError as e: