diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index a599dd798f..6187165878 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -28,7 +28,6 @@ jobs: - "3.12" - "3.11" - "3.10" - - "3.9" os: - ubuntu-24.04 - windows-2025 @@ -66,6 +65,7 @@ jobs: matrix: tox_env: - type + - type-3.10 - dev - docs - pkg_meta @@ -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 }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index c22cb6281d..0db2cb126a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -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: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 94915b19ae..402705492b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,9 +13,9 @@ 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 @@ -23,7 +23,7 @@ repos: 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"] @@ -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: diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 75a59bfa74..18c949f983 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -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 -- diff --git a/docs/installation.rst b/docs/installation.rst index 51b1bcad74..2e124b1e6e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -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: @@ -80,7 +80,7 @@ Python and OS Compatibility tox works with the following Python interpreter implementations: -- `CPython `_ versions 3.9, 3.10, 3.11, 3.12, 3.13 +- `CPython `_ 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. diff --git a/pyproject.toml b/pyproject.toml index a7323bf836..c519a2f2b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ maintainers = [ authors = [ { name = "Bernát Gábor", email = "gaborjbernat@gmail.com" }, ] -requires-python = ">=3.9" +requires-python = ">=3.10" classifiers = [ "Development Status :: 5 - Production/Stable", "Framework :: tox", @@ -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", @@ -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" @@ -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] @@ -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 @@ -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 = [ diff --git a/src/tox/config/cli/env_var.py b/src/tox/config/cli/env_var.py index 92162945f7..41cf07255d 100644 --- a/src/tox/config/cli/env_var.py +++ b/src/tox/config/cli/env_var.py @@ -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 @@ -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: diff --git a/src/tox/config/cli/parse.py b/src/tox/config/cli/parse.py index a61de06baa..e206857c58 100644 --- a/src/tox/config/cli/parse.py +++ b/src/tox/config/cli/parse.py @@ -6,7 +6,7 @@ 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 @@ -14,6 +14,8 @@ from .parser import Parsed, ToxParser if TYPE_CHECKING: + from collections.abc import Callable, Sequence + from tox.session.state import State @@ -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' diff --git a/src/tox/config/cli/parser.py b/src/tox/config/cli/parser.py index 0b1133b4ba..e739c27355 100644 --- a/src/tox/config/cli/parser.py +++ b/src/tox/config/cli/parser.py @@ -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 @@ -26,6 +27,8 @@ from typing_extensions import Self if TYPE_CHECKING: + from collections.abc import Callable, Sequence + from tox.session.state import State @@ -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 @@ -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): @@ -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", ) @@ -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=[], ) @@ -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)) @@ -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( @@ -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( @@ -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)", ) diff --git a/src/tox/config/loader/api.py b/src/tox/config/loader/api.py index e4f1eb8b7b..83873c8910 100644 --- a/src/tox/config/loader/api.py +++ b/src/tox/config/loader/api.py @@ -2,7 +2,8 @@ from abc import abstractmethod from argparse import ArgumentTypeError -from typing import TYPE_CHECKING, Any, List, Mapping, TypeVar +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, TypeVar from tox.plugin import impl from tox.tox_env.python.pip.req_file import PythonDeps @@ -11,6 +12,8 @@ from .str_convert import StrConvert if TYPE_CHECKING: + from types import UnionType + from tox.config.cli.parser import ToxParser from tox.config.main import Config @@ -70,7 +73,7 @@ def copy(self) -> ConfigLoadArgs: return ConfigLoadArgs(self.chain.copy(), self.name, self.env_name) -OverrideMap = Mapping[str, List[Override]] +OverrideMap = Mapping[str, list[Override]] T = TypeVar("T") V = TypeVar("V") @@ -119,7 +122,7 @@ def __contains__(self, item: str) -> bool: def load( self, key: str, - of_type: type[V], + of_type: type[V] | UnionType, factory: Factory[V], conf: Config | None, args: ConfigLoadArgs, @@ -169,7 +172,7 @@ def load( def build( # noqa: PLR0913 self, key: str, # noqa: ARG002 - of_type: type[V], + of_type: type[V] | UnionType, factory: Factory[V], conf: Config | None, # noqa: ARG002 raw: T, diff --git a/src/tox/config/loader/convert.py b/src/tox/config/loader/convert.py index e844f40173..00f78f592a 100644 --- a/src/tox/config/loader/convert.py +++ b/src/tox/config/loader/convert.py @@ -3,23 +3,27 @@ import typing from abc import ABC, abstractmethod from collections import OrderedDict +from collections.abc import Callable, Iterator from inspect import isclass from pathlib import Path -from typing import Any, Callable, Dict, Generic, Iterator, List, Literal, Optional, Set, TypeVar, Union, cast +from typing import Any, Generic, Literal, TypeVar, Union, cast from tox.config.types import Command, EnvList +if typing.TYPE_CHECKING: + from types import UnionType + _NO_MAPPING = object() T = TypeVar("T") V = TypeVar("V") -Factory = Optional[Callable[[object], T]] # note the argument is anything, due e.g. memory loader can inject anything +Factory = Callable[[object], T] | None # note the argument is anything, due e.g. memory loader can inject anything class Convert(ABC, Generic[T]): """A class that converts a raw type to a given tox (python) type.""" - def to(self, raw: T, of_type: type[V], factory: Factory[V]) -> V: # noqa: PLR0911, C901 + def to(self, raw: T, of_type: type[V] | UnionType, factory: Factory[V]) -> V: # noqa: PLR0911 """ Convert given raw type to python type. @@ -29,7 +33,9 @@ def to(self, raw: T, of_type: type[V], factory: Factory[V]) -> V: # noqa: PLR09 :return: the converted type """ from_module = getattr(of_type, "__module__", None) - if from_module in {"typing", "typing_extensions"}: + if from_module in {"typing", "typing_extensions"} or ( + hasattr(typing, "GenericAlias") and isinstance(of_type, typing.GenericAlias) + ): return self._to_typing(raw, of_type, factory) if isclass(of_type): if issubclass(of_type, Path): @@ -42,35 +48,32 @@ def to(self, raw: T, of_type: type[V], factory: Factory[V]) -> V: # noqa: PLR09 return self.to_env_list(raw) # type: ignore[return-value] if issubclass(of_type, str): return self.to_str(raw) # type: ignore[return-value] - # python does not allow use of parametrized generics with isinstance, so we need to check for them. - if hasattr(typing, "GenericAlias") and isinstance(of_type, typing.GenericAlias): - return list(self.to_list(raw, of_type=of_type)) # type: ignore[return-value] - if isinstance(raw, of_type): # already target type no need to transform it + if isinstance(raw, cast("type[V]", of_type)): # already target type no need to transform it # do it this late to allow normalization - e.g. string strip return raw if factory: return factory(raw) - return of_type(raw) # type: ignore[no-any-return] + return cast("type[V]", of_type)(raw) # type: ignore[call-arg] - def _to_typing(self, raw: T, of_type: type[V], factory: Factory[V]) -> V: # noqa: C901 + def _to_typing(self, raw: T, of_type: type[V] | UnionType, factory: Factory[V]) -> V: # noqa: C901 origin = getattr(of_type, "__origin__", of_type.__class__) result: Any = _NO_MAPPING - if origin in {list, List}: - entry_type = of_type.__args__[0] # type: ignore[attr-defined] + if origin in {list, list}: + entry_type = cast("type[V]", of_type).__args__[0] # type: ignore[attr-defined] result = [self.to(i, entry_type, factory) for i in self.to_list(raw, entry_type)] if isclass(entry_type) and issubclass(entry_type, Command): result = [i for i in result if i is not None] - elif origin in {set, Set}: - entry_type = of_type.__args__[0] # type: ignore[attr-defined] + elif origin in {set, set}: + entry_type = cast("type[V]", of_type).__args__[0] # type: ignore[attr-defined] result = {self.to(i, entry_type, factory) for i in self.to_set(raw, entry_type)} - elif origin in {dict, Dict}: - key_type, value_type = of_type.__args__[0], of_type.__args__[1] # type: ignore[attr-defined] + elif origin in {dict, dict}: + key_type, value_type = cast("type[V]", of_type).__args__[0], cast("type[V]", of_type).__args__[1] # type: ignore[attr-defined] result = OrderedDict( (self.to(k, key_type, factory), self.to(v, value_type, factory)) for k, v in self.to_dict(raw, (key_type, value_type)) ) elif origin == Union: # handle Optional values - args: list[type[Any]] = of_type.__args__ # type: ignore[attr-defined] + args: list[type[Any]] = of_type.__args__ # type: ignore[union-attr,assignment] none = type(None) if len(args) == 2 and none in args: # noqa: PLR2004 if isinstance(raw, str): @@ -81,7 +84,7 @@ def _to_typing(self, raw: T, of_type: type[V], factory: Factory[V]) -> V: # noq new_type = next(i for i in args if i != none) # pragma: no cover result = self.to(raw, new_type, factory) elif origin in {Literal, type(Literal)}: - choice = of_type.__args__ # type: ignore[attr-defined] + choice = cast("type[V]", of_type).__args__ # type: ignore[attr-defined] if raw not in choice: msg = f"{raw} must be one of {choice}" raise ValueError(msg) diff --git a/src/tox/config/loader/ini/__init__.py b/src/tox/config/loader/ini/__init__.py index 9d97a3312f..ffb5fc588f 100644 --- a/src/tox/config/loader/ini/__init__.py +++ b/src/tox/config/loader/ini/__init__.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from configparser import ConfigParser, SectionProxy + from types import UnionType from tox.config.loader.convert import Factory from tox.config.loader.section import Section @@ -60,7 +61,7 @@ def process_raw(conf: Config | None, env_name: str | None, value: str) -> str: def build( # noqa: PLR0913 self, key: str, - of_type: type[V], + of_type: type[V] | UnionType, factory: Factory[V], conf: Config | None, raw: str, diff --git a/src/tox/config/loader/ini/factor.py b/src/tox/config/loader/ini/factor.py index 11b4fa66d5..9a5bc91d9b 100644 --- a/src/tox/config/loader/ini/factor.py +++ b/src/tox/config/loader/ini/factor.py @@ -4,7 +4,10 @@ import re from itertools import chain, groupby, product -from typing import Iterator +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterator def filter_for_env(value: str, name: str | None) -> str: diff --git a/src/tox/config/loader/ini/replace.py b/src/tox/config/loader/ini/replace.py index f225cedbc6..9802281cf4 100644 --- a/src/tox/config/loader/ini/replace.py +++ b/src/tox/config/loader/ini/replace.py @@ -4,13 +4,16 @@ import re from configparser import SectionProxy -from functools import lru_cache -from typing import TYPE_CHECKING, Iterator, Pattern +from functools import cache +from re import Pattern +from typing import TYPE_CHECKING from tox.config.loader.replacer import ReplaceReference from tox.config.loader.stringify import stringify if TYPE_CHECKING: + from collections.abc import Iterator + from tox.config.loader.api import ConfigLoadArgs from tox.config.loader.ini import IniLoader from tox.config.main import Config @@ -82,7 +85,7 @@ def _config_value_sources( yield value -@lru_cache(maxsize=None) +@cache def _replace_ref(env: str | None) -> Pattern[str]: return re.compile( rf""" diff --git a/src/tox/config/loader/memory.py b/src/tox/config/loader/memory.py index 0484dee765..2b8b0daaeb 100644 --- a/src/tox/config/loader/memory.py +++ b/src/tox/config/loader/memory.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path -from typing import TYPE_CHECKING, Any, Iterator +from typing import TYPE_CHECKING, Any from tox.config.types import Command, EnvList @@ -10,6 +10,8 @@ from .str_convert import StrConvert if TYPE_CHECKING: + from collections.abc import Iterator + from tox.config.main import Config diff --git a/src/tox/config/loader/replacer.py b/src/tox/config/loader/replacer.py index 5d9fd70ebd..c91ed5b397 100644 --- a/src/tox/config/loader/replacer.py +++ b/src/tox/config/loader/replacer.py @@ -6,7 +6,8 @@ import os import sys from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Final, Sequence, Union +from collections.abc import Sequence +from typing import TYPE_CHECKING, Any, Final, Union from tox.config.types import CircularChainError from tox.execute.request import shell_cmd diff --git a/src/tox/config/loader/str_convert.py b/src/tox/config/loader/str_convert.py index 248afe2685..7fda1c9bae 100644 --- a/src/tox/config/loader/str_convert.py +++ b/src/tox/config/loader/str_convert.py @@ -7,13 +7,14 @@ from inspect import isclass from itertools import chain from pathlib import Path -from typing import TYPE_CHECKING, Any, Iterator +from typing import TYPE_CHECKING, Any 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: + from collections.abc import Iterator from typing import Final diff --git a/src/tox/config/loader/stringify.py b/src/tox/config/loader/stringify.py index 94d7d96ab9..098382b88a 100644 --- a/src/tox/config/loader/stringify.py +++ b/src/tox/config/loader/stringify.py @@ -1,7 +1,8 @@ from __future__ import annotations +from collections.abc import Mapping, Sequence from pathlib import Path -from typing import Any, Mapping, Sequence, Set +from typing import Any from tox.config.set_env import SetEnv from tox.config.types import Command, EnvList @@ -23,7 +24,7 @@ def stringify(value: Any) -> tuple[str, bool]: # noqa: PLR0911 return "\n".join(f"{stringify(k)[0]}={stringify(v)[0]}" for k, v in value.items()), True if isinstance(value, Sequence): return "\n".join(stringify(i)[0] for i in value), True - if isinstance(value, Set): # sort it to make it stable + if isinstance(value, set): # sort it to make it stable return "\n".join(sorted(stringify(i)[0] for i in value)), True if isinstance(value, EnvList): return "\n".join(e for e in value.envs), True diff --git a/src/tox/config/loader/toml/__init__.py b/src/tox/config/loader/toml/__init__.py index 4c4c6cb033..a17f2a7549 100644 --- a/src/tox/config/loader/toml/__init__.py +++ b/src/tox/config/loader/toml/__init__.py @@ -3,7 +3,7 @@ import inspect import logging from pathlib import Path -from typing import TYPE_CHECKING, Dict, Iterator, List, Mapping, TypeVar, cast +from typing import TYPE_CHECKING, TypeVar, cast from tox.config.loader.api import ConfigLoadArgs, Loader, Override from tox.config.set_env import SetEnv @@ -15,6 +15,9 @@ from ._validate import validate if TYPE_CHECKING: + from collections.abc import Iterator, Mapping + from types import UnionType + from tox.config.loader.convert import Factory from tox.config.loader.section import Section from tox.config.main import Config @@ -59,7 +62,7 @@ def load_raw_from_root(self, path: str) -> TomlTypes: def build( # noqa: PLR0913 self, key: str, # noqa: ARG002 - of_type: type[_T], + of_type: type[_T] | UnionType, factory: Factory[_T], conf: Config | None, raw: TomlTypes, @@ -84,17 +87,17 @@ def to_bool(value: TomlTypes) -> bool: @staticmethod def to_list(value: TomlTypes, of_type: type[_T]) -> Iterator[_T]: - of = List[of_type] # type: ignore[valid-type] # no mypy support + of = list[of_type] # type: ignore[valid-type] # no mypy support return iter(validate(value, of)) # type: ignore[call-overload,no-any-return] @staticmethod def to_set(value: TomlTypes, of_type: type[_T]) -> Iterator[_T]: - of = List[of_type] # type: ignore[valid-type] # no mypy support + of = list[of_type] # type: ignore[valid-type] # no mypy support return iter(validate(value, of)) # type: ignore[call-overload,no-any-return] @staticmethod def to_dict(value: TomlTypes, of_type: tuple[type[_T], type[_V]]) -> Iterator[tuple[_T, _V]]: - of = Dict[of_type[0], of_type[1]] # type: ignore[valid-type] # no mypy support + of = dict[of_type[0], of_type[1]] # type: ignore[valid-type] # no mypy support return validate(value, of).items() # type: ignore[attr-defined,no-any-return] @staticmethod @@ -104,7 +107,7 @@ def to_path(value: TomlTypes) -> Path: @staticmethod def to_command(value: TomlTypes) -> Command | None: if value: - return Command(args=cast("List[str]", value)) # validated during load in _ensure_type_correct + return Command(args=cast("list[str]", value)) # validated during load in _ensure_type_correct return None @staticmethod diff --git a/src/tox/config/loader/toml/_api.py b/src/tox/config/loader/toml/_api.py index 9a9596df9d..f23f7c0373 100644 --- a/src/tox/config/loader/toml/_api.py +++ b/src/tox/config/loader/toml/_api.py @@ -1,16 +1,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Dict, List, Union +from typing import TYPE_CHECKING if TYPE_CHECKING: - import sys + from typing import TypeAlias - if sys.version_info >= (3, 10): # pragma: no cover (py310+) - from typing import TypeAlias - else: # pragma: no cover (py310+) - from typing_extensions import TypeAlias - -TomlTypes: TypeAlias = Union[Dict[str, "TomlTypes"], List["TomlTypes"], str, int, float, bool, None] +TomlTypes: TypeAlias = dict[str, "TomlTypes"] | list["TomlTypes"] | str | int | float | bool | None __all__ = [ "TomlTypes", diff --git a/src/tox/config/loader/toml/_replace.py b/src/tox/config/loader/toml/_replace.py index a7664aee1e..4324693246 100644 --- a/src/tox/config/loader/toml/_replace.py +++ b/src/tox/config/loader/toml/_replace.py @@ -1,7 +1,7 @@ from __future__ import annotations import re -from typing import TYPE_CHECKING, Any, Iterator, List, cast +from typing import TYPE_CHECKING, Any, cast from tox.config.loader.replacer import MatchRecursionError, ReplaceReference, load_posargs, replace, replace_env from tox.config.loader.stringify import stringify @@ -9,6 +9,8 @@ from ._validate import validate if TYPE_CHECKING: + from collections.abc import Iterator + from tox.config.loader.api import ConfigLoadArgs from tox.config.loader.toml import TomlLoader from tox.config.main import Config @@ -40,7 +42,7 @@ def __call__(self, value: TomlTypes, depth: int = 0) -> TomlTypes: # noqa: C901 for val in value: # apply replacement for every entry got = self(val, depth) if isinstance(val, dict) and val.get("replace") and val.get("extend"): - res_list.extend(cast("List[Any]", got)) + res_list.extend(cast("list[Any]", got)) else: res_list.append(got) value = res_list @@ -50,7 +52,7 @@ def __call__(self, value: TomlTypes, depth: int = 0) -> TomlTypes: # noqa: C901 if replace_type == "posargs" and self.conf is not None: got_posargs = load_posargs(self.conf, self.args) return ( - [self(v, depth) for v in cast("List[str]", value.get("default", []))] + [self(v, depth) for v in cast("list[str]", value.get("default", []))] if got_posargs is None else list(got_posargs) ) @@ -76,7 +78,7 @@ def _replace_ref(self, value: dict[str, TomlTypes], depth: int) -> TomlTypes: if self.conf is not None and (env := value.get("env")) and (key := value.get("key")): return cast("TomlTypes", self.conf.get_env(cast("str", env))[cast("str", key)]) if of := value.get("of"): - validated_of = cast("List[str]", validate(of, List[str])) + validated_of = cast("list[str]", validate(of, list[str])) loaded = self.loader.load_raw_from_root(self.loader.section.SEP.join(validated_of)) return self(loaded, depth) return value diff --git a/src/tox/config/loader/toml/_validate.py b/src/tox/config/loader/toml/_validate.py index 89258990e7..b56031ae62 100644 --- a/src/tox/config/loader/toml/_validate.py +++ b/src/tox/config/loader/toml/_validate.py @@ -4,8 +4,6 @@ from typing import ( TYPE_CHECKING, Any, - Dict, - List, Literal, TypeVar, Union, @@ -22,7 +20,7 @@ if sys.version_info >= (3, 11): # pragma: no cover (py311+) from typing import TypeGuard else: # pragma: no cover (py311+) - from typing_extensions import TypeGuard + from typing import TypeGuard T = TypeVar("T") @@ -30,7 +28,7 @@ def validate(val: TomlTypes, of_type: type[T]) -> TypeGuard[T]: # noqa: C901, PLR0912 casting_to = getattr(of_type, "__origin__", of_type.__class__) msg = "" - if casting_to in {list, List}: + if casting_to in {list, list}: entry_type = of_type.__args__[0] # type: ignore[attr-defined] if isinstance(val, list): for va in val: @@ -39,8 +37,8 @@ def validate(val: TomlTypes, of_type: type[T]) -> TypeGuard[T]: # noqa: C901, P msg = f"{val!r} is not list" elif isclass(of_type) and issubclass(of_type, Command): # first we cast it to list then create commands, so for now validate it as a nested list - validate(val, List[str]) - elif casting_to in {dict, Dict}: + validate(val, list[str]) + elif casting_to in {dict, dict}: key_type, value_type = of_type.__args__[0], of_type.__args__[1] # type: ignore[attr-defined] if isinstance(val, dict): for va in val: diff --git a/src/tox/config/main.py b/src/tox/config/main.py index 9fbd9f4bcb..48c75cbe37 100644 --- a/src/tox/config/main.py +++ b/src/tox/config/main.py @@ -4,11 +4,13 @@ from collections import OrderedDict, defaultdict from itertools import chain, tee from pathlib import Path -from typing import TYPE_CHECKING, Any, Iterable, Iterator, Sequence, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar from .sets import ConfigSet, CoreConfigSet, EnvConfigSet if TYPE_CHECKING: + from collections.abc import Iterable, Iterator, Sequence + from tox.config.loader.api import Loader, OverrideMap from .cli.parser import Parsed diff --git a/src/tox/config/of_type.py b/src/tox/config/of_type.py index 4d49d16ef3..899a19cdd6 100644 --- a/src/tox/config/of_type.py +++ b/src/tox/config/of_type.py @@ -4,12 +4,15 @@ from abc import ABC, abstractmethod from itertools import product -from typing import TYPE_CHECKING, Callable, Generic, Iterable, TypeVar, cast +from typing import TYPE_CHECKING, Generic, TypeVar, cast from tox.config.loader.api import ConfigLoadArgs, Loader from tox.config.types import CircularChainError if TYPE_CHECKING: + from collections.abc import Callable, Iterable + from types import UnionType + from tox.config.loader.convert import Factory from tox.config.main import Config # pragma: no cover @@ -74,7 +77,7 @@ def __init__( # noqa: PLR0913 self, keys: Iterable[str], desc: str, - of_type: type[T], + of_type: type[T] | UnionType, default: Callable[[Config, str | None], T] | T, post_process: Callable[[T], T] | None = None, factory: Factory[T] | None = None, diff --git a/src/tox/config/set_env.py b/src/tox/config/set_env.py index f74c0c1920..010efc6d87 100644 --- a/src/tox/config/set_env.py +++ b/src/tox/config/set_env.py @@ -1,8 +1,8 @@ from __future__ import annotations +from collections.abc import Callable, Iterator, Mapping from functools import reduce from pathlib import Path -from typing import Callable, Iterator, Mapping from tox.config.loader.api import ConfigLoadArgs from tox.tox_env.errors import Fail diff --git a/src/tox/config/sets.py b/src/tox/config/sets.py index 97e60b0e57..5d261c9e0b 100644 --- a/src/tox/config/sets.py +++ b/src/tox/config/sets.py @@ -3,13 +3,16 @@ import sys from abc import ABC, abstractmethod from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, Generator, Iterator, Mapping, Sequence, TypeVar, cast +from typing import TYPE_CHECKING, Any, TypeVar, cast, overload from .of_type import ConfigConstantDefinition, ConfigDefinition, ConfigDynamicDefinition, ConfigLoadArgs from .set_env import SetEnv from .types import EnvList if TYPE_CHECKING: + from collections.abc import Callable, Generator, Iterator, Mapping, Sequence + from types import UnionType + from tox.config.loader.api import Loader from tox.config.main import Config @@ -46,7 +49,8 @@ def register_config(self) -> None: def mark_finalized(self) -> None: self._final = True - def add_config( # noqa: PLR0913 + @overload + def add_config( self, keys: str | Sequence[str], of_type: type[V], @@ -54,6 +58,27 @@ def add_config( # noqa: PLR0913 desc: str, post_process: Callable[[V], V] | None = None, factory: Factory[Any] | None = None, + ) -> ConfigDynamicDefinition[V]: ... + + @overload + def add_config( + self, + keys: str | Sequence[str], + of_type: UnionType, + default: Callable[[Config, str | None], V | None] | V | None, + desc: str, + post_process: Callable[[V | None], V | None] | None = None, + factory: Factory[Any] | None = None, + ) -> ConfigDynamicDefinition[V | None]: ... + + def add_config( # noqa: PLR0913 + self, + keys: str | Sequence[str], + of_type: type[V] | UnionType, + default: Callable[[Config, str | None], V] | V, + desc: str, + post_process: Callable[[V], V] | None = None, + factory: Factory[Any] | None = None, ) -> ConfigDynamicDefinition[V]: """ Add configuration value. diff --git a/src/tox/config/source/api.py b/src/tox/config/source/api.py index b8b703e625..484071082b 100644 --- a/src/tox/config/source/api.py +++ b/src/tox/config/source/api.py @@ -3,11 +3,12 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, Iterator, List +from typing import TYPE_CHECKING, Any from tox.config.loader.section import Section if TYPE_CHECKING: + from collections.abc import Iterator from pathlib import Path from tox.config.loader.api import Loader, OverrideMap @@ -57,7 +58,7 @@ def get_loaders( if base is not None: conf.add_config( keys="base", - of_type=List[str], + of_type=list[str], desc="inherit missing keys from these sections", default=base, ) diff --git a/src/tox/config/source/ini.py b/src/tox/config/source/ini.py index bee5bc72cb..44dcf6262c 100644 --- a/src/tox/config/source/ini.py +++ b/src/tox/config/source/ini.py @@ -5,7 +5,7 @@ from collections import defaultdict from configparser import ConfigParser from itertools import chain -from typing import TYPE_CHECKING, Iterable, Iterator +from typing import TYPE_CHECKING from tox.config.loader.ini import IniLoader from tox.config.loader.ini.factor import find_envs @@ -15,6 +15,7 @@ from .ini_section import CORE, PKG_ENV_PREFIX, TEST_ENV_PREFIX, IniSection if TYPE_CHECKING: + from collections.abc import Iterable, Iterator from pathlib import Path from tox.config.loader.api import OverrideMap diff --git a/src/tox/config/source/toml_pyproject.py b/src/tox/config/source/toml_pyproject.py index b70b1404b3..2b90266986 100644 --- a/src/tox/config/source/toml_pyproject.py +++ b/src/tox/config/source/toml_pyproject.py @@ -3,7 +3,8 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any, Final, Iterator, Mapping, cast +from collections.abc import Iterator, Mapping +from typing import TYPE_CHECKING, Any, Final, cast from tox.config.loader.section import Section from tox.config.loader.toml import TomlLoader diff --git a/src/tox/config/types.py b/src/tox/config/types.py index 0e9070ad40..a32adb41f2 100644 --- a/src/tox/config/types.py +++ b/src/tox/config/types.py @@ -1,10 +1,13 @@ from __future__ import annotations from collections import OrderedDict -from typing import Iterator, Sequence +from typing import TYPE_CHECKING from tox.execute.request import shell_cmd +if TYPE_CHECKING: + from collections.abc import Iterator, Sequence + class CircularChainError(ValueError): """circular chain in config""" diff --git a/src/tox/execute/api.py b/src/tox/execute/api.py index b236110b2b..37b079fd10 100644 --- a/src/tox/execute/api.py +++ b/src/tox/execute/api.py @@ -6,8 +6,9 @@ import sys import time from abc import ABC, abstractmethod +from collections.abc import Callable, Iterator, Sequence from contextlib import contextmanager -from typing import TYPE_CHECKING, Any, Callable, Iterator, NoReturn, Sequence, cast +from typing import TYPE_CHECKING, Any, NoReturn, cast from colorama import Fore diff --git a/src/tox/execute/local_sub_process/__init__.py b/src/tox/execute/local_sub_process/__init__.py index e20e0210f2..e7ec71f23a 100644 --- a/src/tox/execute/local_sub_process/__init__.py +++ b/src/tox/execute/local_sub_process/__init__.py @@ -8,7 +8,7 @@ import shutil import sys from subprocess import DEVNULL, PIPE, TimeoutExpired -from typing import TYPE_CHECKING, Any, Generator, Sequence +from typing import TYPE_CHECKING, Any from tox.execute.api import Execute, ExecuteInstance, ExecuteOptions, ExecuteStatus from tox.execute.request import ExecuteRequest, StdinSource @@ -17,6 +17,7 @@ if TYPE_CHECKING: import io + from collections.abc import Generator, Sequence from types import TracebackType from tox.execute.stream import SyncWrite diff --git a/src/tox/execute/local_sub_process/read_via_thread.py b/src/tox/execute/local_sub_process/read_via_thread.py index d96d696eb5..7ddfd969e3 100644 --- a/src/tox/execute/local_sub_process/read_via_thread.py +++ b/src/tox/execute/local_sub_process/read_via_thread.py @@ -4,10 +4,11 @@ from abc import ABC, abstractmethod from threading import Event, Thread -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING if TYPE_CHECKING: import sys + from collections.abc import Callable from types import TracebackType if sys.version_info >= (3, 11): # pragma: no cover (py311+) diff --git a/src/tox/execute/local_sub_process/read_via_thread_unix.py b/src/tox/execute/local_sub_process/read_via_thread_unix.py index f4a22560c6..48e816f833 100644 --- a/src/tox/execute/local_sub_process/read_via_thread_unix.py +++ b/src/tox/execute/local_sub_process/read_via_thread_unix.py @@ -5,10 +5,13 @@ import errno # pragma: win32 no cover import os # pragma: win32 no cover import select # pragma: win32 no cover -from typing import Callable +from typing import TYPE_CHECKING from .read_via_thread import ReadViaThread # pragma: win32 no cover +if TYPE_CHECKING: + from collections.abc import Callable + STOP_EVENT_CHECK_PERIODICITY_IN_MS = 0.01 # pragma: win32 no cover diff --git a/src/tox/execute/local_sub_process/read_via_thread_windows.py b/src/tox/execute/local_sub_process/read_via_thread_windows.py index 3e73ae41c1..cebc077a8c 100644 --- a/src/tox/execute/local_sub_process/read_via_thread_windows.py +++ b/src/tox/execute/local_sub_process/read_via_thread_windows.py @@ -6,10 +6,13 @@ import logging # pragma: win32 cover from asyncio.windows_utils import BUFSIZE # type: ignore[attr-defined] # pragma: win32 cover from time import sleep # pragma: win32 cover -from typing import Callable # pragma: win32 cover +from typing import TYPE_CHECKING from .read_via_thread import ReadViaThread # pragma: win32 cover +if TYPE_CHECKING: + from collections.abc import Callable + # mypy: warn-unused-ignores=false diff --git a/src/tox/execute/pep517_backend.py b/src/tox/execute/pep517_backend.py index 566d66b353..0d82e63808 100644 --- a/src/tox/execute/pep517_backend.py +++ b/src/tox/execute/pep517_backend.py @@ -5,7 +5,7 @@ import time from subprocess import TimeoutExpired from threading import Lock -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING from pyproject_api import BackendFailed @@ -16,6 +16,7 @@ from tox.execute.stream import SyncWrite if TYPE_CHECKING: + from collections.abc import Sequence from pathlib import Path from types import TracebackType diff --git a/src/tox/execute/request.py b/src/tox/execute/request.py index 8174440807..11b96cabf6 100644 --- a/src/tox/execute/request.py +++ b/src/tox/execute/request.py @@ -5,7 +5,10 @@ import sys from enum import Enum from pathlib import Path -from typing import Sequence +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Sequence class StdinSource(Enum): diff --git a/src/tox/execute/stream.py b/src/tox/execute/stream.py index ce0125e7c8..928a48f4e6 100644 --- a/src/tox/execute/stream.py +++ b/src/tox/execute/stream.py @@ -2,12 +2,13 @@ from contextlib import contextmanager from threading import Event, Lock, Timer -from typing import IO, TYPE_CHECKING, Iterator +from typing import IO, TYPE_CHECKING from colorama import Fore if TYPE_CHECKING: import sys + from collections.abc import Iterator from types import TracebackType if sys.version_info >= (3, 11): # pragma: no cover (py311+) diff --git a/src/tox/plugin/__init__.py b/src/tox/plugin/__init__.py index c8bff4b03f..83b0c2eec8 100644 --- a/src/tox/plugin/__init__.py +++ b/src/tox/plugin/__init__.py @@ -31,7 +31,8 @@ def tox_add_option(parser: ToxParser) -> None: from __future__ import annotations -from typing import Any, Callable, TypeVar +from collections.abc import Callable +from typing import Any, TypeVar import pluggy diff --git a/src/tox/plugin/manager.py b/src/tox/plugin/manager.py index df6dcfdd1a..323d5ae413 100644 --- a/src/tox/plugin/manager.py +++ b/src/tox/plugin/manager.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, Any, Iterable +from typing import TYPE_CHECKING, Any import pluggy @@ -19,6 +19,7 @@ from .inline import load_inline if TYPE_CHECKING: + from collections.abc import Iterable from pathlib import Path from types import ModuleType diff --git a/src/tox/plugin/spec.py b/src/tox/plugin/spec.py index e69f206b5e..3570033924 100644 --- a/src/tox/plugin/spec.py +++ b/src/tox/plugin/spec.py @@ -1,12 +1,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Iterable +from typing import TYPE_CHECKING, Any import pluggy from . import NAME if TYPE_CHECKING: + from collections.abc import Iterable + from tox.config.cli.parser import ToxParser from tox.config.sets import ConfigSet, EnvConfigSet from tox.execute import Outcome @@ -46,7 +48,7 @@ def tox_extend_envs() -> Iterable[str]: :class:`tox.config.loader.memory.MemoryLoader` early enough before tox starts caching configuration values sourced elsewhere. """ - return () # <- Please MyPy + return () @_spec diff --git a/src/tox/provision.py b/src/tox/provision.py index 86fedf9122..7beb63eeb9 100644 --- a/src/tox/provision.py +++ b/src/tox/provision.py @@ -8,7 +8,7 @@ import sys from importlib.metadata import PackageNotFoundError, distribution from pathlib import Path -from typing import TYPE_CHECKING, List, cast +from typing import TYPE_CHECKING, cast from packaging.requirements import Requirement from packaging.utils import canonicalize_name @@ -76,7 +76,7 @@ def add_tox_requires_min_version(reqs: list[Requirement]) -> list[Requirement]: state.conf.core.add_config( keys="requires", - of_type=List[Requirement], + of_type=list[Requirement], default=[], desc="Name of the virtual environment used to provision a tox.", post_process=add_tox_requires_min_version, diff --git a/src/tox/pytest.py b/src/tox/pytest.py index bfdc7e6d4c..31b3e6f027 100644 --- a/src/tox/pytest.py +++ b/src/tox/pytest.py @@ -13,7 +13,7 @@ from contextlib import closing, contextmanager from pathlib import Path from types import ModuleType, TracebackType -from typing import TYPE_CHECKING, Any, Callable, Iterator, Protocol, Sequence, cast +from typing import TYPE_CHECKING, Any, Protocol, cast import pytest from _pytest.fixtures import SubRequest # noqa: PLC2701 @@ -32,6 +32,7 @@ from tox.tox_env.api import ToxEnv if TYPE_CHECKING: + from collections.abc import Callable, Iterator, Sequence from unittest.mock import MagicMock from pytest_mock import MockerFixture @@ -285,8 +286,6 @@ def our_setup_state(value: Sequence[str]) -> State: m.setenv("VIRTUALENV_SYMLINK_APP_DATA", "1") m.setenv("VIRTUALENV_SYMLINKS", "1") m.setenv("VIRTUALENV_PIP", "embed") - if sys.version_info[:2] < (3, 9): - m.setenv("VIRTUALENV_WHEEL", "embed") m.setenv("VIRTUALENV_SETUPTOOLS", "embed") try: tox_run(args) diff --git a/src/tox/report.py b/src/tox/report.py index ef46bc702f..929f86bd9e 100644 --- a/src/tox/report.py +++ b/src/tox/report.py @@ -11,10 +11,13 @@ from pathlib import Path from threading import Thread, current_thread, local from threading import enumerate as enumerate_threads -from typing import IO, ClassVar, Iterator, Tuple +from typing import IO, TYPE_CHECKING, ClassVar from colorama import Fore, Style, init +if TYPE_CHECKING: + from collections.abc import Iterator + LEVELS = { 0: logging.CRITICAL, 1: logging.ERROR, @@ -26,7 +29,7 @@ MAX_LEVEL = max(LEVELS.keys()) LOGGER = logging.getLogger() -OutErr = Tuple[TextIOWrapper, TextIOWrapper] +OutErr = tuple[TextIOWrapper, TextIOWrapper] class _LogThreadLocal(local): diff --git a/src/tox/run.py b/src/tox/run.py index c4c857ed1c..5cfc9f8664 100644 --- a/src/tox/run.py +++ b/src/tox/run.py @@ -7,12 +7,15 @@ import os import sys import time -from typing import Sequence +from typing import TYPE_CHECKING from tox.config.cli.parse import get_options from tox.report import HandledError, ToxHandler from tox.session.state import State +if TYPE_CHECKING: + from collections.abc import Sequence + def run(args: Sequence[str] | None = None) -> None: try: diff --git a/src/tox/session/cmd/run/common.py b/src/tox/session/cmd/run/common.py index 4655076339..29c9777346 100644 --- a/src/tox/session/cmd/run/common.py +++ b/src/tox/session/cmd/run/common.py @@ -10,7 +10,7 @@ from pathlib import Path from signal import SIGINT, Handlers, signal from threading import Event, Thread -from typing import TYPE_CHECKING, Any, Iterator, Optional, Sequence, cast +from typing import TYPE_CHECKING, Any, cast from colorama import Fore @@ -21,6 +21,8 @@ from tox.util.spinner import MISS_DURATION, Spinner if TYPE_CHECKING: + from collections.abc import Iterator, Sequence + from tox.config.types import EnvList from tox.session.state import State from tox.tox_env.api import ToxEnv @@ -91,7 +93,7 @@ def env_run_create_flags(parser: ArgumentParser, mode: str) -> None: "--installpkg", help="use specified package for installation into venv, instead of packaging the project", default=None, - of_type=Optional[Path], + of_type=Path | None, action=InstallPackageAction, dest="install_pkg", ) diff --git a/src/tox/session/cmd/show_config.py b/src/tox/session/cmd/show_config.py index 0a4f4e45a1..d4287fda91 100644 --- a/src/tox/session/cmd/show_config.py +++ b/src/tox/session/cmd/show_config.py @@ -4,7 +4,7 @@ import os from textwrap import indent -from typing import TYPE_CHECKING, Iterable +from typing import TYPE_CHECKING from colorama import Fore @@ -14,6 +14,8 @@ from tox.session.env_select import CliEnv, register_env_select_flags if TYPE_CHECKING: + from collections.abc import Iterable + from tox.config.cli.parser import ToxParser from tox.config.sets import ConfigSet from tox.session.state import State diff --git a/src/tox/session/cmd/version_flag.py b/src/tox/session/cmd/version_flag.py index 91c44e9303..5f968e1022 100644 --- a/src/tox/session/cmd/version_flag.py +++ b/src/tox/session/cmd/version_flag.py @@ -5,7 +5,7 @@ import sys from argparse import SUPPRESS, Action, ArgumentParser, Namespace from pathlib import Path -from typing import TYPE_CHECKING, Any, Sequence, cast +from typing import TYPE_CHECKING, Any, cast import tox from tox.plugin import impl @@ -13,6 +13,8 @@ from tox.version import version if TYPE_CHECKING: + from collections.abc import Sequence + from tox.config.cli.parser import HelpFormatter, ToxParser diff --git a/src/tox/session/env_select.py b/src/tox/session/env_select.py index 6bbe58dc21..91c971524b 100644 --- a/src/tox/session/env_select.py +++ b/src/tox/session/env_select.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from difflib import get_close_matches from itertools import chain -from typing import TYPE_CHECKING, Dict, Iterable, Iterator, List, cast +from typing import TYPE_CHECKING, cast from tox.config.loader.str_convert import StrConvert from tox.config.types import EnvList @@ -19,6 +19,7 @@ if TYPE_CHECKING: from argparse import ArgumentParser + from collections.abc import Iterable, Iterator from tox.session.state import State @@ -48,7 +49,7 @@ class CliEnv: # noqa: PLW1641 def __init__(self, value: list[str] | str | None = None) -> None: if isinstance(value, str): - value = StrConvert().to(value, of_type=List[str], factory=None) + value = StrConvert().to(value, of_type=list[str], factory=None) self._names: list[str] | None = value def __iter__(self) -> Iterator[str]: @@ -157,7 +158,7 @@ def __init__(self, state: State) -> None: self._journal = self._state._journal # noqa: SLF001 self._provision: tuple[bool, str] | None = None - self._state.conf.core.add_config("labels", Dict[str, EnvList], {}, "core labels") + self._state.conf.core.add_config("labels", dict[str, EnvList], {}, "core labels") tox_env_filter_regex = getattr(state.conf.options, "skip_env", "").strip() self._filter_re = re.compile(tox_env_filter_regex) if tox_env_filter_regex else None diff --git a/src/tox/session/state.py b/src/tox/session/state.py index 3f59ecd73f..e60d8ef6a7 100644 --- a/src/tox/session/state.py +++ b/src/tox/session/state.py @@ -2,7 +2,7 @@ import sys from itertools import chain, tee -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING from tox.config.main import Config from tox.journal import Journal @@ -12,6 +12,8 @@ from .env_select import EnvSelector if TYPE_CHECKING: + from collections.abc import Sequence + from tox.config.cli.parse import Options from tox.config.cli.parser import ToxParser diff --git a/src/tox/tox_env/api.py b/src/tox/tox_env/api.py index d07177de35..c6bbb161cc 100644 --- a/src/tox/tox_env/api.py +++ b/src/tox/tox_env/api.py @@ -11,7 +11,7 @@ from abc import ABC, abstractmethod from contextlib import contextmanager from pathlib import Path -from typing import TYPE_CHECKING, Any, Iterator, List, NamedTuple, Sequence, Set, cast +from typing import TYPE_CHECKING, Any, NamedTuple, cast from tox.execute.request import ExecuteRequest from tox.tox_env.errors import Fail, Recreate, Skip @@ -19,6 +19,7 @@ from tox.util.path import ensure_empty_dir if TYPE_CHECKING: + from collections.abc import Iterator, Sequence from io import BytesIO from tox.config.cli.parser import Parsed @@ -129,7 +130,7 @@ def register_config(self) -> None: ) self.conf.add_config( keys=["labels"], - of_type=Set[str], + of_type=set[str], default=set(), desc="labels attached to the tox environment", ) @@ -176,7 +177,7 @@ def pass_env_post_process(values: list[str]) -> list[str]: self.conf.add_config( keys=["pass_env", "passenv"], - of_type=List[str], + of_type=list[str], default=[], desc="environment variables to pass on to the tox environment", post_process=pass_env_post_process, @@ -195,7 +196,7 @@ def pass_env_post_process(values: list[str]) -> list[str]: ) self.conf.add_config( "allowlist_externals", - of_type=List[str], + of_type=list[str], default=[], desc="external command glob to allow calling", ) diff --git a/src/tox/tox_env/info.py b/src/tox/tox_env/info.py index 83ee376acb..bd351ad749 100644 --- a/src/tox/tox_env/info.py +++ b/src/tox/tox_env/info.py @@ -7,9 +7,10 @@ import json from contextlib import contextmanager -from typing import TYPE_CHECKING, Any, Iterator +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: + from collections.abc import Iterator from pathlib import Path diff --git a/src/tox/tox_env/package.py b/src/tox/tox_env/package.py index a097b5a67d..272d8d1a6c 100644 --- a/src/tox/tox_env/package.py +++ b/src/tox/tox_env/package.py @@ -6,13 +6,15 @@ from pathlib import Path from threading import RLock from types import MethodType -from typing import TYPE_CHECKING, Any, Callable, Generator, Iterator, cast +from typing import TYPE_CHECKING, Any, cast from filelock import FileLock from .api import ToxEnv, ToxEnvCreateArgs if TYPE_CHECKING: + from collections.abc import Callable, Generator, Iterator + from tox.config.main import Config from tox.config.sets import EnvConfigSet diff --git a/src/tox/tox_env/python/api.py b/src/tox/tox_env/python/api.py index ae0375904c..8cf18364c4 100644 --- a/src/tox/tox_env/python/api.py +++ b/src/tox/tox_env/python/api.py @@ -9,7 +9,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass from pathlib import Path -from typing import TYPE_CHECKING, Any, List, NamedTuple +from typing import TYPE_CHECKING, Any, NamedTuple from virtualenv.discovery.py_spec import PythonSpec @@ -79,7 +79,7 @@ def validate_base_python(value: list[str]) -> list[str]: self.conf.add_config( keys=["base_python", "basepython"], - of_type=List[str], + of_type=list[str], default=self.default_base_python, desc="environment identifier for python, first one found wins", post_process=validate_base_python, diff --git a/src/tox/tox_env/python/package.py b/src/tox/tox_env/python/package.py index 262b14e8a7..a34004b2fd 100644 --- a/src/tox/tox_env/python/package.py +++ b/src/tox/tox_env/python/package.py @@ -3,7 +3,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Generator, Iterator, List, Sequence, cast +from typing import TYPE_CHECKING, cast from packaging.requirements import Requirement @@ -13,6 +13,7 @@ from .api import NoInterpreter, Python if TYPE_CHECKING: + from collections.abc import Generator, Iterator, Sequence from pathlib import Path from tox.config.main import Config @@ -69,7 +70,7 @@ def register_run_env(self, run_env: RunToxEnv) -> Generator[tuple[str, str], Pac if run_env.conf["package"] != "skip" and "deps" not in self.conf: self.conf.add_config( keys="deps", - of_type=List[Requirement], + of_type=list[Requirement], default=[], desc="Name of the python dependencies as specified by PEP-440", ) diff --git a/src/tox/tox_env/python/pip/pip_install.py b/src/tox/tox_env/python/pip/pip_install.py index f36d0b9c9e..2595ac4e2f 100644 --- a/src/tox/tox_env/python/pip/pip_install.py +++ b/src/tox/tox_env/python/pip/pip_install.py @@ -4,9 +4,10 @@ import operator from abc import ABC, abstractmethod from collections import defaultdict +from collections.abc import Callable, Sequence from functools import partial from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, Sequence, cast +from typing import TYPE_CHECKING, Any, cast from packaging.requirements import Requirement diff --git a/src/tox/tox_env/python/pip/req/args.py b/src/tox/tox_env/python/pip/req/args.py index e2c5b4aaeb..6f78ef0cd0 100644 --- a/src/tox/tox_env/python/pip/req/args.py +++ b/src/tox/tox_env/python/pip/req/args.py @@ -3,10 +3,13 @@ import bisect import re from argparse import Action, ArgumentParser, ArgumentTypeError, Namespace -from typing import Any, NoReturn, Protocol, Sequence, TypeVar +from typing import TYPE_CHECKING, Any, NoReturn, Protocol, TypeVar from tox.tox_env.python.pip.req.util import handle_binary_option +if TYPE_CHECKING: + from collections.abc import Sequence + _T_contra = TypeVar("_T_contra", contravariant=True) @@ -22,8 +25,7 @@ def print_usage(self, file: _SupportsWrite[str] | None = None) -> None: def exit(self, status: int = 0, message: str | None = None) -> NoReturn: # noqa: ARG002, PLR6301 message = "" if message is None else message msg = message.lstrip(": ").rstrip() - if msg.startswith("error: "): - msg = msg[len("error: ") :] + msg = msg.removeprefix("error: ") raise ValueError(msg) diff --git a/src/tox/tox_env/python/pip/req/file.py b/src/tox/tox_env/python/pip/req/file.py index 03357dede3..70ff528953 100644 --- a/src/tox/tox_env/python/pip/req/file.py +++ b/src/tox/tox_env/python/pip/req/file.py @@ -8,8 +8,9 @@ import sys import urllib.parse from argparse import ArgumentParser, Namespace +from collections.abc import Iterator from pathlib import Path -from typing import IO, Any, Iterator, List, Tuple, cast +from typing import IO, Any, cast from urllib.request import urlopen import chardet @@ -26,7 +27,7 @@ # https://www.python.org/dev/peps/pep-0508/#extras _EXTRA_PATH = re.compile(r"(.*)\[([-._,\sa-zA-Z0-9]*)]") _EXTRA_ELEMENT = re.compile(r"[a-zA-Z0-9]*[-._a-zA-Z0-9]") -ReqFileLines = Iterator[Tuple[int, str]] +ReqFileLines = Iterator[tuple[int, str]] DEFAULT_INDEX_URL = "https://pypi.org/simple" @@ -168,7 +169,7 @@ def options(self) -> Namespace: @property def requirements(self) -> list[ParsedRequirement]: self._ensure_requirements_parsed() - return cast("List[ParsedRequirement]", self._requirements) + return cast("list[ParsedRequirement]", self._requirements) @property def _parser(self) -> ArgumentParser: diff --git a/src/tox/tox_env/python/runner.py b/src/tox/tox_env/python/runner.py index 5c012857d4..b1884cd8d1 100644 --- a/src/tox/tox_env/python/runner.py +++ b/src/tox/tox_env/python/runner.py @@ -4,7 +4,7 @@ from abc import ABC from functools import partial -from typing import TYPE_CHECKING, Set +from typing import TYPE_CHECKING from packaging.utils import canonicalize_name @@ -42,7 +42,7 @@ def register_config(self) -> None: ) self.conf.add_config( keys=["dependency_groups"], - of_type=Set[str], + of_type=set[str], default=set(), desc="dependency groups to install of the target package", post_process=_normalize_extras, @@ -144,7 +144,7 @@ def skip_missing_interpreters_post_process(value: bool) -> bool: # noqa: FBT001 def add_extras_to_env(conf: EnvConfigSet) -> None: conf.add_config( keys=["extras"], - of_type=Set[str], + of_type=set[str], default=set(), desc="extras to install of the target package", post_process=_normalize_extras, diff --git a/src/tox/tox_env/python/virtual_env/package/cmd_builder.py b/src/tox/tox_env/python/virtual_env/package/cmd_builder.py index cac1d39333..bd823188e7 100644 --- a/src/tox/tox_env/python/virtual_env/package/cmd_builder.py +++ b/src/tox/tox_env/python/virtual_env/package/cmd_builder.py @@ -7,7 +7,7 @@ from functools import partial from io import TextIOWrapper from pathlib import Path -from typing import TYPE_CHECKING, Generator, Iterator, List, cast +from typing import TYPE_CHECKING, cast from zipfile import ZipFile from packaging.requirements import Requirement @@ -26,6 +26,7 @@ from .util import dependencies_with_extras if TYPE_CHECKING: + from collections.abc import Generator, Iterator from os import PathLike from tox.config.sets import EnvConfigSet @@ -54,7 +55,7 @@ def register_config(self) -> None: ) self.conf.add_config( keys=["commands"], - of_type=List[Command], + of_type=list[Command], default=[], desc="the commands to be called for testing", ) diff --git a/src/tox/tox_env/python/virtual_env/package/pyproject.py b/src/tox/tox_env/python/virtual_env/package/pyproject.py index 37fb4625a2..805879c815 100644 --- a/src/tox/tox_env/python/virtual_env/package/pyproject.py +++ b/src/tox/tox_env/python/virtual_env/package/pyproject.py @@ -9,7 +9,7 @@ from itertools import chain from pathlib import Path from threading import RLock -from typing import TYPE_CHECKING, Any, Dict, Generator, Iterator, Literal, NoReturn, Optional, Sequence, cast +from typing import TYPE_CHECKING, Any, Literal, NoReturn, cast from cachetools import cached from packaging.requirements import Requirement @@ -38,6 +38,8 @@ from .util import dependencies_with_extras, dependencies_with_extras_from_markers if TYPE_CHECKING: + from collections.abc import Generator, Iterator, Sequence + from tox.config.sets import EnvConfigSet from tox.execute.api import ExecuteStatus from tox.tox_env.api import ToxEnvCreateArgs @@ -52,7 +54,7 @@ else: # pragma: no cover (py311+) import tomli as tomllib -ConfigSettings = Optional[Dict[str, Any]] +ConfigSettings = dict[str, Any] | None class ToxBackendFailed(Fail, BackendFailed): @@ -170,7 +172,7 @@ def _add_config_settings(self, build_type: str) -> None: for key in keys.get(build_type, []): self.conf.add_config( keys=[f"config_settings_{key}"], - of_type=Dict[str, str], + of_type=dict[str, str], default=None, # type: ignore[arg-type] desc=f"config settings passed to the {key} backend API endpoint", ) diff --git a/src/tox/tox_env/python/virtual_env/package/util.py b/src/tox/tox_env/python/virtual_env/package/util.py index 4d262a7f54..21f0ebf337 100644 --- a/src/tox/tox_env/python/virtual_env/package/util.py +++ b/src/tox/tox_env/python/virtual_env/package/util.py @@ -1,7 +1,7 @@ from __future__ import annotations from copy import deepcopy -from typing import TYPE_CHECKING, Optional, Set, cast +from typing import TYPE_CHECKING, cast if TYPE_CHECKING: from packaging._parser import Op, Variable @@ -70,7 +70,7 @@ def _extract_extra_markers(req: Requirement) -> tuple[Requirement, set[str | Non cast("Marker", req.marker)._markers = new_markers # noqa: SLF001 else: req.marker = None - return req, cast("Set[Optional[str]]", extra_markers) or {None} + return req, cast("set[str | None]", extra_markers) or {None} def _get_extra(_marker: str | tuple[Variable, Op, Variable]) -> str | None: diff --git a/src/tox/tox_env/register.py b/src/tox/tox_env/register.py index b421c0e7c0..6b26c90226 100644 --- a/src/tox/tox_env/register.py +++ b/src/tox/tox_env/register.py @@ -2,9 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable +from typing import TYPE_CHECKING if TYPE_CHECKING: + from collections.abc import Iterable + from tox.plugin.manager import Plugin from .package import PackageToxEnv diff --git a/src/tox/tox_env/runner.py b/src/tox/tox_env/runner.py index 9d9b61bb34..a2ce0d026f 100644 --- a/src/tox/tox_env/runner.py +++ b/src/tox/tox_env/runner.py @@ -5,7 +5,7 @@ import re from abc import ABC, abstractmethod from hashlib import sha256 -from typing import TYPE_CHECKING, Any, Iterable, List +from typing import TYPE_CHECKING, Any from tox.config.types import Command, EnvList @@ -14,6 +14,8 @@ from .util import add_change_dir_conf if TYPE_CHECKING: + from collections.abc import Iterable + from tox.journal import EnvJournal @@ -44,19 +46,19 @@ def ensure_one_line(value: str) -> str: super().register_config() self.conf.add_config( keys=["commands_pre"], - of_type=List[Command], + of_type=list[Command], default=[], desc="the commands to be called before testing", ) self.conf.add_config( keys=["commands"], - of_type=List[Command], + of_type=list[Command], default=[], desc="the commands to be called for testing", ) self.conf.add_config( keys=["commands_post"], - of_type=List[Command], + of_type=list[Command], default=[], desc="the commands to be called after testing", ) diff --git a/src/tox/util/spinner.py b/src/tox/util/spinner.py index a6d2b905b9..faf5a92e16 100644 --- a/src/tox/util/spinner.py +++ b/src/tox/util/spinner.py @@ -8,7 +8,7 @@ import threading import time from collections import OrderedDict -from typing import IO, TYPE_CHECKING, NamedTuple, Sequence, TypeVar +from typing import IO, TYPE_CHECKING, NamedTuple, TypeVar from colorama import Fore @@ -18,6 +18,7 @@ from typing_extensions import Self if TYPE_CHECKING: + from collections.abc import Sequence from types import TracebackType from typing import Any, ClassVar diff --git a/tests/config/cli/conftest.py b/tests/config/cli/conftest.py index 69da088438..fa5eb9984b 100644 --- a/tests/config/cli/conftest.py +++ b/tests/config/cli/conftest.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import pytest @@ -16,6 +16,8 @@ from tox.session.cmd.show_config import show_config if TYPE_CHECKING: + from collections.abc import Callable + from tox.session.state import State diff --git a/tests/config/cli/test_cli_env_var.py b/tests/config/cli/test_cli_env_var.py index 442fa65e5f..bf64c41032 100644 --- a/tests/config/cli/test_cli_env_var.py +++ b/tests/config/cli/test_cli_env_var.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING from unittest.mock import ANY import pytest @@ -11,6 +11,8 @@ from tox.util.ci import is_ci if TYPE_CHECKING: + from collections.abc import Callable + from tox.pytest import CaptureFixture, LogCaptureFixture, MonkeyPatch from tox.session.state import State diff --git a/tests/config/cli/test_cli_ini.py b/tests/config/cli/test_cli_ini.py index c78957dbea..48d2275ec5 100644 --- a/tests/config/cli/test_cli_ini.py +++ b/tests/config/cli/test_cli_ini.py @@ -4,7 +4,7 @@ import sys import textwrap from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable +from typing import TYPE_CHECKING, Any from unittest.mock import ANY import pytest @@ -19,6 +19,8 @@ from tox.util.ci import is_ci if TYPE_CHECKING: + from collections.abc import Callable + from pytest_mock import MockerFixture from tox.pytest import CaptureFixture, LogCaptureFixture, MonkeyPatch diff --git a/tests/config/loader/ini/conftest.py b/tests/config/loader/ini/conftest.py index 7e056ac464..14993633d3 100644 --- a/tests/config/loader/ini/conftest.py +++ b/tests/config/loader/ini/conftest.py @@ -1,11 +1,12 @@ from __future__ import annotations from configparser import ConfigParser -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import pytest if TYPE_CHECKING: + from collections.abc import Callable from pathlib import Path diff --git a/tests/config/loader/ini/replace/test_replace_env_var.py b/tests/config/loader/ini/replace/test_replace_env_var.py index a0911ad648..d6bcdc9a2b 100644 --- a/tests/config/loader/ini/replace/test_replace_env_var.py +++ b/tests/config/loader/ini/replace/test_replace_env_var.py @@ -1,11 +1,13 @@ from __future__ import annotations import threading -from typing import TYPE_CHECKING, Generator +from typing import TYPE_CHECKING import pytest if TYPE_CHECKING: + from collections.abc import Generator + from tests.config.loader.conftest import ReplaceOne from tox.pytest import LogCaptureFixture, MonkeyPatch diff --git a/tests/config/loader/ini/replace/test_replace_tox_env.py b/tests/config/loader/ini/replace/test_replace_tox_env.py index 663a61251a..53bfe4a131 100644 --- a/tests/config/loader/ini/replace/test_replace_tox_env.py +++ b/tests/config/loader/ini/replace/test_replace_tox_env.py @@ -1,7 +1,8 @@ from __future__ import annotations +from collections.abc import Callable from pathlib import Path -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import pytest diff --git a/tests/config/loader/ini/test_factor.py b/tests/config/loader/ini/test_factor.py index 5dbc50b576..049658a822 100644 --- a/tests/config/loader/ini/test_factor.py +++ b/tests/config/loader/ini/test_factor.py @@ -1,7 +1,7 @@ from __future__ import annotations from textwrap import dedent -from typing import TYPE_CHECKING, Callable, List +from typing import TYPE_CHECKING import pytest @@ -10,6 +10,7 @@ from tox.config.source.ini_section import IniSection if TYPE_CHECKING: + from collections.abc import Callable from configparser import ConfigParser from tests.conftest import ToxIniCreator @@ -140,7 +141,7 @@ def test_factor_config(tox_ini_conf: ToxIniCreator) -> None: assert list(config) == ["py36-django15", "py36-django16", "py37-django15", "py37-django16"] for env in config.core["env_list"]: env_config = config.get_env(env) - env_config.add_config(keys="deps-x", of_type=List[str], default=[], desc="deps") + env_config.add_config(keys="deps-x", of_type=list[str], default=[], desc="deps") deps = env_config["deps-x"] assert "pytest" in deps if "py36" in env: @@ -296,12 +297,12 @@ def test_generative_section_name(tox_ini_conf: ToxIniCreator) -> None: assert list(config) == ["py311-black", "py311-lint", "py310-black", "py310-lint"] env_config = config.get_env("py311-black") - env_config.add_config(keys="deps-x", of_type=List[str], default=[], desc="deps") + env_config.add_config(keys="deps-x", of_type=list[str], default=[], desc="deps") deps = env_config["deps-x"] assert deps == ["black"] env_config = config.get_env("py311-lint") - env_config.add_config(keys="deps-x", of_type=List[str], default=[], desc="deps") + env_config.add_config(keys="deps-x", of_type=list[str], default=[], desc="deps") deps = env_config["deps-x"] assert deps == ["flake8"] diff --git a/tests/config/loader/ini/test_ini_loader.py b/tests/config/loader/ini/test_ini_loader.py index f05f850417..78e9de9913 100644 --- a/tests/config/loader/ini/test_ini_loader.py +++ b/tests/config/loader/ini/test_ini_loader.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import pytest @@ -9,6 +9,7 @@ from tox.config.source.ini_section import IniSection if TYPE_CHECKING: + from collections.abc import Callable from configparser import ConfigParser diff --git a/tests/config/loader/test_memory_loader.py b/tests/config/loader/test_memory_loader.py index 508566df17..fdb8d9aa16 100644 --- a/tests/config/loader/test_memory_loader.py +++ b/tests/config/loader/test_memory_loader.py @@ -2,7 +2,7 @@ import os from pathlib import Path -from typing import Any, Dict, List, Optional, Set +from typing import Any, Dict, List, Optional, Set # noqa: UP035 import pytest @@ -27,28 +27,34 @@ def test_memory_loader_override() -> None: @pytest.mark.parametrize( ("value", "of_type", "outcome"), [ - (True, bool, True), - (1, int, 1), - ("magic", str, "magic"), - ({"1"}, Set[str], {"1"}), - ([1], List[int], [1]), - ({1: 2}, Dict[int, int], {1: 2}), - (Path.cwd(), Path, Path.cwd()), - (Command(["a"]), Command, Command(["a"])), - (EnvList("a,b"), EnvList, EnvList("a,b")), - (1, Optional[int], 1), - ("1", Optional[str], "1"), - (0, bool, False), - (1, bool, True), - ("1", int, 1), - (1, str, "1"), - ({1}, Set[str], {"1"}), - ({"1"}, List[int], [1]), - ({"1": "2"}, Dict[int, int], {1: 2}), - (os.getcwd(), Path, Path.cwd()), # noqa: PTH109 - ("pip list", Command, Command(["pip", "list"])), - ("a\nb", EnvList, EnvList(["a", "b"])), - ("1", Optional[int], 1), + pytest.param(True, bool, True, id="bool_true"), + pytest.param(1, int, 1, id="int_1"), + pytest.param("magic", str, "magic", id="str_magic"), + pytest.param({"1"}, set[str], {"1"}, id="set_str_1"), + pytest.param({"1"}, Set[str], {"1"}, id="set_typing_str_1"), # noqa: UP006 + pytest.param([1], list[int], [1], id="list_int_1"), + pytest.param([1], List[int], [1], id="list_typing_int_1"), # noqa: UP006 + pytest.param({1: 2}, dict[int, int], {1: 2}, id="dict_int_int_1_2"), + pytest.param({1: 2}, Dict[int, int], {1: 2}, id="dict_int_int_1_2"), # noqa: UP006 + pytest.param(Path.cwd(), Path, Path.cwd(), id="path_cwd"), + pytest.param(Command(["a"]), Command, Command(["a"]), id="command_a"), + pytest.param(EnvList("a,b"), EnvList, EnvList("a,b"), id="envlist_a_b"), + pytest.param(1, Optional[int], 1, id="optional_int_1"), # noqa: UP045 + pytest.param("1", Optional[str], "1", id="optional_str_1"), # noqa: UP045 + pytest.param(1, int | None, 1, id="int_or_none_1"), + pytest.param("1", str | None, "1", id="str_or_none_1"), + pytest.param(0, bool, False, id="bool_false_from_0"), + pytest.param(1, bool, True, id="bool_true_from_1"), + pytest.param("1", int, 1, id="int_from_str_1"), + pytest.param(1, str, "1", id="str_from_int_1"), + pytest.param({1}, set[str], {"1"}, id="set_str_from_int_1"), + pytest.param({"1"}, list[int], [1], id="list_int_from_str_1"), + pytest.param({"1": "2"}, dict[int, int], {1: 2}, id="dict_int_int_from_str_1_2"), + pytest.param(os.getcwd(), Path, Path.cwd(), id="path_from_getcwd"), # noqa: PTH109 + pytest.param("pip list", Command, Command(["pip", "list"]), id="command_pip_list"), + pytest.param("a\nb", EnvList, EnvList(["a", "b"]), id="envlist_a_b_newline"), + pytest.param("1", Optional[int], 1, id="optional_int_from_str_1"), # noqa: UP045 + pytest.param("1", int | None, 1, id="int_or_none_from_str_1"), ], ) def test_memory_loader(value: Any, of_type: type[Any], outcome: Any) -> None: @@ -62,10 +68,10 @@ def test_memory_loader(value: Any, of_type: type[Any], outcome: Any) -> None: ("value", "of_type", "exception", "msg"), [ ("m", int, ValueError, "invalid literal for int"), - ({"m"}, Set[int], ValueError, "invalid literal for int"), - (["m"], List[int], ValueError, "invalid literal for int"), - ({"m": 1}, Dict[int, int], ValueError, "invalid literal for int"), - ({1: "m"}, Dict[int, int], ValueError, "invalid literal for int"), + ({"m"}, set[int], ValueError, "invalid literal for int"), + (["m"], list[int], ValueError, "invalid literal for int"), + ({"m": 1}, dict[int, int], ValueError, "invalid literal for int"), + ({1: "m"}, dict[int, int], ValueError, "invalid literal for int"), (object, Path, TypeError, r"str(, bytes)? or (an )?os\.PathLike object"), (1, Command, TypeError, "1"), (1, EnvList, TypeError, "1"), diff --git a/tests/config/loader/test_str_convert.py b/tests/config/loader/test_str_convert.py index 7c24c1fad2..fc8f23992d 100644 --- a/tests/config/loader/test_str_convert.py +++ b/tests/config/loader/test_str_convert.py @@ -5,7 +5,7 @@ from pathlib import Path from textwrap import dedent from types import ModuleType -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, TypeVar, Union +from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union import pytest @@ -21,37 +21,43 @@ @pytest.mark.parametrize( ("raw", "value", "of_type"), [ - ("true", True, bool), - ("false", False, bool), - ("True", True, bool), - ("False", False, bool), - ("TruE", True, bool), - ("FalsE", False, bool), - ("1", True, bool), - ("0", False, bool), - ("1", 1, int), - ("0", 0, int), - ("+1", 1, int), - ("-1", -1, int), - ("1.1", 1.1, float), - ("0.1", 0.1, float), - ("+1.1", 1.1, float), - ("-1.1", -1.1, float), - ("magic", "magic", str), - ("1", {"1"}, Set[str]), - ("1", [1], List[int]), - ("1=2", {1: 2}, Dict[int, int]), - ("a=1\n\nc=2", {"a": 1, "c": 2}, Dict[str, int]), - ("a", Path("a"), Path), - ("a", Command(["a"]), Command), - ("a,b", EnvList(["a", "b"]), EnvList), - ("", None, Optional[int]), - ("1", 1, Optional[int]), - ("", None, Optional[str]), - ("1", "1", Optional[str]), - ("", None, Optional[List[str]]), - ("1,2", ["1", "2"], Optional[List[str]]), - ("1", "1", Literal["1", "2"]), + pytest.param("true", True, bool, id="str_true_to_bool"), + pytest.param("false", False, bool, id="str_false_to_bool"), + pytest.param("True", True, bool, id="str_True_to_bool"), + pytest.param("False", False, bool, id="str_False_to_bool"), + pytest.param("TruE", True, bool, id="str_TruE_to_bool"), + pytest.param("FalsE", False, bool, id="str_FalsE_to_bool"), + pytest.param("1", True, bool, id="str_1_to_bool"), + pytest.param("0", False, bool, id="str_0_to_bool"), + pytest.param("1", 1, int, id="str_1_to_int"), + pytest.param("0", 0, int, id="str_0_to_int"), + pytest.param("+1", 1, int, id="str_plus1_to_int"), + pytest.param("-1", -1, int, id="str_minus1_to_int"), + pytest.param("1.1", 1.1, float, id="str_1.1_to_float"), + pytest.param("0.1", 0.1, float, id="str_0.1_to_float"), + pytest.param("+1.1", 1.1, float, id="str_plus1.1_to_float"), + pytest.param("-1.1", -1.1, float, id="str_minus1.1_to_float"), + pytest.param("magic", "magic", str, id="str_magic_to_str"), + pytest.param("1", {"1"}, set[str], id="str_1_to_set_str"), + pytest.param("1", [1], list[int], id="str_1_to_list_int"), + pytest.param("1=2", {1: 2}, dict[int, int], id="str_1eq2_to_dict_int_int"), + pytest.param("a=1\n\nc=2", {"a": 1, "c": 2}, dict[str, int], id="str_multiline_dict_str_int"), + pytest.param("a", Path("a"), Path, id="str_a_to_path"), + pytest.param("a", Command(["a"]), Command, id="str_a_to_command"), + pytest.param("a,b", EnvList(["a", "b"]), EnvList, id="str_a_b_to_envlist"), + pytest.param("", None, Optional[int], id="empty_to_optional_int"), # noqa: UP045 + pytest.param("1", 1, Optional[int], id="str_1_to_optional_int"), # noqa: UP045 + pytest.param("", None, Optional[str], id="empty_to_optional_str"), # noqa: UP045 + pytest.param("1", "1", Optional[str], id="str_1_to_optional_str"), # noqa: UP045 + pytest.param("", None, Optional[list[str]], id="empty_to_optional_list_str"), # noqa: UP045 + pytest.param("1,2", ["1", "2"], Optional[list[str]], id="str_1_2_to_optional_list_str"), # noqa: UP045 + pytest.param("", None, int | None, id="empty_to_int_or_none"), + pytest.param("1", 1, int | None, id="str_1_to_int_or_none"), + pytest.param("", None, str | None, id="empty_to_str_or_none"), + pytest.param("1", "1", str | None, id="str_1_to_str_or_none"), + pytest.param("", None, list[str] | None, id="empty_to_list_str_or_none"), + pytest.param("1,2", ["1", "2"], list[str] | None, id="str_1_2_to_list_str_or_none"), + pytest.param("1", "1", Literal["1", "2"], id="str_1_to_literal_1_2"), ], ) def test_str_convert_ok(raw: str, value: Any, of_type: type[Any]) -> None: @@ -59,28 +65,26 @@ def test_str_convert_ok(raw: str, value: Any, of_type: type[Any]) -> None: assert result == value -# Tests that can work only with py39 or newer due to type not being subscriptible before -if sys.version_info >= (3, 9): - - @pytest.mark.parametrize( - ("raw", "value", "of_type"), - [ - ("", None, Optional[list[str]]), - ("1,2", ["1", "2"], Optional[list[str]]), - ], - ) - def test_str_convert_ok_py39(raw: str, value: Any, of_type: type[Any]) -> None: - result = StrConvert().to(raw, of_type, None) - assert result == value - - @pytest.mark.parametrize( ("raw", "of_type", "exc_type", "msg"), [ - ("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\]|str \| int)"), - ("", Command, ValueError, r"attempting to parse '' into a command failed"), + pytest.param("a", TypeVar, TypeError, r"a cannot cast to .*typing.TypeVar.*", id="fail_typevar"), + pytest.param("3", Literal["1", "2"], ValueError, r"3 must be one of \('1', '2'\)", id="fail_literal_1_2"), + pytest.param( + "3", + Union[str, int], # noqa: UP007 + TypeError, + r"3 cannot cast to (typing.Union\[str, int\]|str \| int)", + id="fail_union_str_int", + ), + pytest.param( + "3", + str | int, + TypeError, + r"3 cannot cast to (typing.Union\[str, int\]|str \| int)", + id="fail_union_bar_str_int", + ), + pytest.param("", Command, ValueError, r"attempting to parse '' into a command failed", id="fail_empty_command"), ], ) def test_str_convert_nok(raw: str, of_type: type[Any], msg: str, exc_type: type[Exception]) -> None: diff --git a/tests/config/loader/test_toml_loader.py b/tests/config/loader/test_toml_loader.py index 3a3b835459..655060f549 100644 --- a/tests/config/loader/test_toml_loader.py +++ b/tests/config/loader/test_toml_loader.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path -from typing import Any, Dict, List, Literal, Optional, TypeVar +from typing import Any, Literal, TypeVar import pytest @@ -59,36 +59,36 @@ def test_toml_loader_bool_nok() -> None: def test_toml_loader_list_ok() -> None: - assert perform_load(["a"], List[str]) == ["a"] + assert perform_load(["a"], list[str]) == ["a"] def test_toml_loader_list_nok() -> None: with pytest.raises(TypeError, match=r"{} is not list"): - perform_load({}, List[str]) + perform_load({}, list[str]) def test_toml_loader_list_nok_element() -> None: with pytest.raises(TypeError, match="2 is not of type 'str'"): - perform_load(["a", 2], List[str]) + perform_load(["a", 2], list[str]) def test_toml_loader_dict_ok() -> None: - assert perform_load({"a": "1"}, Dict[str, str]) == {"a": "1"} + assert perform_load({"a": "1"}, dict[str, str]) == {"a": "1"} def test_toml_loader_dict_nok() -> None: with pytest.raises(TypeError, match=r"{'a'} is not dictionary"): - perform_load({"a"}, Dict[str, str]) + perform_load({"a"}, dict[str, str]) def test_toml_loader_dict_nok_key() -> None: with pytest.raises(TypeError, match="1 is not of type 'str'"): - perform_load({"a": 1, 1: "2"}, Dict[str, int]) + perform_load({"a": 1, 1: "2"}, dict[str, int]) def test_toml_loader_dict_nok_value() -> None: with pytest.raises(TypeError, match="'2' is not of type 'int'"): - perform_load({"a": 1, "b": "2"}, Dict[str, int]) + perform_load({"a": 1, "b": "2"}, dict[str, int]) def test_toml_loader_path_ok() -> None: @@ -101,7 +101,7 @@ def test_toml_loader_path_nok() -> None: def test_toml_loader_command_ok() -> None: - commands = perform_load([["a", "b"], ["c"]], List[Command]) + commands = perform_load([["a", "b"], ["c"]], list[Command]) assert isinstance(commands, list) assert len(commands) == 2 assert all(isinstance(i, Command) for i in commands) @@ -112,7 +112,7 @@ def test_toml_loader_command_ok() -> None: def test_toml_loader_command_nok() -> None: with pytest.raises(TypeError, match="1 is not of type 'str'"): - perform_load([["a", 1]], List[Command]) + perform_load([["a", 1]], list[Command]) def test_toml_loader_env_list_ok() -> None: @@ -127,18 +127,18 @@ def test_toml_loader_env_list_nok() -> None: def test_toml_loader_list_optional_ok() -> None: - assert perform_load(["a", None], List[Optional[str]]) == ["a", None] + assert perform_load(["a", None], list[str | None]) == ["a", None] def test_toml_loader_list_optional_nok() -> None: with pytest.raises(TypeError, match="1 is not union of str, NoneType"): - perform_load(["a", None, 1], List[Optional[str]]) + perform_load(["a", None, 1], list[str | None]) def test_toml_loader_list_literal_ok() -> None: - assert perform_load(["a", "b"], List[Literal["a", "b"]]) == ["a", "b"] + assert perform_load(["a", "b"], list[Literal["a", "b"]]) == ["a", "b"] def test_toml_loader_list_literal_nok() -> None: with pytest.raises(TypeError, match="'c' is not one of literal 'a','b'"): - perform_load(["a", "c"], List[Literal["a", "b"]]) + perform_load(["a", "c"], list[Literal["a", "b"]]) diff --git a/tests/config/test_main.py b/tests/config/test_main.py index 853ef9bb44..42d4e7d9fc 100644 --- a/tests/config/test_main.py +++ b/tests/config/test_main.py @@ -3,7 +3,7 @@ import os from functools import partial from pathlib import Path -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING import pytest @@ -74,7 +74,7 @@ def test_config_override_appends_to_list(tox_ini_conf: ToxIniCreator) -> None: passenv = foo """ conf = tox_ini_conf(example, override=[Override("testenv.passenv+=bar")]).get_env("testenv") - conf.add_config("passenv", of_type=List[str], default=[], desc="desc") + conf.add_config("passenv", of_type=list[str], default=[], desc="desc") assert conf["passenv"] == ["foo", "bar"] @@ -85,13 +85,13 @@ def test_config_override_sequence(tox_ini_conf: ToxIniCreator) -> None: """ overrides = [Override("testenv.passenv=bar"), Override("testenv.passenv+=baz")] conf = tox_ini_conf(example, override=overrides).get_env("testenv") - conf.add_config("passenv", of_type=List[str], default=[], desc="desc") + conf.add_config("passenv", of_type=list[str], default=[], desc="desc") assert conf["passenv"] == ["bar", "baz"] def test_config_override_appends_to_empty_list(tox_ini_conf: ToxIniCreator) -> None: conf = tox_ini_conf("[testenv]", override=[Override("testenv.passenv+=bar")]).get_env("testenv") - conf.add_config("passenv", of_type=List[str], default=[], desc="desc") + conf.add_config("passenv", of_type=list[str], default=[], desc="desc") assert conf["passenv"] == ["bar"] diff --git a/tests/config/test_sets.py b/tests/config/test_sets.py index 91aec92c15..b7b4cac8e2 100644 --- a/tests/config/test_sets.py +++ b/tests/config/test_sets.py @@ -2,8 +2,9 @@ import re from collections import OrderedDict +from collections.abc import Callable from pathlib import Path -from typing import TYPE_CHECKING, Callable, Dict, Optional, Set, TypeVar +from typing import TYPE_CHECKING, TypeVar import pytest @@ -46,7 +47,7 @@ def test_config_path(conf_builder: ConfBuilder) -> None: def test_config_set(conf_builder: ConfBuilder) -> None: config_set = conf_builder("set = 1\n 2\n 3") - config_set.add_config(keys="set", of_type=Set[int], default=set(), desc="set") + config_set.add_config(keys="set", of_type=set[int], default=set(), desc="set") set_materialize = config_set["set"] assert set_materialize == {1, 2, 3} @@ -55,7 +56,7 @@ def test_config_optional_none(conf_builder: ConfBuilder) -> None: config_set = conf_builder("") config_set.add_config( keys="optional_none", - of_type=Optional[int], # type: ignore[arg-type] + of_type=int | None, default=None, desc="optional_none", ) @@ -65,7 +66,7 @@ def test_config_optional_none(conf_builder: ConfBuilder) -> None: def test_config_dict(conf_builder: ConfBuilder) -> None: config_set = conf_builder("dict = a=1\n b=2\n c=3") - config_set.add_config(keys="dict", of_type=Dict[str, int], default={}, desc="dict") + config_set.add_config(keys="dict", of_type=dict[str, int], default={}, desc="dict") dict_val = config_set["dict"] assert dict_val == OrderedDict([("a", 1), ("b", 2), ("c", 3)]) @@ -82,7 +83,7 @@ def test_config_bad_type(conf_builder: ConfBuilder) -> None: def test_config_bad_dict(conf_builder: ConfBuilder) -> None: config_set = conf_builder("bad_dict = something") - config_set.add_config(keys="bad_dict", of_type=Dict[str, str], default={}, desc="bad_dict") + config_set.add_config(keys="bad_dict", of_type=dict[str, str], default={}, desc="bad_dict") with pytest.raises(TypeError) as context: assert config_set["bad_dict"] assert str(context.value) == "dictionary lines must be of form key=value, found 'something'" diff --git a/tests/conftest.py b/tests/conftest.py index 4732f5ac32..2eabaabd78 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import sys import sysconfig from pathlib import Path -from typing import TYPE_CHECKING, Callable, Iterator, Protocol, Sequence +from typing import TYPE_CHECKING, Protocol from unittest.mock import patch from uuid import uuid4 @@ -20,6 +20,8 @@ from tox.tox_env.python.virtual_env.api import VirtualEnv if TYPE_CHECKING: + from collections.abc import Callable, Iterator, Sequence + from build import DistributionType from pytest_mock import MockerFixture diff --git a/tests/execute/local_subprocess/test_local_subprocess.py b/tests/execute/local_subprocess/test_local_subprocess.py index 9f782ece83..e8097b00c3 100644 --- a/tests/execute/local_subprocess/test_local_subprocess.py +++ b/tests/execute/local_subprocess/test_local_subprocess.py @@ -165,9 +165,13 @@ def test_local_execute_terminal_size(os_env: dict[str, str], monkeypatch: Monkey main, child = pty.openpty() # type: ignore[attr-defined, unused-ignore] # Use ReadViaThreadUnix to help with debugging the test itself. pipe_out = ReadViaThreadUnix(main, sys.stdout.buffer.write, name="testout", drain=True) # type: ignore[arg-type] - with pipe_out, monkeypatch.context() as monkey, open( # noqa: PTH123 - child, "w", encoding=locale.getpreferredencoding(False) - ) as stdout_mock: + with ( + pipe_out, + monkeypatch.context() as monkey, + open( # noqa: PTH123 + child, "w", encoding=locale.getpreferredencoding(False) + ) as stdout_mock, + ): # Switch stdout with test pty monkey.setattr(sys, "stdout", stdout_mock) monkey.setenv("COLUMNS", "84") diff --git a/tests/plugin/conftest.py b/tests/plugin/conftest.py index e9aeac36c6..78de7a518e 100644 --- a/tests/plugin/conftest.py +++ b/tests/plugin/conftest.py @@ -1,7 +1,10 @@ from __future__ import annotations from pathlib import Path -from typing import Any, Sequence +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from collections.abc import Sequence HERE = Path(__file__).parent diff --git a/tests/plugin/test_plugin.py b/tests/plugin/test_plugin.py index c3c1f051cd..2ccc25e33d 100644 --- a/tests/plugin/test_plugin.py +++ b/tests/plugin/test_plugin.py @@ -304,9 +304,11 @@ def fake_load_entrypoints(self: pluggy.PluginManager, name: str) -> None: # noq self.register(DummyPluginB(), name="dummy_plugin_b") project = tox_project({"tox.ini": ""}) - with patch("pluggy.PluginManager.load_setuptools_entrypoints", fake_load_entrypoints), patch( - "tox.plugin.manager.MANAGER", Plugin() - ), patch.dict(os.environ, {"TOX_DISABLED_EXTERNAL_PLUGINS": env_val}, clear=False): + with ( + patch("pluggy.PluginManager.load_setuptools_entrypoints", fake_load_entrypoints), + patch("tox.plugin.manager.MANAGER", Plugin()), + patch.dict(os.environ, {"TOX_DISABLED_EXTERNAL_PLUGINS": env_val}, clear=False), + ): result = project.run("--version") result.assert_success() diff --git a/tests/plugin/test_plugin_custom_config_set.py b/tests/plugin/test_plugin_custom_config_set.py index 7ef35ff92d..cfa0e7d8d6 100644 --- a/tests/plugin/test_plugin_custom_config_set.py +++ b/tests/plugin/test_plugin_custom_config_set.py @@ -2,7 +2,7 @@ import logging from functools import partial -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import pytest @@ -33,7 +33,7 @@ def factory(for_env: str, raw: object) -> DockerConfigSet: env_conf.add_config( "docker", - of_type=Optional[DockerConfigSet], # type: ignore[arg-type] # mypy fails to understand the type info + of_type=DockerConfigSet | None, default=None, desc="docker env", factory=partial(factory, env_conf.name), diff --git a/tests/pytest_/test_init.py b/tests/pytest_/test_init.py index 50764a503b..db6022146c 100644 --- a/tests/pytest_/test_init.py +++ b/tests/pytest_/test_init.py @@ -4,7 +4,7 @@ import sys from itertools import chain, combinations from textwrap import dedent -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING import pytest @@ -12,6 +12,7 @@ from tox.report import HandledError if TYPE_CHECKING: + from collections.abc import Sequence from pathlib import Path from pytest_mock import MockerFixture diff --git a/tests/session/cmd/test_depends.py b/tests/session/cmd/test_depends.py index a10ace70e1..dd5fe11864 100644 --- a/tests/session/cmd/test_depends.py +++ b/tests/session/cmd/test_depends.py @@ -2,9 +2,11 @@ import sys from textwrap import dedent -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING if TYPE_CHECKING: + from collections.abc import Callable + from tox.pytest import ToxProjectCreator diff --git a/tests/session/cmd/test_show_config.py b/tests/session/cmd/test_show_config.py index d869c5a867..d91939347b 100644 --- a/tests/session/cmd/test_show_config.py +++ b/tests/session/cmd/test_show_config.py @@ -4,13 +4,14 @@ import sys from configparser import ConfigParser from textwrap import dedent -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import pytest from tox.config.types import Command if TYPE_CHECKING: + from collections.abc import Callable from pathlib import Path from pytest_mock import MockerFixture diff --git a/tests/session/test_env_select.py b/tests/session/test_env_select.py index 52d8030b7e..703d6466f7 100644 --- a/tests/session/test_env_select.py +++ b/tests/session/test_env_select.py @@ -37,7 +37,6 @@ def test_clienv(user_input: str, env_names: tuple[str], is_all: bool, is_default: bool) -> None: ce = CliEnv(user_input) assert (ce.is_all, ce.is_default_list, tuple(ce)) == (is_all, is_default, tuple(env_names)) - assert ce is not ce.is_default_list assert CliEnv(user_input) == ce diff --git a/tests/test_provision.py b/tests/test_provision.py index 2cde4f68bd..d1b6b2db77 100644 --- a/tests/test_provision.py +++ b/tests/test_provision.py @@ -7,7 +7,7 @@ from contextlib import contextmanager from pathlib import Path from subprocess import check_call -from typing import TYPE_CHECKING, Callable, Iterator, Sequence +from typing import TYPE_CHECKING from unittest import mock from zipfile import ZipFile @@ -16,6 +16,8 @@ from packaging.requirements import Requirement if TYPE_CHECKING: + from collections.abc import Callable, Iterator, Sequence + from build import DistributionType from devpi_process import Index, IndexServer diff --git a/tests/tox_env/python/pip/req/test_file.py b/tests/tox_env/python/pip/req/test_file.py index b888287568..bd363e1f56 100644 --- a/tests/tox_env/python/pip/req/test_file.py +++ b/tests/tox_env/python/pip/req/test_file.py @@ -4,13 +4,14 @@ import sys from contextlib import contextmanager from io import BytesIO -from typing import IO, TYPE_CHECKING, Any, Iterator +from typing import IO, TYPE_CHECKING, Any import pytest from tox.tox_env.python.pip.req.file import ParsedRequirement, RequirementsFile if TYPE_CHECKING: + from collections.abc import Iterator from pathlib import Path from pytest_mock import MockerFixture diff --git a/tests/tox_env/python/test_python_api.py b/tests/tox_env/python/test_python_api.py index 413725ecd8..f0e3cfeaff 100644 --- a/tests/tox_env/python/test_python_api.py +++ b/tests/tox_env/python/test_python_api.py @@ -3,7 +3,7 @@ import sys import sysconfig from types import SimpleNamespace -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING from unittest.mock import patch import pytest @@ -12,6 +12,7 @@ from tox.tox_env.python.api import Python if TYPE_CHECKING: + from collections.abc import Callable from pathlib import Path from pytest_mock import MockerFixture diff --git a/tests/tox_env/python/virtual_env/package/conftest.py b/tests/tox_env/python/virtual_env/package/conftest.py index e96910954e..cd17480160 100644 --- a/tests/tox_env/python/virtual_env/package/conftest.py +++ b/tests/tox_env/python/virtual_env/package/conftest.py @@ -2,11 +2,12 @@ import sys from textwrap import dedent -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import pytest if TYPE_CHECKING: + from collections.abc import Callable from pathlib import Path diff --git a/tests/tox_env/python/virtual_env/package/test_package_cmd_builder.py b/tests/tox_env/python/virtual_env/package/test_package_cmd_builder.py index ee4aed0310..15ac1aaff5 100644 --- a/tests/tox_env/python/virtual_env/package/test_package_cmd_builder.py +++ b/tests/tox_env/python/virtual_env/package/test_package_cmd_builder.py @@ -1,11 +1,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING from zipfile import ZipFile import pytest if TYPE_CHECKING: + from collections.abc import Callable from pathlib import Path from tox.pytest import ToxProjectCreator diff --git a/tests/type_check/add_config_container_factory.py b/tests/type_check/add_config_container_factory.py index 3193c07b14..50ad5a270c 100644 --- a/tests/type_check/add_config_container_factory.py +++ b/tests/type_check/add_config_container_factory.py @@ -2,8 +2,6 @@ from __future__ import annotations -from typing import List - from tox.config.sets import ConfigSet @@ -14,7 +12,7 @@ def factory(container_name: object) -> str: self.add_config( keys=["k"], - of_type=List[str], + of_type=list[str], default=[], desc="desc", factory=factory, diff --git a/tox.toml b/tox.toml index d0986bc708..dcb19a4eb5 100644 --- a/tox.toml +++ b/tox.toml @@ -1,5 +1,5 @@ requires = [ "tox>=4.27" ] -env_list = [ "fix", "3.14t", "3.14", "3.13", "3.12", "3.11", "3.10", "3.9", "cov", "type", "docs", "pkg_meta" ] +env_list = [ "fix", "3.14t", "3.14", "3.13", "3.12", "3.11", "3.10", "cov", "type", "docs", "pkg_meta" ] skip_missing_interpreters = true [env_run_base] @@ -62,13 +62,19 @@ pass_env = [ commands = [ [ "pre-commit", "run", "--all-files", "--show-diff-on-failure", { replace = "posargs", extend = true } ] ] [env.type] +base_python = ["3.14"] description = "run type check on code base" dependency_groups = [ "type" ] commands = [ [ "mypy", "src{/}tox" ], [ "mypy", "tests" ] ] +[env.type-3.10] +base_python = ["3.10"] +base = ["env", "type"] + [env.docs] description = "build documentation" dependency_groups = [ "docs" ] +base_pythons = ["3.14"] commands = [ [ "sphinx-build",