Skip to content

Commit 0dddd30

Browse files
committed
Add 3.14 support and drop 3.9
Signed-off-by: Bernát Gábor <[email protected]>
1 parent 1f28422 commit 0dddd30

File tree

94 files changed

+475
-320
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+475
-320
lines changed

.github/workflows/check.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ jobs:
2828
- "3.12"
2929
- "3.11"
3030
- "3.10"
31-
- "3.9"
3231
os:
3332
- ubuntu-24.04
3433
- windows-2025
@@ -66,6 +65,7 @@ jobs:
6665
matrix:
6766
tox_env:
6867
- type
68+
- type-3.10
6969
- dev
7070
- docs
7171
- pkg_meta
@@ -89,7 +89,10 @@ jobs:
8989
shell: bash
9090
run: echo "$USERPROFILE/.local/bin" >> $GITHUB_PATH
9191
- name: Install tox@self
92-
run: uv tool install --python-preference only-managed --python 3.13 tox@.
92+
run: uv tool install --python-preference only-managed --python 3.14 tox@.
93+
- name: Install Python 3.10 for type-3.10
94+
if: matrix.tox_env == 'type-3.10'
95+
run: uv python install 3.10
9396
- name: Setup check suite
9497
run: tox r -vv --notest --skip-missing-interpreters false -e ${{ matrix.tox_env }}
9598
- name: Run check for ${{ matrix.tox_env }}

.github/workflows/release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
cache-dependency-glob: "pyproject.toml"
2222
github-token: ${{ secrets.GITHUB_TOKEN }}
2323
- name: Build package
24-
run: uv build --python 3.13 --python-preference only-managed --sdist --wheel . --out-dir dist
24+
run: uv build --python 3.14 --python-preference only-managed --sdist --wheel . --out-dir dist
2525
- name: Store the distribution packages
2626
uses: actions/upload-artifact@v4
2727
with:

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ repos:
1313
rev: v2.4.1
1414
hooks:
1515
- id: codespell
16-
additional_dependencies: ["tomli>=2.2.1"]
16+
additional_dependencies: ["tomli>=2.3"]
1717
- repo: https://github.com/tox-dev/pyproject-fmt
18-
rev: "v2.6.0"
18+
rev: "v2.8.0"
1919
hooks:
2020
- id: pyproject-fmt
2121
- repo: https://github.com/abravalheri/validate-pyproject
2222
rev: "v0.24.1"
2323
hooks:
2424
- id: validate-pyproject
2525
- repo: https://github.com/astral-sh/ruff-pre-commit
26-
rev: "v0.13.2"
26+
rev: "v0.14.0"
2727
hooks:
2828
- id: ruff-check
2929
args: ["--fix", "--unsafe-fixes", "--exit-non-zero-on-fix"]
@@ -32,7 +32,7 @@ repos:
3232
rev: 1.20.0
3333
hooks:
3434
- id: blacken-docs
35-
additional_dependencies: [black==25.1]
35+
additional_dependencies: [black==25.9]
3636
- repo: https://github.com/pre-commit/pygrep-hooks
3737
rev: v1.10.0
3838
hooks:

.readthedocs.yaml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ build:
55
python: "3"
66
commands:
77
- pip install uv
8-
- uv venv
8+
- uv venv -p 3.14
99
- uv pip install tox-uv tox@.
10-
- .venv/bin/tox run -e docs --
10+
- .venv/bin/tox run -e docs --notest
11+
- .venv/bin/tox run -e docs --skip-pkg-install --

docs/installation.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Installation
44
As tool
55
-------
66

7-
:pypi:`tox` is a CLI tool that needs a Python interpreter (version 3.9 or higher) to run. We recommend either
7+
:pypi:`tox` is a CLI tool that needs a Python interpreter (version 3.10 or higher) to run. We recommend either
88
:pypi:`pipx` or :pypi:`uv` to install tox into an isolated environment. This has the added benefit that later you'll
99
be able to upgrade tox without affecting other parts of the system. We provide method for ``pip`` too here but we
1010
discourage that path if you can:
@@ -80,7 +80,7 @@ Python and OS Compatibility
8080

8181
tox works with the following Python interpreter implementations:
8282

83-
- `CPython <https://www.python.org/>`_ versions 3.9, 3.10, 3.11, 3.12, 3.13
83+
- `CPython <https://www.python.org/>`_ versions 3.10, 3.11, 3.12, 3.13, 3.14
8484

8585
This means tox works on the latest patch version of each of these minor versions. Previous patch versions are supported
8686
on a best effort approach.

pyproject.toml

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ maintainers = [
2626
authors = [
2727
{ name = "Bernát Gábor", email = "[email protected]" },
2828
]
29-
requires-python = ">=3.9"
29+
requires-python = ">=3.10"
3030
classifiers = [
3131
"Development Status :: 5 - Production/Stable",
3232
"Framework :: tox",
@@ -36,7 +36,6 @@ classifiers = [
3636
"Operating System :: Microsoft :: Windows",
3737
"Operating System :: POSIX",
3838
"Programming Language :: Python :: 3 :: Only",
39-
"Programming Language :: Python :: 3.9",
4039
"Programming Language :: Python :: 3.10",
4140
"Programming Language :: Python :: 3.11",
4241
"Programming Language :: Python :: 3.12",
@@ -50,17 +49,17 @@ dynamic = [
5049
"version",
5150
]
5251
dependencies = [
53-
"cachetools>=6.1",
52+
"cachetools>=6.2",
5453
"chardet>=5.2",
5554
"colorama>=0.4.6",
56-
"filelock>=3.18",
55+
"filelock>=3.19.1",
5756
"packaging>=25",
58-
"platformdirs>=4.3.8",
57+
"platformdirs>=4.5",
5958
"pluggy>=1.6",
6059
"pyproject-api>=1.9.1",
61-
"tomli>=2.2.1; python_version<'3.11'",
62-
"typing-extensions>=4.14.1; python_version<'3.11'",
63-
"virtualenv>=20.31.2",
60+
"tomli>=2.3; python_version<'3.11'",
61+
"typing-extensions>=4.15; python_version<'3.11'",
62+
"virtualenv>=20.35",
6463
]
6564
urls.Documentation = "https://tox.wiki"
6665
urls.Homepage = "http://tox.readthedocs.org"
@@ -76,55 +75,55 @@ dev = [
7675
{ include-group = "type" },
7776
]
7877
test = [
79-
"build[virtualenv]>=1.2.2.post1",
78+
"build[virtualenv]>=1.3",
8079
"covdefaults>=2.3",
81-
"coverage>=7.9.2",
80+
"coverage>=7.10.7",
8281
"detect-test-pollution>=1.2",
8382
"devpi-process>=1.0.2",
84-
"diff-cover>=9.6",
83+
"diff-cover>=9.7.1",
8584
"distlib>=0.4",
8685
"flaky>=3.8.1",
8786
"hatch-vcs>=0.5",
8887
"hatchling>=1.27",
89-
"pdm-backend",
90-
"psutil>=7",
91-
"pytest>=8.4.1",
92-
"pytest-cov>=6.2.1",
93-
"pytest-mock>=3.14.1",
88+
"pdm-backend>=2.4.5",
89+
"psutil>=7.1",
90+
"pytest>=8.4.2",
91+
"pytest-cov>=7",
92+
"pytest-mock>=3.15.1",
9493
"pytest-xdist>=3.8",
9594
"re-assert>=1.1",
9695
"setuptools>=80.9",
97-
"time-machine>=2.16; implementation_name!='pypy'",
96+
"time-machine>=2.19; implementation_name!='pypy'",
9897
"wheel>=0.45.1",
9998
]
10099
type = [
101-
"mypy==1.17",
102-
"types-cachetools>=6.1.0.20250717",
100+
"mypy==1.18.2",
101+
"types-cachetools>=6.2.0.20250827",
103102
"types-chardet>=5.0.4.6",
104103
{ include-group = "test" },
105104
]
106105
docs = [
107-
"furo>=2025.7.19",
106+
"furo>=2025.9.25",
108107
"sphinx>=8.2.3",
109-
"sphinx-argparse-cli>=1.19",
110-
"sphinx-autodoc-typehints>=3.2",
108+
"sphinx-argparse-cli>=1.20.1",
109+
"sphinx-autodoc-typehints>=3.4",
111110
"sphinx-copybutton>=0.5.2",
112111
"sphinx-inline-tabs>=2023.4.21",
113112
"sphinxcontrib-towncrier>=0.2.1a0",
114-
"towncrier>=24.8",
113+
"towncrier>=25.8",
115114
]
116115
fix = [
117-
"pre-commit-uv>=4.1.4",
116+
"pre-commit-uv>=4.1.5",
118117
]
119118
pkg-meta = [
120-
"check-wheel-contents>=0.6.2",
121-
"twine>=6.1",
122-
"uv>=0.8",
119+
"check-wheel-contents>=0.6.3",
120+
"twine>=6.2",
121+
"uv>=0.9",
123122
]
124123
release = [
125-
"gitpython>=3.1.44",
124+
"gitpython>=3.1.45",
126125
"packaging>=25",
127-
"towncrier>=24.8",
126+
"towncrier>=25.8",
128127
]
129128

130129
[tool.hatch]
@@ -140,7 +139,6 @@ build.targets.sdist.include = [
140139
version.source = "vcs"
141140

142141
[tool.ruff]
143-
target-version = "py38"
144142
line-length = 120
145143
format.preview = true
146144
format.docstring-code-line-length = 100
@@ -238,7 +236,6 @@ template = "docs/changelog/template.jinja2"
238236
# possible types, all default: feature, bugfix, doc, removal, misc
239237

240238
[tool.mypy]
241-
python_version = "3.11"
242239
show_error_codes = true
243240
strict = true
244241
overrides = [

src/tox/config/cli/env_var.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import logging
66
import os
7-
from typing import Any, List
7+
from typing import Any
88

99
from tox.config.loader.str_convert import StrConvert
1010

@@ -25,7 +25,7 @@ def get_env_var(key: str, of_type: type[Any]) -> tuple[Any, str] | None:
2525
value = os.environ[environ_key]
2626
origin = getattr(of_type, "__origin__", of_type.__class__)
2727
try:
28-
if origin in {list, List}:
28+
if origin in {list, list}:
2929
entry_type = of_type.__args__[0]
3030
result = [CONVERT.to(raw=v, of_type=entry_type, factory=None) for v in value.split(";")]
3131
else:

src/tox/config/cli/parse.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@
66
import os
77
from contextlib import redirect_stderr
88
from pathlib import Path
9-
from typing import TYPE_CHECKING, Callable, NamedTuple, Sequence, cast
9+
from typing import TYPE_CHECKING, NamedTuple, cast
1010

1111
from tox.config.source import Source, discover_source
1212
from tox.report import ToxHandler, setup_report
1313

1414
from .parser import Parsed, ToxParser
1515

1616
if TYPE_CHECKING:
17+
from collections.abc import Callable, Sequence
18+
1719
from tox.session.state import State
1820

1921

@@ -47,9 +49,10 @@ def _get_base(args: Sequence[str]) -> tuple[int, ToxHandler, Source]:
4749
tox_parser = ToxParser.base()
4850
parsed = Parsed()
4951
try:
50-
with Path(os.devnull).open(
51-
"w", encoding=locale.getpreferredencoding(do_setlocale=False)
52-
) as file_handler, redirect_stderr(file_handler):
52+
with (
53+
Path(os.devnull).open("w", encoding=locale.getpreferredencoding(do_setlocale=False)) as file_handler,
54+
redirect_stderr(file_handler),
55+
):
5356
tox_parser.parse_known_args(args, namespace=parsed)
5457
except SystemExit:
5558
... # ignore parse errors, such as -va raises ignored explicit argument 'a'

src/tox/config/cli/parser.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
import sys
1010
from argparse import SUPPRESS, Action, ArgumentDefaultsHelpFormatter, ArgumentError, ArgumentParser, Namespace
1111
from pathlib import Path
12-
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple, Type, TypeVar, cast
12+
from types import UnionType
13+
from typing import TYPE_CHECKING, Any, Literal, TypeVar, cast
1314

1415
from colorama import Fore
1516

@@ -26,6 +27,8 @@
2627
from typing_extensions import Self
2728

2829
if TYPE_CHECKING:
30+
from collections.abc import Callable, Sequence
31+
2932
from tox.session.state import State
3033

3134

@@ -64,7 +67,7 @@ def get_type(action: Action) -> type[Any]:
6467
of_type: type[Any] | None = getattr(action, "of_type", None)
6568
if of_type is None:
6669
if isinstance(action, argparse._AppendAction): # noqa: SLF001
67-
of_type = List[action.type] # type: ignore[name-defined]
70+
of_type = list[action.type] # type: ignore[name-defined]
6871
elif isinstance(action, argparse._StoreAction) and action.choices: # noqa: SLF001
6972
loc = locals()
7073
loc["Literal"] = Literal
@@ -135,7 +138,7 @@ def is_colored(self) -> bool:
135138
exit_and_dump_after: int
136139

137140

138-
ArgumentArgs = Tuple[Tuple[str, ...], Optional[Type[Any]], Dict[str, Any]]
141+
ArgumentArgs = tuple[tuple[str, ...], type[Any] | UnionType | None, dict[str, Any]]
139142

140143

141144
class ToxParser(ArgumentParserWithEnvAndConfig):
@@ -230,7 +233,7 @@ def __call__(
230233
help="set PYTHONHASHSEED to SEED before running commands. Defaults to a random integer in the range "
231234
"[1, 4294967295] ([1, 1024] on Windows). Passing 'notset' suppresses this behavior.",
232235
action=SeedAction,
233-
of_type=Optional[int], # type: ignore[arg-type]
236+
of_type=int | None,
234237
default=hashseed_default,
235238
dest="hash_seed",
236239
)
@@ -239,7 +242,7 @@ def __call__(
239242
dest="discover",
240243
nargs="+",
241244
metavar="path",
242-
of_type=List[str],
245+
of_type=list[str],
243246
help="for Python discovery first try these Python executables",
244247
default=[],
245248
)
@@ -280,7 +283,7 @@ def add_argument(*a_args: str, of_type: type[Any] | None = None, **a_kwargs: Any
280283
self._groups.append((args, kwargs, excl))
281284
return result
282285

283-
def add_argument(self, *args: str, of_type: type[Any] | None = None, **kwargs: Any) -> Action:
286+
def add_argument(self, *args: str, of_type: type[Any] | UnionType | None = None, **kwargs: Any) -> Action:
284287
result = super().add_argument(*args, **kwargs)
285288
if self.of_cmd is None and result.dest != "help":
286289
self._arguments.append((args, of_type, kwargs))
@@ -403,7 +406,7 @@ def add_core_arguments(parser: ArgumentParser) -> None:
403406
metavar="file",
404407
default=None,
405408
type=Path,
406-
of_type=Optional[Path],
409+
of_type=Path | None,
407410
help="configuration file/folder for tox (if not specified will discover one)",
408411
)
409412
parser.add_argument(
@@ -412,7 +415,7 @@ def add_core_arguments(parser: ArgumentParser) -> None:
412415
metavar="dir",
413416
default=None,
414417
type=Path,
415-
of_type=Optional[Path],
418+
of_type=Path | None,
416419
help="tox working directory (if not specified will be the folder of the config file)",
417420
)
418421
parser.add_argument(
@@ -421,7 +424,7 @@ def add_core_arguments(parser: ArgumentParser) -> None:
421424
metavar="dir",
422425
default=None,
423426
type=Path,
424-
of_type=Optional[Path],
427+
of_type=Path | None,
425428
help="project root directory (if not specified will be the folder of the config file)",
426429
)
427430

0 commit comments

Comments
 (0)