Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ jobs:
- "3.12"
- "3.11"
- "3.10"
- "3.9"
os:
- ubuntu-24.04
- windows-2025
Expand Down Expand Up @@ -66,6 +65,7 @@ jobs:
matrix:
tox_env:
- type
- type-3.10
- dev
- docs
- pkg_meta
Expand All @@ -89,7 +89,10 @@ jobs:
shell: bash
run: echo "$USERPROFILE/.local/bin" >> $GITHUB_PATH
- name: Install tox@self
run: uv tool install --python-preference only-managed --python 3.13 tox@.
run: uv tool install --python-preference only-managed --python 3.14 tox@.
- name: Install Python 3.10 for type-3.10
if: matrix.tox_env == 'type-3.10'
run: uv python install 3.10
- name: Setup check suite
run: tox r -vv --notest --skip-missing-interpreters false -e ${{ matrix.tox_env }}
- name: Run check for ${{ matrix.tox_env }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
cache-dependency-glob: "pyproject.toml"
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build package
run: uv build --python 3.13 --python-preference only-managed --sdist --wheel . --out-dir dist
run: uv build --python 3.14 --python-preference only-managed --sdist --wheel . --out-dir dist
- name: Store the distribution packages
uses: actions/upload-artifact@v4
with:
Expand Down
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ repos:
rev: v2.4.1
hooks:
- id: codespell
additional_dependencies: ["tomli>=2.2.1"]
additional_dependencies: ["tomli>=2.3"]
- repo: https://github.com/tox-dev/pyproject-fmt
rev: "v2.6.0"
rev: "v2.8.0"
hooks:
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
rev: "v0.24.1"
hooks:
- id: validate-pyproject
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.13.2"
rev: "v0.14.0"
hooks:
- id: ruff-check
args: ["--fix", "--unsafe-fixes", "--exit-non-zero-on-fix"]
Expand All @@ -32,7 +32,7 @@ repos:
rev: 1.20.0
hooks:
- id: blacken-docs
additional_dependencies: [black==25.1]
additional_dependencies: [black==25.9]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
Expand Down
5 changes: 3 additions & 2 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ build:
python: "3"
commands:
- pip install uv
- uv venv
- uv venv -p 3.14
- uv pip install tox-uv tox@.
- .venv/bin/tox run -e docs --
- .venv/bin/tox run -e docs --notest
- .venv/bin/tox run -e docs --skip-pkg-install --
4 changes: 2 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Installation
As tool
-------

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

tox works with the following Python interpreter implementations:

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

This means tox works on the latest patch version of each of these minor versions. Previous patch versions are supported
on a best effort approach.
59 changes: 28 additions & 31 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ maintainers = [
authors = [
{ name = "Bernát Gábor", email = "[email protected]" },
]
requires-python = ">=3.9"
requires-python = ">=3.10"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: tox",
Expand All @@ -36,7 +36,6 @@ classifiers = [
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -50,17 +49,17 @@ dynamic = [
"version",
]
dependencies = [
"cachetools>=6.1",
"cachetools>=6.2",
"chardet>=5.2",
"colorama>=0.4.6",
"filelock>=3.18",
"filelock>=3.19.1",
"packaging>=25",
"platformdirs>=4.3.8",
"platformdirs>=4.5",
"pluggy>=1.6",
"pyproject-api>=1.9.1",
"tomli>=2.2.1; python_version<'3.11'",
"typing-extensions>=4.14.1; python_version<'3.11'",
"virtualenv>=20.31.2",
"tomli>=2.3; python_version<'3.11'",
"typing-extensions>=4.15; python_version<'3.11'",
"virtualenv>=20.34",
]
urls.Documentation = "https://tox.wiki"
urls.Homepage = "http://tox.readthedocs.org"
Expand All @@ -76,55 +75,55 @@ dev = [
{ include-group = "type" },
]
test = [
"build[virtualenv]>=1.2.2.post1",
"build[virtualenv]>=1.3",
"covdefaults>=2.3",
"coverage>=7.9.2",
"coverage>=7.10.7",
"detect-test-pollution>=1.2",
"devpi-process>=1.0.2",
"diff-cover>=9.6",
"diff-cover>=9.7.1",
"distlib>=0.4",
"flaky>=3.8.1",
"hatch-vcs>=0.5",
"hatchling>=1.27",
"pdm-backend",
"psutil>=7",
"pytest>=8.4.1",
"pytest-cov>=6.2.1",
"pytest-mock>=3.14.1",
"pdm-backend>=2.4.5",
"psutil>=7.1",
"pytest>=8.4.2",
"pytest-cov>=7",
"pytest-mock>=3.15.1",
"pytest-xdist>=3.8",
"re-assert>=1.1",
"setuptools>=80.9",
"time-machine>=2.16; implementation_name!='pypy'",
"time-machine>=2.19; implementation_name!='pypy'",
"wheel>=0.45.1",
]
type = [
"mypy==1.17",
"types-cachetools>=6.1.0.20250717",
"mypy==1.18.2",
"types-cachetools>=6.2.0.20250827",
"types-chardet>=5.0.4.6",
{ include-group = "test" },
]
docs = [
"furo>=2025.7.19",
"furo>=2025.9.25",
"sphinx>=8.2.3",
"sphinx-argparse-cli>=1.19",
"sphinx-autodoc-typehints>=3.2",
"sphinx-argparse-cli>=1.20.1",
"sphinx-autodoc-typehints>=3.4",
"sphinx-copybutton>=0.5.2",
"sphinx-inline-tabs>=2023.4.21",
"sphinxcontrib-towncrier>=0.2.1a0",
"towncrier>=24.8",
"towncrier>=25.8",
]
fix = [
"pre-commit-uv>=4.1.4",
"pre-commit-uv>=4.1.5",
]
pkg-meta = [
"check-wheel-contents>=0.6.2",
"twine>=6.1",
"uv>=0.8",
"check-wheel-contents>=0.6.3",
"twine>=6.2",
"uv>=0.9",
]
release = [
"gitpython>=3.1.44",
"gitpython>=3.1.45",
"packaging>=25",
"towncrier>=24.8",
"towncrier>=25.8",
]

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

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

[tool.mypy]
python_version = "3.11"
show_error_codes = true
strict = true
overrides = [
Expand Down
4 changes: 2 additions & 2 deletions src/tox/config/cli/env_var.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import logging
import os
from typing import Any, List
from typing import Any

from tox.config.loader.str_convert import StrConvert

Expand All @@ -25,7 +25,7 @@ def get_env_var(key: str, of_type: type[Any]) -> tuple[Any, str] | None:
value = os.environ[environ_key]
origin = getattr(of_type, "__origin__", of_type.__class__)
try:
if origin in {list, List}:
if origin in {list, list}:
entry_type = of_type.__args__[0]
result = [CONVERT.to(raw=v, of_type=entry_type, factory=None) for v in value.split(";")]
else:
Expand Down
11 changes: 7 additions & 4 deletions src/tox/config/cli/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
import os
from contextlib import redirect_stderr
from pathlib import Path
from typing import TYPE_CHECKING, Callable, NamedTuple, Sequence, cast
from typing import TYPE_CHECKING, NamedTuple, cast

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

from .parser import Parsed, ToxParser

if TYPE_CHECKING:
from collections.abc import Callable, Sequence

from tox.session.state import State


Expand Down Expand Up @@ -47,9 +49,10 @@ def _get_base(args: Sequence[str]) -> tuple[int, ToxHandler, Source]:
tox_parser = ToxParser.base()
parsed = Parsed()
try:
with Path(os.devnull).open(
"w", encoding=locale.getpreferredencoding(do_setlocale=False)
) as file_handler, redirect_stderr(file_handler):
with (
Path(os.devnull).open("w", encoding=locale.getpreferredencoding(do_setlocale=False)) as file_handler,
redirect_stderr(file_handler),
):
tox_parser.parse_known_args(args, namespace=parsed)
except SystemExit:
... # ignore parse errors, such as -va raises ignored explicit argument 'a'
Expand Down
21 changes: 12 additions & 9 deletions src/tox/config/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
import sys
from argparse import SUPPRESS, Action, ArgumentDefaultsHelpFormatter, ArgumentError, ArgumentParser, Namespace
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Literal, Optional, Sequence, Tuple, Type, TypeVar, cast
from types import UnionType
from typing import TYPE_CHECKING, Any, Literal, TypeVar, cast

from colorama import Fore

Expand All @@ -26,6 +27,8 @@
from typing_extensions import Self

if TYPE_CHECKING:
from collections.abc import Callable, Sequence

from tox.session.state import State


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


ArgumentArgs = Tuple[Tuple[str, ...], Optional[Type[Any]], Dict[str, Any]]
ArgumentArgs = tuple[tuple[str, ...], type[Any] | UnionType | None, dict[str, Any]]


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

def add_argument(self, *args: str, of_type: type[Any] | None = None, **kwargs: Any) -> Action:
def add_argument(self, *args: str, of_type: type[Any] | UnionType | None = None, **kwargs: Any) -> Action:
result = super().add_argument(*args, **kwargs)
if self.of_cmd is None and result.dest != "help":
self._arguments.append((args, of_type, kwargs))
Expand Down Expand Up @@ -403,7 +406,7 @@ def add_core_arguments(parser: ArgumentParser) -> None:
metavar="file",
default=None,
type=Path,
of_type=Optional[Path],
of_type=Path | None,
help="configuration file/folder for tox (if not specified will discover one)",
)
parser.add_argument(
Expand All @@ -412,7 +415,7 @@ def add_core_arguments(parser: ArgumentParser) -> None:
metavar="dir",
default=None,
type=Path,
of_type=Optional[Path],
of_type=Path | None,
help="tox working directory (if not specified will be the folder of the config file)",
)
parser.add_argument(
Expand All @@ -421,7 +424,7 @@ def add_core_arguments(parser: ArgumentParser) -> None:
metavar="dir",
default=None,
type=Path,
of_type=Optional[Path],
of_type=Path | None,
help="project root directory (if not specified will be the folder of the config file)",
)

Expand Down
Loading
Loading