diff --git a/devtools/create_tag.sh b/devtools/create_tag.sh index 731b4ebc..8cb97a4a 100755 --- a/devtools/create_tag.sh +++ b/devtools/create_tag.sh @@ -48,7 +48,7 @@ fi # check if tag to create has already been created WORKING_DIR=`dirname $0` -VERSION=`python setup.py --version` +VERSION=`hatch project metadata version` EXISTS=`git tag | grep $VERSION` if [ "$VERSION" == "$EXISTS" ] diff --git a/pyproject.toml b/pyproject.toml index 2b90cd4a..cb9b5a55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["hatchling >= 1.26", "versioningit"] +requires = ["hatchling", "versioningit"] build-backend = "hatchling.build" [tool.hatch.build.targets.sdist] @@ -26,7 +26,7 @@ name = "crate" dynamic = ["version"] description = "CrateDB Python Client" authors = [{ name = "Crate.io", email = "office@crate.io" }] -requires-python = ">=3.10" +requires-python = ">=3.6" readme = "README.rst" license = "Apache-2.0" classifiers = [ @@ -42,25 +42,29 @@ classifiers = [ "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", + "Programming Language :: SQL", "Topic :: Database", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator", + "Topic :: System :: Networking", ] dependencies = [ "importlib-metadata; python_version<'3.8'", - "orjson>=3.11.3", + "orjson", "urllib3", "verlib2>=0.3.1", ] [dependency-groups] dev = [ - "certifi>=2025.10.5", + "backports.zoneinfo<1; python_version<'3.9'", + "certifi", "coverage<8", "mypy<1.20", "poethepoet<1", "pytest<10", - "pytz>=2025.2", + "pytz", "ruff<0.15", - "setuptools>=80.9.0", "stopit<1.2", ] diff --git a/requirements.txt b/requirements.txt index 942e58b2..20aa2769 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ zc.buildout==5.1.1 -zope.interface==8.1.1 +zope.interface>=8 zope.testrunner>=5,<9 diff --git a/setup.py b/setup.py deleted file mode 100644 index ef52b684..00000000 --- a/setup.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8; -*- -# -# Licensed to CRATE Technology GmbH ("Crate") under one or more contributor -# license agreements. See the NOTICE file distributed with this work for -# additional information regarding copyright ownership. Crate licenses -# this file to you 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. -# -# However, if you have executed another commercial license agreement -# with Crate these terms will supersede the license and you may use the -# software solely pursuant to the terms of the relevant commercial agreement. - -import os -import re - -from setuptools import find_namespace_packages, setup - - -def read(path): - with open(os.path.join(os.path.dirname(__file__), path)) as f: - return f.read() - - -long_description = read("README.rst") -versionf_content = read("src/crate/client/__init__.py") -version_rex = r'^__version__ = [\'"]([^\'"]*)[\'"]$' -m = re.search(version_rex, versionf_content, re.M) -if m: - version = m.group(1) -else: - raise RuntimeError("Unable to find version string") - -setup( - name="crate", - version=version, - url="https://github.com/crate/crate-python", - author="Crate.io", - author_email="office@crate.io", - description="CrateDB Python Client", - long_description=long_description, - long_description_content_type="text/x-rst", - platforms=["any"], - license="Apache-2.0", - license_files=["LICENSE"], - keywords="cratedb db api dbapi database sql http rdbms olap", - packages=find_namespace_packages("src"), - package_dir={"": "src"}, - install_requires=[ - "orjson<4", - "urllib3", - "verlib2>=0.3", - ], - extras_require={ - "doc": [ - "crate-docs-theme>=0.26.5", - "sphinx>=3.5,<9", - ], - "test": [ - 'backports.zoneinfo<1; python_version<"3.9"', - "certifi", - "createcoverage>=1,<2", - "mypy<1.20", - "poethepoet<1", - "ruff<0.15", - "stopit>=1.1.2,<2", - "pytz", - "zc.customdoctests>=1.0.1,<2", - "zope.testing>=4,<7", - "zope.testrunner>=5,<9", - ], - }, - python_requires=">=3.6", - package_data={"": ["*.txt"]}, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Database", - ], -) diff --git a/src/crate/client/cursor.py b/src/crate/client/cursor.py index 587f1491..a223999f 100644 --- a/src/crate/client/cursor.py +++ b/src/crate/client/cursor.py @@ -221,6 +221,8 @@ def duration(self): def _convert_rows(self): """ Iterate rows, apply type converters, and generate converted rows. + + The converter is only supported on Python >= 3.10. """ if not ("col_types" in self._result and self._result["col_types"]): raise ValueError( @@ -238,7 +240,7 @@ def _convert_rows(self): for row in self._result["rows"]: yield [ convert(value) - for convert, value in zip(converters, row, strict=False) + for convert, value in zip(converters, row, strict=False) # type: ignore[call-overload] ] @property diff --git a/tests/client/test_cursor.py b/tests/client/test_cursor.py index 976491f9..b7dd9a65 100644 --- a/tests/client/test_cursor.py +++ b/tests/client/test_cursor.py @@ -20,6 +20,7 @@ # software solely pursuant to the terms of the relevant commercial agreement. import datetime +import sys from ipaddress import IPv4Address from unittest import mock @@ -58,6 +59,9 @@ def test_cursor_fetch(mocked_connection): ] +@pytest.mark.skipif( + sys.version_info < (3, 10), reason="Test needs Python >= 3.10" +) def test_cursor_description(mocked_connection): cursor = mocked_connection.cursor() response = { @@ -249,6 +253,9 @@ def test_execute_with_bulk_args(mocked_connection): mocked_connection.client.sql.assert_called_once_with(statement, None, [[1]]) +@pytest.mark.skipif( + sys.version_info < (3, 10), reason="Converter needs Python >= 3.10" +) def test_execute_custom_converter(mocked_connection): """ Verify that a custom converter is correctly applied when passed to a cursor. @@ -299,6 +306,9 @@ def test_execute_custom_converter(mocked_connection): ] +@pytest.mark.skipif( + sys.version_info < (3, 10), reason="Converter needs Python >= 3.10" +) def test_execute_with_converter_and_invalid_data_type(mocked_connection): converter = DefaultTypeConverter() @@ -323,6 +333,9 @@ def test_execute_with_converter_and_invalid_data_type(mocked_connection): assert e.exception.args == "999 is not a valid DataType" +@pytest.mark.skipif( + sys.version_info < (3, 10), reason="Converter needs Python >= 3.10" +) def test_execute_array_with_converter(mocked_connection): converter = DefaultTypeConverter() cursor = mocked_connection.cursor(converter=converter) @@ -345,6 +358,9 @@ def test_execute_array_with_converter(mocked_connection): ] +@pytest.mark.skipif( + sys.version_info < (3, 10), reason="Converter needs Python >= 3.10" +) def test_execute_array_with_converter_invalid(mocked_connection): converter = DefaultTypeConverter() cursor = mocked_connection.cursor(converter=converter) @@ -368,6 +384,9 @@ def test_execute_array_with_converter_invalid(mocked_connection): ) +@pytest.mark.skipif( + sys.version_info < (3, 10), reason="Converter needs Python >= 3.10" +) def test_execute_nested_array_with_converter(mocked_connection): converter = DefaultTypeConverter() cursor = mocked_connection.cursor(converter=converter) @@ -405,6 +424,9 @@ def test_execute_nested_array_with_converter(mocked_connection): ] +@pytest.mark.skipif( + sys.version_info < (3, 10), reason="Converter needs Python >= 3.10" +) def test_executemany_with_converter(mocked_connection): converter = DefaultTypeConverter() cursor = mocked_connection.cursor(converter=converter) @@ -426,6 +448,9 @@ def test_executemany_with_converter(mocked_connection): assert result == [] +@pytest.mark.skipif( + sys.version_info < (3, 10), reason="Converter needs Python >= 3.10" +) def test_execute_with_timezone(mocked_connection): # Create a `Cursor` object with `time_zone`. tz_mst = datetime.timezone(datetime.timedelta(hours=7), name="MST") diff --git a/tests/client/tests.py b/tests/client/tests.py index ae6a479f..65a007bd 100644 --- a/tests/client/tests.py +++ b/tests/client/tests.py @@ -1,4 +1,5 @@ import doctest +import sys import unittest from .layer import ( @@ -18,14 +19,17 @@ def test_suite(): suite.addTest(doctest.DocTestSuite("crate.client.connection")) suite.addTest(doctest.DocTestSuite("crate.client.http")) - s = doctest.DocFileSuite( - "docs/by-example/connection.rst", - "docs/by-example/cursor.rst", - module_relative=False, - optionflags=flags, - encoding="utf-8", - ) - suite.addTest(s) + if sys.version_info >= (3, 10): + # This suite includes converter tests, + # which are only available with Python 3.10 and newer. + s = doctest.DocFileSuite( + "docs/by-example/connection.rst", + "docs/by-example/cursor.rst", + module_relative=False, + optionflags=flags, + encoding="utf-8", + ) + suite.addTest(s) s = doctest.DocFileSuite( "docs/by-example/https.rst",