From 794e6be20f8314f989c78699723f4039ab3b22f2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 13:16:24 -0700 Subject: [PATCH 1/6] [pre-commit.ci] pre-commit autoupdate (#3496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.31.2 → 0.31.3](https://github.com/python-jsonschema/check-jsonschema/compare/0.31.2...0.31.3) - [github.com/astral-sh/ruff-pre-commit: v0.9.9 → v0.9.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.9...v0.9.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 60631f23f..597f4cb81 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.31.2 + rev: 0.31.3 hooks: - id: check-github-workflows args: ["--verbose"] @@ -23,7 +23,7 @@ repos: hooks: - id: validate-pyproject - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.9.9" + rev: "v0.9.10" hooks: - id: ruff-format - id: ruff From beba4be197d49abdb8797ae1218dad1e6d1ee005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 18 Mar 2025 15:34:06 +0100 Subject: [PATCH 2/6] Tests: Adjust expected exception message for Python 3.14.0a6 (#3500) E AssertionError: Regex pattern did not match. E Regex: '3 cannot cast to typing.Union\\[str, int\\]' E Input: '3 cannot cast to str | int' Change caused likely by https://github.com/python/cpython/commit/dc6d66f44c0a25b69dfec7e4ffc4a6fa5e4feada --- docs/changelog/3500.bugfix.rst | 2 ++ tests/config/loader/test_str_convert.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/3500.bugfix.rst diff --git a/docs/changelog/3500.bugfix.rst b/docs/changelog/3500.bugfix.rst new file mode 100644 index 000000000..9df1cf05d --- /dev/null +++ b/docs/changelog/3500.bugfix.rst @@ -0,0 +1,2 @@ +Make tox tests pass with Python 3.14.0a6 +- by :user:`hroncok` diff --git a/tests/config/loader/test_str_convert.py b/tests/config/loader/test_str_convert.py index 8540e2105..b06929f48 100644 --- a/tests/config/loader/test_str_convert.py +++ b/tests/config/loader/test_str_convert.py @@ -77,7 +77,7 @@ def test_str_convert_ok_py39(raw: str, value: Any, of_type: type[Any]) -> None: [ ("a", TypeVar, TypeError, r"a cannot cast to .*typing.TypeVar.*"), ("3", Literal["1", "2"], ValueError, r"3 must be one of \('1', '2'\)"), - ("3", Union[str, int], TypeError, r"3 cannot cast to typing.Union\[str, int\]"), + ("3", Union[str, int], TypeError, r"3 cannot cast to (typing.Union\[str, int\]|str \| int)"), ("", Command, ValueError, r"attempting to parse '' into a command failed"), ], ) From f5f5cb1d7a9269a7a628af9c57eb8f7fbc18cf9f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 08:32:19 -0700 Subject: [PATCH 3/6] [pre-commit.ci] pre-commit autoupdate (#3499) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/abravalheri/validate-pyproject: v0.23 → v0.24](https://github.com/abravalheri/validate-pyproject/compare/v0.23...v0.24) - [github.com/astral-sh/ruff-pre-commit: v0.9.10 → v0.11.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.10...v0.11.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 597f4cb81..a5ddfda8b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,11 +19,11 @@ repos: hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject - rev: "v0.23" + rev: "v0.24" hooks: - id: validate-pyproject - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.9.10" + rev: "v0.11.0" hooks: - id: ruff-format - id: ruff From 5a67ae1a9e350e1e5a0149d6835bd29c517cc3ee Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 11:47:20 -0700 Subject: [PATCH 4/6] [pre-commit.ci] pre-commit autoupdate (#3505) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/abravalheri/validate-pyproject: v0.24 → v0.24.1](https://github.com/abravalheri/validate-pyproject/compare/v0.24...v0.24.1) - [github.com/astral-sh/ruff-pre-commit: v0.11.0 → v0.11.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.0...v0.11.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a5ddfda8b..59820e00b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,11 +19,11 @@ repos: hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject - rev: "v0.24" + rev: "v0.24.1" hooks: - id: validate-pyproject - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.11.0" + rev: "v0.11.2" hooks: - id: ruff-format - id: ruff From 0e6b4ad70b96c750e581ed02ae8dcdcad83cee66 Mon Sep 17 00:00:00 2001 From: Martin Imre Date: Thu, 27 Mar 2025 16:11:42 +0100 Subject: [PATCH 5/6] feat(config): Allow ranges in envlist (#3503) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(config): Allow ranges in envlist Implements #3502. Now it is possible to use ranges within the {} of an env specifier such as py3{10-13}. I chose to implement it as a pre-processing string replacement that just replaces the range with a literal enumeration of the range members. This is mainly to avoid more in-depth handling of these ranges when it coto generative environment lists. Also moves CircularChainError from `of_type` to `types` to avoid a circular import error. (kinda ironic :D) * fixup! feat(config): Allow ranges in envlist * fixup! feat(config): Allow ranges in envlist * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fixup! feat(config): Allow ranges in envlist * fixup! feat(config): Allow ranges in envlist --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bernát Gábor --- docs/changelog/3502.feature.rst | 1 + docs/config.rst | 45 +++++++++++--- src/tox/config/loader/ini/factor.py | 16 ++++- src/tox/config/loader/replacer.py | 2 +- src/tox/config/loader/str_convert.py | 2 + src/tox/config/of_type.py | 5 +- src/tox/config/source/ini_section.py | 4 +- src/tox/config/types.py | 4 ++ tests/config/loader/ini/test_factor.py | 82 ++++++++++++++++++++++++++ 9 files changed, 144 insertions(+), 17 deletions(-) create mode 100644 docs/changelog/3502.feature.rst diff --git a/docs/changelog/3502.feature.rst b/docs/changelog/3502.feature.rst new file mode 100644 index 000000000..47772b8de --- /dev/null +++ b/docs/changelog/3502.feature.rst @@ -0,0 +1 @@ +Add support for number ranges in generative environments, more details :ref:`here`. - by :user:`mimre25` diff --git a/docs/config.rst b/docs/config.rst index fb099c3d9..86e282688 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -1554,6 +1554,8 @@ Conditional settings Here pip will be always installed as the configuration value is not conditional. black is only used for the ``format`` environment, while ``pytest`` is only installed for the ``py310`` and ``py39`` environments. +.. _generative-environment-list: + Generative environment list ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1563,7 +1565,7 @@ If you have a large matrix of dependencies, python versions and/or environments .. code-block:: ini [tox] - env_list = py{311,310,39}-django{41,40}-{sqlite,mysql} + env_list = py3{9-11}-django{41,40}-{sqlite,mysql} [testenv] deps = @@ -1582,24 +1584,49 @@ This will generate the following tox environments: > tox l default environments: - py311-django41-sqlite -> [no description] - py311-django41-mysql -> [no description] - py311-django40-sqlite -> [no description] - py311-django40-mysql -> [no description] - py310-django41-sqlite -> [no description] - py310-django41-mysql -> [no description] - py310-django40-sqlite -> [no description] - py310-django40-mysql -> [no description] py39-django41-sqlite -> [no description] py39-django41-mysql -> [no description] py39-django40-sqlite -> [no description] py39-django40-mysql -> [no description] + py310-django41-sqlite -> [no description] + py310-django41-mysql -> [no description] + py310-django40-sqlite -> [no description] + py310-django40-mysql -> [no description] + py311-django41-sqlite -> [no description] + py311-django41-mysql -> [no description] + py311-django40-sqlite -> [no description] + py311-django40-mysql -> [no description] + +Both enumerations (``{1,2,3}``) and numerical ranges (``{1-3}``) are supported, and can be mixed together: + +.. code-block:: ini + + [tox] + env_list = py3{8-10, 11, 13-14} + +will create the following envs: + +.. code-block:: shell + + > tox l + default environments: + py38 -> [no description] + py39 -> [no description] + py310 -> [no description] + py311 -> [no description] + py313 -> [no description] + py314 -> [no description] + +Negative ranges will also be expanded (``{3-1}`` -> ``{3,2,1}``), however, open ranges such as ``{1-}``, ``{-2}``, ``{a-}``, and ``{-b}`` will not be expanded. + + Generative section names ~~~~~~~~~~~~~~~~~~~~~~~~ Suppose you have some binary packages, and need to run tests both in 32 and 64 bits. You also want an environment to create your virtual env for the developers. +This also supports ranges in the same way as generative environment lists. .. code-block:: ini diff --git a/src/tox/config/loader/ini/factor.py b/src/tox/config/loader/ini/factor.py index 1bb639bbf..11b4fa66d 100644 --- a/src/tox/config/loader/ini/factor.py +++ b/src/tox/config/loader/ini/factor.py @@ -66,7 +66,7 @@ def find_factor_groups(value: str) -> Iterator[list[tuple[str, bool]]]: yield result -_FACTOR_RE = re.compile(r"!?[\w._][\w._-]*") +_FACTOR_RE = re.compile(r"(?:!?[\w._][\w._-]*|^$)") def expand_env_with_negation(value: str) -> Iterator[str]: @@ -93,8 +93,22 @@ def is_negated(factor: str) -> bool: return factor.startswith("!") +def expand_ranges(value: str) -> str: + """Expand ranges in env expressions, eg py3{10-13} -> "py3{10,11,12,13}""" + matches = re.findall(r"((\d+)-(\d+)|\d+)(?:,|})", value) + for src, start_, end_ in matches: + if src and start_ and end_: + start = int(start_) + end = int(end_) + direction = 1 if start < end else -1 + expansion = ",".join(str(x) for x in range(start, end + direction, direction)) + value = value.replace(src, expansion, 1) + return value + + __all__ = ( "expand_factors", + "expand_ranges", "extend_factors", "filter_for_env", "find_envs", diff --git a/src/tox/config/loader/replacer.py b/src/tox/config/loader/replacer.py index 05e3c060b..5d9fd70eb 100644 --- a/src/tox/config/loader/replacer.py +++ b/src/tox/config/loader/replacer.py @@ -8,7 +8,7 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Any, Final, Sequence, Union -from tox.config.of_type import CircularChainError +from tox.config.types import CircularChainError from tox.execute.request import shell_cmd if TYPE_CHECKING: diff --git a/src/tox/config/loader/str_convert.py b/src/tox/config/loader/str_convert.py index 65dd46dad..248afe268 100644 --- a/src/tox/config/loader/str_convert.py +++ b/src/tox/config/loader/str_convert.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Any, Iterator from tox.config.loader.convert import Convert +from tox.config.loader.ini.factor import expand_ranges from tox.config.types import Command, EnvList if TYPE_CHECKING: @@ -113,6 +114,7 @@ def to_command(value: str) -> Command | None: def to_env_list(value: str) -> EnvList: from tox.config.loader.ini.factor import extend_factors # noqa: PLC0415 + value = expand_ranges(value) elements = list(chain.from_iterable(extend_factors(expr) for expr in value.split("\n"))) return EnvList(elements) diff --git a/src/tox/config/of_type.py b/src/tox/config/of_type.py index d31922f6c..4d49d16ef 100644 --- a/src/tox/config/of_type.py +++ b/src/tox/config/of_type.py @@ -7,16 +7,13 @@ from typing import TYPE_CHECKING, Callable, Generic, Iterable, TypeVar, cast from tox.config.loader.api import ConfigLoadArgs, Loader +from tox.config.types import CircularChainError if TYPE_CHECKING: from tox.config.loader.convert import Factory from tox.config.main import Config # pragma: no cover -class CircularChainError(ValueError): - """circular chain in config""" - - T = TypeVar("T") V = TypeVar("V") diff --git a/src/tox/config/source/ini_section.py b/src/tox/config/source/ini_section.py index d7d26720f..7ed3267f7 100644 --- a/src/tox/config/source/ini_section.py +++ b/src/tox/config/source/ini_section.py @@ -1,6 +1,6 @@ from __future__ import annotations -from tox.config.loader.ini.factor import extend_factors +from tox.config.loader.ini.factor import expand_ranges, extend_factors from tox.config.loader.section import Section @@ -15,7 +15,7 @@ def is_test_env(self) -> bool: @property def names(self) -> list[str]: - return list(extend_factors(self.name)) + return list(extend_factors(expand_ranges(self.name))) TEST_ENV_PREFIX = "testenv" diff --git a/src/tox/config/types.py b/src/tox/config/types.py index 8d1300aa4..42c2c4814 100644 --- a/src/tox/config/types.py +++ b/src/tox/config/types.py @@ -6,6 +6,10 @@ from tox.execute.request import shell_cmd +class CircularChainError(ValueError): + """circular chain in config""" + + class Command: # noqa: PLW1641 """A command to execute.""" diff --git a/tests/config/loader/ini/test_factor.py b/tests/config/loader/ini/test_factor.py index b73abd03b..5dbc50b57 100644 --- a/tests/config/loader/ini/test_factor.py +++ b/tests/config/loader/ini/test_factor.py @@ -178,6 +178,76 @@ def test_factor_config_no_env_list_creates_env(tox_ini_conf: ToxIniCreator) -> N assert list(config) == ["py37-django15", "py37-django16", "py36"] +@pytest.mark.parametrize( + ("env_list", "expected_envs"), + [ + pytest.param("py3{10-13}", ["py310", "py311", "py312", "py313"], id="Expand positive range"), + pytest.param("py3{10-11},a", ["py310", "py311", "a"], id="Expand range and add additional env"), + pytest.param("py3{10-11},a{1-2}", ["py310", "py311", "a1", "a2"], id="Expand multiple env with ranges"), + pytest.param( + "py3{10-12,14}", + ["py310", "py311", "py312", "py314"], + id="Expand ranges, and allow extra parameter in generator", + ), + pytest.param( + "py3{8-10,12,14-16}", + ["py38", "py39", "py310", "py312", "py314", "py315", "py316"], + id="Expand multiple ranges for one generator", + ), + pytest.param( + "py3{10-11}-django1.{3-5}", + [ + "py310-django1.3", + "py310-django1.4", + "py310-django1.5", + "py311-django1.3", + "py311-django1.4", + "py311-django1.5", + ], + id="Expand ranges and factor multiple environment parts", + ), + pytest.param( + "py3{10-11, 13}-django1.{3-4, 6}", + [ + "py310-django1.3", + "py310-django1.4", + "py310-django1.6", + "py311-django1.3", + "py311-django1.4", + "py311-django1.6", + "py313-django1.3", + "py313-django1.4", + "py313-django1.6", + ], + id="Expand ranges and parameters and factor multiple environment parts", + ), + pytest.param( + "py3{10-11},a{1-2}-b{3-4}", + ["py310", "py311", "a1-b3", "a1-b4", "a2-b3", "a2-b4"], + id="Expand ranges and parameters & factor multiple environment parts for multiple generative environments", + ), + pytest.param("py3{13-11}", ["py313", "py312", "py311"], id="Expand negative ranges"), + pytest.param("3.{10-13}", ["3.10", "3.11", "3.12", "3.13"], id="Expand new-style python envs"), + pytest.param("py3{-11}", ["py3-11"], id="Don't expand left-open numerical range"), + pytest.param("foo{11-}", ["foo11-"], id="Don't expand right-open numerical range"), + pytest.param("foo{a-}", ["fooa-"], id="Don't expand right-open range"), + pytest.param("foo{-a}", ["foo-a"], id="Don't expand left-open range"), + pytest.param("foo{a-11}", ["fooa-11"], id="Don't expand alpha-umerical range"), + pytest.param("foo{13-a}", ["foo13-a"], id="Don't expand numerical-alpha range"), + pytest.param("foo{a-b}", ["fooa-b"], id="Don't expand non-numerical range"), + ], +) +def test_env_list_expands_ranges(env_list: str, expected_envs: list[str], tox_ini_conf: ToxIniCreator) -> None: + config = tox_ini_conf( + f""" + [tox] + env_list = {env_list} + """ + ) + + assert list(config) == expected_envs + + @pytest.mark.parametrize( ("env", "result"), [ @@ -202,6 +272,18 @@ def test_ini_loader_raw_with_factors( assert outcome == result +def test_generative_section_name_with_ranges(tox_ini_conf: ToxIniCreator) -> None: + config = tox_ini_conf( + """ + [testenv:py3{11-13}-{black,lint}] + deps-x = + black: black + lint: flake8 + """, + ) + assert list(config) == ["py311-black", "py311-lint", "py312-black", "py312-lint", "py313-black", "py313-lint"] + + def test_generative_section_name(tox_ini_conf: ToxIniCreator) -> None: config = tox_ini_conf( """ From 3d35559ca1e9411708b9e5f73d610691a4fbdefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Thu, 27 Mar 2025 08:12:21 -0700 Subject: [PATCH 6/6] release 4.25.0 --- docs/changelog.rst | 12 ++++++++++++ docs/changelog/3500.bugfix.rst | 2 -- docs/changelog/3502.feature.rst | 1 - 3 files changed, 12 insertions(+), 3 deletions(-) delete mode 100644 docs/changelog/3500.bugfix.rst delete mode 100644 docs/changelog/3502.feature.rst diff --git a/docs/changelog.rst b/docs/changelog.rst index 623d7a456..fa6077355 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,18 @@ Release History .. towncrier release notes start +v4.25.0 (2025-03-27) +-------------------- + +Features - 4.25.0 +~~~~~~~~~~~~~~~~~ +- Add support for number ranges in generative environments, more details :ref:`here`. - by :user:`mimre25` (:issue:`3502`) + +Bugfixes - 4.25.0 +~~~~~~~~~~~~~~~~~ +- Make tox tests pass with Python 3.14.0a6 + - by :user:`hroncok` (:issue:`3500`) + v4.24.2 (2025-03-07) -------------------- diff --git a/docs/changelog/3500.bugfix.rst b/docs/changelog/3500.bugfix.rst deleted file mode 100644 index 9df1cf05d..000000000 --- a/docs/changelog/3500.bugfix.rst +++ /dev/null @@ -1,2 +0,0 @@ -Make tox tests pass with Python 3.14.0a6 -- by :user:`hroncok` diff --git a/docs/changelog/3502.feature.rst b/docs/changelog/3502.feature.rst deleted file mode 100644 index 47772b8de..000000000 --- a/docs/changelog/3502.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Add support for number ranges in generative environments, more details :ref:`here`. - by :user:`mimre25`