diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index dde4172..315f6b0 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -12,25 +12,10 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.9', 'pypy3.9', '3.10', 'pypy3.10', '3.11', '3.12', '3.13'] + python-version: ['3.10', '3.11', 'pypy3.11', '3.12', '3.13', '3.14'] exclude: - os: macos-latest - python-version: '3.9' - - os: macos-latest - python-version: 'pypy3.9' - - os: macos-latest - python-version: '3.10' - - os: macos-latest - python-version: 'pypy3.10' - include: - - os: macos-13 - python-version: '3.9' - - os: macos-13 - python-version: 'pypy3.9' - - os: macos-13 python-version: '3.10' - - os: macos-13 - python-version: 'pypy3.10' steps: - uses: actions/checkout@v5 with: @@ -65,7 +50,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version: '3.14' - run: pip install build~=1.2 - run: python -m build - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/Dockerfile b/Dockerfile index d72808a..494c6fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG PYTHON_VERSION="3.13" +ARG PYTHON_VERSION="3.14" FROM python:${PYTHON_VERSION} diff --git a/NOTICE b/NOTICE index 11992d3..2f6f002 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ version-query -Copyright (c) 2017-2025 Mateusz Bysiek https://mbdevpl.github.io/ +Copyright (c) 2017-2026 Mateusz Bysiek https://mbdevpl.github.io/ Copyright (c) 2020 John Vandenberg https://github.com/jayvdb Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/README.rst b/README.rst index 28ab667..adb8824 100644 --- a/README.rst +++ b/README.rst @@ -517,7 +517,7 @@ using version-query without any issues. Requirements ============ -Python version 3.9 or later. +Python version 3.10 or later. Python libraries as specified in ``_. diff --git a/requirements_ci.txt b/requirements_ci.txt index 8d9b526..7e8f43a 100644 --- a/requirements_ci.txt +++ b/requirements_ci.txt @@ -4,6 +4,7 @@ coverage ~= 7.2 flake518 ~= 1.6 mypy ~= 1.5 pydocstyle ~= 6.3 -pylint ~= 3.1 +pylint ~= 3.3; python_version < '3.10' +pylint ~= 4.0; python_version >= '3.10' twine ~= 6.1 types-setuptools >= 67.4 diff --git a/setup.py b/setup.py index ffe4c0e..287d98f 100644 --- a/setup.py +++ b/setup.py @@ -19,11 +19,11 @@ class Package(boilerplates.setup.Package): 'Operating System :: MacOS', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', - '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 :: 3.14', 'Programming Language :: Python :: 3 :: Only', 'Topic :: Software Development :: Version Control', 'Topic :: Software Development :: Version Control :: Git', diff --git a/test/__init__.py b/test/__init__.py index e2a3ef0..e7135ae 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -12,3 +12,6 @@ class TestsLogging(Logging): TestsLogging.configure() +logging.getLogger('version_query.git_query').setLevel(logging.INFO) +logging.getLogger('version_query.parser').setLevel(logging.INFO) +logging.getLogger('version_query.version').setLevel(logging.INFO) diff --git a/test/test_cli.py b/test/test_cli.py index a9e8527..54df6fc 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -3,6 +3,7 @@ import contextlib import io import logging +import os import runpy import sys import unittest @@ -42,6 +43,9 @@ class Tests(unittest.TestCase): def test_not_as_main(self): # pylint: disable = no-self-use run_module('version_query', run_name='__not_main__') + @unittest.skipUnless( + os.environ.get('TEST_CLI') or os.environ.get('CI'), + 'skipping CLI test which breaks test logging') def test_help(self): sio = io.StringIO() with contextlib.redirect_stderr(sio), preserve_logger_level('version_query'), \ @@ -49,6 +53,9 @@ def test_help(self): run_module('version_query') _LOG.info('%s', sio.getvalue()) + @unittest.skipUnless( + os.environ.get('TEST_CLI') or os.environ.get('CI'), + 'skipping CLI test which breaks test logging') def test_bad_usage(self): sio = io.StringIO() with contextlib.redirect_stderr(sio), preserve_logger_level('version_query'), \ @@ -56,6 +63,9 @@ def test_bad_usage(self): run_module('version_query', '-p', '-i', '.') _LOG.info('%s', sio.getvalue()) + @unittest.skipUnless( + os.environ.get('TEST_CLI') or os.environ.get('CI'), + 'skipping CLI test which breaks test logging') def test_here(self): sio = io.StringIO() with temporarily_set_logger_level('version_query', logging.ERROR), \ @@ -64,6 +74,9 @@ def test_here(self): self.assertEqual(sio.getvalue().rstrip(), query_caller().to_str()) self.assertEqual(sio.getvalue().rstrip(), query_version_str()) + @unittest.skipUnless( + os.environ.get('TEST_CLI') or os.environ.get('CI'), + 'skipping CLI test which breaks test logging') def test_increment_here(self): sio = io.StringIO() with temporarily_set_logger_level('version_query', logging.ERROR), \ @@ -72,6 +85,9 @@ def test_increment_here(self): self.assertEqual(sio.getvalue().rstrip(), query_caller().increment(VersionComponent.Patch).to_str()) + @unittest.skipUnless( + os.environ.get('TEST_CLI') or os.environ.get('CI'), + 'skipping CLI test which breaks test logging') def test_predict_here(self): sio = io.StringIO() with temporarily_set_logger_level('version_query', logging.ERROR), \ diff --git a/test/test_git.py b/test/test_git.py index 0b6ec40..c3c44d7 100644 --- a/test/test_git.py +++ b/test/test_git.py @@ -2,6 +2,7 @@ import itertools import logging +import os import platform import unittest @@ -79,6 +80,19 @@ def test_nonversion_tags(self): version.local = (f'git{self.repo_head_hexsha}',) self.assertEqual(version, upcoming_version) + def test_too_short_version_tag(self): + self.git_commit_new_file() + self.repo.create_tag('v1.0') + self.git_commit_new_file() + self.repo.create_tag('v') + self.git_commit_new_file() + self.repo.create_tag('ver') + current_version = query_git_repo(self.repo_path) + _LOG.debug('current version is %s', current_version) + self.assertEqual(current_version.to_str(), '1.0') + + @unittest.skipUnless( + os.environ.get('TEST_LONG') or os.environ.get('CI'), 'skipping long test') def test_too_long_no_tag(self): self.git_commit_new_file() self.repo.create_tag('v4.0.0') diff --git a/test/test_query.py b/test/test_query.py index c471c38..3459d72 100644 --- a/test/test_query.py +++ b/test/test_query.py @@ -60,8 +60,11 @@ def _query_test_case(self, paths, query_function): else: _LOG.debug('%s: %s', path, version) + @unittest.skipUnless( + os.environ.get('TEST_LONG') or os.environ.get('CI'), 'skipping long test') def test_query_git_repo(self): self._check_examples_count('git repo', GIT_REPO_EXAMPLES) + _LOG.debug('testing query_git_repo() on %i repositories', len(GIT_REPO_EXAMPLES)) self._query_test_case(GIT_REPO_EXAMPLES, query_git_repo) def test_predict_caller_bad(self): @@ -81,7 +84,10 @@ def test_predict_caller_bad(self): _LOG.warning('removed %s from sys.path', project_path_str) project_file_path.unlink() + @unittest.skipUnless( + os.environ.get('TEST_LONG') or os.environ.get('CI'), 'skipping long test') def test_predict_git_repo(self): + _LOG.debug('testing predict_git_repo() on %i repositories', len(GIT_REPO_EXAMPLES)) self._query_test_case(GIT_REPO_EXAMPLES, predict_git_repo) @unittest.skipIf(not METADATA_JSON_EXAMPLE_PATHS, 'no "metadata.json" files found') diff --git a/test/test_version.py b/test/test_version.py index cd08309..9aaa1b9 100644 --- a/test/test_version.py +++ b/test/test_version.py @@ -4,7 +4,6 @@ import unittest import packaging.version -import pkg_resources import semver from version_query.version import VersionComponent, Version @@ -23,13 +22,12 @@ def test_from_str(self): for version_str, (args, kwargs) in STR_CASES.items(): version_tuple = case_to_version_tuple(args, kwargs) with self.subTest(version_str=version_str, version_tuple=version_tuple): - py_version = \ - pkg_resources.parse_version(version_str) + packaging.version.parse(version_str) _LOG.debug('packaging parsed version string %s into %s: %s', repr(version_str), type(py_version), py_version) - # self.assertIsInstance( - # py_version, packaging.version.Version, msg=(type(py_version), py_version)) + self.assertIsInstance( + py_version, packaging.version.Version, msg=(type(py_version), py_version)) try: sem_version = semver.VersionInfo.parse(version_str) @@ -38,8 +36,8 @@ def test_from_str(self): else: _LOG.debug('semver parsed version string %s into %s: %s', repr(version_str), type(sem_version), sem_version) - # self.assertIsInstance( - # sem_version, semver.VersionInfo, msg=(type(sem_version), sem_version)) + self.assertIsInstance( + sem_version, semver.VersionInfo, msg=(type(sem_version), sem_version)) self.assertEqual(Version.from_str(version_str).to_tuple(), version_tuple) @@ -59,7 +57,7 @@ def test_from_py_version(self): py_version = packaging.version.Version(version_str) self.assertEqual(Version.from_py_version(py_version).to_tuple(), version_tuple, py_version) - py_version_setuptools = pkg_resources.parse_version(version_str) + py_version_setuptools = packaging.version.parse(version_str) self.assertEqual(Version.from_py_version(py_version_setuptools).to_tuple(), version_tuple, py_version_setuptools) @@ -71,7 +69,6 @@ def test_to_py_version(self): py_version = packaging.version.Version(version_str) self.assertEqual(version.to_py_version(), py_version, msg=(version.to_py_version(), py_version)) - # py_version_setuptools = pkg_resources.parse_version(version_str) def test_from_sem_version(self): for version_str, (args, kwargs) in COMPATIBLE_STR_CASES.items(): diff --git a/version_query/git_query.py b/version_query/git_query.py index 380e407..510b7a1 100644 --- a/version_query/git_query.py +++ b/version_query/git_query.py @@ -15,19 +15,23 @@ def preprocess_git_version_tag(tag: str): """Remove a prefix from a version tag.""" if tag.startswith('ver'): + if len(tag) == 3: + raise ValueError(f'the tag "{tag}" does not contain any version information') return tag[3:] if tag.startswith('v'): + if len(tag) == 1: + raise ValueError(f'the tag "{tag}" does not contain any version information') return tag[1:] if tag and tag[0] in ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'): return tag - raise ValueError(f'given tag "{tag}" does not appear to be a version tag') + raise ValueError(f'the tag "{tag}" does not appear to be a version tag') def _git_version_tags(repo: git.Repo) -> t.Mapping[git.Tag, Version]: versions = {} for tag in repo.tags: try: - tag_str = preprocess_git_version_tag(str(tag)) + tag_str = preprocess_git_version_tag(tag.name) except ValueError: _LOG.debug('%s: ignoring non-version tag %s', repo, tag) continue @@ -35,7 +39,7 @@ def _git_version_tags(repo: git.Repo) -> t.Mapping[git.Tag, Version]: versions[tag] = Version.from_str(tag_str) except ValueError: # except packaging.version.InvalidVersion: - _LOG.warning('%s: failed to convert %s to version', repo, tag_str) + _LOG.warning('%s: failed to convert %s (%r) to version', repo, tag.name, tag_str) continue return versions diff --git a/version_query/main.py b/version_query/main.py index 5084d05..ab9907a 100644 --- a/version_query/main.py +++ b/version_query/main.py @@ -20,7 +20,7 @@ def main(args=None, namespace=None) -> None: description='''Tool for querying current versions of Python packages. Use LOGGING_LEVEL environment variable to adjust logging level.''', epilog=make_copyright_notice( - 2017, 2025, author='the contributors', url='https://github.com/mbdevpl/version-query'), + 2017, 2026, author='the contributors', url='https://github.com/mbdevpl/version-query'), formatter_class=argparse.ArgumentDefaultsHelpFormatter) add_version_option(parser, VERSION)