diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..19e7eb4 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,36 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/zope-product +name: pre-commit + +on: + pull_request: + push: + branches: + - master + # Allow to run this workflow manually from the Actions tab + workflow_dispatch: + +env: + FORCE_COLOR: 1 + +jobs: + pre-commit: + permissions: + contents: read + pull-requests: write + name: linting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd #v3.0.1 + with: + extra_args: --all-files --show-diff-on-failure + env: + PRE_COMMIT_COLOR: always + - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 #v1.1.0 + if: always() + with: + msg: Apply pre-commit code formatting diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2e30321..943e022 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,6 +12,9 @@ on: jobs: build: + permissions: + contents: read + pull-requests: write strategy: # We want to see all failures: fail-fast: false @@ -20,46 +23,39 @@ jobs: - ["ubuntu", "ubuntu-latest"] config: # [Python version, tox env] - - ["3.11", "release-check"] - - ["3.11", "lint"] - - ["3.8", "py38"] - - ["3.9", "py39"] - - ["3.10", "py310"] - - ["3.11", "py311"] - - ["3.12", "py312"] - - ["3.11", "coverage"] + - ["3.11", "release-check"] + - ["3.9", "py39"] + - ["3.10", "py310"] + - ["3.11", "py311"] + - ["3.12", "py312"] + - ["3.13", "py313"] + - ["3.11", "coverage"] runs-on: ${{ matrix.os[1] }} if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: ${{ matrix.config[1] }} steps: - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 with: - python-version: ${{ matrix.config[0] }} - - name: Pip cache - uses: actions/cache@v4 + persist-credentials: false + - name: Install uv + caching + uses: astral-sh/setup-uv@v5 with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} - restore-keys: | - ${{ runner.os }}-pip-${{ matrix.config[0] }}- - ${{ runner.os }}-pip- - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox + enable-cache: true + cache-dependency-glob: | + setup.* + tox.ini + python-version: ${{ matrix.matrix.config[0] }} + github-token: ${{ secrets.GITHUB_TOKEN }} - name: Test if: ${{ !startsWith(runner.os, 'Mac') }} - run: tox -e ${{ matrix.config[1] }} + run: uvx --with tox-uv tox -e ${{ matrix.config[1] }} - name: Test (macOS) if: ${{ startsWith(runner.os, 'Mac') }} - run: tox -e ${{ matrix.config[1] }}-universal2 + run: uvx --with tox-uv tox -e ${{ matrix.config[1] }}-universal2 - name: Coverage if: matrix.config[1] == 'coverage' run: | - pip install coveralls - coveralls --service=github + uvx coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.meta.toml b/.meta.toml index 82a8245..88f4442 100644 --- a/.meta.toml +++ b/.meta.toml @@ -2,7 +2,7 @@ # https://github.com/zopefoundation/meta/tree/master/config/zope-product [meta] template = "zope-product" -commit-id = "b1221c3c" +commit-id = "a0de4e93" [python] with-pypy = false @@ -16,7 +16,7 @@ with-docs = false use-flake8 = true [coverage] -fail-under = 70 +fail-under = 73 [coverage-run] source = "src" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7f83638 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,28 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/zope-product +minimum_pre_commit_version: '3.6' +repos: + - repo: https://github.com/pycqa/isort + rev: "6.0.1" + hooks: + - id: isort + - repo: https://github.com/hhatto/autopep8 + rev: "v2.3.2" + hooks: + - id: autopep8 + args: [--in-place, --aggressive, --aggressive] + - repo: https://github.com/asottile/pyupgrade + rev: v3.19.1 + hooks: + - id: pyupgrade + args: [--py39-plus] + - repo: https://github.com/isidentical/teyit + rev: 0.4.3 + hooks: + - id: teyit + - repo: https://github.com/PyCQA/flake8 + rev: "7.1.2" + hooks: + - id: flake8 + additional_dependencies: + - flake8-debugger == 4.1.2 diff --git a/CHANGES.rst b/CHANGES.rst index 5148e15..f21cfb2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,10 @@ Changelog ---------------- +- Add support for Python 3.13. + +- Drop support for Python 3.8. + - Drop support for Python 3.7. 4.1 (2024-01-04) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5c34d4..ce446a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ +--> # Contributing to zopefoundation projects The projects under the zopefoundation GitHub organization are open source and diff --git a/MANIFEST.in b/MANIFEST.in index c69cebb..e22e408 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,6 +5,7 @@ include *.rst include *.txt include buildout.cfg include tox.ini +include .pre-commit-config.yaml recursive-include src *.py recursive-include src *.dtml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..bf33e5d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/zope-product + +[build-system] +requires = [ + "setuptools == 75.8.2", + "wheel", +] +build-backend = "setuptools.build_meta" + +[tool.coverage.run] +branch = true +source = ["src"] + +[tool.coverage.report] +fail_under = 73 +precision = 2 +ignore_errors = true +show_missing = true +exclude_lines = [ + "pragma: no cover", + "pragma: nocover", + "except ImportError:", + "raise NotImplementedError", + "if __name__ == '__main__':", + "self.fail", + "raise AssertionError", + "raise unittest.Skip", +] + +[tool.coverage.html] +directory = "parts/htmlcov" diff --git a/setup.py b/setup.py index 5e5723c..a5650b4 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ '/Products.ZSQLMethods/issues'), 'Sources': 'https://github.com/zopefoundation/Products.ZSQLMethods', }, - license='ZPL 2.1', + license='ZPL-2.1', description='SQL method support for Zope.', author='Zope Foundation and Contributors', author_email='zope-dev@zope.dev', @@ -47,16 +47,16 @@ 'License :: OSI Approved :: Zope Public License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', - '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', 'Topic :: Database', 'Topic :: Database :: Front-Ends', ], - python_requires='>=3.8', + python_requires='>=3.9', install_requires=[ 'setuptools', 'Zope >= 4.2.1', diff --git a/src/Shared/DC/ZRDB/Connection.py b/src/Shared/DC/ZRDB/Connection.py index b942ef0..c066676 100644 --- a/src/Shared/DC/ZRDB/Connection.py +++ b/src/Shared/DC/ZRDB/Connection.py @@ -199,8 +199,8 @@ def connect(self, s): except Exception: t, v, tb = sys.exc_info() raise BadRequest( - 'Error connecting to DB.
\n' - '\n' % (t, v)).with_traceback(tb) + 'Error connecting to DB.
\n' + '\n' % (t, v)).with_traceback(tb) finally: tb = None self._v_connected = DateTime() diff --git a/src/Shared/DC/ZRDB/tests/testArgs.py b/src/Shared/DC/ZRDB/tests/testArgs.py index 9602d17..debfcc7 100644 --- a/src/Shared/DC/ZRDB/tests/testArgs.py +++ b/src/Shared/DC/ZRDB/tests/testArgs.py @@ -13,8 +13,6 @@ import pickle from unittest import TestCase -from unittest import TestSuite -from unittest import makeSuite class TestArgs(TestCase): @@ -56,7 +54,7 @@ def test_dict_getters(self): args = self._makeOne({'arg1': {'type': 'string', 'default': 'n/a'}}, ['arg1']) self.assertIn('arg1', args) - self.assertTrue('arg1' in args) + self.assertIn('arg1', args) self.assertDictEqual(args['arg1'], {'default': 'n/a', 'type': 'string'}) self.assertEqual(args.keys(), ['arg1']) @@ -81,7 +79,3 @@ def test__delitem__(self): del args['arg2'] self.assertEqual(len(args), 0) - - -def test_suite(): - return TestSuite((makeSuite(TestArgs),)) diff --git a/src/Shared/DC/ZRDB/tests/testResult.py b/src/Shared/DC/ZRDB/tests/testResult.py index 743dc42..52ac850 100644 --- a/src/Shared/DC/ZRDB/tests/testResult.py +++ b/src/Shared/DC/ZRDB/tests/testResult.py @@ -1,7 +1,5 @@ from io import StringIO from unittest import TestCase -from unittest import TestSuite -from unittest import makeSuite from ExtensionClass import Base @@ -40,7 +38,7 @@ def test_results(self): self.assertEqual(row.bar, 4) self.assertEqual(row.FOO, 3) self.assertEqual(row.BAR, 4) - self.assertTrue(isinstance(row, Brain)) + self.assertIsInstance(row, Brain) def test_rdb_file(self): infile = StringIO("""\ @@ -67,8 +65,4 @@ def test_rdb_file(self): self.assertEqual(row.bar, 4) self.assertEqual(row.FOO, 3) self.assertEqual(row.BAR, 4) - self.assertTrue(isinstance(row, Brain)) - - -def test_suite(): - return TestSuite((makeSuite(TestResults),)) + self.assertIsInstance(row, Brain) diff --git a/src/Shared/DC/ZRDB/tests/testTHUNK.py b/src/Shared/DC/ZRDB/tests/testTHUNK.py index 79fee5e..69b8530 100644 --- a/src/Shared/DC/ZRDB/tests/testTHUNK.py +++ b/src/Shared/DC/ZRDB/tests/testTHUNK.py @@ -12,8 +12,6 @@ ############################################################################## from unittest import TestCase -from unittest import TestSuite -from unittest import makeSuite class TestTM(TestCase): @@ -55,7 +53,3 @@ def test_sortKey(self): tm.setSortKey([]) self.assertEqual(tm.sortKey(), '[]') - - -def test_suite(): - return TestSuite((makeSuite(TestTM),)) diff --git a/src/Shared/DC/ZRDB/tests/testTM.py b/src/Shared/DC/ZRDB/tests/testTM.py index 483cd54..de77a06 100644 --- a/src/Shared/DC/ZRDB/tests/testTM.py +++ b/src/Shared/DC/ZRDB/tests/testTM.py @@ -12,8 +12,6 @@ ############################################################################## from unittest import TestCase -from unittest import TestSuite -from unittest import makeSuite class TestTM(TestCase): @@ -55,7 +53,3 @@ def test_sortKey(self): tm.setSortKey([]) self.assertEqual(tm.sortKey(), '[]') - - -def test_suite(): - return TestSuite((makeSuite(TestTM),)) diff --git a/src/Shared/DC/ZRDB/tests/test_caching.py b/src/Shared/DC/ZRDB/tests/test_caching.py index 16896e8..cb758d3 100644 --- a/src/Shared/DC/ZRDB/tests/test_caching.py +++ b/src/Shared/DC/ZRDB/tests/test_caching.py @@ -1,8 +1,6 @@ from pprint import pprint from time import time from unittest import TestCase -from unittest import TestSuite -from unittest import makeSuite class DummyDB: @@ -357,11 +355,3 @@ def test_cached_result_not_called_for_no_caching(self): self.da.max_cache_ = 1 # check that we get an exception self.assertRaises(TypeError, self.da) - - -def test_suite(): - suite = TestSuite() - suite.addTest(makeSuite(TestCaching)) - suite.addTest(makeSuite(TestCacheKeys)) - suite.addTest(makeSuite(TestFullChain)) - return suite diff --git a/src/Shared/DC/ZRDB/tests/test_results.py b/src/Shared/DC/ZRDB/tests/test_results.py index b7d3061..ecff83e 100644 --- a/src/Shared/DC/ZRDB/tests/test_results.py +++ b/src/Shared/DC/ZRDB/tests/test_results.py @@ -73,12 +73,12 @@ def test_getattr_and_aliases(self): def test_suppliedbrain(self): ob = self._makeOne((self.columns, self.data), brains=Brain) row = ob[0] - self.assertTrue(isinstance(row, Brain)) + self.assertIsInstance(row, Brain) def test_suppliedparent(self): ob = self._makeOne((self.columns, self.data), parent=Parent) row = ob[0] - self.assertTrue(aq_parent(row) is Parent) + self.assertIs(aq_parent(row), Parent) def test_tuples(self): ob = self._makeOne((self.columns, self.data)) @@ -146,7 +146,7 @@ def test_record_of(self): def test_record_hash(self): ob = self._makeOne((self.columns, self.data)) row = ob[0] - self.assertTrue(isinstance(hash(row), int)) + self.assertIsInstance(hash(row), int) def test_record_len(self): ob = self._makeOne((self.columns, self.data)) diff --git a/tox.ini b/tox.ini index 1670a3a..f5fcda4 100644 --- a/tox.ini +++ b/tox.ini @@ -5,32 +5,40 @@ minversion = 3.18 envlist = release-check lint - py38 py39 py310 py311 py312 + py313 coverage [testenv] skip_install = true deps = - setuptools < 69 - zc.buildout >= 3.0.1 - wheel > 0.37 + setuptools == 75.8.2 + zc.buildout + wheel setenv = - py312: VIRTUALENV_PIP=23.1.2 - py312: PIP_REQUIRE_VIRTUALENV=0 commands_pre = {envbindir}/buildout -nc {toxinidir}/buildout.cfg buildout:directory={envdir} buildout:develop={toxinidir} install test commands = {envbindir}/test {posargs:-cv} +[testenv:setuptools-latest] +basepython = python3 +deps = + git+https://github.com/pypa/setuptools.git\#egg=setuptools + zc.buildout + wheel + + [testenv:release-check] description = ensure that the distribution is ready to release basepython = python3 skip_install = true deps = + setuptools == 75.8.2 + wheel twine build check-manifest @@ -44,29 +52,14 @@ commands = twine check dist/* [testenv:lint] +description = This env runs all linters configured in .pre-commit-config.yaml basepython = python3 -commands_pre = - mkdir -p {toxinidir}/parts/flake8 -allowlist_externals = - mkdir -commands = - isort --check-only --diff {toxinidir}/src {toxinidir}/setup.py - flake8 {toxinidir}/src {toxinidir}/setup.py +skip_install = true deps = - flake8 - isort - # Useful flake8 plugins that are Python and Plone specific: - flake8-coding - flake8-debugger - mccabe - -[testenv:isort-apply] -basepython = python3 + pre-commit commands_pre = -deps = - isort commands = - isort {toxinidir}/src {toxinidir}/setup.py [] + pre-commit run --all-files --show-diff-on-failure [testenv:coverage] basepython = python3 @@ -76,28 +69,9 @@ allowlist_externals = mkdir deps = {[testenv]deps} - coverage + coverage[toml] commands = mkdir -p {toxinidir}/parts/htmlcov coverage run {envbindir}/test {posargs:-cv} coverage html - coverage report -m --fail-under=70 - -[coverage:run] -branch = True -source = src - -[coverage:report] -precision = 2 -ignore_errors = True -exclude_lines = - pragma: no cover - pragma: nocover - except ImportError: - raise NotImplementedError - if __name__ == '__main__': - self.fail - raise AssertionError - -[coverage:html] -directory = parts/htmlcov + coverage report