diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index f09b08660e7..f07c425bd77 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -34,3 +34,5 @@ c7ee560e00b85f7486b452c14ff49e4737996eda # Blacken tools/ 94999255d5ede440c37137d210666fdf64302e75 # Reformat the codebase, with black 585037a80a1177f1fa92e159a7079855782e543e # Cleanup implicit string concatenation 8a6f6ac19b80a6dc35900a47016c851d9fcd2ee2 # Blacken src/pip/_internal/resolution directory +c71318918ebc683ab1bf770514c25979a1306871 # pyupgrade --py39-plus src/pip/_internal/**.py +9adbfcecbb2ba83e06a8efaa6fc5918d969c07af # pyupgrade --py39-plus everything else diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa6dea0f30e..8b582e10499 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,6 @@ jobs: matrix: os: [ubuntu-22.04, macos-13, macos-latest] python: - - "3.8" - "3.9" - "3.10" - "3.11" @@ -172,10 +171,9 @@ jobs: matrix: os: [Windows] python: - - "3.8" + - "3.9" # Commented out, since Windows tests are expensively slow, # only test the oldest and newest Python supported by pip - # - "3.9" # - "3.10" # - "3.11" # - "3.12" diff --git a/docs/html/conf.py b/docs/html/conf.py index be95eca2941..c0d995b90e3 100644 --- a/docs/html/conf.py +++ b/docs/html/conf.py @@ -5,7 +5,6 @@ import pathlib import re import sys -from typing import List, Tuple # Add the docs/ directory to sys.path, because pip_sphinxext.py is there. docs_dir = os.path.dirname(os.path.dirname(__file__)) @@ -91,7 +90,7 @@ # List of manual pages generated -def determine_man_pages() -> List[Tuple[str, str, str, str, int]]: +def determine_man_pages() -> list[tuple[str, str, str, str, int]]: """Determine which man pages need to be generated.""" def to_document_name(path: str, base_dir: str) -> str: diff --git a/docs/html/development/ci.rst b/docs/html/development/ci.rst index ef1f7125dfe..60cbd19f285 100644 --- a/docs/html/development/ci.rst +++ b/docs/html/development/ci.rst @@ -18,7 +18,6 @@ Supported interpreters pip support a variety of Python interpreters: -- CPython 3.8 - CPython 3.9 - CPython 3.10 - CPython 3.11 @@ -38,7 +37,7 @@ and on different architectures: - x86 - arm64 (macOS only) -so 49 hypothetical interpreters. +so 42 hypothetical interpreters. Checks @@ -91,9 +90,7 @@ Actual testing +------------------------------+---------------+-----------------+ | **interpreter** | **unit** | **integration** | +-----------+----------+-------+---------------+-----------------+ -| | x86 | CP3.8 | | | -| | +-------+---------------+-----------------+ -| | | CP3.9 | | | +| | x86 | CP3.9 | | | | | +-------+---------------+-----------------+ | | | CP3.10| | | | | +-------+---------------+-----------------+ @@ -105,9 +102,7 @@ Actual testing | | +-------+---------------+-----------------+ | | | PyPy3 | | | | Windows +----------+-------+---------------+-----------------+ -| | x64 | CP3.8 | GitHub | GitHub | -| | +-------+---------------+-----------------+ -| | | CP3.9 | | | +| | x64 | CP3.9 | GitHub | GitHub | | | +-------+---------------+-----------------+ | | | CP3.10| | | | | +-------+---------------+-----------------+ @@ -119,9 +114,7 @@ Actual testing | | +-------+---------------+-----------------+ | | | PyPy3 | | | +-----------+----------+-------+---------------+-----------------+ -| | x86 | CP3.8 | | | -| | +-------+---------------+-----------------+ -| | | CP3.9 | | | +| | x86 | CP3.9 | | | | | +-------+---------------+-----------------+ | | | CP3.10| | | | | +-------+---------------+-----------------+ @@ -133,9 +126,7 @@ Actual testing | | +-------+---------------+-----------------+ | | | PyPy3 | | | | Linux +----------+-------+---------------+-----------------+ -| | x64 | CP3.8 | GitHub | GitHub | -| | +-------+---------------+-----------------+ -| | | CP3.9 | GitHub | GitHub | +| | x64 | CP3.9 | GitHub | GitHub | | | +-------+---------------+-----------------+ | | | CP3.10| GitHub | GitHub | | | +-------+---------------+-----------------+ @@ -147,9 +138,7 @@ Actual testing | | +-------+---------------+-----------------+ | | | PyPy3 | | | +-----------+----------+-------+---------------+-----------------+ -| | arm64 | CP3.8 | GitHub | GitHub | -| | +-------+---------------+-----------------+ -| | | CP3.9 | GitHub | GitHub | +| | arm64 | CP3.9 | GitHub | GitHub | | | +-------+---------------+-----------------+ | | | CP3.10| GitHub | GitHub | | | +-------+---------------+-----------------+ @@ -161,9 +150,7 @@ Actual testing | | +-------+---------------+-----------------+ | | | PyPy3 | | | | macOS +----------+-------+---------------+-----------------+ -| | x64 | CP3.8 | GitHub | GitHub | -| | +-------+---------------+-----------------+ -| | | CP3.9 | GitHub | GitHub | +| | x64 | CP3.9 | GitHub | GitHub | | | +-------+---------------+-----------------+ | | | CP3.10| GitHub | GitHub | | | +-------+---------------+-----------------+ diff --git a/docs/html/installation.md b/docs/html/installation.md index ffc8199ddc2..11c48dfc768 100644 --- a/docs/html/installation.md +++ b/docs/html/installation.md @@ -126,7 +126,7 @@ $ pip install --upgrade pip The current version of pip works on: - Windows, Linux and macOS. -- CPython 3.8, 3.9, 3.10, 3.11, 3.12, 3.13, and latest PyPy3. +- CPython 3.9, 3.10, 3.11, 3.12, 3.13, and latest PyPy3. pip is tested to work on the latest patch version of the Python interpreter, for each of the minor versions listed above. Previous patch versions are diff --git a/docs/pip_sphinxext.py b/docs/pip_sphinxext.py index 16a3206da50..b9ac9df0981 100644 --- a/docs/pip_sphinxext.py +++ b/docs/pip_sphinxext.py @@ -4,8 +4,9 @@ import pathlib import re import sys +from collections.abc import Iterable, Iterator from textwrap import dedent -from typing import Dict, Iterable, Iterator, List, Optional, Union +from typing import Optional, Union from docutils import nodes, statemachine from docutils.parsers import rst @@ -24,7 +25,7 @@ def convert_cli_option_to_envvar(opt_name: str) -> str: return f"PIP_{normalized_opt_name}" -def convert_cli_opt_names_to_envvars(original_cli_opt_names: List[str]) -> List[str]: +def convert_cli_opt_names_to_envvars(original_cli_opt_names: list[str]) -> list[str]: return [ convert_cli_option_to_envvar(opt_name) for opt_name in original_cli_opt_names ] @@ -69,7 +70,7 @@ def _iter_lines_with_refs(self, lines: Iterable[str]) -> Iterator[str]: if prev is not None: yield prev - def run(self) -> List[nodes.Node]: + def run(self) -> list[nodes.Node]: source = self.state_machine.input_lines.source( self.lineno - self.state_machine.input_offset - 1, ) @@ -90,7 +91,7 @@ class PipCommandUsage(rst.Directive): required_arguments = 1 optional_arguments = 3 - def run(self) -> List[nodes.Node]: + def run(self) -> list[nodes.Node]: cmd = create_command(self.arguments[0]) cmd_prefix = "python -m pip" if len(self.arguments) > 1: @@ -105,7 +106,7 @@ def run(self) -> List[nodes.Node]: class PipCommandDescription(rst.Directive): required_arguments = 1 - def run(self) -> List[nodes.Node]: + def run(self) -> list[nodes.Node]: node = nodes.paragraph() node.document = self.state.document desc = ViewList() @@ -121,7 +122,7 @@ def run(self) -> List[nodes.Node]: class PipOptions(rst.Directive): def _format_option( self, option: optparse.Option, cmd_name: Optional[str] = None - ) -> List[str]: + ) -> list[str]: bookmark_line = ( f".. _`{cmd_name}_{option._long_opts[0]}`:" if cmd_name @@ -165,7 +166,7 @@ def _format_options( for line in self._format_option(option, cmd_name): self.view_list.append(line, "") - def run(self) -> List[nodes.Node]: + def run(self) -> list[nodes.Node]: node = nodes.paragraph() node.document = self.state.document self.view_list = ViewList() @@ -242,7 +243,7 @@ class PipCLIDirective(rst.Directive): has_content = True optional_arguments = 1 - def run(self) -> List[nodes.Node]: + def run(self) -> list[nodes.Node]: node = nodes.paragraph() node.document = self.state.document @@ -310,7 +311,7 @@ def run(self) -> List[nodes.Node]: return [node] -def setup(app: Sphinx) -> Dict[str, Union[bool, str]]: +def setup(app: Sphinx) -> dict[str, Union[bool, str]]: app.add_directive("pip-command-usage", PipCommandUsage) app.add_directive("pip-command-description", PipCommandDescription) app.add_directive("pip-command-options", PipCommandOptions) diff --git a/news/12989.removal.rst b/news/12989.removal.rst new file mode 100644 index 00000000000..17a92adf91b --- /dev/null +++ b/news/12989.removal.rst @@ -0,0 +1 @@ +Drop support for EOL Python 3.8. diff --git a/noxfile.py b/noxfile.py index 70e24a01142..e57101af594 100644 --- a/noxfile.py +++ b/noxfile.py @@ -6,8 +6,8 @@ import os import shutil import sys +from collections.abc import Iterator from pathlib import Path -from typing import Iterator, List, Tuple import nox @@ -70,7 +70,7 @@ def should_update_common_wheels() -> bool: # ----------------------------------------------------------------------------- # Development Commands # ----------------------------------------------------------------------------- -@nox.session(python=["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy3"]) +@nox.session(python=["3.9", "3.10", "3.11", "3.12", "3.13", "pypy3"]) def test(session: nox.Session) -> None: # Get the common wheels. if should_update_common_wheels(): @@ -137,7 +137,7 @@ def test(session: nox.Session) -> None: def docs(session: nox.Session) -> None: session.install("-r", REQUIREMENTS["docs"]) - def get_sphinx_build_command(kind: str) -> List[str]: + def get_sphinx_build_command(kind: str) -> list[str]: # Having the conf.py in the docs/html is weird but needed because we # can not use a different configuration directory vs source directory # on RTD currently. So, we'll pass "-c docs/html" here. @@ -214,7 +214,7 @@ def vendoring(session: nox.Session) -> None: session.run("vendoring", "sync", "-v") return - def pinned_requirements(path: Path) -> Iterator[Tuple[str, str]]: + def pinned_requirements(path: Path) -> Iterator[tuple[str, str]]: for line in path.read_text().splitlines(keepends=False): one, sep, two = line.partition("==") if not sep: @@ -357,7 +357,7 @@ def build_release(session: nox.Session) -> None: shutil.copy(dist, final) -def build_dists(session: nox.Session) -> List[str]: +def build_dists(session: nox.Session) -> list[str]: """Return dists with valid metadata.""" session.log( "# Check if there's any Git-untracked files before building the wheel", diff --git a/pyproject.toml b/pyproject.toml index ed79101bf06..e793cdf0fea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -28,7 +27,7 @@ authors = [ # NOTE: requires-python is duplicated in __pip-runner__.py. # When changing this value, please change the other copy as well. -requires-python = ">=3.8" +requires-python = ">=3.9" [project.scripts] pip = "pip._internal.cli.main:main" diff --git a/src/pip/__init__.py b/src/pip/__init__.py index 72909e402d3..eb8c5a8d48f 100644 --- a/src/pip/__init__.py +++ b/src/pip/__init__.py @@ -1,9 +1,9 @@ -from typing import List, Optional +from typing import Optional __version__ = "25.1.dev0" -def main(args: Optional[List[str]] = None) -> int: +def main(args: Optional[list[str]] = None) -> int: """This is an internal API only meant for use by pip's own console scripts. For additional details, see https://github.com/pypa/pip/issues/7498. diff --git a/src/pip/__pip-runner__.py b/src/pip/__pip-runner__.py index c633787fced..d6be157831a 100644 --- a/src/pip/__pip-runner__.py +++ b/src/pip/__pip-runner__.py @@ -9,7 +9,7 @@ import sys # Copied from pyproject.toml -PYTHON_REQUIRES = (3, 8) +PYTHON_REQUIRES = (3, 9) def version_str(version): # type: ignore diff --git a/src/pip/_internal/__init__.py b/src/pip/_internal/__init__.py index 1a5b7f87f97..720dc828718 100755 --- a/src/pip/_internal/__init__.py +++ b/src/pip/_internal/__init__.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from pip._internal.utils import _log @@ -7,7 +7,7 @@ _log.init_logging() -def main(args: Optional[List[str]] = None) -> int: +def main(args: Optional[list[str]] = None) -> int: """This is preserved for old console scripts that may still be referencing it. diff --git a/src/pip/_internal/build_env.py b/src/pip/_internal/build_env.py index e820dc3d5fb..9917c0d40f6 100644 --- a/src/pip/_internal/build_env.py +++ b/src/pip/_internal/build_env.py @@ -8,8 +8,9 @@ import sys import textwrap from collections import OrderedDict +from collections.abc import Iterable from types import TracebackType -from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type, Union +from typing import TYPE_CHECKING, Optional, Union from pip._vendor.certifi import where from pip._vendor.packaging.version import Version @@ -29,7 +30,7 @@ logger = logging.getLogger(__name__) -def _dedup(a: str, b: str) -> Union[Tuple[str], Tuple[str, str]]: +def _dedup(a: str, b: str) -> Union[tuple[str], tuple[str, str]]: return (a, b) if a != b else (a,) @@ -58,7 +59,7 @@ def get_runnable_pip() -> str: return os.fsdecode(source / "__pip-runner__.py") -def _get_system_sitepackages() -> Set[str]: +def _get_system_sitepackages() -> set[str]: """Get system site packages Usually from site.getsitepackages, @@ -89,8 +90,8 @@ def __init__(self) -> None: for name in ("normal", "overlay") ) - self._bin_dirs: List[str] = [] - self._lib_dirs: List[str] = [] + self._bin_dirs: list[str] = [] + self._lib_dirs: list[str] = [] for prefix in reversed(list(self._prefixes.values())): self._bin_dirs.append(prefix.bin_dir) self._lib_dirs.extend(prefix.lib_dirs) @@ -158,7 +159,7 @@ def __enter__(self) -> None: def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: @@ -170,7 +171,7 @@ def __exit__( def check_requirements( self, reqs: Iterable[str] - ) -> Tuple[Set[Tuple[str, str]], Set[str]]: + ) -> tuple[set[tuple[str, str]], set[str]]: """Return 2 sets: - conflicting requirements: set of (installed, wanted) reqs tuples - missing requirements: set of reqs @@ -232,7 +233,7 @@ def _install_requirements( *, kind: str, ) -> None: - args: List[str] = [ + args: list[str] = [ sys.executable, pip_runnable, "install", @@ -303,7 +304,7 @@ def __enter__(self) -> None: def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: diff --git a/src/pip/_internal/cache.py b/src/pip/_internal/cache.py index 6b4512672db..39c19c24801 100644 --- a/src/pip/_internal/cache.py +++ b/src/pip/_internal/cache.py @@ -6,7 +6,7 @@ import logging import os from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any, Optional from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version from pip._vendor.packaging.utils import canonicalize_name @@ -23,7 +23,7 @@ ORIGIN_JSON_NAME = "origin.json" -def _hash_dict(d: Dict[str, str]) -> str: +def _hash_dict(d: dict[str, str]) -> str: """Return a stable sha224 of a dictionary.""" s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True) return hashlib.sha224(s.encode("ascii")).hexdigest() @@ -40,7 +40,7 @@ def __init__(self, cache_dir: str) -> None: assert not cache_dir or os.path.isabs(cache_dir) self.cache_dir = cache_dir or None - def _get_cache_path_parts(self, link: Link) -> List[str]: + def _get_cache_path_parts(self, link: Link) -> list[str]: """Get parts of part that must be os.path.joined with cache_dir""" # We want to generate an url to use as our cache key, we don't want to @@ -73,7 +73,7 @@ def _get_cache_path_parts(self, link: Link) -> List[str]: return parts - def _get_candidates(self, link: Link, canonical_package_name: str) -> List[Any]: + def _get_candidates(self, link: Link, canonical_package_name: str) -> list[Any]: can_not_cache = not self.cache_dir or not canonical_package_name or not link if can_not_cache: return [] @@ -91,7 +91,7 @@ def get( self, link: Link, package_name: Optional[str], - supported_tags: List[Tag], + supported_tags: list[Tag], ) -> Link: """Returns a link to a cached item if it exists, otherwise returns the passed link. @@ -129,7 +129,7 @@ def get( self, link: Link, package_name: Optional[str], - supported_tags: List[Tag], + supported_tags: list[Tag], ) -> Link: candidates = [] @@ -227,7 +227,7 @@ def get( self, link: Link, package_name: Optional[str], - supported_tags: List[Tag], + supported_tags: list[Tag], ) -> Link: cache_entry = self.get_cache_entry(link, package_name, supported_tags) if cache_entry is None: @@ -238,7 +238,7 @@ def get_cache_entry( self, link: Link, package_name: Optional[str], - supported_tags: List[Tag], + supported_tags: list[Tag], ) -> Optional[CacheEntry]: """Returns a CacheEntry with a link to a cached item if it exists or None. The cache entry indicates if the item was found in the persistent diff --git a/src/pip/_internal/cli/autocompletion.py b/src/pip/_internal/cli/autocompletion.py index f3f70ac8553..20eb6a4ae10 100644 --- a/src/pip/_internal/cli/autocompletion.py +++ b/src/pip/_internal/cli/autocompletion.py @@ -4,8 +4,9 @@ import optparse import os import sys +from collections.abc import Iterable from itertools import chain -from typing import Any, Iterable, List, Optional +from typing import Any, Optional from pip._internal.cli.main_parser import create_main_parser from pip._internal.commands import commands_dict, create_command @@ -122,7 +123,7 @@ def autocomplete() -> None: def get_path_completion_type( - cwords: List[str], cword: int, opts: Iterable[Any] + cwords: list[str], cword: int, opts: Iterable[Any] ) -> Optional[str]: """Get the type of path completion (``file``, ``dir``, ``path`` or None) diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py index 362f84b6b0b..e492c4d4f3c 100644 --- a/src/pip/_internal/cli/base_command.py +++ b/src/pip/_internal/cli/base_command.py @@ -7,7 +7,7 @@ import sys import traceback from optparse import Values -from typing import List, Optional, Tuple +from typing import Optional from pip._vendor.rich import reconfigure from pip._vendor.rich import traceback as rich_traceback @@ -88,10 +88,10 @@ def handle_pip_version_check(self, options: Values) -> None: # are present. assert not hasattr(options, "no_index") - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: raise NotImplementedError - def _run_wrapper(self, level_number: int, options: Values, args: List[str]) -> int: + def _run_wrapper(self, level_number: int, options: Values, args: list[str]) -> int: def _inner_run() -> int: try: return self.run(options, args) @@ -148,18 +148,18 @@ def _inner_run() -> int: return UNKNOWN_ERROR - def parse_args(self, args: List[str]) -> Tuple[Values, List[str]]: + def parse_args(self, args: list[str]) -> tuple[Values, list[str]]: # factored out for testability return self.parser.parse_args(args) - def main(self, args: List[str]) -> int: + def main(self, args: list[str]) -> int: try: with self.main_context(): return self._main(args) finally: logging.shutdown() - def _main(self, args: List[str]) -> int: + def _main(self, args: list[str]) -> int: # We must initialize this before the tempdir manager, otherwise the # configuration would not be accessible by the time we clean up the # tempdir manager. diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index eeb7e651b79..7d86527d49b 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -17,7 +17,7 @@ from functools import partial from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values from textwrap import dedent -from typing import Any, Callable, Dict, Optional, Tuple +from typing import Any, Callable, Optional from pip._vendor.packaging.utils import canonicalize_name @@ -47,7 +47,7 @@ def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None: parser.error(msg) -def make_option_group(group: Dict[str, Any], parser: ConfigOptionParser) -> OptionGroup: +def make_option_group(group: dict[str, Any], parser: ConfigOptionParser) -> OptionGroup: """ Return an OptionGroup object group -- assumed to be dict with 'name' and 'options' keys @@ -545,7 +545,7 @@ def only_binary() -> Option: # This was made a separate function for unit-testing purposes. -def _convert_python_version(value: str) -> Tuple[Tuple[int, ...], Optional[str]]: +def _convert_python_version(value: str) -> tuple[tuple[int, ...], Optional[str]]: """ Convert a version string like "3", "37", or "3.7.3" into a tuple of ints. @@ -1033,7 +1033,7 @@ def check_list_path_option(options: Values) -> None: # groups # ########## -general_group: Dict[str, Any] = { +general_group: dict[str, Any] = { "name": "General Options", "options": [ help_, @@ -1064,7 +1064,7 @@ def check_list_path_option(options: Values) -> None: ], } -index_group: Dict[str, Any] = { +index_group: dict[str, Any] = { "name": "Package Index Options", "options": [ index_url, diff --git a/src/pip/_internal/cli/command_context.py b/src/pip/_internal/cli/command_context.py index 139995ac3f1..9c167bdc339 100644 --- a/src/pip/_internal/cli/command_context.py +++ b/src/pip/_internal/cli/command_context.py @@ -1,5 +1,6 @@ -from contextlib import ExitStack, contextmanager -from typing import ContextManager, Generator, TypeVar +from collections.abc import Generator +from contextlib import AbstractContextManager, ExitStack, contextmanager +from typing import TypeVar _T = TypeVar("_T", covariant=True) @@ -21,7 +22,7 @@ def main_context(self) -> Generator[None, None, None]: finally: self._in_main_context = False - def enter_context(self, context_provider: ContextManager[_T]) -> _T: + def enter_context(self, context_provider: AbstractContextManager[_T]) -> _T: assert self._in_main_context return self._main_context.enter_context(context_provider) diff --git a/src/pip/_internal/cli/index_command.py b/src/pip/_internal/cli/index_command.py index 295108ed605..a5d9683a7bb 100644 --- a/src/pip/_internal/cli/index_command.py +++ b/src/pip/_internal/cli/index_command.py @@ -10,7 +10,7 @@ import os import sys from optparse import Values -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, Optional from pip._vendor import certifi @@ -57,7 +57,7 @@ def __init__(self) -> None: self._session: Optional[PipSession] = None @classmethod - def _get_index_urls(cls, options: Values) -> Optional[List[str]]: + def _get_index_urls(cls, options: Values) -> Optional[list[str]]: """Return a list of index urls from user-provided options.""" index_urls = [] if not getattr(options, "no_index", False): diff --git a/src/pip/_internal/cli/main.py b/src/pip/_internal/cli/main.py index 563ac79c984..2951ab979ca 100644 --- a/src/pip/_internal/cli/main.py +++ b/src/pip/_internal/cli/main.py @@ -6,7 +6,7 @@ import os import sys import warnings -from typing import List, Optional +from typing import Optional from pip._internal.cli.autocompletion import autocomplete from pip._internal.cli.main_parser import parse_command @@ -44,7 +44,7 @@ # main, this should not be an issue in practice. -def main(args: Optional[List[str]] = None) -> int: +def main(args: Optional[list[str]] = None) -> int: if args is None: args = sys.argv[1:] diff --git a/src/pip/_internal/cli/main_parser.py b/src/pip/_internal/cli/main_parser.py index 5ade356b9c2..eb1f9f40372 100644 --- a/src/pip/_internal/cli/main_parser.py +++ b/src/pip/_internal/cli/main_parser.py @@ -4,7 +4,7 @@ import os import subprocess import sys -from typing import List, Optional, Tuple +from typing import Optional from pip._internal.build_env import get_runnable_pip from pip._internal.cli import cmdoptions @@ -66,7 +66,7 @@ def identify_python_interpreter(python: str) -> Optional[str]: return None -def parse_command(args: List[str]) -> Tuple[str, List[str]]: +def parse_command(args: list[str]) -> tuple[str, list[str]]: parser = create_main_parser() # Note: parser calls disable_interspersed_args(), so the result of this diff --git a/src/pip/_internal/cli/parser.py b/src/pip/_internal/cli/parser.py index bc4aca032d4..9dfa8a39f72 100644 --- a/src/pip/_internal/cli/parser.py +++ b/src/pip/_internal/cli/parser.py @@ -5,8 +5,9 @@ import shutil import sys import textwrap +from collections.abc import Generator from contextlib import suppress -from typing import Any, Dict, Generator, List, NoReturn, Optional, Tuple +from typing import Any, NoReturn, Optional from pip._internal.cli.status_codes import UNKNOWN_ERROR from pip._internal.configuration import Configuration, ConfigurationError @@ -142,7 +143,7 @@ def insert_option_group( return group @property - def option_list_all(self) -> List[optparse.Option]: + def option_list_all(self) -> list[optparse.Option]: """Get a list of all options, including those in option groups.""" res = self.option_list[:] for i in self.option_groups: @@ -177,12 +178,12 @@ def check_default(self, option: optparse.Option, key: str, val: Any) -> Any: def _get_ordered_configuration_items( self, - ) -> Generator[Tuple[str, Any], None, None]: + ) -> Generator[tuple[str, Any], None, None]: # Configuration gives keys in an unordered manner. Order them. override_order = ["global", self.name, ":env:"] # Pool the options into different groups - section_items: Dict[str, List[Tuple[str, Any]]] = { + section_items: dict[str, list[tuple[str, Any]]] = { name: [] for name in override_order } for section_key, val in self.config.items(): @@ -203,7 +204,7 @@ def _get_ordered_configuration_items( for key, val in section_items[section]: yield key, val - def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]: + def _update_defaults(self, defaults: dict[str, Any]) -> dict[str, Any]: """Updates the given defaults with values from the config files and the environ. Does a little special handling for certain types of options (lists).""" diff --git a/src/pip/_internal/cli/progress_bars.py b/src/pip/_internal/cli/progress_bars.py index 3d9dde8ed88..3e2e028c7e8 100644 --- a/src/pip/_internal/cli/progress_bars.py +++ b/src/pip/_internal/cli/progress_bars.py @@ -1,6 +1,7 @@ import functools import sys -from typing import Callable, Generator, Iterable, Iterator, Optional, Tuple +from collections.abc import Generator, Iterable, Iterator +from typing import Callable, Optional from pip._vendor.rich.progress import ( BarColumn, @@ -31,7 +32,7 @@ def _rich_progress_bar( if not size: total = float("inf") - columns: Tuple[ProgressColumn, ...] = ( + columns: tuple[ProgressColumn, ...] = ( TextColumn("[progress.description]{task.description}"), SpinnerColumn("line", speed=1.5), FileSizeColumn(), diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index 92900f94ff4..1e8c98754a6 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -8,7 +8,7 @@ import logging from functools import partial from optparse import Values -from typing import Any, List, Optional, Tuple +from typing import Any, Optional from pip._internal.cache import WheelCache from pip._internal.cli import cmdoptions @@ -57,7 +57,7 @@ def configure_tempdir_registry(registry: TempDirectoryTypeRegistry) -> None: registry.set_delete(t, False) def wrapper( - self: RequirementCommand, options: Values, args: List[Any] + self: RequirementCommand, options: Values, args: list[Any] ) -> Optional[int]: assert self.tempdir_registry is not None if options.no_clean: @@ -157,7 +157,7 @@ def make_resolver( force_reinstall: bool = False, upgrade_strategy: str = "to-satisfy-only", use_pep517: Optional[bool] = None, - py_version_info: Optional[Tuple[int, ...]] = None, + py_version_info: Optional[tuple[int, ...]] = None, ) -> BaseResolver: """ Create a Resolver instance for the given parameters. @@ -205,15 +205,15 @@ def make_resolver( def get_requirements( self, - args: List[str], + args: list[str], options: Values, finder: PackageFinder, session: PipSession, - ) -> List[InstallRequirement]: + ) -> list[InstallRequirement]: """ Parse command-line arguments into the corresponding requirements. """ - requirements: List[InstallRequirement] = [] + requirements: list[InstallRequirement] = [] for filename in options.constraints: for parsed_req in parse_requirements( filename, diff --git a/src/pip/_internal/cli/spinners.py b/src/pip/_internal/cli/spinners.py index cf2b976f377..73366b10ed5 100644 --- a/src/pip/_internal/cli/spinners.py +++ b/src/pip/_internal/cli/spinners.py @@ -3,7 +3,8 @@ import logging import sys import time -from typing import IO, Generator, Optional +from collections.abc import Generator +from typing import IO, Optional from pip._internal.utils.compat import WINDOWS from pip._internal.utils.logging import get_indentation diff --git a/src/pip/_internal/commands/__init__.py b/src/pip/_internal/commands/__init__.py index 858a4101416..8d74617c7f4 100644 --- a/src/pip/_internal/commands/__init__.py +++ b/src/pip/_internal/commands/__init__.py @@ -4,7 +4,7 @@ import importlib from collections import namedtuple -from typing import Any, Dict, Optional +from typing import Any, Optional from pip._internal.cli.base_command import Command @@ -17,7 +17,7 @@ # Even though the module path starts with the same "pip._internal.commands" # prefix, the full path makes testing easier (specifically when modifying # `commands_dict` in test setup / teardown). -commands_dict: Dict[str, CommandInfo] = { +commands_dict: dict[str, CommandInfo] = { "install": CommandInfo( "pip._internal.commands.install", "InstallCommand", diff --git a/src/pip/_internal/commands/cache.py b/src/pip/_internal/commands/cache.py index ad65641edb2..24f60416c80 100644 --- a/src/pip/_internal/commands/cache.py +++ b/src/pip/_internal/commands/cache.py @@ -1,7 +1,7 @@ import os import textwrap from optparse import Values -from typing import Any, List +from typing import Any from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import ERROR, SUCCESS @@ -49,7 +49,7 @@ def add_options(self) -> None: self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: handlers = { "dir": self.get_cache_dir, "info": self.get_cache_info, @@ -81,13 +81,13 @@ def run(self, options: Values, args: List[str]) -> int: return SUCCESS - def get_cache_dir(self, options: Values, args: List[Any]) -> None: + def get_cache_dir(self, options: Values, args: list[Any]) -> None: if args: raise CommandError("Too many arguments") logger.info(options.cache_dir) - def get_cache_info(self, options: Values, args: List[Any]) -> None: + def get_cache_info(self, options: Values, args: list[Any]) -> None: if args: raise CommandError("Too many arguments") @@ -129,7 +129,7 @@ def get_cache_info(self, options: Values, args: List[Any]) -> None: logger.info(message) - def list_cache_items(self, options: Values, args: List[Any]) -> None: + def list_cache_items(self, options: Values, args: list[Any]) -> None: if len(args) > 1: raise CommandError("Too many arguments") @@ -144,7 +144,7 @@ def list_cache_items(self, options: Values, args: List[Any]) -> None: else: self.format_for_abspath(files) - def format_for_human(self, files: List[str]) -> None: + def format_for_human(self, files: list[str]) -> None: if not files: logger.info("No locally built wheels cached.") return @@ -157,11 +157,11 @@ def format_for_human(self, files: List[str]) -> None: logger.info("Cache contents:\n") logger.info("\n".join(sorted(results))) - def format_for_abspath(self, files: List[str]) -> None: + def format_for_abspath(self, files: list[str]) -> None: if files: logger.info("\n".join(sorted(files))) - def remove_cache_items(self, options: Values, args: List[Any]) -> None: + def remove_cache_items(self, options: Values, args: list[Any]) -> None: if len(args) > 1: raise CommandError("Too many arguments") @@ -188,7 +188,7 @@ def remove_cache_items(self, options: Values, args: List[Any]) -> None: logger.verbose("Removed %s", filename) logger.info("Files removed: %s (%s)", len(files), format_size(bytes_removed)) - def purge_cache(self, options: Values, args: List[Any]) -> None: + def purge_cache(self, options: Values, args: list[Any]) -> None: if args: raise CommandError("Too many arguments") @@ -197,14 +197,14 @@ def purge_cache(self, options: Values, args: List[Any]) -> None: def _cache_dir(self, options: Values, subdir: str) -> str: return os.path.join(options.cache_dir, subdir) - def _find_http_files(self, options: Values) -> List[str]: + def _find_http_files(self, options: Values) -> list[str]: old_http_dir = self._cache_dir(options, "http") new_http_dir = self._cache_dir(options, "http-v2") return filesystem.find_files(old_http_dir, "*") + filesystem.find_files( new_http_dir, "*" ) - def _find_wheels(self, options: Values, pattern: str) -> List[str]: + def _find_wheels(self, options: Values, pattern: str) -> list[str]: wheel_dir = self._cache_dir(options, "wheels") # The wheel filename format, as specified in PEP 427, is: diff --git a/src/pip/_internal/commands/check.py b/src/pip/_internal/commands/check.py index f54a16dc0a1..516757eead7 100644 --- a/src/pip/_internal/commands/check.py +++ b/src/pip/_internal/commands/check.py @@ -1,6 +1,5 @@ import logging from optparse import Values -from typing import List from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import ERROR, SUCCESS @@ -23,7 +22,7 @@ class CheckCommand(Command): usage = """ %prog [options]""" - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: package_set, parsing_probs = create_package_set_from_installed() missing, conflicting = check_package_set(package_set) unsupported = list( diff --git a/src/pip/_internal/commands/completion.py b/src/pip/_internal/commands/completion.py index 9e89e279883..fd8b51b3df1 100644 --- a/src/pip/_internal/commands/completion.py +++ b/src/pip/_internal/commands/completion.py @@ -1,7 +1,6 @@ import sys import textwrap from optparse import Values -from typing import List from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import SUCCESS @@ -113,7 +112,7 @@ def add_options(self) -> None: self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: """Prints the completion code of the given shell""" shells = COMPLETION_SCRIPTS.keys() shell_options = ["--" + shell for shell in sorted(shells)] diff --git a/src/pip/_internal/commands/configuration.py b/src/pip/_internal/commands/configuration.py index 1a1dc6b6cd8..a02a9b1c4f9 100644 --- a/src/pip/_internal/commands/configuration.py +++ b/src/pip/_internal/commands/configuration.py @@ -2,7 +2,7 @@ import os import subprocess from optparse import Values -from typing import Any, List, Optional +from typing import Any, Optional from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import ERROR, SUCCESS @@ -93,7 +93,7 @@ def add_options(self) -> None: self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: handlers = { "list": self.list_values, "edit": self.open_in_editor, @@ -168,31 +168,31 @@ def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]: "(--user, --site, --global) to perform." ) - def list_values(self, options: Values, args: List[str]) -> None: + def list_values(self, options: Values, args: list[str]) -> None: self._get_n_args(args, "list", n=0) for key, value in sorted(self.configuration.items()): write_output("%s=%r", key, value) - def get_name(self, options: Values, args: List[str]) -> None: + def get_name(self, options: Values, args: list[str]) -> None: key = self._get_n_args(args, "get [name]", n=1) value = self.configuration.get_value(key) write_output("%s", value) - def set_name_value(self, options: Values, args: List[str]) -> None: + def set_name_value(self, options: Values, args: list[str]) -> None: key, value = self._get_n_args(args, "set [name] [value]", n=2) self.configuration.set_value(key, value) self._save_configuration() - def unset_name(self, options: Values, args: List[str]) -> None: + def unset_name(self, options: Values, args: list[str]) -> None: key = self._get_n_args(args, "unset [name]", n=1) self.configuration.unset_value(key) self._save_configuration() - def list_config_values(self, options: Values, args: List[str]) -> None: + def list_config_values(self, options: Values, args: list[str]) -> None: """List config key-value pairs across different config files""" self._get_n_args(args, "debug", n=0) @@ -222,7 +222,7 @@ def print_env_var_values(self) -> None: env_var = f"PIP_{key.upper()}" write_output("%s=%r", env_var, value) - def open_in_editor(self, options: Values, args: List[str]) -> None: + def open_in_editor(self, options: Values, args: list[str]) -> None: editor = self._determine_editor(options) fname = self.configuration.get_file_to_edit() @@ -244,7 +244,7 @@ def open_in_editor(self, options: Values, args: List[str]) -> None: except subprocess.CalledProcessError as e: raise PipError(f"Editor Subprocess exited with exit code {e.returncode}") - def _get_n_args(self, args: List[str], example: str, n: int) -> Any: + def _get_n_args(self, args: list[str], example: str, n: int) -> Any: """Helper to make sure the command got the right number of arguments""" if len(args) != n: msg = ( diff --git a/src/pip/_internal/commands/debug.py b/src/pip/_internal/commands/debug.py index 567ca967e5b..b8e8ebd68e0 100644 --- a/src/pip/_internal/commands/debug.py +++ b/src/pip/_internal/commands/debug.py @@ -4,7 +4,7 @@ import sys from optparse import Values from types import ModuleType -from typing import Any, Dict, List, Optional +from typing import Any, Optional import pip._vendor from pip._vendor.certifi import where @@ -34,7 +34,7 @@ def show_sys_implementation() -> None: show_value("name", implementation_name) -def create_vendor_txt_map() -> Dict[str, str]: +def create_vendor_txt_map() -> dict[str, str]: with open_text_resource("pip._vendor", "vendor.txt") as f: # Purge non version specifying lines. # Also, remove any space prefix or suffixes (including comments). @@ -79,7 +79,7 @@ def get_vendor_version_from_module(module_name: str) -> Optional[str]: return version -def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None: +def show_actual_vendor_versions(vendor_txt_versions: dict[str, str]) -> None: """Log the actual version and print extra info if there is a conflict or if the actual version could not be imported. """ @@ -169,7 +169,7 @@ def add_options(self) -> None: self.parser.insert_option_group(0, self.cmd_opts) self.parser.config.load() - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: logger.warning( "This command is only meant for debugging. " "Do not use this with automation for parsing and getting these " diff --git a/src/pip/_internal/commands/download.py b/src/pip/_internal/commands/download.py index 917bbb91d83..900fb403d6f 100644 --- a/src/pip/_internal/commands/download.py +++ b/src/pip/_internal/commands/download.py @@ -1,7 +1,6 @@ import logging import os from optparse import Values -from typing import List from pip._internal.cli import cmdoptions from pip._internal.cli.cmdoptions import make_target_python @@ -75,7 +74,7 @@ def add_options(self) -> None: self.parser.insert_option_group(0, self.cmd_opts) @with_cleanup - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: options.ignore_installed = True # editable doesn't really make sense for `pip download`, but the bowels # of the RequirementSet code require that property. @@ -131,7 +130,7 @@ def run(self, options: Values, args: List[str]) -> int: requirement_set = resolver.resolve(reqs, check_supported_wheels=True) - downloaded: List[str] = [] + downloaded: list[str] = [] for req in requirement_set.requirements.values(): if req.satisfied_by is None: assert req.name is not None diff --git a/src/pip/_internal/commands/freeze.py b/src/pip/_internal/commands/freeze.py index 885fdfeb83b..752e4357019 100644 --- a/src/pip/_internal/commands/freeze.py +++ b/src/pip/_internal/commands/freeze.py @@ -1,6 +1,6 @@ +import collections.abc import sys from optparse import Values -from typing import AbstractSet, List from pip._internal.cli import cmdoptions from pip._internal.cli.base_command import Command @@ -13,7 +13,7 @@ def _should_suppress_build_backends() -> bool: return sys.version_info < (3, 12) -def _dev_pkgs() -> AbstractSet[str]: +def _dev_pkgs() -> collections.abc.Set[str]: pkgs = {"pip"} if _should_suppress_build_backends(): @@ -86,7 +86,7 @@ def add_options(self) -> None: self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: skip = set(stdlib_pkgs) if not options.freeze_all: skip.update(_dev_pkgs()) diff --git a/src/pip/_internal/commands/hash.py b/src/pip/_internal/commands/hash.py index 042dac813e7..271a4c91a7f 100644 --- a/src/pip/_internal/commands/hash.py +++ b/src/pip/_internal/commands/hash.py @@ -2,7 +2,6 @@ import logging import sys from optparse import Values -from typing import List from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import ERROR, SUCCESS @@ -37,7 +36,7 @@ def add_options(self) -> None: ) self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: if not args: self.parser.print_usage(sys.stderr) return ERROR diff --git a/src/pip/_internal/commands/help.py b/src/pip/_internal/commands/help.py index 62066318b74..2ae658ff5eb 100644 --- a/src/pip/_internal/commands/help.py +++ b/src/pip/_internal/commands/help.py @@ -1,5 +1,4 @@ from optparse import Values -from typing import List from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import SUCCESS @@ -13,7 +12,7 @@ class HelpCommand(Command): %prog """ ignore_require_venv = True - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: from pip._internal.commands import ( commands_dict, create_command, diff --git a/src/pip/_internal/commands/index.py b/src/pip/_internal/commands/index.py index 2e2661bba71..1685faa3d08 100644 --- a/src/pip/_internal/commands/index.py +++ b/src/pip/_internal/commands/index.py @@ -1,6 +1,7 @@ import logging +from collections.abc import Iterable from optparse import Values -from typing import Any, Iterable, List, Optional +from typing import Any, Optional from pip._vendor.packaging.version import Version @@ -45,7 +46,7 @@ def add_options(self) -> None: self.parser.insert_option_group(0, index_opts) self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: handlers = { "versions": self.get_available_package_versions, } @@ -100,7 +101,7 @@ def _build_package_finder( target_python=target_python, ) - def get_available_package_versions(self, options: Values, args: List[Any]) -> None: + def get_available_package_versions(self, options: Values, args: list[Any]) -> None: if len(args) != 1: raise CommandError("You need to specify exactly one argument") diff --git a/src/pip/_internal/commands/inspect.py b/src/pip/_internal/commands/inspect.py index e810c13166b..e262012ee4d 100644 --- a/src/pip/_internal/commands/inspect.py +++ b/src/pip/_internal/commands/inspect.py @@ -1,6 +1,6 @@ import logging from optparse import Values -from typing import Any, Dict, List +from typing import Any from pip._vendor.packaging.markers import default_environment from pip._vendor.rich import print_json @@ -45,7 +45,7 @@ def add_options(self) -> None: self.cmd_opts.add_option(cmdoptions.list_path()) self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: cmdoptions.check_list_path_option(options) dists = get_environment(options.path).iter_installed_distributions( local_only=options.local, @@ -62,8 +62,8 @@ def run(self, options: Values, args: List[str]) -> int: print_json(data=output) return SUCCESS - def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]: - res: Dict[str, Any] = { + def _dist_to_dict(self, dist: BaseDistribution) -> dict[str, Any]: + res: dict[str, Any] = { "metadata": dist.metadata_dict, "metadata_location": dist.info_location, } diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 232a34a6d3e..5f099bfedb6 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -5,7 +5,7 @@ import shutil import site from optparse import SUPPRESS_HELP, Values -from typing import List, Optional +from typing import Optional from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.rich import print_json @@ -271,7 +271,7 @@ def add_options(self) -> None: ) @with_cleanup - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: if options.use_user_site and options.target_dir is not None: raise CommandError("Can not combine '--user' and '--target'") @@ -579,7 +579,7 @@ def _handle_target_dir( shutil.move(os.path.join(lib_dir, item), target_item_dir) def _determine_conflicts( - self, to_install: List[InstallRequirement] + self, to_install: list[InstallRequirement] ) -> Optional[ConflictDetails]: try: return check_install_conflicts(to_install) @@ -597,7 +597,7 @@ def _warn_about_conflicts( if not missing and not conflicting: return - parts: List[str] = [] + parts: list[str] = [] if resolver_variant == "legacy": parts.append( "pip's legacy dependency resolver does not consider dependency " @@ -647,7 +647,7 @@ def get_lib_location_guesses( root: Optional[str] = None, isolated: bool = False, prefix: Optional[str] = None, -) -> List[str]: +) -> list[str]: scheme = get_scheme( "", user=user, diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py index 84943702410..eed8385dc6c 100644 --- a/src/pip/_internal/commands/list.py +++ b/src/pip/_internal/commands/list.py @@ -1,7 +1,8 @@ import json import logging +from collections.abc import Generator, Sequence from optparse import Values -from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast +from typing import TYPE_CHECKING, Optional, cast from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.packaging.version import Version @@ -161,7 +162,7 @@ def _build_package_finder( selection_prefs=selection_prefs, ) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: if options.outdated and options.uptodate: raise CommandError("Options --outdated and --uptodate cannot be combined.") @@ -294,7 +295,7 @@ def output_package_listing( write_output(format_for_json(packages, options)) def output_package_listing_columns( - self, data: List[List[str]], header: List[str] + self, data: list[list[str]], header: list[str] ) -> None: # insert the header first: we need to know the size of column names if len(data) > 0: @@ -312,7 +313,7 @@ def output_package_listing_columns( def format_for_columns( pkgs: "_ProcessedDists", options: Values -) -> Tuple[List[List[str]], List[str]]: +) -> tuple[list[list[str]], list[str]]: """ Convert the package data into something usable by output_package_listing_columns. diff --git a/src/pip/_internal/commands/search.py b/src/pip/_internal/commands/search.py index 74b8d656b47..0444460fdf4 100644 --- a/src/pip/_internal/commands/search.py +++ b/src/pip/_internal/commands/search.py @@ -5,7 +5,7 @@ import xmlrpc.client from collections import OrderedDict from optparse import Values -from typing import TYPE_CHECKING, Dict, List, Optional, TypedDict +from typing import Optional, TypedDict from pip._vendor.packaging.version import parse as parse_version @@ -19,12 +19,11 @@ from pip._internal.utils.logging import indent_log from pip._internal.utils.misc import write_output -if TYPE_CHECKING: - class TransformedHit(TypedDict): - name: str - summary: str - versions: List[str] +class TransformedHit(TypedDict): + name: str + summary: str + versions: list[str] logger = logging.getLogger(__name__) @@ -49,7 +48,7 @@ def add_options(self) -> None: self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: if not args: raise CommandError("Missing required argument (search query).") query = args @@ -65,7 +64,7 @@ def run(self, options: Values, args: List[str]) -> int: return SUCCESS return NO_MATCHES_FOUND - def search(self, query: List[str], options: Values) -> List[Dict[str, str]]: + def search(self, query: list[str], options: Values) -> list[dict[str, str]]: index_url = options.index session = self.get_default_session(options) @@ -83,13 +82,13 @@ def search(self, query: List[str], options: Values) -> List[Dict[str, str]]: return hits -def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]: +def transform_hits(hits: list[dict[str, str]]) -> list["TransformedHit"]: """ The list from pypi is really a list of versions. We want a list of packages with the list of versions stored inline. This converts the list from pypi into one we can use. """ - packages: Dict[str, TransformedHit] = OrderedDict() + packages: dict[str, TransformedHit] = OrderedDict() for hit in hits: name = hit["name"] summary = hit["summary"] @@ -131,7 +130,7 @@ def print_dist_installation_info(name: str, latest: str) -> None: def print_results( - hits: List["TransformedHit"], + hits: list["TransformedHit"], name_column_width: Optional[int] = None, terminal_width: Optional[int] = None, ) -> None: @@ -168,5 +167,5 @@ def print_results( pass -def highest_version(versions: List[str]) -> str: +def highest_version(versions: list[str]) -> str: return max(versions, key=parse_version) diff --git a/src/pip/_internal/commands/show.py b/src/pip/_internal/commands/show.py index b47500cf8b4..51bd0bb6e08 100644 --- a/src/pip/_internal/commands/show.py +++ b/src/pip/_internal/commands/show.py @@ -1,6 +1,7 @@ import logging +from collections.abc import Generator, Iterable, Iterator from optparse import Values -from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional +from typing import NamedTuple, Optional from pip._vendor.packaging.requirements import InvalidRequirement from pip._vendor.packaging.utils import canonicalize_name @@ -36,7 +37,7 @@ def add_options(self) -> None: self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: if not args: logger.warning("ERROR: Please provide a package name or names.") return ERROR @@ -55,23 +56,23 @@ class _PackageInfo(NamedTuple): version: str location: str editable_project_location: Optional[str] - requires: List[str] - required_by: List[str] + requires: list[str] + required_by: list[str] installer: str metadata_version: str - classifiers: List[str] + classifiers: list[str] summary: str homepage: str - project_urls: List[str] + project_urls: list[str] author: str author_email: str license: str license_expression: str - entry_points: List[str] - files: Optional[List[str]] + entry_points: list[str] + files: Optional[list[str]] -def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None]: +def search_packages_info(query: list[str]) -> Generator[_PackageInfo, None, None]: """ Gather details from installed distributions. Print distribution name, version, location, and installed files. Installed files requires a @@ -124,7 +125,7 @@ def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[str]: files_iter = dist.iter_declared_entries() if files_iter is None: - files: Optional[List[str]] = None + files: Optional[list[str]] = None else: files = sorted(files_iter) diff --git a/src/pip/_internal/commands/uninstall.py b/src/pip/_internal/commands/uninstall.py index bc0edeac9fb..9c4f031f934 100644 --- a/src/pip/_internal/commands/uninstall.py +++ b/src/pip/_internal/commands/uninstall.py @@ -1,6 +1,5 @@ import logging from optparse import Values -from typing import List from pip._vendor.packaging.utils import canonicalize_name @@ -62,7 +61,7 @@ def add_options(self) -> None: self.cmd_opts.add_option(cmdoptions.override_externally_managed()) self.parser.insert_option_group(0, self.cmd_opts) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: session = self.get_default_session(options) reqs_to_uninstall = {} diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py index 278719f4e0c..d95683d7802 100644 --- a/src/pip/_internal/commands/wheel.py +++ b/src/pip/_internal/commands/wheel.py @@ -2,7 +2,6 @@ import os import shutil from optparse import Values -from typing import List from pip._internal.cache import WheelCache from pip._internal.cli import cmdoptions @@ -101,7 +100,7 @@ def add_options(self) -> None: self.parser.insert_option_group(0, self.cmd_opts) @with_cleanup - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: session = self.get_default_session(options) finder = self._build_package_finder(options, session) @@ -146,7 +145,7 @@ def run(self, options: Values, args: List[str]) -> int: requirement_set = resolver.resolve(reqs, check_supported_wheels=True) - reqs_to_build: List[InstallRequirement] = [] + reqs_to_build: list[InstallRequirement] = [] for req in requirement_set.requirements.values(): if req.is_wheel: preparer.save_linked_requirement(req) diff --git a/src/pip/_internal/configuration.py b/src/pip/_internal/configuration.py index ffeda1d47a1..359956b167e 100644 --- a/src/pip/_internal/configuration.py +++ b/src/pip/_internal/configuration.py @@ -15,7 +15,8 @@ import locale import os import sys -from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple +from collections.abc import Iterable +from typing import Any, NewType, Optional from pip._internal.exceptions import ( ConfigurationError, @@ -55,7 +56,7 @@ def _normalize_name(name: str) -> str: return name -def _disassemble_key(name: str) -> List[str]: +def _disassemble_key(name: str) -> list[str]: if "." not in name: error_message = ( "Key does not contain dot separated section and key. " @@ -65,7 +66,7 @@ def _disassemble_key(name: str) -> List[str]: return name.split(".", 1) -def get_configuration_files() -> Dict[Kind, List[str]]: +def get_configuration_files() -> dict[Kind, list[str]]: global_config_files = [ os.path.join(path, CONFIG_BASENAME) for path in appdirs.site_config_dirs("pip") ] @@ -111,13 +112,13 @@ def __init__(self, isolated: bool, load_only: Optional[Kind] = None) -> None: self.load_only = load_only # Because we keep track of where we got the data from - self._parsers: Dict[Kind, List[Tuple[str, RawConfigParser]]] = { + self._parsers: dict[Kind, list[tuple[str, RawConfigParser]]] = { variant: [] for variant in OVERRIDE_ORDER } - self._config: Dict[Kind, Dict[str, Any]] = { + self._config: dict[Kind, dict[str, Any]] = { variant: {} for variant in OVERRIDE_ORDER } - self._modified_parsers: List[Tuple[str, RawConfigParser]] = [] + self._modified_parsers: list[tuple[str, RawConfigParser]] = [] def load(self) -> None: """Loads configuration from configuration files and environment""" @@ -134,7 +135,7 @@ def get_file_to_edit(self) -> Optional[str]: except IndexError: return None - def items(self) -> Iterable[Tuple[str, Any]]: + def items(self) -> Iterable[tuple[str, Any]]: """Returns key-value pairs like dict.items() representing the loaded configuration """ @@ -230,7 +231,7 @@ def _ensure_have_load_only(self) -> None: logger.debug("Will be working with %s variant only", self.load_only) @property - def _dictionary(self) -> Dict[str, Any]: + def _dictionary(self) -> dict[str, Any]: """A dictionary representing the loaded configuration.""" # NOTE: Dictionaries are not populated if not loaded. So, conditionals # are not needed here. @@ -302,8 +303,8 @@ def _load_environment_vars(self) -> None: ) def _normalized_keys( - self, section: str, items: Iterable[Tuple[str, Any]] - ) -> Dict[str, Any]: + self, section: str, items: Iterable[tuple[str, Any]] + ) -> dict[str, Any]: """Normalizes items to construct a dictionary with normalized keys. This routine is where the names become keys and are made the same @@ -315,7 +316,7 @@ def _normalized_keys( normalized[key] = val return normalized - def get_environ_vars(self) -> Iterable[Tuple[str, str]]: + def get_environ_vars(self) -> Iterable[tuple[str, str]]: """Returns a generator with all environmental vars with prefix PIP_""" for key, val in os.environ.items(): if key.startswith("PIP_"): @@ -324,7 +325,7 @@ def get_environ_vars(self) -> Iterable[Tuple[str, str]]: yield name, val # XXX: This is patched in the tests. - def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]: + def iter_config_files(self) -> Iterable[tuple[Kind, list[str]]]: """Yields variant and configuration files associated with it. This should be treated like items of a dictionary. The order @@ -356,11 +357,11 @@ def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]: else: yield kinds.ENV, [] - def get_values_in_config(self, variant: Kind) -> Dict[str, Any]: + def get_values_in_config(self, variant: Kind) -> dict[str, Any]: """Get values present in a config file""" return self._config[variant] - def _get_parser_to_modify(self) -> Tuple[str, RawConfigParser]: + def _get_parser_to_modify(self) -> tuple[str, RawConfigParser]: # Determine which parser to modify assert self.load_only parsers = self._parsers[self.load_only] diff --git a/src/pip/_internal/distributions/sdist.py b/src/pip/_internal/distributions/sdist.py index 28ea5cea16c..d178da69d9a 100644 --- a/src/pip/_internal/distributions/sdist.py +++ b/src/pip/_internal/distributions/sdist.py @@ -1,5 +1,6 @@ import logging -from typing import TYPE_CHECKING, Iterable, Optional, Set, Tuple +from collections.abc import Iterable +from typing import TYPE_CHECKING, Optional from pip._internal.build_env import BuildEnvironment from pip._internal.distributions.base import AbstractDistribution @@ -132,7 +133,7 @@ def _install_build_reqs(self, finder: "PackageFinder") -> None: ) def _raise_conflicts( - self, conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]] + self, conflicting_with: str, conflicting_reqs: set[tuple[str, str]] ) -> None: format_string = ( "Some build dependencies for {requirement} " @@ -148,7 +149,7 @@ def _raise_conflicts( ) raise InstallationError(error_message) - def _raise_missing_reqs(self, missing: Set[str]) -> None: + def _raise_missing_reqs(self, missing: set[str]) -> None: format_string = ( "Some build dependencies for {requirement} are missing: {missing}." ) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index 45a876a850d..6acc74313c9 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -12,8 +12,9 @@ import pathlib import re import sys +from collections.abc import Iterator from itertools import chain, groupby, repeat -from typing import TYPE_CHECKING, Dict, Iterator, List, Literal, Optional, Union +from typing import TYPE_CHECKING, Literal, Optional, Union from pip._vendor.packaging.requirements import InvalidRequirement from pip._vendor.packaging.version import InvalidVersion @@ -377,7 +378,7 @@ def __init__( *, command_description: str, exit_code: int, - output_lines: Optional[List[str]], + output_lines: Optional[list[str]], ) -> None: if output_lines is None: output_prompt = Text("See above for output.") @@ -431,7 +432,7 @@ class HashErrors(InstallationError): """Multiple HashError instances rolled into one for reporting""" def __init__(self) -> None: - self.errors: List[HashError] = [] + self.errors: list[HashError] = [] def append(self, error: "HashError") -> None: self.errors.append(error) @@ -589,7 +590,7 @@ class HashMismatch(HashError): "someone may have tampered with them." ) - def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None: + def __init__(self, allowed: dict[str, list[str]], gots: dict[str, "_Hash"]) -> None: """ :param allowed: A dict of algorithm names pointing to lists of allowed hex digests @@ -619,7 +620,7 @@ def hash_then_or(hash_name: str) -> "chain[str]": # away with hard-coding space literals. return chain([hash_name], repeat(" or")) - lines: List[str] = [] + lines: list[str] = [] for hash_name, expecteds in self.allowed.items(): prefix = hash_then_or(hash_name) lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds) diff --git a/src/pip/_internal/index/collector.py b/src/pip/_internal/index/collector.py index 5f8fdee3d46..828af9cb5b4 100644 --- a/src/pip/_internal/index/collector.py +++ b/src/pip/_internal/index/collector.py @@ -11,20 +11,15 @@ import os import urllib.parse import urllib.request +from collections.abc import Iterable, MutableMapping, Sequence from dataclasses import dataclass from html.parser import HTMLParser from optparse import Values from typing import ( Callable, - Dict, - Iterable, - List, - MutableMapping, NamedTuple, Optional, Protocol, - Sequence, - Tuple, Union, ) @@ -207,12 +202,12 @@ def with_cached_index_content(fn: ParseLinks) -> ParseLinks: `page` has `page.cache_link_parsing == False`. """ - @functools.lru_cache(maxsize=None) - def wrapper(cacheable_page: CacheablePageContent) -> List[Link]: + @functools.cache + def wrapper(cacheable_page: CacheablePageContent) -> list[Link]: return list(fn(cacheable_page.page)) @functools.wraps(fn) - def wrapper_wrapper(page: "IndexContent") -> List[Link]: + def wrapper_wrapper(page: "IndexContent") -> list[Link]: if page.cache_link_parsing: return wrapper(CacheablePageContent(page)) return list(fn(page)) @@ -281,9 +276,9 @@ def __init__(self, url: str) -> None: self.url: str = url self.base_url: Optional[str] = None - self.anchors: List[Dict[str, Optional[str]]] = [] + self.anchors: list[dict[str, Optional[str]]] = [] - def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None: + def handle_starttag(self, tag: str, attrs: list[tuple[str, Optional[str]]]) -> None: if tag == "base" and self.base_url is None: href = self.get_href(attrs) if href is not None: @@ -291,7 +286,7 @@ def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> N elif tag == "a": self.anchors.append(dict(attrs)) - def get_href(self, attrs: List[Tuple[str, Optional[str]]]) -> Optional[str]: + def get_href(self, attrs: list[tuple[str, Optional[str]]]) -> Optional[str]: for name, value in attrs: if name == "href": return value @@ -438,7 +433,7 @@ def create( return link_collector @property - def find_links(self) -> List[str]: + def find_links(self) -> list[str]: return self.search_scope.find_links def fetch_response(self, location: Link) -> Optional[IndexContent]: diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index 85628ee5d7a..d14dad040d8 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -5,8 +5,9 @@ import itertools import logging import re +from collections.abc import Iterable from dataclasses import dataclass -from typing import TYPE_CHECKING, FrozenSet, Iterable, List, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING, Optional, Union from pip._vendor.packaging import specifiers from pip._vendor.packaging.tags import Tag @@ -45,13 +46,13 @@ logger = getLogger(__name__) -BuildTag = Union[Tuple[()], Tuple[int, str]] -CandidateSortingKey = Tuple[int, int, int, _BaseVersion, Optional[int], BuildTag] +BuildTag = Union[tuple[()], tuple[int, str]] +CandidateSortingKey = tuple[int, int, int, _BaseVersion, Optional[int], BuildTag] def _check_link_requires_python( link: Link, - version_info: Tuple[int, int, int], + version_info: tuple[int, int, int], ignore_requires_python: bool = False, ) -> bool: """ @@ -121,7 +122,7 @@ def __init__( self, project_name: str, canonical_name: str, - formats: FrozenSet[str], + formats: frozenset[str], target_python: TargetPython, allow_yanked: bool, ignore_requires_python: Optional[bool] = None, @@ -154,7 +155,7 @@ def __init__( self.project_name = project_name - def evaluate_link(self, link: Link) -> Tuple[LinkType, str]: + def evaluate_link(self, link: Link) -> tuple[LinkType, str]: """ Determine whether a link is a candidate for installation. @@ -250,10 +251,10 @@ def evaluate_link(self, link: Link) -> Tuple[LinkType, str]: def filter_unallowed_hashes( - candidates: List[InstallationCandidate], + candidates: list[InstallationCandidate], hashes: Optional[Hashes], project_name: str, -) -> List[InstallationCandidate]: +) -> list[InstallationCandidate]: """ Filter out candidates whose hashes aren't allowed, and return a new list of candidates. @@ -347,8 +348,8 @@ class BestCandidateResult: if no applicable candidates were found. """ - all_candidates: List[InstallationCandidate] - applicable_candidates: List[InstallationCandidate] + all_candidates: list[InstallationCandidate] + applicable_candidates: list[InstallationCandidate] best_candidate: Optional[InstallationCandidate] def __post_init__(self) -> None: @@ -405,7 +406,7 @@ def create( def __init__( self, project_name: str, - supported_tags: List[Tag], + supported_tags: list[Tag], specifier: specifiers.BaseSpecifier, prefer_binary: bool = False, allow_all_prereleases: bool = False, @@ -430,8 +431,8 @@ def __init__( def get_applicable_candidates( self, - candidates: List[InstallationCandidate], - ) -> List[InstallationCandidate]: + candidates: list[InstallationCandidate], + ) -> list[InstallationCandidate]: """ Return the applicable candidates from a list of candidates. """ @@ -534,7 +535,7 @@ def _sort_key(self, candidate: InstallationCandidate) -> CandidateSortingKey: def sort_best_candidate( self, - candidates: List[InstallationCandidate], + candidates: list[InstallationCandidate], ) -> Optional[InstallationCandidate]: """ Return the best candidate per the instance's sort order, or None if @@ -547,7 +548,7 @@ def sort_best_candidate( def compute_best_candidate( self, - candidates: List[InstallationCandidate], + candidates: list[InstallationCandidate], ) -> BestCandidateResult: """ Compute and return a `BestCandidateResult` instance. @@ -603,7 +604,7 @@ def __init__( self.format_control = format_control # These are boring links that have already been logged somehow. - self._logged_links: Set[Tuple[Link, LinkType, str]] = set() + self._logged_links: set[tuple[Link, LinkType, str]] = set() # Don't include an allow_yanked default value to make sure each call # site considers whether yanked releases are allowed. This also causes @@ -654,11 +655,11 @@ def search_scope(self, search_scope: SearchScope) -> None: self._link_collector.search_scope = search_scope @property - def find_links(self) -> List[str]: + def find_links(self) -> list[str]: return self._link_collector.find_links @property - def index_urls(self) -> List[str]: + def index_urls(self) -> list[str]: return self.search_scope.index_urls @property @@ -698,7 +699,7 @@ def prefer_binary(self) -> bool: def set_prefer_binary(self) -> None: self._candidate_prefs.prefer_binary = True - def requires_python_skipped_reasons(self) -> List[str]: + def requires_python_skipped_reasons(self) -> list[str]: reasons = { detail for _, result, detail in self._logged_links @@ -719,13 +720,13 @@ def make_link_evaluator(self, project_name: str) -> LinkEvaluator: ignore_requires_python=self._ignore_requires_python, ) - def _sort_links(self, links: Iterable[Link]) -> List[Link]: + def _sort_links(self, links: Iterable[Link]) -> list[Link]: """ Returns elements of links in order, non-egg links first, egg links second, while eliminating duplicates """ eggs, no_eggs = [], [] - seen: Set[Link] = set() + seen: set[Link] = set() for link in links: if link not in seen: seen.add(link) @@ -771,7 +772,7 @@ def get_install_candidate( def evaluate_links( self, link_evaluator: LinkEvaluator, links: Iterable[Link] - ) -> List[InstallationCandidate]: + ) -> list[InstallationCandidate]: """ Convert links that are candidates to InstallationCandidate objects. """ @@ -785,7 +786,7 @@ def evaluate_links( def process_project_url( self, project_url: Link, link_evaluator: LinkEvaluator - ) -> List[InstallationCandidate]: + ) -> list[InstallationCandidate]: logger.debug( "Fetching project page and analyzing links: %s", project_url, @@ -804,8 +805,8 @@ def process_project_url( return package_links - @functools.lru_cache(maxsize=None) - def find_all_candidates(self, project_name: str) -> List[InstallationCandidate]: + @functools.cache + def find_all_candidates(self, project_name: str) -> list[InstallationCandidate]: """Find all available InstallationCandidate for project_name This checks index_urls and find_links. @@ -874,7 +875,7 @@ def make_candidate_evaluator( hashes=hashes, ) - @functools.lru_cache(maxsize=None) + @functools.cache def find_best_candidate( self, project_name: str, diff --git a/src/pip/_internal/index/sources.py b/src/pip/_internal/index/sources.py index 3dafb30e6eb..3cc461f78d6 100644 --- a/src/pip/_internal/index/sources.py +++ b/src/pip/_internal/index/sources.py @@ -2,7 +2,8 @@ import mimetypes import os from collections import defaultdict -from typing import Callable, Dict, Iterable, List, Optional, Tuple +from collections.abc import Iterable +from typing import Callable, Optional from pip._vendor.packaging.utils import ( InvalidSdistFilename, @@ -49,8 +50,8 @@ class _FlatDirectoryToUrls: def __init__(self, path: str) -> None: self._path = path - self._page_candidates: List[str] = [] - self._project_name_to_urls: Dict[str, List[str]] = defaultdict(list) + self._page_candidates: list[str] = [] + self._project_name_to_urls: dict[str, list[str]] = defaultdict(list) self._scanned_directory = False def _scan_directory(self) -> None: @@ -77,14 +78,14 @@ def _scan_directory(self) -> None: self._scanned_directory = True @property - def page_candidates(self) -> List[str]: + def page_candidates(self) -> list[str]: if not self._scanned_directory: self._scan_directory() return self._page_candidates @property - def project_name_to_urls(self) -> Dict[str, List[str]]: + def project_name_to_urls(self) -> dict[str, list[str]]: if not self._scanned_directory: self._scan_directory() @@ -100,7 +101,7 @@ class _FlatDirectorySource(LinkSource): * ``file_candidates``: Archives in the directory. """ - _paths_to_urls: Dict[str, _FlatDirectoryToUrls] = {} + _paths_to_urls: dict[str, _FlatDirectoryToUrls] = {} def __init__( self, @@ -230,7 +231,7 @@ def build_source( expand_dir: bool, cache_link_parsing: bool, project_name: str, -) -> Tuple[Optional[str], Optional[LinkSource]]: +) -> tuple[Optional[str], Optional[LinkSource]]: path: Optional[str] = None url: Optional[str] = None if os.path.exists(location): # Is a local path. diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index 32382be7fe5..0969502d604 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -4,7 +4,8 @@ import pathlib import sys import sysconfig -from typing import Any, Dict, Generator, Optional, Tuple +from collections.abc import Generator +from typing import Any, Optional from pip._internal.models.scheme import SCHEME_KEYS, Scheme from pip._internal.utils.compat import WINDOWS @@ -87,7 +88,7 @@ def _looks_like_bpo_44860() -> bool: return unix_user_platlib == "$usersite" -def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool: +def _looks_like_red_hat_patched_platlib_purelib(scheme: dict[str, str]) -> bool: platlib = scheme["platlib"] if "/$platlibdir/" in platlib: platlib = platlib.replace("/$platlibdir/", f"/{_PLATLIBDIR}/") @@ -97,7 +98,7 @@ def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool: return unpatched.replace("$platbase/", "$base/") == scheme["purelib"] -@functools.lru_cache(maxsize=None) +@functools.cache def _looks_like_red_hat_lib() -> bool: """Red Hat patches platlib in unix_prefix and unix_home, but not purelib. @@ -112,7 +113,7 @@ def _looks_like_red_hat_lib() -> bool: ) -@functools.lru_cache(maxsize=None) +@functools.cache def _looks_like_debian_scheme() -> bool: """Debian adds two additional schemes.""" from distutils.command.install import INSTALL_SCHEMES @@ -120,7 +121,7 @@ def _looks_like_debian_scheme() -> bool: return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES -@functools.lru_cache(maxsize=None) +@functools.cache def _looks_like_red_hat_scheme() -> bool: """Red Hat patches ``sys.prefix`` and ``sys.exec_prefix``. @@ -140,7 +141,7 @@ def _looks_like_red_hat_scheme() -> bool: ) -@functools.lru_cache(maxsize=None) +@functools.cache def _looks_like_slackware_scheme() -> bool: """Slackware patches sysconfig but fails to patch distutils and site. @@ -156,7 +157,7 @@ def _looks_like_slackware_scheme() -> bool: return "/lib64/" in paths["purelib"] and "/lib64/" not in user_site -@functools.lru_cache(maxsize=None) +@functools.cache def _looks_like_msys2_mingw_scheme() -> bool: """MSYS2 patches distutils and sysconfig to use a UNIX-like scheme. @@ -174,7 +175,7 @@ def _looks_like_msys2_mingw_scheme() -> bool: ) -def _fix_abiflags(parts: Tuple[str]) -> Generator[str, None, None]: +def _fix_abiflags(parts: tuple[str]) -> Generator[str, None, None]: ldversion = sysconfig.get_config_var("LDVERSION") abiflags = getattr(sys, "abiflags", None) @@ -190,7 +191,7 @@ def _fix_abiflags(parts: Tuple[str]) -> Generator[str, None, None]: yield part -@functools.lru_cache(maxsize=None) +@functools.cache def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None: issue_url = "https://github.com/pypa/pip/issues/10151" message = ( @@ -208,7 +209,7 @@ def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool return True -@functools.lru_cache(maxsize=None) +@functools.cache def _log_context( *, user: bool = False, diff --git a/src/pip/_internal/locations/_distutils.py b/src/pip/_internal/locations/_distutils.py index 3d856256986..f2a0dedfc2b 100644 --- a/src/pip/_internal/locations/_distutils.py +++ b/src/pip/_internal/locations/_distutils.py @@ -21,7 +21,7 @@ from distutils.command.install import SCHEME_KEYS from distutils.command.install import install as distutils_install_command from distutils.sysconfig import get_python_lib -from typing import Dict, List, Optional, Union +from typing import Optional, Union from pip._internal.models.scheme import Scheme from pip._internal.utils.compat import WINDOWS @@ -41,13 +41,13 @@ def distutils_scheme( prefix: Optional[str] = None, *, ignore_config_files: bool = False, -) -> Dict[str, str]: +) -> dict[str, str]: """ Return a distutils install scheme """ from distutils.dist import Distribution - dist_args: Dict[str, Union[str, List[str]]] = {"name": dist_name} + dist_args: dict[str, Union[str, list[str]]] = {"name": dist_name} if isolated: dist_args["script_args"] = ["--no-user-cfg"] @@ -78,7 +78,7 @@ def distutils_scheme( i.root = root or i.root i.finalize_options() - scheme: Dict[str, str] = {} + scheme: dict[str, str] = {} for key in SCHEME_KEYS: scheme[key] = getattr(i, "install_" + key) diff --git a/src/pip/_internal/locations/base.py b/src/pip/_internal/locations/base.py index 3f9f896e632..17f88ce7737 100644 --- a/src/pip/_internal/locations/base.py +++ b/src/pip/_internal/locations/base.py @@ -76,6 +76,6 @@ def get_src_prefix() -> str: user_site = site.USER_SITE -@functools.lru_cache(maxsize=None) +@functools.cache def is_osx_framework() -> bool: return bool(sysconfig.get_config_var("PYTHONFRAMEWORK")) diff --git a/src/pip/_internal/main.py b/src/pip/_internal/main.py index 33c6d24cd85..be17a778fa3 100644 --- a/src/pip/_internal/main.py +++ b/src/pip/_internal/main.py @@ -1,7 +1,7 @@ -from typing import List, Optional +from typing import Optional -def main(args: Optional[List[str]] = None) -> int: +def main(args: Optional[list[str]] = None) -> int: """This is preserved for old console scripts that may still be referencing it. diff --git a/src/pip/_internal/metadata/__init__.py b/src/pip/_internal/metadata/__init__.py index 1ea1e7fd2e5..f0fb42e2677 100644 --- a/src/pip/_internal/metadata/__init__.py +++ b/src/pip/_internal/metadata/__init__.py @@ -2,17 +2,12 @@ import functools import os import sys -from typing import TYPE_CHECKING, List, Optional, Type, cast +from typing import Literal, Optional, Protocol, cast from pip._internal.utils.misc import strtobool from .base import BaseDistribution, BaseEnvironment, FilesystemWheel, MemoryWheel, Wheel -if TYPE_CHECKING: - from typing import Literal, Protocol -else: - Protocol = object - __all__ = [ "BaseDistribution", "BaseEnvironment", @@ -51,11 +46,11 @@ def _should_use_importlib_metadata() -> bool: class Backend(Protocol): NAME: 'Literal["importlib", "pkg_resources"]' - Distribution: Type[BaseDistribution] - Environment: Type[BaseEnvironment] + Distribution: type[BaseDistribution] + Environment: type[BaseEnvironment] -@functools.lru_cache(maxsize=None) +@functools.cache def select_backend() -> Backend: if _should_use_importlib_metadata(): from . import importlib @@ -76,7 +71,7 @@ def get_default_environment() -> BaseEnvironment: return select_backend().Environment.default() -def get_environment(paths: Optional[List[str]]) -> BaseEnvironment: +def get_environment(paths: Optional[list[str]]) -> BaseEnvironment: """Get a representation of the environment specified by ``paths``. This returns an Environment instance from the chosen backend based on the diff --git a/src/pip/_internal/metadata/_json.py b/src/pip/_internal/metadata/_json.py index f3aeab3225f..b6f949092d6 100644 --- a/src/pip/_internal/metadata/_json.py +++ b/src/pip/_internal/metadata/_json.py @@ -2,7 +2,7 @@ from email.header import Header, decode_header, make_header from email.message import Message -from typing import Any, Dict, List, Union, cast +from typing import Any, Union, cast METADATA_FIELDS = [ # Name, Multiple-Use @@ -40,7 +40,7 @@ def json_name(field: str) -> str: return field.lower().replace("-", "_") -def msg_to_json(msg: Message) -> Dict[str, Any]: +def msg_to_json(msg: Message) -> dict[str, Any]: """Convert a Message object into a JSON-compatible dictionary.""" def sanitise_header(h: Union[Header, str]) -> str: @@ -65,7 +65,7 @@ def sanitise_header(h: Union[Header, str]) -> str: continue key = json_name(field) if multi: - value: Union[str, List[str]] = [ + value: Union[str, list[str]] = [ sanitise_header(v) for v in msg.get_all(field) # type: ignore ] else: diff --git a/src/pip/_internal/metadata/base.py b/src/pip/_internal/metadata/base.py index 9eabcdb278b..c33a4a04855 100644 --- a/src/pip/_internal/metadata/base.py +++ b/src/pip/_internal/metadata/base.py @@ -6,19 +6,13 @@ import pathlib import re import zipfile +from collections.abc import Collection, Container, Iterable, Iterator from typing import ( IO, Any, - Collection, - Container, - Dict, - Iterable, - Iterator, - List, NamedTuple, Optional, Protocol, - Tuple, Union, ) @@ -61,8 +55,8 @@ def group(self) -> str: def _convert_installed_files_path( - entry: Tuple[str, ...], - info: Tuple[str, ...], + entry: tuple[str, ...], + info: tuple[str, ...], ) -> str: """Convert a legacy installed-files.txt path into modern RECORD path. @@ -396,7 +390,7 @@ def metadata(self) -> email.message.Message: return metadata @property - def metadata_dict(self) -> Dict[str, Any]: + def metadata_dict(self) -> dict[str, Any]: """PEP 566 compliant JSON-serializable representation of METADATA or PKG-INFO. This should return an empty dict if the metadata file is unavailable. @@ -587,7 +581,7 @@ def default(cls) -> "BaseEnvironment": raise NotImplementedError() @classmethod - def from_paths(cls, paths: Optional[List[str]]) -> "BaseEnvironment": + def from_paths(cls, paths: Optional[list[str]]) -> "BaseEnvironment": raise NotImplementedError() def get_distribution(self, name: str) -> Optional["BaseDistribution"]: diff --git a/src/pip/_internal/metadata/importlib/_compat.py b/src/pip/_internal/metadata/importlib/_compat.py index ec1e815cdbd..a73084fddad 100644 --- a/src/pip/_internal/metadata/importlib/_compat.py +++ b/src/pip/_internal/metadata/importlib/_compat.py @@ -1,6 +1,6 @@ import importlib.metadata import os -from typing import Any, Optional, Protocol, Tuple, cast +from typing import Any, Optional, Protocol, cast from pip._vendor.packaging.utils import NormalizedName, canonicalize_name @@ -48,7 +48,7 @@ def get_info_location(d: importlib.metadata.Distribution) -> Optional[BasePath]: def parse_name_and_version_from_info_directory( dist: importlib.metadata.Distribution, -) -> Tuple[Optional[str], Optional[str]]: +) -> tuple[Optional[str], Optional[str]]: """Get a name and version from the metadata directory name. This is much faster than reading distribution metadata. diff --git a/src/pip/_internal/metadata/importlib/_dists.py b/src/pip/_internal/metadata/importlib/_dists.py index d220b616e28..4bb7374be48 100644 --- a/src/pip/_internal/metadata/importlib/_dists.py +++ b/src/pip/_internal/metadata/importlib/_dists.py @@ -2,15 +2,10 @@ import importlib.metadata import pathlib import zipfile +from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence from os import PathLike from typing import ( - Collection, - Dict, - Iterable, - Iterator, - Mapping, Optional, - Sequence, Union, cast, ) @@ -215,7 +210,7 @@ def iter_provided_extras(self) -> Iterable[NormalizedName]: ] def iter_dependencies(self, extras: Collection[str] = ()) -> Iterable[Requirement]: - contexts: Sequence[Dict[str, str]] = [{"extra": e} for e in extras] + contexts: Sequence[dict[str, str]] = [{"extra": e} for e in extras] for req_string in self.metadata.get_all("Requires-Dist", []): # strip() because email.message.Message.get_all() may return a leading \n # in case a long header was wrapped. diff --git a/src/pip/_internal/metadata/importlib/_envs.py b/src/pip/_internal/metadata/importlib/_envs.py index 4d906fd3149..921183f5eab 100644 --- a/src/pip/_internal/metadata/importlib/_envs.py +++ b/src/pip/_internal/metadata/importlib/_envs.py @@ -6,7 +6,8 @@ import sys import zipfile import zipimport -from typing import Iterator, List, Optional, Sequence, Set, Tuple +from collections.abc import Iterator, Sequence +from typing import Optional from pip._vendor.packaging.utils import NormalizedName, canonicalize_name @@ -44,10 +45,10 @@ class _DistributionFinder: installations as well. It's useful feature, after all. """ - FoundResult = Tuple[importlib.metadata.Distribution, Optional[BasePath]] + FoundResult = tuple[importlib.metadata.Distribution, Optional[BasePath]] def __init__(self) -> None: - self._found_names: Set[NormalizedName] = set() + self._found_names: set[NormalizedName] = set() def _find_impl(self, location: str) -> Iterator[FoundResult]: """Find distributions in a location.""" @@ -145,7 +146,7 @@ def find_eggs(self, location: str) -> Iterator[BaseDistribution]: yield from self._find_eggs_in_zip(location) -@functools.lru_cache(maxsize=None) # Warn a distribution exactly once. +@functools.cache # Warn a distribution exactly once. def _emit_egg_deprecation(location: Optional[str]) -> None: deprecated( reason=f"Loading egg at {location} is deprecated.", @@ -164,7 +165,7 @@ def default(cls) -> BaseEnvironment: return cls(sys.path) @classmethod - def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment: + def from_paths(cls, paths: Optional[list[str]]) -> BaseEnvironment: if paths is None: return cls(sys.path) return cls(paths) diff --git a/src/pip/_internal/metadata/pkg_resources.py b/src/pip/_internal/metadata/pkg_resources.py index 4ea84f93a6f..498411fe733 100644 --- a/src/pip/_internal/metadata/pkg_resources.py +++ b/src/pip/_internal/metadata/pkg_resources.py @@ -3,12 +3,8 @@ import logging import os import zipfile +from collections.abc import Collection, Iterable, Iterator, Mapping from typing import ( - Collection, - Iterable, - Iterator, - List, - Mapping, NamedTuple, Optional, ) @@ -73,7 +69,7 @@ def get_metadata_lines(self, name: str) -> Iterable[str]: def metadata_isdir(self, name: str) -> bool: return False - def metadata_listdir(self, name: str) -> List[str]: + def metadata_listdir(self, name: str) -> list[str]: return [] def run_script(self, script_name: str, namespace: str) -> None: @@ -259,7 +255,7 @@ def default(cls) -> BaseEnvironment: return cls(pkg_resources.working_set) @classmethod - def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment: + def from_paths(cls, paths: Optional[list[str]]) -> BaseEnvironment: return cls(pkg_resources.WorkingSet(paths)) def _iter_distributions(self) -> Iterator[BaseDistribution]: diff --git a/src/pip/_internal/models/direct_url.py b/src/pip/_internal/models/direct_url.py index fc5ec8d4aa9..9e364053e3e 100644 --- a/src/pip/_internal/models/direct_url.py +++ b/src/pip/_internal/models/direct_url.py @@ -3,8 +3,9 @@ import json import re import urllib.parse +from collections.abc import Iterable from dataclasses import dataclass -from typing import Any, ClassVar, Dict, Iterable, Optional, Type, TypeVar, Union +from typing import Any, ClassVar, Optional, TypeVar, Union __all__ = [ "DirectUrl", @@ -25,7 +26,7 @@ class DirectUrlValidationError(Exception): def _get( - d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None + d: dict[str, Any], expected_type: type[T], key: str, default: Optional[T] = None ) -> Optional[T]: """Get value from dictionary and verify expected type.""" if key not in d: @@ -39,7 +40,7 @@ def _get( def _get_required( - d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None + d: dict[str, Any], expected_type: type[T], key: str, default: Optional[T] = None ) -> T: value = _get(d, expected_type, key, default) if value is None: @@ -61,7 +62,7 @@ def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType": return infos[0] -def _filter_none(**kwargs: Any) -> Dict[str, Any]: +def _filter_none(**kwargs: Any) -> dict[str, Any]: """Make dict excluding None values.""" return {k: v for k, v in kwargs.items() if v is not None} @@ -75,7 +76,7 @@ class VcsInfo: requested_revision: Optional[str] = None @classmethod - def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]: + def _from_dict(cls, d: Optional[dict[str, Any]]) -> Optional["VcsInfo"]: if d is None: return None return cls( @@ -84,7 +85,7 @@ def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]: requested_revision=_get(d, str, "requested_revision"), ) - def _to_dict(self) -> Dict[str, Any]: + def _to_dict(self) -> dict[str, Any]: return _filter_none( vcs=self.vcs, requested_revision=self.requested_revision, @@ -98,7 +99,7 @@ class ArchiveInfo: def __init__( self, hash: Optional[str] = None, - hashes: Optional[Dict[str, str]] = None, + hashes: Optional[dict[str, str]] = None, ) -> None: # set hashes before hash, since the hash setter will further populate hashes self.hashes = hashes @@ -127,12 +128,12 @@ def hash(self, value: Optional[str]) -> None: self._hash = value @classmethod - def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]: + def _from_dict(cls, d: Optional[dict[str, Any]]) -> Optional["ArchiveInfo"]: if d is None: return None return cls(hash=_get(d, str, "hash"), hashes=_get(d, dict, "hashes")) - def _to_dict(self) -> Dict[str, Any]: + def _to_dict(self) -> dict[str, Any]: return _filter_none(hash=self.hash, hashes=self.hashes) @@ -143,12 +144,12 @@ class DirInfo: editable: bool = False @classmethod - def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["DirInfo"]: + def _from_dict(cls, d: Optional[dict[str, Any]]) -> Optional["DirInfo"]: if d is None: return None return cls(editable=_get_required(d, bool, "editable", default=False)) - def _to_dict(self) -> Dict[str, Any]: + def _to_dict(self) -> dict[str, Any]: return _filter_none(editable=self.editable or None) @@ -192,7 +193,7 @@ def validate(self) -> None: self.from_dict(self.to_dict()) @classmethod - def from_dict(cls, d: Dict[str, Any]) -> "DirectUrl": + def from_dict(cls, d: dict[str, Any]) -> "DirectUrl": return DirectUrl( url=_get_required(d, str, "url"), subdirectory=_get(d, str, "subdirectory"), @@ -205,7 +206,7 @@ def from_dict(cls, d: Dict[str, Any]) -> "DirectUrl": ), ) - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: res = _filter_none( url=self.redacted_url, subdirectory=self.subdirectory, diff --git a/src/pip/_internal/models/format_control.py b/src/pip/_internal/models/format_control.py index ccd11272c03..f1855e8f161 100644 --- a/src/pip/_internal/models/format_control.py +++ b/src/pip/_internal/models/format_control.py @@ -1,4 +1,4 @@ -from typing import FrozenSet, Optional, Set +from typing import Optional from pip._vendor.packaging.utils import canonicalize_name @@ -12,8 +12,8 @@ class FormatControl: def __init__( self, - no_binary: Optional[Set[str]] = None, - only_binary: Optional[Set[str]] = None, + no_binary: Optional[set[str]] = None, + only_binary: Optional[set[str]] = None, ) -> None: if no_binary is None: no_binary = set() @@ -36,7 +36,7 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}({self.no_binary}, {self.only_binary})" @staticmethod - def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None: + def handle_mutual_excludes(value: str, target: set[str], other: set[str]) -> None: if value.startswith("-"): raise CommandError( "--no-binary / --only-binary option requires 1 argument." @@ -58,7 +58,7 @@ def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> Non other.discard(name) target.add(name) - def get_allowed_formats(self, canonical_name: str) -> FrozenSet[str]: + def get_allowed_formats(self, canonical_name: str) -> frozenset[str]: result = {"binary", "source"} if canonical_name in self.only_binary: result.discard("source") diff --git a/src/pip/_internal/models/installation_report.py b/src/pip/_internal/models/installation_report.py index b9c6330df32..3e8e9683bed 100644 --- a/src/pip/_internal/models/installation_report.py +++ b/src/pip/_internal/models/installation_report.py @@ -1,4 +1,5 @@ -from typing import Any, Dict, Sequence +from collections.abc import Sequence +from typing import Any from pip._vendor.packaging.markers import default_environment @@ -11,7 +12,7 @@ def __init__(self, install_requirements: Sequence[InstallRequirement]): self._install_requirements = install_requirements @classmethod - def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]: + def _install_req_to_dict(cls, ireq: InstallRequirement) -> dict[str, Any]: assert ireq.download_info, f"No download_info for {ireq}" res = { # PEP 610 json for the download URL. download_info.archive_info.hashes may @@ -39,7 +40,7 @@ def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]: res["requested_extras"] = sorted(ireq.extras) return res - def to_dict(self) -> Dict[str, Any]: + def to_dict(self) -> dict[str, Any]: return { "version": "1", "pip_version": __version__, diff --git a/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py index 27ad016090c..e6002a4a937 100644 --- a/src/pip/_internal/models/link.py +++ b/src/pip/_internal/models/link.py @@ -5,16 +5,13 @@ import posixpath import re import urllib.parse +from collections.abc import Mapping from dataclasses import dataclass from typing import ( TYPE_CHECKING, Any, - Dict, - List, - Mapping, NamedTuple, Optional, - Tuple, Union, ) @@ -69,7 +66,7 @@ def __post_init__(self) -> None: assert self.name in _SUPPORTED_HASHES @classmethod - @functools.lru_cache(maxsize=None) + @functools.cache def find_hash_url_fragment(cls, url: str) -> Optional["LinkHash"]: """Search a string for a checksum algorithm name and encoded output value.""" match = cls._hash_url_fragment_re.search(url) @@ -78,7 +75,7 @@ def find_hash_url_fragment(cls, url: str) -> Optional["LinkHash"]: name, value = match.groups() return cls(name=name, value=value) - def as_dict(self) -> Dict[str, str]: + def as_dict(self) -> dict[str, str]: return {self.name: self.value} def as_hashes(self) -> Hashes: @@ -98,14 +95,14 @@ def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool: class MetadataFile: """Information about a core metadata file associated with a distribution.""" - hashes: Optional[Dict[str, str]] + hashes: Optional[dict[str, str]] def __post_init__(self) -> None: if self.hashes is not None: assert all(name in _SUPPORTED_HASHES for name in self.hashes) -def supported_hashes(hashes: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]: +def supported_hashes(hashes: Optional[dict[str, str]]) -> Optional[dict[str, str]]: # Remove any unsupported hash types from the mapping. If this leaves no # supported hashes, return None if hashes is None: @@ -274,7 +271,7 @@ def __init__( @classmethod def from_json( cls, - file_data: Dict[str, Any], + file_data: dict[str, Any], page_url: str, ) -> Optional["Link"]: """ @@ -325,7 +322,7 @@ def from_json( @classmethod def from_element( cls, - anchor_attribs: Dict[str, Optional[str]], + anchor_attribs: dict[str, Optional[str]], page_url: str, base_url: str, ) -> Optional["Link"]: @@ -437,7 +434,7 @@ def netloc(self) -> str: def path(self) -> str: return self._path - def splitext(self) -> Tuple[str, str]: + def splitext(self) -> tuple[str, str]: return splitext(posixpath.basename(self.path.rstrip("/"))) @property @@ -568,9 +565,9 @@ class _CleanResult(NamedTuple): """ parsed: urllib.parse.SplitResult - query: Dict[str, List[str]] + query: dict[str, list[str]] subdirectory: str - hashes: Dict[str, str] + hashes: dict[str, str] def _clean_link(link: Link) -> _CleanResult: @@ -599,6 +596,6 @@ def _clean_link(link: Link) -> _CleanResult: ) -@functools.lru_cache(maxsize=None) +@functools.cache def links_equivalent(link1: Link, link2: Link) -> bool: return _clean_link(link1) == _clean_link(link2) diff --git a/src/pip/_internal/models/search_scope.py b/src/pip/_internal/models/search_scope.py index ee7bc86229a..136163ca096 100644 --- a/src/pip/_internal/models/search_scope.py +++ b/src/pip/_internal/models/search_scope.py @@ -4,7 +4,6 @@ import posixpath import urllib.parse from dataclasses import dataclass -from typing import List from pip._vendor.packaging.utils import canonicalize_name @@ -23,15 +22,15 @@ class SearchScope: __slots__ = ["find_links", "index_urls", "no_index"] - find_links: List[str] - index_urls: List[str] + find_links: list[str] + index_urls: list[str] no_index: bool @classmethod def create( cls, - find_links: List[str], - index_urls: List[str], + find_links: list[str], + index_urls: list[str], no_index: bool, ) -> "SearchScope": """ @@ -42,7 +41,7 @@ def create( # it and if it exists, use the normalized version. # This is deliberately conservative - it might be fine just to # blindly normalize anything starting with a ~... - built_find_links: List[str] = [] + built_find_links: list[str] = [] for link in find_links: if link.startswith("~"): new_link = normalize_path(link) @@ -104,7 +103,7 @@ def get_formatted_locations(self) -> str: ) return "\n".join(lines) - def get_index_urls_locations(self, project_name: str) -> List[str]: + def get_index_urls_locations(self, project_name: str) -> list[str]: """Returns the locations found via self.index_urls Checks the url_name on the main (first in the list) index and diff --git a/src/pip/_internal/models/target_python.py b/src/pip/_internal/models/target_python.py index 88925a9fd01..60085c2df9c 100644 --- a/src/pip/_internal/models/target_python.py +++ b/src/pip/_internal/models/target_python.py @@ -1,5 +1,5 @@ import sys -from typing import List, Optional, Set, Tuple +from typing import Optional from pip._vendor.packaging.tags import Tag @@ -26,9 +26,9 @@ class TargetPython: def __init__( self, - platforms: Optional[List[str]] = None, - py_version_info: Optional[Tuple[int, ...]] = None, - abis: Optional[List[str]] = None, + platforms: Optional[list[str]] = None, + py_version_info: Optional[tuple[int, ...]] = None, + abis: Optional[list[str]] = None, implementation: Optional[str] = None, ) -> None: """ @@ -62,8 +62,8 @@ def __init__( self.py_version_info = py_version_info # This is used to cache the return value of get_(un)sorted_tags. - self._valid_tags: Optional[List[Tag]] = None - self._valid_tags_set: Optional[Set[Tag]] = None + self._valid_tags: Optional[list[Tag]] = None + self._valid_tags_set: Optional[set[Tag]] = None def format_given(self) -> str: """ @@ -85,7 +85,7 @@ def format_given(self) -> str: f"{key}={value!r}" for key, value in key_values if value is not None ) - def get_sorted_tags(self) -> List[Tag]: + def get_sorted_tags(self) -> list[Tag]: """ Return the supported PEP 425 tags to check wheel candidates against. @@ -110,7 +110,7 @@ def get_sorted_tags(self) -> List[Tag]: return self._valid_tags - def get_unsorted_tags(self) -> Set[Tag]: + def get_unsorted_tags(self) -> set[Tag]: """Exactly the same as get_sorted_tags, but returns a set. This is important for performance. diff --git a/src/pip/_internal/models/wheel.py b/src/pip/_internal/models/wheel.py index ea8560089d3..8810b0940f9 100644 --- a/src/pip/_internal/models/wheel.py +++ b/src/pip/_internal/models/wheel.py @@ -3,7 +3,7 @@ """ import re -from typing import Dict, Iterable, List +from collections.abc import Iterable from pip._vendor.packaging.tags import Tag from pip._vendor.packaging.utils import ( @@ -67,11 +67,11 @@ def __init__(self, filename: str) -> None: Tag(x, y, z) for x in self.pyversions for y in self.abis for z in self.plats } - def get_formatted_file_tags(self) -> List[str]: + def get_formatted_file_tags(self) -> list[str]: """Return the wheel's tags as a sorted list of strings.""" return sorted(str(tag) for tag in self.file_tags) - def support_index_min(self, tags: List[Tag]) -> int: + def support_index_min(self, tags: list[Tag]) -> int: """Return the lowest index that one of the wheel's file_tag combinations achieves in the given list of supported tags. @@ -90,7 +90,7 @@ def support_index_min(self, tags: List[Tag]) -> int: raise ValueError() def find_most_preferred_tag( - self, tags: List[Tag], tag_to_priority: Dict[Tag, int] + self, tags: list[Tag], tag_to_priority: dict[Tag, int] ) -> int: """Return the priority of the most preferred tag that one of the wheel's file tag combinations achieves in the given list of supported tags using the given diff --git a/src/pip/_internal/network/auth.py b/src/pip/_internal/network/auth.py index 1a2606ed080..522095226af 100644 --- a/src/pip/_internal/network/auth.py +++ b/src/pip/_internal/network/auth.py @@ -12,10 +12,10 @@ import typing import urllib.parse from abc import ABC, abstractmethod -from functools import lru_cache +from functools import cache from os.path import commonprefix from pathlib import Path -from typing import Any, Dict, List, NamedTuple, Optional, Tuple +from typing import Any, NamedTuple, Optional from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth from pip._vendor.requests.models import Request, Response @@ -159,7 +159,7 @@ def _set_password(self, service_name: str, username: str, password: str) -> None return None -@lru_cache(maxsize=None) +@cache def get_keyring_provider(provider: str) -> KeyRingBaseProvider: logger.verbose("Keyring provider requested: %s", provider) @@ -225,13 +225,13 @@ class MultiDomainBasicAuth(AuthBase): def __init__( self, prompting: bool = True, - index_urls: Optional[List[str]] = None, + index_urls: Optional[list[str]] = None, keyring_provider: str = "auto", ) -> None: self.prompting = prompting self.index_urls = index_urls self.keyring_provider = keyring_provider # type: ignore[assignment] - self.passwords: Dict[str, AuthInfo] = {} + self.passwords: dict[str, AuthInfo] = {} # When the user is prompted to enter credentials and keyring is # available, we will offer to save them. If the user accepts, # this value is set to the credentials they entered. After the @@ -391,7 +391,7 @@ def _get_new_credentials( def _get_url_and_credentials( self, original_url: str - ) -> Tuple[str, Optional[str], Optional[str]]: + ) -> tuple[str, Optional[str], Optional[str]]: """Return the credentials to use for the provided URL. If allowed, netrc and keyring may be used to obtain the @@ -456,7 +456,7 @@ def __call__(self, req: Request) -> Request: # Factored out to allow for easy patching in tests def _prompt_for_password( self, netloc: str - ) -> Tuple[Optional[str], Optional[str], bool]: + ) -> tuple[Optional[str], Optional[str], bool]: username = ask_input(f"User for {netloc}: ") if self.prompting else None if not username: return None, None, False diff --git a/src/pip/_internal/network/cache.py b/src/pip/_internal/network/cache.py index fca04e6945f..c952175d805 100644 --- a/src/pip/_internal/network/cache.py +++ b/src/pip/_internal/network/cache.py @@ -2,9 +2,10 @@ """ import os +from collections.abc import Generator from contextlib import contextmanager from datetime import datetime -from typing import BinaryIO, Generator, Optional, Union +from typing import BinaryIO, Optional, Union from pip._vendor.cachecontrol.cache import SeparateBodyBaseCache from pip._vendor.cachecontrol.caches import SeparateBodyFileCache diff --git a/src/pip/_internal/network/download.py b/src/pip/_internal/network/download.py index 5c3bce3d2fd..cf769131bfa 100644 --- a/src/pip/_internal/network/download.py +++ b/src/pip/_internal/network/download.py @@ -5,7 +5,8 @@ import logging import mimetypes import os -from typing import Iterable, Optional, Tuple +from collections.abc import Iterable +from typing import Optional from pip._vendor.requests.models import Response @@ -129,7 +130,7 @@ def __init__( self._session = session self._progress_bar = progress_bar - def __call__(self, link: Link, location: str) -> Tuple[str, str]: + def __call__(self, link: Link, location: str) -> tuple[str, str]: """Download the file given by link into location.""" try: resp = _http_get_download(self._session, link) @@ -162,7 +163,7 @@ def __init__( def __call__( self, links: Iterable[Link], location: str - ) -> Iterable[Tuple[Link, Tuple[str, str]]]: + ) -> Iterable[tuple[Link, tuple[str, str]]]: """Download the files given by links into location.""" for link in links: try: diff --git a/src/pip/_internal/network/lazy_wheel.py b/src/pip/_internal/network/lazy_wheel.py index 03f883c1fc4..1b57ca59381 100644 --- a/src/pip/_internal/network/lazy_wheel.py +++ b/src/pip/_internal/network/lazy_wheel.py @@ -3,9 +3,10 @@ __all__ = ["HTTPRangeRequestUnsupported", "dist_from_wheel_url"] from bisect import bisect_left, bisect_right +from collections.abc import Generator from contextlib import contextmanager from tempfile import NamedTemporaryFile -from typing import Any, Dict, Generator, List, Optional, Tuple +from typing import Any, Optional from zipfile import BadZipFile, ZipFile from pip._vendor.packaging.utils import canonicalize_name @@ -56,8 +57,8 @@ def __init__( self._length = int(head.headers["Content-Length"]) self._file = NamedTemporaryFile() self.truncate(self._length) - self._left: List[int] = [] - self._right: List[int] = [] + self._left: list[int] = [] + self._right: list[int] = [] if "bytes" not in head.headers.get("Accept-Ranges", "none"): raise HTTPRangeRequestUnsupported("range request is not supported") self._check_zip() @@ -166,7 +167,7 @@ def _check_zip(self) -> None: break def _stream_response( - self, start: int, end: int, base_headers: Dict[str, str] = HEADERS + self, start: int, end: int, base_headers: dict[str, str] = HEADERS ) -> Response: """Return HTTP response to a range request from start to end.""" headers = base_headers.copy() @@ -177,7 +178,7 @@ def _stream_response( def _merge( self, start: int, end: int, left: int, right: int - ) -> Generator[Tuple[int, int], None, None]: + ) -> Generator[tuple[int, int], None, None]: """Return a generator of intervals to be fetched. Args: diff --git a/src/pip/_internal/network/session.py b/src/pip/_internal/network/session.py index 5e10f8f5615..86087490af0 100644 --- a/src/pip/_internal/network/session.py +++ b/src/pip/_internal/network/session.py @@ -16,16 +16,11 @@ import sys import urllib.parse import warnings +from collections.abc import Generator, Mapping, Sequence from typing import ( TYPE_CHECKING, Any, - Dict, - Generator, - List, - Mapping, Optional, - Sequence, - Tuple, Union, ) @@ -58,14 +53,14 @@ logger = logging.getLogger(__name__) -SecureOrigin = Tuple[str, str, Optional[Union[int, str]]] +SecureOrigin = tuple[str, str, Optional[Union[int, str]]] # Ignore warning raised when using --trusted-host. warnings.filterwarnings("ignore", category=InsecureRequestWarning) -SECURE_ORIGINS: List[SecureOrigin] = [ +SECURE_ORIGINS: list[SecureOrigin] = [ # protocol, hostname, port # Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC) ("https", "*", "*"), @@ -112,7 +107,7 @@ def user_agent() -> str: """ Return a string representing the user agent. """ - data: Dict[str, Any] = { + data: dict[str, Any] = { "installer": {"name": "pip", "version": __version__}, "python": platform.python_version(), "implementation": { @@ -140,7 +135,7 @@ def user_agent() -> str: from pip._vendor import distro linux_distribution = distro.name(), distro.version(), distro.codename() - distro_infos: Dict[str, Any] = dict( + distro_infos: dict[str, Any] = dict( filter( lambda x: x[1], zip(["name", "version", "id"], linux_distribution), @@ -214,9 +209,9 @@ def send( self, request: PreparedRequest, stream: bool = False, - timeout: Optional[Union[float, Tuple[float, float]]] = None, + timeout: Optional[Union[float, tuple[float, float]]] = None, verify: Union[bool, str] = True, - cert: Optional[Union[str, Tuple[str, str]]] = None, + cert: Optional[Union[str, tuple[str, str]]] = None, proxies: Optional[Mapping[str, str]] = None, ) -> Response: pathname = url_to_path(request.url) @@ -301,7 +296,7 @@ def cert_verify( conn: ConnectionPool, url: str, verify: Union[bool, str], - cert: Optional[Union[str, Tuple[str, str]]], + cert: Optional[Union[str, tuple[str, str]]], ) -> None: super().cert_verify(conn=conn, url=url, verify=False, cert=cert) @@ -312,7 +307,7 @@ def cert_verify( conn: ConnectionPool, url: str, verify: Union[bool, str], - cert: Optional[Union[str, Tuple[str, str]]], + cert: Optional[Union[str, tuple[str, str]]], ) -> None: super().cert_verify(conn=conn, url=url, verify=False, cert=cert) @@ -326,7 +321,7 @@ def __init__( retries: int = 0, cache: Optional[str] = None, trusted_hosts: Sequence[str] = (), - index_urls: Optional[List[str]] = None, + index_urls: Optional[list[str]] = None, ssl_context: Optional["SSLContext"] = None, **kwargs: Any, ) -> None: @@ -338,7 +333,7 @@ def __init__( # Namespace the attribute with "pip_" just in case to prevent # possible conflicts with the base class. - self.pip_trusted_origins: List[Tuple[str, Optional[int]]] = [] + self.pip_trusted_origins: list[tuple[str, Optional[int]]] = [] self.pip_proxy = None # Attach our User Agent to the request @@ -401,7 +396,7 @@ def __init__( for host in trusted_hosts: self.add_trusted_host(host, suppress_logging=True) - def update_index_urls(self, new_index_urls: List[str]) -> None: + def update_index_urls(self, new_index_urls: list[str]) -> None: """ :param new_index_urls: New index urls to update the authentication handler with. diff --git a/src/pip/_internal/network/utils.py b/src/pip/_internal/network/utils.py index bba4c265e89..74d3111cff0 100644 --- a/src/pip/_internal/network/utils.py +++ b/src/pip/_internal/network/utils.py @@ -1,4 +1,4 @@ -from typing import Dict, Generator +from collections.abc import Generator from pip._vendor.requests.models import Response @@ -23,7 +23,7 @@ # you're not asking for a compressed file and will then decompress it # before sending because if that's the case I don't think it'll ever be # possible to make this work. -HEADERS: Dict[str, str] = {"Accept-Encoding": "identity"} +HEADERS: dict[str, str] = {"Accept-Encoding": "identity"} DOWNLOAD_CHUNK_SIZE = 256 * 1024 diff --git a/src/pip/_internal/network/xmlrpc.py b/src/pip/_internal/network/xmlrpc.py index 22ec8d2f4a6..46d7700a28c 100644 --- a/src/pip/_internal/network/xmlrpc.py +++ b/src/pip/_internal/network/xmlrpc.py @@ -4,7 +4,7 @@ import logging import urllib.parse import xmlrpc.client -from typing import TYPE_CHECKING, Tuple +from typing import TYPE_CHECKING from pip._internal.exceptions import NetworkConnectionError from pip._internal.network.session import PipSession @@ -37,7 +37,7 @@ def request( handler: str, request_body: "SizedBuffer", verbose: bool = False, - ) -> Tuple["_Marshallable", ...]: + ) -> tuple["_Marshallable", ...]: assert isinstance(host, str) parts = (self._scheme, host, handler, None, None, None) url = urllib.parse.urlunparse(parts) diff --git a/src/pip/_internal/operations/build/build_tracker.py b/src/pip/_internal/operations/build/build_tracker.py index 0ed8dd23596..5e33979d277 100644 --- a/src/pip/_internal/operations/build/build_tracker.py +++ b/src/pip/_internal/operations/build/build_tracker.py @@ -2,8 +2,9 @@ import hashlib import logging import os +from collections.abc import Generator from types import TracebackType -from typing import Dict, Generator, Optional, Type, Union +from typing import Optional, Union from pip._internal.req.req_install import InstallRequirement from pip._internal.utils.temp_dir import TempDirectory @@ -17,7 +18,7 @@ def update_env_context_manager(**changes: str) -> Generator[None, None, None]: # Save values from the target and change them. non_existent_marker = object() - saved_values: Dict[str, Union[object, str]] = {} + saved_values: dict[str, Union[object, str]] = {} for name, new_value in changes.items(): try: saved_values[name] = target[name] @@ -65,7 +66,7 @@ class BuildTracker: def __init__(self, root: str) -> None: self._root = root - self._entries: Dict[TrackerId, InstallRequirement] = {} + self._entries: dict[TrackerId, InstallRequirement] = {} logger.debug("Created build tracker: %s", self._root) def __enter__(self) -> "BuildTracker": @@ -74,7 +75,7 @@ def __enter__(self) -> "BuildTracker": def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: diff --git a/src/pip/_internal/operations/build/wheel_legacy.py b/src/pip/_internal/operations/build/wheel_legacy.py index 3ee2a7058d3..97c6b3851e3 100644 --- a/src/pip/_internal/operations/build/wheel_legacy.py +++ b/src/pip/_internal/operations/build/wheel_legacy.py @@ -1,6 +1,6 @@ import logging import os.path -from typing import List, Optional +from typing import Optional from pip._internal.cli.spinners import open_spinner from pip._internal.utils.setuptools_build import make_setuptools_bdist_wheel_args @@ -10,7 +10,7 @@ def format_command_result( - command_args: List[str], + command_args: list[str], command_output: str, ) -> str: """Format command information for logging.""" @@ -30,10 +30,10 @@ def format_command_result( def get_legacy_build_wheel_path( - names: List[str], + names: list[str], temp_dir: str, name: str, - command_args: List[str], + command_args: list[str], command_output: str, ) -> Optional[str]: """Return the path to the wheel in the temporary build directory.""" @@ -60,8 +60,8 @@ def build_wheel_legacy( name: str, setup_py_path: str, source_dir: str, - global_options: List[str], - build_options: List[str], + global_options: list[str], + build_options: list[str], tempd: str, ) -> Optional[str]: """Build one unpacked package using the "legacy" build process. diff --git a/src/pip/_internal/operations/check.py b/src/pip/_internal/operations/check.py index 4b6fbc4c375..88ee92d390a 100644 --- a/src/pip/_internal/operations/check.py +++ b/src/pip/_internal/operations/check.py @@ -2,20 +2,14 @@ """ import logging +from collections.abc import Generator, Iterable from contextlib import suppress from email.parser import Parser from functools import reduce from typing import ( Callable, - Dict, - FrozenSet, - Generator, - Iterable, - List, NamedTuple, Optional, - Set, - Tuple, ) from pip._vendor.packaging.requirements import Requirement @@ -33,21 +27,21 @@ class PackageDetails(NamedTuple): version: Version - dependencies: List[Requirement] + dependencies: list[Requirement] # Shorthands -PackageSet = Dict[NormalizedName, PackageDetails] -Missing = Tuple[NormalizedName, Requirement] -Conflicting = Tuple[NormalizedName, Version, Requirement] +PackageSet = dict[NormalizedName, PackageDetails] +Missing = tuple[NormalizedName, Requirement] +Conflicting = tuple[NormalizedName, Version, Requirement] -MissingDict = Dict[NormalizedName, List[Missing]] -ConflictingDict = Dict[NormalizedName, List[Conflicting]] -CheckResult = Tuple[MissingDict, ConflictingDict] -ConflictDetails = Tuple[PackageSet, CheckResult] +MissingDict = dict[NormalizedName, list[Missing]] +ConflictingDict = dict[NormalizedName, list[Conflicting]] +CheckResult = tuple[MissingDict, ConflictingDict] +ConflictDetails = tuple[PackageSet, CheckResult] -def create_package_set_from_installed() -> Tuple[PackageSet, bool]: +def create_package_set_from_installed() -> tuple[PackageSet, bool]: """Converts a list of distributions into a PackageSet.""" package_set = {} problems = False @@ -78,8 +72,8 @@ def check_package_set( for package_name, package_detail in package_set.items(): # Info about dependencies of package_name - missing_deps: Set[Missing] = set() - conflicting_deps: Set[Conflicting] = set() + missing_deps: set[Missing] = set() + conflicting_deps: set[Conflicting] = set() if should_ignore and should_ignore(package_name): continue @@ -109,7 +103,7 @@ def check_package_set( return missing, conflicting -def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDetails: +def check_install_conflicts(to_install: list[InstallRequirement]) -> ConflictDetails: """For checking if the dependency graph would be consistent after \ installing given requirements """ @@ -136,7 +130,7 @@ def check_unsupported( for p in packages: with suppress(FileNotFoundError): wheel_file = p.read_text("WHEEL") - wheel_tags: FrozenSet[Tag] = reduce( + wheel_tags: frozenset[Tag] = reduce( frozenset.union, map(parse_tag, Parser().parsestr(wheel_file).get_all("Tag", [])), frozenset(), @@ -146,8 +140,8 @@ def check_unsupported( def _simulate_installation_of( - to_install: List[InstallRequirement], package_set: PackageSet -) -> Set[NormalizedName]: + to_install: list[InstallRequirement], package_set: PackageSet +) -> set[NormalizedName]: """Computes the version of packages after installing to_install.""" # Keep track of packages that were installed installed = set() @@ -165,8 +159,8 @@ def _simulate_installation_of( def _create_whitelist( - would_be_installed: Set[NormalizedName], package_set: PackageSet -) -> Set[NormalizedName]: + would_be_installed: set[NormalizedName], package_set: PackageSet +) -> set[NormalizedName]: packages_affected = set(would_be_installed) for package_name in package_set: diff --git a/src/pip/_internal/operations/freeze.py b/src/pip/_internal/operations/freeze.py index ae5dd37f9db..92a3165708c 100644 --- a/src/pip/_internal/operations/freeze.py +++ b/src/pip/_internal/operations/freeze.py @@ -1,8 +1,9 @@ import collections import logging import os +from collections.abc import Container, Generator, Iterable from dataclasses import dataclass, field -from typing import Container, Dict, Generator, Iterable, List, NamedTuple, Optional, Set +from typing import NamedTuple, Optional from pip._vendor.packaging.utils import NormalizedName, canonicalize_name from pip._vendor.packaging.version import InvalidVersion @@ -21,19 +22,19 @@ class _EditableInfo(NamedTuple): requirement: str - comments: List[str] + comments: list[str] def freeze( - requirement: Optional[List[str]] = None, + requirement: Optional[list[str]] = None, local_only: bool = False, user_only: bool = False, - paths: Optional[List[str]] = None, + paths: Optional[list[str]] = None, isolated: bool = False, exclude_editable: bool = False, skip: Container[str] = (), ) -> Generator[str, None, None]: - installations: Dict[str, FrozenRequirement] = {} + installations: dict[str, FrozenRequirement] = {} dists = get_environment(paths).iter_installed_distributions( local_only=local_only, @@ -51,10 +52,10 @@ def freeze( # should only be emitted once, even if the same option is in multiple # requirements files, so we need to keep track of what has been emitted # so that we don't emit it again if it's seen again - emitted_options: Set[str] = set() + emitted_options: set[str] = set() # keep track of which files a requirement is in so that we can # give an accurate warning if a requirement appears multiple times. - req_files: Dict[str, List[str]] = collections.defaultdict(list) + req_files: dict[str, list[str]] = collections.defaultdict(list) for req_file_path in requirement: with open(req_file_path) as req_file: for line in req_file: diff --git a/src/pip/_internal/operations/install/editable_legacy.py b/src/pip/_internal/operations/install/editable_legacy.py index 9aaa699a645..15434c26093 100644 --- a/src/pip/_internal/operations/install/editable_legacy.py +++ b/src/pip/_internal/operations/install/editable_legacy.py @@ -2,7 +2,8 @@ """ import logging -from typing import Optional, Sequence +from collections.abc import Sequence +from typing import Optional from pip._internal.build_env import BuildEnvironment from pip._internal.utils.logging import indent_log diff --git a/src/pip/_internal/operations/install/wheel.py b/src/pip/_internal/operations/install/wheel.py index aef42aa9eef..74a0d9bbbda 100644 --- a/src/pip/_internal/operations/install/wheel.py +++ b/src/pip/_internal/operations/install/wheel.py @@ -13,25 +13,18 @@ import sys import warnings from base64 import urlsafe_b64encode +from collections.abc import Generator, Iterable, Iterator, Sequence from email.message import Message +from io import StringIO from itertools import chain, filterfalse, starmap from typing import ( IO, - TYPE_CHECKING, Any, BinaryIO, Callable, - Dict, - Generator, - Iterable, - Iterator, - List, NewType, Optional, Protocol, - Sequence, - Set, - Tuple, Union, cast, ) @@ -51,7 +44,7 @@ from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, DirectUrl from pip._internal.models.scheme import SCHEME_KEYS, Scheme from pip._internal.utils.filesystem import adjacent_tmp_file, replace -from pip._internal.utils.misc import StreamWrapper, ensure_dir, hash_file, partition +from pip._internal.utils.misc import ensure_dir, hash_file, partition from pip._internal.utils.unpacking import ( current_umask, is_within_directory, @@ -60,31 +53,30 @@ ) from pip._internal.utils.wheel import parse_wheel -if TYPE_CHECKING: - class File(Protocol): - src_record_path: "RecordPath" - dest_path: str - changed: bool +class File(Protocol): + src_record_path: "RecordPath" + dest_path: str + changed: bool - def save(self) -> None: - pass + def save(self) -> None: + pass logger = logging.getLogger(__name__) RecordPath = NewType("RecordPath", str) -InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]] +InstalledCSVRow = tuple[RecordPath, str, Union[int, str]] -def rehash(path: str, blocksize: int = 1 << 20) -> Tuple[str, str]: +def rehash(path: str, blocksize: int = 1 << 20) -> tuple[str, str]: """Return (encoded_digest, length) for path using hashlib.sha256()""" h, length = hash_file(path, blocksize) digest = "sha256=" + urlsafe_b64encode(h.digest()).decode("latin1").rstrip("=") return (digest, str(length)) -def csv_io_kwargs(mode: str) -> Dict[str, Any]: +def csv_io_kwargs(mode: str) -> dict[str, Any]: """Return keyword arguments to properly open a CSV file in the given mode. """ @@ -115,7 +107,7 @@ def wheel_root_is_purelib(metadata: Message) -> bool: return metadata.get("Root-Is-Purelib", "").lower() == "true" -def get_entrypoints(dist: BaseDistribution) -> Tuple[Dict[str, str], Dict[str, str]]: +def get_entrypoints(dist: BaseDistribution) -> tuple[dict[str, str], dict[str, str]]: console_scripts = {} gui_scripts = {} for entry_point in dist.iter_entry_points(): @@ -135,7 +127,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]: return None # Group scripts by the path they were installed in - grouped_by_dir: Dict[str, Set[str]] = collections.defaultdict(set) + grouped_by_dir: dict[str, set[str]] = collections.defaultdict(set) for destfile in scripts: parent_dir = os.path.dirname(destfile) script_name = os.path.basename(destfile) @@ -151,7 +143,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]: not_warn_dirs.append( os.path.normcase(os.path.normpath(os.path.dirname(sys.executable))) ) - warn_for: Dict[str, Set[str]] = { + warn_for: dict[str, set[str]] = { parent_dir: scripts for parent_dir, scripts in grouped_by_dir.items() if os.path.normcase(os.path.normpath(parent_dir)) not in not_warn_dirs @@ -162,7 +154,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]: # Format a message msg_lines = [] for parent_dir, dir_scripts in warn_for.items(): - sorted_scripts: List[str] = sorted(dir_scripts) + sorted_scripts: list[str] = sorted(dir_scripts) if len(sorted_scripts) == 1: start_text = f"script {sorted_scripts[0]} is" else: @@ -200,7 +192,7 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]: def _normalized_outrows( outrows: Iterable[InstalledCSVRow], -) -> List[Tuple[str, str, str]]: +) -> list[tuple[str, str, str]]: """Normalize the given rows of a RECORD file. Items in each row are converted into str. Rows are then sorted to make @@ -239,17 +231,17 @@ def _fs_to_record_path(path: str, lib_dir: str) -> RecordPath: def get_csv_rows_for_installed( - old_csv_rows: List[List[str]], - installed: Dict[RecordPath, RecordPath], - changed: Set[RecordPath], - generated: List[str], + old_csv_rows: list[list[str]], + installed: dict[RecordPath, RecordPath], + changed: set[RecordPath], + generated: list[str], lib_dir: str, -) -> List[InstalledCSVRow]: +) -> list[InstalledCSVRow]: """ :param installed: A map from archive RECORD path to installation RECORD path. """ - installed_rows: List[InstalledCSVRow] = [] + installed_rows: list[InstalledCSVRow] = [] for row in old_csv_rows: if len(row) > 3: logger.warning("RECORD line has more than three elements: %s", row) @@ -270,7 +262,7 @@ def get_csv_rows_for_installed( ] -def get_console_script_specs(console: Dict[str, str]) -> List[str]: +def get_console_script_specs(console: dict[str, str]) -> list[str]: """ Given the mapping from entrypoint name to callable, return the relevant console script specs. @@ -413,8 +405,8 @@ def _raise_for_invalid_entrypoint(specification: str) -> None: class PipScriptMaker(ScriptMaker): def make( - self, specification: str, options: Optional[Dict[str, Any]] = None - ) -> List[str]: + self, specification: str, options: Optional[dict[str, Any]] = None + ) -> list[str]: _raise_for_invalid_entrypoint(specification) return super().make(specification, options) @@ -455,9 +447,9 @@ def _install_wheel( # noqa: C901, PLR0915 function is too long # installed = files copied from the wheel to the destination # changed = files changed while installing (scripts #! line typically) # generated = files newly generated during the install (script wrappers) - installed: Dict[RecordPath, RecordPath] = {} - changed: Set[RecordPath] = set() - generated: List[str] = [] + installed: dict[RecordPath, RecordPath] = {} + changed: set[RecordPath] = set() + generated: list[str] = [] def record_installed( srcfile: RecordPath, destfile: str, modified: bool = False @@ -529,7 +521,7 @@ def make_data_scheme_file(record_path: RecordPath) -> "File": def is_data_scheme_path(path: RecordPath) -> bool: return path.split("/", 1)[0].endswith(".data") - paths = cast(List[RecordPath], wheel_zip.namelist()) + paths = cast(list[RecordPath], wheel_zip.namelist()) file_paths = filterfalse(is_dir_path, paths) root_scheme_paths, data_scheme_paths = partition(is_data_scheme_path, file_paths) @@ -609,9 +601,7 @@ def pyc_output_path(path: str) -> str: # Compile all of the pyc files for the installed files if pycompile: - with contextlib.redirect_stdout( - StreamWrapper.from_stream(sys.stdout) - ) as stdout: + with contextlib.redirect_stdout(StringIO()) as stdout: with warnings.catch_warnings(): warnings.filterwarnings("ignore") for path in pyc_source_file_paths(): diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index e6aa3447200..cfefe13cca8 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -7,9 +7,10 @@ import mimetypes import os import shutil +from collections.abc import Iterable from dataclasses import dataclass from pathlib import Path -from typing import Dict, Iterable, List, Optional +from typing import Optional from pip._vendor.packaging.utils import canonicalize_name @@ -268,7 +269,7 @@ def __init__( self.legacy_resolver = legacy_resolver # Memoized downloaded files, as mapping of url: path. - self._downloaded: Dict[str, str] = {} + self._downloaded: dict[str, str] = {} # Previous "header" printed for a link-based InstallRequirement self._previous_requirement_header = ("", "") @@ -457,7 +458,7 @@ def _complete_partial_requirements( # Map each link to the requirement that owns it. This allows us to set # `req.local_file_path` on the appropriate requirement after passing # all the links at once into BatchDownloader. - links_to_fully_download: Dict[Link, InstallRequirement] = {} + links_to_fully_download: dict[Link, InstallRequirement] = {} for req in partially_downloaded_reqs: assert req.link links_to_fully_download[req.link] = req @@ -542,7 +543,7 @@ def prepare_linked_requirements_more( # Prepare requirements we found were already downloaded for some # reason. The other downloads will be completed separately. - partially_downloaded_reqs: List[InstallRequirement] = [] + partially_downloaded_reqs: list[InstallRequirement] = [] for req in reqs: if req.needs_more_preparation: partially_downloaded_reqs.append(req) diff --git a/src/pip/_internal/pyproject.py b/src/pip/_internal/pyproject.py index 0e8452f39dc..9cd6be912d1 100644 --- a/src/pip/_internal/pyproject.py +++ b/src/pip/_internal/pyproject.py @@ -2,7 +2,7 @@ import os import sys from collections import namedtuple -from typing import Any, List, Optional +from typing import Any, Optional if sys.version_info >= (3, 11): import tomllib @@ -166,7 +166,7 @@ def load_pyproject_toml( backend = build_system.get("build-backend") backend_path = build_system.get("backend-path", []) - check: List[str] = [] + check: list[str] = [] if backend is None: # If the user didn't specify a backend, we assume they want to use # the setuptools backend. But we can't be sure they have included diff --git a/src/pip/_internal/req/__init__.py b/src/pip/_internal/req/__init__.py index 422d851d729..00ddefe36a2 100644 --- a/src/pip/_internal/req/__init__.py +++ b/src/pip/_internal/req/__init__.py @@ -1,7 +1,8 @@ import collections import logging +from collections.abc import Generator, Sequence from dataclasses import dataclass -from typing import Generator, List, Optional, Sequence, Tuple +from typing import Optional from pip._internal.utils.logging import indent_log @@ -25,15 +26,15 @@ class InstallationResult: def _validate_requirements( - requirements: List[InstallRequirement], -) -> Generator[Tuple[str, InstallRequirement], None, None]: + requirements: list[InstallRequirement], +) -> Generator[tuple[str, InstallRequirement], None, None]: for req in requirements: assert req.name, f"invalid to-be-installed requirement: {req}" yield req.name, req def install_given_reqs( - requirements: List[InstallRequirement], + requirements: list[InstallRequirement], global_options: Sequence[str], root: Optional[str], home: Optional[str], @@ -41,7 +42,7 @@ def install_given_reqs( warn_script_location: bool, use_user_site: bool, pycompile: bool, -) -> List[InstallationResult]: +) -> list[InstallationResult]: """ Install everything in the given list. diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py index 56a964f3177..175aed5791e 100644 --- a/src/pip/_internal/req/constructors.py +++ b/src/pip/_internal/req/constructors.py @@ -12,8 +12,9 @@ import logging import os import re +from collections.abc import Collection from dataclasses import dataclass -from typing import Collection, Dict, List, Optional, Set, Tuple, Union +from typing import Optional, Union from pip._vendor.packaging.markers import Marker from pip._vendor.packaging.requirements import InvalidRequirement, Requirement @@ -41,7 +42,7 @@ operators = Specifier._operators.keys() -def _strip_extras(path: str) -> Tuple[str, Optional[str]]: +def _strip_extras(path: str) -> tuple[str, Optional[str]]: m = re.match(r"^(.+)(\[[^\]]+\])$", path) extras = None if m: @@ -53,13 +54,13 @@ def _strip_extras(path: str) -> Tuple[str, Optional[str]]: return path_no_extras, extras -def convert_extras(extras: Optional[str]) -> Set[str]: +def convert_extras(extras: Optional[str]) -> set[str]: if not extras: return set() return get_requirement("placeholder" + extras.lower()).extras -def _set_requirement_extras(req: Requirement, new_extras: Set[str]) -> Requirement: +def _set_requirement_extras(req: Requirement, new_extras: set[str]) -> Requirement: """ Returns a new requirement based on the given one, with the supplied extras. If the given requirement already has extras those are replaced (or dropped if no new extras @@ -84,7 +85,7 @@ def _set_requirement_extras(req: Requirement, new_extras: Set[str]) -> Requireme return get_requirement(f"{pre}{extras}{post}") -def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]: +def parse_editable(editable_req: str) -> tuple[Optional[str], str, set[str]]: """Parses an editable requirement into: - a requirement name - an URL @@ -197,7 +198,7 @@ class RequirementParts: requirement: Optional[Requirement] link: Optional[Link] markers: Optional[Marker] - extras: Set[str] + extras: set[str] def parse_req_from_editable(editable_req: str) -> RequirementParts: @@ -225,12 +226,12 @@ def install_req_from_editable( *, use_pep517: Optional[bool] = None, isolated: bool = False, - global_options: Optional[List[str]] = None, - hash_options: Optional[Dict[str, List[str]]] = None, + global_options: Optional[list[str]] = None, + hash_options: Optional[dict[str, list[str]]] = None, constraint: bool = False, user_supplied: bool = False, permit_editable_wheels: bool = False, - config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, + config_settings: Optional[dict[str, Union[str, list[str]]]] = None, ) -> InstallRequirement: parts = parse_req_from_editable(editable_req) @@ -389,12 +390,12 @@ def install_req_from_line( *, use_pep517: Optional[bool] = None, isolated: bool = False, - global_options: Optional[List[str]] = None, - hash_options: Optional[Dict[str, List[str]]] = None, + global_options: Optional[list[str]] = None, + hash_options: Optional[dict[str, list[str]]] = None, constraint: bool = False, line_source: Optional[str] = None, user_supplied: bool = False, - config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, + config_settings: Optional[dict[str, Union[str, list[str]]]] = None, ) -> InstallRequirement: """Creates an InstallRequirement from a name, which might be a requirement, directory containing 'setup.py', filename, or URL. @@ -463,7 +464,7 @@ def install_req_from_parsed_requirement( isolated: bool = False, use_pep517: Optional[bool] = None, user_supplied: bool = False, - config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, + config_settings: Optional[dict[str, Union[str, list[str]]]] = None, ) -> InstallRequirement: if parsed_req.is_editable: req = install_req_from_editable( diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py index f6ba70fe7f6..8c048536b76 100644 --- a/src/pip/_internal/req/req_file.py +++ b/src/pip/_internal/req/req_file.py @@ -11,19 +11,15 @@ import shlex import sys import urllib.parse +from collections.abc import Generator, Iterable from dataclasses import dataclass from optparse import Values from typing import ( TYPE_CHECKING, Any, Callable, - Dict, - Generator, - Iterable, - List, NoReturn, Optional, - Tuple, ) from pip._internal.cli import cmdoptions @@ -36,9 +32,9 @@ __all__ = ["parse_requirements"] -ReqFileLines = Iterable[Tuple[int, str]] +ReqFileLines = Iterable[tuple[int, str]] -LineParser = Callable[[str], Tuple[str, Values]] +LineParser = Callable[[str], tuple[str, Values]] SCHEME_RE = re.compile(r"^(http|https|file):", re.I) COMMENT_RE = re.compile(r"(^|\s+)#.*$") @@ -49,7 +45,7 @@ # 2013 Edition. ENV_VAR_RE = re.compile(r"(?P\$\{(?P[A-Z0-9_]+)\})") -SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [ +SUPPORTED_OPTIONS: list[Callable[..., optparse.Option]] = [ cmdoptions.index_url, cmdoptions.extra_index_url, cmdoptions.no_index, @@ -67,13 +63,13 @@ ] # options to be passed to requirements -SUPPORTED_OPTIONS_REQ: List[Callable[..., optparse.Option]] = [ +SUPPORTED_OPTIONS_REQ: list[Callable[..., optparse.Option]] = [ cmdoptions.global_options, cmdoptions.hash, cmdoptions.config_settings, ] -SUPPORTED_OPTIONS_EDITABLE_REQ: List[Callable[..., optparse.Option]] = [ +SUPPORTED_OPTIONS_EDITABLE_REQ: list[Callable[..., optparse.Option]] = [ cmdoptions.config_settings, ] @@ -86,7 +82,7 @@ # order of BOMS is important: codecs.BOM_UTF16_LE is a prefix of codecs.BOM_UTF32_LE # so data.startswith(BOM_UTF16_LE) would be true for UTF32_LE data -BOMS: List[Tuple[bytes, str]] = [ +BOMS: list[tuple[bytes, str]] = [ (codecs.BOM_UTF8, "utf-8"), (codecs.BOM_UTF32, "utf-32"), (codecs.BOM_UTF32_BE, "utf-32-be"), @@ -118,7 +114,7 @@ class ParsedRequirement: is_editable: bool comes_from: str constraint: bool - options: Optional[Dict[str, Any]] + options: Optional[dict[str, Any]] line_source: Optional[str] @@ -354,7 +350,7 @@ def _parse_and_recurse( self, filename: str, constraint: bool, - parsed_files_stack: List[Dict[str, Optional[str]]], + parsed_files_stack: list[dict[str, Optional[str]]], ) -> Generator[ParsedLine, None, None]: for line in self._parse_file(filename, constraint): if line.requirement is None and ( @@ -427,7 +423,7 @@ def _parse_file( def get_line_parser(finder: Optional["PackageFinder"]) -> LineParser: - def parse_line(line: str) -> Tuple[str, Values]: + def parse_line(line: str) -> tuple[str, Values]: # Build new parser for each line since it accumulates appendable # options. parser = build_parser() @@ -450,7 +446,7 @@ def parse_line(line: str) -> Tuple[str, Values]: return parse_line -def break_args_options(line: str) -> Tuple[str, str]: +def break_args_options(line: str) -> tuple[str, str]: """Break up the line into an args and options string. We only want to shlex (and then optparse) the options, not the args. args can contain markers which are corrupted by shlex. @@ -500,7 +496,7 @@ def join_lines(lines_enum: ReqFileLines) -> ReqFileLines: comments). The joined line takes on the index of the first line. """ primary_line_number = None - new_line: List[str] = [] + new_line: list[str] = [] for line_number, line in lines_enum: if not line.endswith("\\") or COMMENT_RE.match(line): if COMMENT_RE.match(line): @@ -564,7 +560,7 @@ def expand_env_variables(lines_enum: ReqFileLines) -> ReqFileLines: yield line_number, line -def get_file_content(url: str, session: "PipSession") -> Tuple[str, str]: +def get_file_content(url: str, session: "PipSession") -> tuple[str, str]: """Gets the content of a file; it may be a filename, file: URL, or http: URL. Returns (location, content). Content is unicode. Respects # -*- coding: declarations on the retrieved files. diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 3262d82658e..d23ec0d4751 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -5,9 +5,10 @@ import sys import uuid import zipfile +from collections.abc import Collection, Iterable, Sequence from optparse import Values from pathlib import Path -from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Union +from typing import Any, Optional, Union from pip._vendor.packaging.markers import Marker from pip._vendor.packaging.requirements import Requirement @@ -79,9 +80,9 @@ def __init__( use_pep517: Optional[bool] = None, isolated: bool = False, *, - global_options: Optional[List[str]] = None, - hash_options: Optional[Dict[str, List[str]]] = None, - config_settings: Optional[Dict[str, Union[str, List[str]]]] = None, + global_options: Optional[list[str]] = None, + hash_options: Optional[dict[str, list[str]]] = None, + config_settings: Optional[dict[str, Union[str, list[str]]]] = None, constraint: bool = False, extras: Collection[str] = (), user_supplied: bool = False, @@ -166,10 +167,10 @@ def __init__( self.metadata_directory: Optional[str] = None # The static build requirements (from pyproject.toml) - self.pyproject_requires: Optional[List[str]] = None + self.pyproject_requires: Optional[list[str]] = None # Build requirements that we will check are available - self.requirements_to_check: List[str] = [] + self.requirements_to_check: list[str] = [] # The PEP 517 backend we should use to build the project self.pep517_backend: Optional[BuildBackendHookCaller] = None @@ -905,7 +906,7 @@ def check_invalid_constraint_type(req: InstallRequirement) -> str: return problem -def _has_option(options: Values, reqs: List[InstallRequirement], option: str) -> bool: +def _has_option(options: Values, reqs: list[InstallRequirement], option: str) -> bool: if getattr(options, option, None): return True for req in reqs: @@ -916,7 +917,7 @@ def _has_option(options: Values, reqs: List[InstallRequirement], option: str) -> def check_legacy_setup_py_options( options: Values, - reqs: List[InstallRequirement], + reqs: list[InstallRequirement], ) -> None: has_build_options = _has_option(options, reqs, "build_options") has_global_options = _has_option(options, reqs, "global_options") diff --git a/src/pip/_internal/req/req_set.py b/src/pip/_internal/req/req_set.py index ec7a6e07a25..3451b24f27b 100644 --- a/src/pip/_internal/req/req_set.py +++ b/src/pip/_internal/req/req_set.py @@ -1,6 +1,5 @@ import logging from collections import OrderedDict -from typing import Dict, List from pip._vendor.packaging.utils import canonicalize_name @@ -13,10 +12,10 @@ class RequirementSet: def __init__(self, check_supported_wheels: bool = True) -> None: """Create a RequirementSet.""" - self.requirements: Dict[str, InstallRequirement] = OrderedDict() + self.requirements: dict[str, InstallRequirement] = OrderedDict() self.check_supported_wheels = check_supported_wheels - self.unnamed_requirements: List[InstallRequirement] = [] + self.unnamed_requirements: list[InstallRequirement] = [] def __str__(self) -> str: requirements = sorted( @@ -65,11 +64,11 @@ def get_requirement(self, name: str) -> InstallRequirement: raise KeyError(f"No project with the name {name!r}") @property - def all_requirements(self) -> List[InstallRequirement]: + def all_requirements(self) -> list[InstallRequirement]: return self.unnamed_requirements + list(self.requirements.values()) @property - def requirements_to_install(self) -> List[InstallRequirement]: + def requirements_to_install(self) -> list[InstallRequirement]: """Return the list of requirements that need to be installed. TODO remove this property together with the legacy resolver, since the new diff --git a/src/pip/_internal/req/req_uninstall.py b/src/pip/_internal/req/req_uninstall.py index 26df20844b3..9f8118c8327 100644 --- a/src/pip/_internal/req/req_uninstall.py +++ b/src/pip/_internal/req/req_uninstall.py @@ -2,8 +2,9 @@ import os import sys import sysconfig +from collections.abc import Generator, Iterable from importlib.util import cache_from_source -from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Set, Tuple +from typing import Any, Callable, Optional from pip._internal.exceptions import LegacyDistutilsInstall, UninstallMissingRecord from pip._internal.locations import get_bin_prefix, get_bin_user @@ -42,7 +43,7 @@ def _unique( ) -> Callable[..., Generator[Any, None, None]]: @functools.wraps(fn) def unique(*args: Any, **kw: Any) -> Generator[Any, None, None]: - seen: Set[Any] = set() + seen: set[Any] = set() for item in fn(*args, **kw): if item not in seen: seen.add(item) @@ -85,14 +86,14 @@ def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]: yield path -def compact(paths: Iterable[str]) -> Set[str]: +def compact(paths: Iterable[str]) -> set[str]: """Compact a path set to contain the minimal number of paths necessary to contain all paths in the set. If /a/path/ and /a/path/to/a/file.txt are both in the set, leave only the shorter path.""" sep = os.path.sep - short_paths: Set[str] = set() + short_paths: set[str] = set() for path in sorted(paths, key=len): should_skip = any( path.startswith(shortpath.rstrip("*")) @@ -104,7 +105,7 @@ def compact(paths: Iterable[str]) -> Set[str]: return short_paths -def compress_for_rename(paths: Iterable[str]) -> Set[str]: +def compress_for_rename(paths: Iterable[str]) -> set[str]: """Returns a set containing the paths that need to be renamed. This set may include directories when the original sequence of paths @@ -113,7 +114,7 @@ def compress_for_rename(paths: Iterable[str]) -> Set[str]: case_map = {os.path.normcase(p): p for p in paths} remaining = set(case_map) unchecked = sorted({os.path.split(p)[0] for p in case_map.values()}, key=len) - wildcards: Set[str] = set() + wildcards: set[str] = set() def norm_join(*a: str) -> str: return os.path.normcase(os.path.join(*a)) @@ -123,8 +124,8 @@ def norm_join(*a: str) -> str: # This directory has already been handled. continue - all_files: Set[str] = set() - all_subdirs: Set[str] = set() + all_files: set[str] = set() + all_subdirs: set[str] = set() for dirname, subdirs, files in os.walk(root): all_subdirs.update(norm_join(root, dirname, d) for d in subdirs) all_files.update(norm_join(root, dirname, f) for f in files) @@ -138,7 +139,7 @@ def norm_join(*a: str) -> str: return set(map(case_map.__getitem__, remaining)) | wildcards -def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str]]: +def compress_for_output_listing(paths: Iterable[str]) -> tuple[set[str], set[str]]: """Returns a tuple of 2 sets of which paths to display to user The first set contains paths that would be deleted. Files of a package @@ -194,10 +195,10 @@ class StashedUninstallPathSet: def __init__(self) -> None: # Mapping from source file root to [Adjacent]TempDirectory # for files under that directory. - self._save_dirs: Dict[str, TempDirectory] = {} + self._save_dirs: dict[str, TempDirectory] = {} # (old path, new path) tuples for each move that may need # to be undone. - self._moves: List[Tuple[str, str]] = [] + self._moves: list[tuple[str, str]] = [] def _get_directory_stash(self, path: str) -> str: """Stashes a directory. @@ -297,9 +298,9 @@ class UninstallPathSet: requirement.""" def __init__(self, dist: BaseDistribution) -> None: - self._paths: Set[str] = set() - self._refuse: Set[str] = set() - self._pth: Dict[str, UninstallPthEntries] = {} + self._paths: set[str] = set() + self._refuse: set[str] = set() + self._pth: dict[str, UninstallPthEntries] = {} self._dist = dist self._moved_paths = StashedUninstallPathSet() # Create local cache of normalize_path results. Creating an UninstallPathSet @@ -578,8 +579,8 @@ def iter_scripts_to_remove( class UninstallPthEntries: def __init__(self, pth_file: str) -> None: self.file = pth_file - self.entries: Set[str] = set() - self._saved_lines: Optional[List[bytes]] = None + self.entries: set[str] = set() + self._saved_lines: Optional[list[bytes]] = None def add(self, entry: str) -> None: entry = os.path.normcase(entry) diff --git a/src/pip/_internal/resolution/base.py b/src/pip/_internal/resolution/base.py index 42dade18c1e..5ec4d96aa78 100644 --- a/src/pip/_internal/resolution/base.py +++ b/src/pip/_internal/resolution/base.py @@ -1,4 +1,4 @@ -from typing import Callable, List, Optional +from typing import Callable, Optional from pip._internal.req.req_install import InstallRequirement from pip._internal.req.req_set import RequirementSet @@ -10,11 +10,11 @@ class BaseResolver: def resolve( - self, root_reqs: List[InstallRequirement], check_supported_wheels: bool + self, root_reqs: list[InstallRequirement], check_supported_wheels: bool ) -> RequirementSet: raise NotImplementedError() def get_installation_order( self, req_set: RequirementSet - ) -> List[InstallRequirement]: + ) -> list[InstallRequirement]: raise NotImplementedError() diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py index 1dd0d7041bb..9b864da18c4 100644 --- a/src/pip/_internal/resolution/legacy/resolver.py +++ b/src/pip/_internal/resolution/legacy/resolver.py @@ -13,8 +13,9 @@ import logging import sys from collections import defaultdict +from collections.abc import Iterable from itertools import chain -from typing import DefaultDict, Iterable, List, Optional, Set, Tuple +from typing import Optional from pip._vendor.packaging import specifiers from pip._vendor.packaging.requirements import Requirement @@ -49,12 +50,12 @@ logger = logging.getLogger(__name__) -DiscoveredDependencies = DefaultDict[Optional[str], List[InstallRequirement]] +DiscoveredDependencies = defaultdict[Optional[str], list[InstallRequirement]] def _check_dist_requires_python( dist: BaseDistribution, - version_info: Tuple[int, int, int], + version_info: tuple[int, int, int], ignore_requires_python: bool = False, ) -> None: """ @@ -125,7 +126,7 @@ def __init__( ignore_requires_python: bool, force_reinstall: bool, upgrade_strategy: str, - py_version_info: Optional[Tuple[int, ...]] = None, + py_version_info: Optional[tuple[int, ...]] = None, ) -> None: super().__init__() assert upgrade_strategy in self._allowed_strategies @@ -152,7 +153,7 @@ def __init__( self._discovered_dependencies: DiscoveredDependencies = defaultdict(list) def resolve( - self, root_reqs: List[InstallRequirement], check_supported_wheels: bool + self, root_reqs: list[InstallRequirement], check_supported_wheels: bool ) -> RequirementSet: """Resolve what operations need to be done @@ -174,7 +175,7 @@ def resolve( # exceptions cannot be checked ahead of time, because # _populate_link() needs to be called before we can make decisions # based on link type. - discovered_reqs: List[InstallRequirement] = [] + discovered_reqs: list[InstallRequirement] = [] hash_errors = HashErrors() for req in chain(requirement_set.all_requirements, discovered_reqs): try: @@ -194,7 +195,7 @@ def _add_requirement_to_set( install_req: InstallRequirement, parent_req_name: Optional[str] = None, extras_requested: Optional[Iterable[str]] = None, - ) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]: + ) -> tuple[list[InstallRequirement], Optional[InstallRequirement]]: """Add install_req as a requirement to install. :param parent_req_name: The name of the requirement that needed this @@ -488,7 +489,7 @@ def _resolve_one( self, requirement_set: RequirementSet, req_to_install: InstallRequirement, - ) -> List[InstallRequirement]: + ) -> list[InstallRequirement]: """Prepare a single requirements file. :return: A list of additional InstallRequirements to also install. @@ -511,7 +512,7 @@ def _resolve_one( ignore_requires_python=self.ignore_requires_python, ) - more_reqs: List[InstallRequirement] = [] + more_reqs: list[InstallRequirement] = [] def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None: # This idiosyncratically converts the Requirement to str and let @@ -569,7 +570,7 @@ def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None: def get_installation_order( self, req_set: RequirementSet - ) -> List[InstallRequirement]: + ) -> list[InstallRequirement]: """Create the installation order. The installation order is topological - requirements are installed @@ -580,7 +581,7 @@ def get_installation_order( # installs the user specified things in the order given, except when # dependencies must come earlier to achieve topological order. order = [] - ordered_reqs: Set[InstallRequirement] = set() + ordered_reqs: set[InstallRequirement] = set() def schedule(req: InstallRequirement) -> None: if req.satisfied_by or req in ordered_reqs: diff --git a/src/pip/_internal/resolution/resolvelib/base.py b/src/pip/_internal/resolution/resolvelib/base.py index 0f31dc9b307..a4edf01e9c6 100644 --- a/src/pip/_internal/resolution/resolvelib/base.py +++ b/src/pip/_internal/resolution/resolvelib/base.py @@ -1,5 +1,6 @@ +from collections.abc import Iterable from dataclasses import dataclass -from typing import FrozenSet, Iterable, Optional, Tuple +from typing import Optional from pip._vendor.packaging.specifiers import SpecifierSet from pip._vendor.packaging.utils import NormalizedName @@ -9,10 +10,10 @@ from pip._internal.req.req_install import InstallRequirement from pip._internal.utils.hashes import Hashes -CandidateLookup = Tuple[Optional["Candidate"], Optional[InstallRequirement]] +CandidateLookup = tuple[Optional["Candidate"], Optional[InstallRequirement]] -def format_name(project: NormalizedName, extras: FrozenSet[NormalizedName]) -> str: +def format_name(project: NormalizedName, extras: frozenset[NormalizedName]) -> str: if not extras: return project extras_expr = ",".join(sorted(extras)) @@ -23,7 +24,7 @@ def format_name(project: NormalizedName, extras: FrozenSet[NormalizedName]) -> s class Constraint: specifier: SpecifierSet hashes: Hashes - links: FrozenSet[Link] + links: frozenset[Link] @classmethod def empty(cls) -> "Constraint": diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 6617644fe53..2a5d7c84ae5 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -1,6 +1,7 @@ import logging import sys -from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union, cast +from collections.abc import Iterable +from typing import TYPE_CHECKING, Any, Optional, Union, cast from pip._vendor.packaging.requirements import InvalidRequirement from pip._vendor.packaging.utils import NormalizedName, canonicalize_name @@ -438,7 +439,7 @@ class ExtrasCandidate(Candidate): def __init__( self, base: BaseCandidate, - extras: FrozenSet[str], + extras: frozenset[str], *, comes_from: Optional[InstallRequirement] = None, ) -> None: @@ -538,7 +539,7 @@ class RequiresPythonCandidate(Candidate): is_installed = False source_link = None - def __init__(self, py_version_info: Optional[Tuple[int, ...]]) -> None: + def __init__(self, py_version_info: Optional[tuple[int, ...]]) -> None: if py_version_info is not None: version_info = normalize_version_info(py_version_info) else: diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 6c273eb88db..61bb81b6743 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -1,21 +1,13 @@ import contextlib import functools import logging +from collections.abc import Iterable, Iterator, Mapping, Sequence from typing import ( TYPE_CHECKING, Callable, - Dict, - FrozenSet, - Iterable, - Iterator, - List, - Mapping, NamedTuple, Optional, Protocol, - Sequence, - Set, - Tuple, TypeVar, cast, ) @@ -84,13 +76,13 @@ class ConflictCause(Protocol): logger = logging.getLogger(__name__) C = TypeVar("C") -Cache = Dict[Link, C] +Cache = dict[Link, C] class CollectedRootRequirements(NamedTuple): - requirements: List[Requirement] - constraints: Dict[str, Constraint] - user_requested: Dict[str, int] + requirements: list[Requirement] + constraints: dict[str, Constraint] + user_requested: dict[str, int] class Factory: @@ -104,7 +96,7 @@ def __init__( force_reinstall: bool, ignore_installed: bool, ignore_requires_python: bool, - py_version_info: Optional[Tuple[int, ...]] = None, + py_version_info: Optional[tuple[int, ...]] = None, ) -> None: self._finder = finder self.preparer = preparer @@ -118,9 +110,9 @@ def __init__( self._build_failures: Cache[InstallationError] = {} self._link_candidate_cache: Cache[LinkCandidate] = {} self._editable_candidate_cache: Cache[EditableCandidate] = {} - self._installed_candidate_cache: Dict[str, AlreadyInstalledCandidate] = {} - self._extras_candidate_cache: Dict[ - Tuple[int, FrozenSet[NormalizedName]], ExtrasCandidate + self._installed_candidate_cache: dict[str, AlreadyInstalledCandidate] = {} + self._extras_candidate_cache: dict[ + tuple[int, frozenset[NormalizedName]], ExtrasCandidate ] = {} self._supported_tags_cache = get_supported() @@ -149,7 +141,7 @@ def _fail_if_link_is_unsupported_wheel(self, link: Link) -> None: def _make_extras_candidate( self, base: BaseCandidate, - extras: FrozenSet[str], + extras: frozenset[str], *, comes_from: Optional[InstallRequirement] = None, ) -> ExtrasCandidate: @@ -164,7 +156,7 @@ def _make_extras_candidate( def _make_candidate_from_dist( self, dist: BaseDistribution, - extras: FrozenSet[str], + extras: frozenset[str], template: InstallRequirement, ) -> Candidate: try: @@ -179,7 +171,7 @@ def _make_candidate_from_dist( def _make_candidate_from_link( self, link: Link, - extras: FrozenSet[str], + extras: frozenset[str], template: InstallRequirement, name: Optional[NormalizedName], version: Optional[Version], @@ -254,7 +246,7 @@ def _iter_found_candidates( specifier: SpecifierSet, hashes: Hashes, prefers_installed: bool, - incompatible_ids: Set[int], + incompatible_ids: set[int], ) -> Iterable[Candidate]: if not ireqs: return () @@ -267,7 +259,7 @@ def _iter_found_candidates( assert template.req, "Candidates found on index must be PEP 508" name = canonicalize_name(template.req.name) - extras: FrozenSet[str] = frozenset() + extras: frozenset[str] = frozenset() for ireq in ireqs: assert ireq.req, "Candidates found on index must be PEP 508" specifier &= ireq.req.specifier @@ -353,7 +345,7 @@ def is_pinned(specifier: SpecifierSet) -> bool: def _iter_explicit_candidates_from_base( self, base_requirements: Iterable[Requirement], - extras: FrozenSet[str], + extras: frozenset[str], ) -> Iterator[Candidate]: """Produce explicit candidates from the base given an extra-ed package. @@ -404,8 +396,8 @@ def find_candidates( is_satisfied_by: Callable[[Requirement, Candidate], bool], ) -> Iterable[Candidate]: # Collect basic lookup information from the requirements. - explicit_candidates: Set[Candidate] = set() - ireqs: List[InstallRequirement] = [] + explicit_candidates: set[Candidate] = set() + ireqs: list[InstallRequirement] = [] for req in requirements[identifier]: cand, ireq = req.get_candidate_lookup() if cand is not None: @@ -524,7 +516,7 @@ def _make_requirements_from_install_req( ) def collect_root_requirements( - self, root_ireqs: List[InstallRequirement] + self, root_ireqs: list[InstallRequirement] ) -> CollectedRootRequirements: collected = CollectedRootRequirements([], {}, {}) for i, ireq in enumerate(root_ireqs): @@ -679,8 +671,8 @@ def _report_single_requirement_conflict( cands = self._finder.find_all_candidates(req.project_name) skipped_by_requires_python = self._finder.requires_python_skipped_reasons() - versions_set: Set[Version] = set() - yanked_versions_set: Set[Version] = set() + versions_set: set[Version] = set() + yanked_versions_set: set[Version] = set() for c in cands: is_yanked = c.link.is_yanked if c.link else False if is_yanked: @@ -723,7 +715,7 @@ def _report_single_requirement_conflict( def get_installation_error( self, e: "ResolutionImpossible[Requirement, Candidate]", - constraints: Dict[str, Constraint], + constraints: dict[str, Constraint], ) -> InstallationError: assert e.causes, "Installation error reported with no cause" @@ -756,7 +748,7 @@ def get_installation_error( # satisfied at once. # A couple of formatting helpers - def text_join(parts: List[str]) -> str: + def text_join(parts: list[str]) -> str: if len(parts) == 1: return parts[0] diff --git a/src/pip/_internal/resolution/resolvelib/found_candidates.py b/src/pip/_internal/resolution/resolvelib/found_candidates.py index a1d57e0f4b2..813257e3298 100644 --- a/src/pip/_internal/resolution/resolvelib/found_candidates.py +++ b/src/pip/_internal/resolution/resolvelib/found_candidates.py @@ -10,8 +10,8 @@ import functools import logging -from collections.abc import Sequence -from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Set, Tuple +from collections.abc import Iterator, Sequence +from typing import Any, Callable, Optional from pip._vendor.packaging.version import _BaseVersion @@ -21,22 +21,7 @@ logger = logging.getLogger(__name__) -IndexCandidateInfo = Tuple[_BaseVersion, Callable[[], Optional[Candidate]]] - -if TYPE_CHECKING: - SequenceCandidate = Sequence[Candidate] -else: - # For compatibility: Python before 3.9 does not support using [] on the - # Sequence class. - # - # >>> from collections.abc import Sequence - # >>> Sequence[str] - # Traceback (most recent call last): - # File "", line 1, in - # TypeError: 'ABCMeta' object is not subscriptable - # - # TODO: Remove this block after dropping Python 3.8 support. - SequenceCandidate = Sequence +IndexCandidateInfo = tuple[_BaseVersion, Callable[[], Optional[Candidate]]] def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]: @@ -45,7 +30,7 @@ def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]: This iterator is used when the package is not already installed. Candidates from index come later in their normal ordering. """ - versions_found: Set[_BaseVersion] = set() + versions_found: set[_BaseVersion] = set() for version, func in infos: if version in versions_found: continue @@ -81,7 +66,7 @@ def _iter_built_with_prepended( normal ordering, except skipped when the version is already installed. """ yield installed - versions_found: Set[_BaseVersion] = {installed.version} + versions_found: set[_BaseVersion] = {installed.version} for version, func in infos: if version in versions_found: continue @@ -105,7 +90,7 @@ def _iter_built_with_inserted( the installed candidate exactly once before we start yielding older or equivalent candidates, or after all other candidates if they are all newer. """ - versions_found: Set[_BaseVersion] = set() + versions_found: set[_BaseVersion] = set() for version, func in infos: if version in versions_found: continue @@ -124,7 +109,7 @@ def _iter_built_with_inserted( yield installed -class FoundCandidates(SequenceCandidate): +class FoundCandidates(Sequence[Candidate]): """A lazy sequence to provide candidates to the resolver. The intended usage is to return this from `find_matches()` so the resolver @@ -138,7 +123,7 @@ def __init__( get_infos: Callable[[], Iterator[IndexCandidateInfo]], installed: Optional[Candidate], prefers_installed: bool, - incompatible_ids: Set[int], + incompatible_ids: set[int], ): self._get_infos = get_infos self._installed = installed diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index fb0dd85f112..e94cdf7749b 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -1,13 +1,9 @@ import collections import math -from functools import lru_cache +from collections.abc import Iterable, Iterator, Mapping, Sequence +from functools import cache from typing import ( TYPE_CHECKING, - Dict, - Iterable, - Iterator, - Mapping, - Sequence, TypeVar, Union, ) @@ -90,17 +86,17 @@ class PipProvider(_ProviderBase): def __init__( self, factory: Factory, - constraints: Dict[str, Constraint], + constraints: dict[str, Constraint], ignore_dependencies: bool, upgrade_strategy: str, - user_requested: Dict[str, int], + user_requested: dict[str, int], ) -> None: self._factory = factory self._constraints = constraints self._ignore_dependencies = ignore_dependencies self._upgrade_strategy = upgrade_strategy self._user_requested = user_requested - self._known_depths: Dict[str, float] = collections.defaultdict(lambda: math.inf) + self._known_depths: dict[str, float] = collections.defaultdict(lambda: math.inf) def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str: return requirement_or_candidate.name @@ -238,7 +234,7 @@ def _eligible_for_upgrade(identifier: str) -> bool: is_satisfied_by=self.is_satisfied_by, ) - @lru_cache(maxsize=None) + @cache def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> bool: return requirement.is_satisfied_by(candidate) diff --git a/src/pip/_internal/resolution/resolvelib/reporter.py b/src/pip/_internal/resolution/resolvelib/reporter.py index 0594569d850..b3a251e3204 100644 --- a/src/pip/_internal/resolution/resolvelib/reporter.py +++ b/src/pip/_internal/resolution/resolvelib/reporter.py @@ -1,6 +1,6 @@ from collections import defaultdict from logging import getLogger -from typing import Any, DefaultDict +from typing import Any from pip._vendor.resolvelib.reporters import BaseReporter @@ -11,7 +11,7 @@ class PipReporter(BaseReporter): def __init__(self) -> None: - self.reject_count_by_package: DefaultDict[str, int] = defaultdict(int) + self.reject_count_by_package: defaultdict[str, int] = defaultdict(int) self._messages_at_reject_count = { 1: ( diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py index c12beef0b2a..2282cd895a4 100644 --- a/src/pip/_internal/resolution/resolvelib/resolver.py +++ b/src/pip/_internal/resolution/resolvelib/resolver.py @@ -2,7 +2,7 @@ import functools import logging import os -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast +from typing import TYPE_CHECKING, Optional, cast from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible @@ -50,7 +50,7 @@ def __init__( ignore_requires_python: bool, force_reinstall: bool, upgrade_strategy: str, - py_version_info: Optional[Tuple[int, ...]] = None, + py_version_info: Optional[tuple[int, ...]] = None, ): super().__init__() assert upgrade_strategy in self._allowed_strategies @@ -71,7 +71,7 @@ def __init__( self._result: Optional[Result] = None def resolve( - self, root_reqs: List[InstallRequirement], check_supported_wheels: bool + self, root_reqs: list[InstallRequirement], check_supported_wheels: bool ) -> RequirementSet: collected = self.factory.collect_root_requirements(root_reqs) provider = PipProvider( @@ -184,7 +184,7 @@ def resolve( def get_installation_order( self, req_set: RequirementSet - ) -> List[InstallRequirement]: + ) -> list[InstallRequirement]: """Get order for installation of requirements in RequirementSet. The returned list contains a requirement before another that depends on @@ -215,8 +215,8 @@ def get_installation_order( def get_topological_weights( - graph: "DirectedGraph[Optional[str]]", requirement_keys: Set[str] -) -> Dict[Optional[str], int]: + graph: "DirectedGraph[Optional[str]]", requirement_keys: set[str] +) -> dict[Optional[str], int]: """Assign weights to each node based on how "deep" they are. This implementation may change at any point in the future without prior @@ -242,8 +242,8 @@ def get_topological_weights( We are only interested in the weights of packages that are in the requirement_keys. """ - path: Set[Optional[str]] = set() - weights: Dict[Optional[str], int] = {} + path: set[Optional[str]] = set() + weights: dict[Optional[str], int] = {} def visit(node: Optional[str]) -> None: if node in path: @@ -304,9 +304,9 @@ def visit(node: Optional[str]) -> None: def _req_set_item_sorter( - item: Tuple[str, InstallRequirement], - weights: Dict[Optional[str], int], -) -> Tuple[int, str]: + item: tuple[str, InstallRequirement], + weights: dict[Optional[str], int], +) -> tuple[int, str]: """Key function used to sort install requirements for installation. Based on the "weight" mapping calculated in ``get_installation_order()``. diff --git a/src/pip/_internal/self_outdated_check.py b/src/pip/_internal/self_outdated_check.py index 2e0e3df3542..1644b76f2c6 100644 --- a/src/pip/_internal/self_outdated_check.py +++ b/src/pip/_internal/self_outdated_check.py @@ -7,7 +7,7 @@ import os.path import sys from dataclasses import dataclass -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable, Optional from pip._vendor.packaging.version import Version from pip._vendor.packaging.version import parse as parse_version @@ -54,7 +54,7 @@ def _convert_date(isodate: str) -> datetime.datetime: class SelfCheckState: def __init__(self, cache_dir: str) -> None: - self._state: Dict[str, Any] = {} + self._state: dict[str, Any] = {} self._statefile_path = None # Try to load the existing state diff --git a/src/pip/_internal/utils/appdirs.py b/src/pip/_internal/utils/appdirs.py index 16933bf8afe..87748b5a280 100644 --- a/src/pip/_internal/utils/appdirs.py +++ b/src/pip/_internal/utils/appdirs.py @@ -8,7 +8,6 @@ import os import sys -from typing import List from pip._vendor import platformdirs as _appdirs @@ -40,7 +39,7 @@ def user_config_dir(appname: str, roaming: bool = True) -> str: # for the discussion regarding site_config_dir locations # see -def site_config_dirs(appname: str) -> List[str]: +def site_config_dirs(appname: str) -> list[str]: if sys.platform == "darwin": return [_appdirs.site_data_dir(appname, appauthor=False, multipath=True)] diff --git a/src/pip/_internal/utils/compatibility_tags.py b/src/pip/_internal/utils/compatibility_tags.py index 2e7b7450dce..2523abcc8ca 100644 --- a/src/pip/_internal/utils/compatibility_tags.py +++ b/src/pip/_internal/utils/compatibility_tags.py @@ -2,7 +2,7 @@ """ import re -from typing import List, Optional, Tuple +from typing import Optional from pip._vendor.packaging.tags import ( PythonVersion, @@ -19,12 +19,12 @@ _apple_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)") -def version_info_to_nodot(version_info: Tuple[int, ...]) -> str: +def version_info_to_nodot(version_info: tuple[int, ...]) -> str: # Only use up to the first two numbers. return "".join(map(str, version_info[:2])) -def _mac_platforms(arch: str) -> List[str]: +def _mac_platforms(arch: str) -> list[str]: match = _apple_arch_pat.match(arch) if match: name, major, minor, actual_arch = match.groups() @@ -44,7 +44,7 @@ def _mac_platforms(arch: str) -> List[str]: return arches -def _ios_platforms(arch: str) -> List[str]: +def _ios_platforms(arch: str) -> list[str]: match = _apple_arch_pat.match(arch) if match: name, major, minor, actual_multiarch = match.groups() @@ -64,7 +64,7 @@ def _ios_platforms(arch: str) -> List[str]: return arches -def _custom_manylinux_platforms(arch: str) -> List[str]: +def _custom_manylinux_platforms(arch: str) -> list[str]: arches = [arch] arch_prefix, arch_sep, arch_suffix = arch.partition("_") if arch_prefix == "manylinux2014": @@ -85,7 +85,7 @@ def _custom_manylinux_platforms(arch: str) -> List[str]: return arches -def _get_custom_platforms(arch: str) -> List[str]: +def _get_custom_platforms(arch: str) -> list[str]: arch_prefix, arch_sep, arch_suffix = arch.partition("_") if arch.startswith("macosx"): arches = _mac_platforms(arch) @@ -98,7 +98,7 @@ def _get_custom_platforms(arch: str) -> List[str]: return arches -def _expand_allowed_platforms(platforms: Optional[List[str]]) -> Optional[List[str]]: +def _expand_allowed_platforms(platforms: Optional[list[str]]) -> Optional[list[str]]: if not platforms: return None @@ -134,10 +134,10 @@ def _get_custom_interpreter( def get_supported( version: Optional[str] = None, - platforms: Optional[List[str]] = None, + platforms: Optional[list[str]] = None, impl: Optional[str] = None, - abis: Optional[List[str]] = None, -) -> List[Tag]: + abis: Optional[list[str]] = None, +) -> list[Tag]: """Return a list of supported tags for each version specified in `versions`. @@ -150,7 +150,7 @@ def get_supported( :param abis: specify a list of abis you want valid tags for, or None. If None, use the local interpreter abi. """ - supported: List[Tag] = [] + supported: list[Tag] = [] python_version: Optional[PythonVersion] = None if version is not None: diff --git a/src/pip/_internal/utils/deprecation.py b/src/pip/_internal/utils/deprecation.py index 0911147e784..0679657231b 100644 --- a/src/pip/_internal/utils/deprecation.py +++ b/src/pip/_internal/utils/deprecation.py @@ -4,7 +4,7 @@ import logging import warnings -from typing import Any, Optional, TextIO, Type, Union +from typing import Any, Optional, TextIO, Union from pip._vendor.packaging.version import parse @@ -23,7 +23,7 @@ class PipDeprecationWarning(Warning): # Warnings <-> Logging Integration def _showwarning( message: Union[Warning, str], - category: Type[Warning], + category: type[Warning], filename: str, lineno: int, file: Optional[TextIO] = None, diff --git a/src/pip/_internal/utils/egg_link.py b/src/pip/_internal/utils/egg_link.py index 4a384a63682..766219f3d30 100644 --- a/src/pip/_internal/utils/egg_link.py +++ b/src/pip/_internal/utils/egg_link.py @@ -1,7 +1,7 @@ import os import re import sys -from typing import List, Optional +from typing import Optional from pip._internal.locations import site_packages, user_site from pip._internal.utils.virtualenv import ( @@ -15,7 +15,7 @@ ] -def _egg_link_names(raw_name: str) -> List[str]: +def _egg_link_names(raw_name: str) -> list[str]: """ Convert a Name metadata value to a .egg-link name, by applying the same substitution as pkg_resources's safe_name function. @@ -61,7 +61,7 @@ def egg_link_path_from_location(raw_name: str) -> Optional[str]: This method will just return the first one found. """ - sites: List[str] = [] + sites: list[str] = [] if running_under_virtualenv(): sites.append(site_packages) if not virtualenv_no_global() and user_site: diff --git a/src/pip/_internal/utils/entrypoints.py b/src/pip/_internal/utils/entrypoints.py index 15013693854..0d9f639b080 100644 --- a/src/pip/_internal/utils/entrypoints.py +++ b/src/pip/_internal/utils/entrypoints.py @@ -2,7 +2,7 @@ import os import shutil import sys -from typing import List, Optional +from typing import Optional from pip._internal.cli.main import main from pip._internal.utils.compat import WINDOWS @@ -20,7 +20,7 @@ ] -def _wrapper(args: Optional[List[str]] = None) -> int: +def _wrapper(args: Optional[list[str]] = None) -> int: """Central wrapper for all old entrypoints. Historically pip has had several entrypoints defined. Because of issues diff --git a/src/pip/_internal/utils/filesystem.py b/src/pip/_internal/utils/filesystem.py index 22e356cdd75..9b848bf7704 100644 --- a/src/pip/_internal/utils/filesystem.py +++ b/src/pip/_internal/utils/filesystem.py @@ -3,9 +3,10 @@ import os.path import random import sys +from collections.abc import Generator from contextlib import contextmanager from tempfile import NamedTemporaryFile -from typing import Any, BinaryIO, Generator, List, Union, cast +from typing import Any, BinaryIO, Union, cast from pip._internal.utils.compat import get_path_uid from pip._internal.utils.misc import format_size @@ -115,10 +116,10 @@ def _test_writable_dir_win(path: str) -> bool: raise OSError("Unexpected condition testing for writable directory") -def find_files(path: str, pattern: str) -> List[str]: +def find_files(path: str, pattern: str) -> list[str]: """Returns a list of absolute paths of files beneath path, recursively, with filenames which match the UNIX-style shell glob pattern.""" - result: List[str] = [] + result: list[str] = [] for root, _, files in os.walk(path): matches = fnmatch.filter(files, pattern) result.extend(os.path.join(root, f) for f in matches) diff --git a/src/pip/_internal/utils/filetypes.py b/src/pip/_internal/utils/filetypes.py index 5948570178f..77e510d4ac9 100644 --- a/src/pip/_internal/utils/filetypes.py +++ b/src/pip/_internal/utils/filetypes.py @@ -1,21 +1,19 @@ """Filetype information. """ -from typing import Tuple - from pip._internal.utils.misc import splitext WHEEL_EXTENSION = ".whl" -BZ2_EXTENSIONS: Tuple[str, ...] = (".tar.bz2", ".tbz") -XZ_EXTENSIONS: Tuple[str, ...] = ( +BZ2_EXTENSIONS: tuple[str, ...] = (".tar.bz2", ".tbz") +XZ_EXTENSIONS: tuple[str, ...] = ( ".tar.xz", ".txz", ".tlz", ".tar.lz", ".tar.lzma", ) -ZIP_EXTENSIONS: Tuple[str, ...] = (".zip", WHEEL_EXTENSION) -TAR_EXTENSIONS: Tuple[str, ...] = (".tar.gz", ".tgz", ".tar") +ZIP_EXTENSIONS: tuple[str, ...] = (".zip", WHEEL_EXTENSION) +TAR_EXTENSIONS: tuple[str, ...] = (".tar.gz", ".tgz", ".tar") ARCHIVE_EXTENSIONS = ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS diff --git a/src/pip/_internal/utils/glibc.py b/src/pip/_internal/utils/glibc.py index 998868ff2a4..8267e67472e 100644 --- a/src/pip/_internal/utils/glibc.py +++ b/src/pip/_internal/utils/glibc.py @@ -1,6 +1,6 @@ import os import sys -from typing import Optional, Tuple +from typing import Optional def glibc_version_string() -> Optional[str]: @@ -88,7 +88,7 @@ def glibc_version_string_ctypes() -> Optional[str]: # versions that was generated by pip 8.1.2 and earlier is useless and # misleading. Solution: instead of using platform, use our code that actually # works. -def libc_ver() -> Tuple[str, str]: +def libc_ver() -> tuple[str, str]: """Try to determine the glibc version Returns a tuple of strings (lib, version) which default to empty strings diff --git a/src/pip/_internal/utils/hashes.py b/src/pip/_internal/utils/hashes.py index 535e94fca0c..a52760cc797 100644 --- a/src/pip/_internal/utils/hashes.py +++ b/src/pip/_internal/utils/hashes.py @@ -1,5 +1,6 @@ import hashlib -from typing import TYPE_CHECKING, BinaryIO, Dict, Iterable, List, NoReturn, Optional +from collections.abc import Iterable +from typing import TYPE_CHECKING, BinaryIO, NoReturn, Optional from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError from pip._internal.utils.misc import read_chunks @@ -24,7 +25,7 @@ class Hashes: """ - def __init__(self, hashes: Optional[Dict[str, List[str]]] = None) -> None: + def __init__(self, hashes: Optional[dict[str, list[str]]] = None) -> None: """ :param hashes: A dict of algorithm names pointing to lists of allowed hex digests @@ -86,7 +87,7 @@ def check_against_chunks(self, chunks: Iterable[bytes]) -> None: return self._raise(gots) - def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn": + def _raise(self, gots: dict[str, "_Hash"]) -> "NoReturn": raise HashMismatch(self._allowed, gots) def check_against_file(self, file: BinaryIO) -> None: @@ -101,7 +102,7 @@ def check_against_path(self, path: str) -> None: with open(path, "rb") as file: return self.check_against_file(file) - def has_one_of(self, hashes: Dict[str, str]) -> bool: + def has_one_of(self, hashes: dict[str, str]) -> bool: """Return whether any of the given hashes are allowed.""" for hash_name, hex_digest in hashes.items(): if self.is_hash_allowed(hash_name, hex_digest): @@ -143,5 +144,5 @@ def __init__(self) -> None: # empty list, it will never match, so an error will always raise. super().__init__(hashes={FAVORITE_HASH: []}) - def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn": + def _raise(self, gots: dict[str, "_Hash"]) -> "NoReturn": raise HashMissing(gots[FAVORITE_HASH].hexdigest()) diff --git a/src/pip/_internal/utils/logging.py b/src/pip/_internal/utils/logging.py index 62035fc40ec..28da78cc574 100644 --- a/src/pip/_internal/utils/logging.py +++ b/src/pip/_internal/utils/logging.py @@ -5,10 +5,11 @@ import os import sys import threading +from collections.abc import Generator from dataclasses import dataclass from io import TextIOWrapper from logging import Filter -from typing import Any, ClassVar, Generator, List, Optional, TextIO, Type +from typing import Any, ClassVar, Optional, TextIO from pip._vendor.rich.console import ( Console, @@ -38,7 +39,7 @@ class BrokenStdoutLoggingError(Exception): """ -def _is_broken_pipe_error(exc_class: Type[BaseException], exc: BaseException) -> bool: +def _is_broken_pipe_error(exc_class: type[BaseException], exc: BaseException) -> bool: if exc_class is BrokenPipeError: return True @@ -145,7 +146,7 @@ def on_broken_pipe(self) -> None: class RichPipStreamHandler(RichHandler): - KEYWORDS: ClassVar[Optional[List[str]]] = [] + KEYWORDS: ClassVar[Optional[list[str]]] = [] def __init__(self, stream: Optional[TextIO], no_color: bool) -> None: super().__init__( diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index 44f6a05fbdd..fac45ecd716 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -9,9 +9,9 @@ import sys import sysconfig import urllib.parse +from collections.abc import Generator, Iterable, Iterator, Mapping, Sequence from dataclasses import dataclass from functools import partial -from io import StringIO from itertools import filterfalse, tee, zip_longest from pathlib import Path from types import FunctionType, TracebackType @@ -19,16 +19,7 @@ Any, BinaryIO, Callable, - Generator, - Iterable, - Iterator, - List, - Mapping, Optional, - Sequence, - TextIO, - Tuple, - Type, TypeVar, Union, cast, @@ -64,9 +55,9 @@ logger = logging.getLogger(__name__) T = TypeVar("T") -ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType] -VersionInfo = Tuple[int, int, int] -NetlocTuple = Tuple[str, Tuple[Optional[str], Optional[str]]] +ExcInfo = tuple[type[BaseException], BaseException, TracebackType] +VersionInfo = tuple[int, int, int] +NetlocTuple = tuple[str, tuple[Optional[str], Optional[str]]] OnExc = Callable[[FunctionType, Path, BaseException], Any] OnErr = Callable[[FunctionType, Path, ExcInfo], Any] @@ -80,7 +71,7 @@ def get_pip_version() -> str: return f"pip {__version__} from {pip_pkg_dir} (python {get_major_minor_version()})" -def normalize_version_info(py_version_info: Tuple[int, ...]) -> Tuple[int, int, int]: +def normalize_version_info(py_version_info: tuple[int, ...]) -> tuple[int, int, int]: """ Convert a tuple of ints representing a Python version to one of length three. @@ -276,7 +267,7 @@ def format_size(bytes: float) -> str: return f"{int(bytes)} bytes" -def tabulate(rows: Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]: +def tabulate(rows: Iterable[Iterable[Any]]) -> tuple[list[str], list[int]]: """Return a list of formatted rows and a list of column sizes. For example:: @@ -331,7 +322,7 @@ def normalize_path(path: str, resolve_symlinks: bool = True) -> str: return os.path.normcase(path) -def splitext(path: str) -> Tuple[str, str]: +def splitext(path: str) -> tuple[str, str]: """Like os.path.splitext, but take off .tar too""" base, ext = posixpath.splitext(path) if base.lower().endswith(".tar"): @@ -375,24 +366,8 @@ def write_output(msg: Any, *args: Any) -> None: logger.info(msg, *args) -class StreamWrapper(StringIO): - orig_stream: TextIO - - @classmethod - def from_stream(cls, orig_stream: TextIO) -> "StreamWrapper": - ret = cls() - ret.orig_stream = orig_stream - return ret - - # compileall.compile_dir() needs stdout.encoding to print to stdout - # type ignore is because TextIOBase.encoding is writeable - @property - def encoding(self) -> str: # type: ignore - return self.orig_stream.encoding - - # Simulates an enum -def enum(*sequential: Any, **named: Any) -> Type[Any]: +def enum(*sequential: Any, **named: Any) -> type[Any]: enums = dict(zip(sequential, range(len(sequential))), **named) reverse = {value: key for key, value in enums.items()} enums["reverse_mapping"] = reverse @@ -421,7 +396,7 @@ def build_url_from_netloc(netloc: str, scheme: str = "https") -> str: return f"{scheme}://{netloc}" -def parse_netloc(netloc: str) -> Tuple[Optional[str], Optional[int]]: +def parse_netloc(netloc: str) -> tuple[Optional[str], Optional[int]]: """ Return the host-port pair from a netloc. """ @@ -480,8 +455,8 @@ def redact_netloc(netloc: str) -> str: def _transform_url( - url: str, transform_netloc: Callable[[str], Tuple[Any, ...]] -) -> Tuple[str, NetlocTuple]: + url: str, transform_netloc: Callable[[str], tuple[Any, ...]] +) -> tuple[str, NetlocTuple]: """Transform and replace netloc in a url. transform_netloc is a function taking the netloc and returning a @@ -503,13 +478,13 @@ def _get_netloc(netloc: str) -> NetlocTuple: return split_auth_from_netloc(netloc) -def _redact_netloc(netloc: str) -> Tuple[str]: +def _redact_netloc(netloc: str) -> tuple[str]: return (redact_netloc(netloc),) def split_auth_netloc_from_url( url: str, -) -> Tuple[str, str, Tuple[Optional[str], Optional[str]]]: +) -> tuple[str, str, tuple[Optional[str], Optional[str]]]: """ Parse a url into separate netloc, auth, and url with no auth. @@ -614,7 +589,7 @@ def is_console_interactive() -> bool: return sys.stdin is not None and sys.stdin.isatty() -def hash_file(path: str, blocksize: int = 1 << 20) -> Tuple[Any, int]: +def hash_file(path: str, blocksize: int = 1 << 20) -> tuple[Any, int]: """Return (hash, length) for path using hashlib.sha256()""" h = hashlib.sha256() @@ -626,7 +601,7 @@ def hash_file(path: str, blocksize: int = 1 << 20) -> Tuple[Any, int]: return h, length -def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]: +def pairwise(iterable: Iterable[Any]) -> Iterator[tuple[Any, Any]]: """ Return paired elements. @@ -639,7 +614,7 @@ def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]: def partition( pred: Callable[[T], bool], iterable: Iterable[T] -) -> Tuple[Iterable[T], Iterable[T]]: +) -> tuple[Iterable[T], Iterable[T]]: """ Use a predicate to partition entries into false entries and true entries, like diff --git a/src/pip/_internal/utils/packaging.py b/src/pip/_internal/utils/packaging.py index caad70f7fd1..9e6657b7f07 100644 --- a/src/pip/_internal/utils/packaging.py +++ b/src/pip/_internal/utils/packaging.py @@ -1,7 +1,7 @@ import functools import logging import re -from typing import NewType, Optional, Tuple, cast +from typing import NewType, Optional, cast from pip._vendor.packaging import specifiers, version from pip._vendor.packaging.requirements import Requirement @@ -13,7 +13,7 @@ @functools.lru_cache(maxsize=32) def check_requires_python( - requires_python: Optional[str], version_info: Tuple[int, ...] + requires_python: Optional[str], version_info: tuple[int, ...] ) -> bool: """ Check if the given Python version matches a "Requires-Python" specifier. diff --git a/src/pip/_internal/utils/setuptools_build.py b/src/pip/_internal/utils/setuptools_build.py index 96d1b246067..5b82efe27ca 100644 --- a/src/pip/_internal/utils/setuptools_build.py +++ b/src/pip/_internal/utils/setuptools_build.py @@ -1,6 +1,7 @@ import sys import textwrap -from typing import List, Optional, Sequence +from collections.abc import Sequence +from typing import Optional # Shim to wrap setup.py invocation with setuptools # Note that __file__ is handled via two {!r} *and* %r, to ensure that paths on @@ -51,7 +52,7 @@ def make_setuptools_shim_args( global_options: Optional[Sequence[str]] = None, no_user_config: bool = False, unbuffered_output: bool = False, -) -> List[str]: +) -> list[str]: """ Get setuptools command arguments with shim wrapped setup file invocation. @@ -77,7 +78,7 @@ def make_setuptools_bdist_wheel_args( global_options: Sequence[str], build_options: Sequence[str], destination_dir: str, -) -> List[str]: +) -> list[str]: # NOTE: Eventually, we'd want to also -S to the flags here, when we're # isolating. Currently, it breaks Python in virtualenvs, because it # relies on site.py to find parts of the standard library outside the @@ -93,7 +94,7 @@ def make_setuptools_bdist_wheel_args( def make_setuptools_clean_args( setup_py_path: str, global_options: Sequence[str], -) -> List[str]: +) -> list[str]: args = make_setuptools_shim_args( setup_py_path, global_options=global_options, unbuffered_output=True ) @@ -109,7 +110,7 @@ def make_setuptools_develop_args( prefix: Optional[str], home: Optional[str], use_user_site: bool, -) -> List[str]: +) -> list[str]: assert not (use_user_site and prefix) args = make_setuptools_shim_args( @@ -135,7 +136,7 @@ def make_setuptools_egg_info_args( setup_py_path: str, egg_info_dir: Optional[str], no_user_config: bool, -) -> List[str]: +) -> list[str]: args = make_setuptools_shim_args(setup_py_path, no_user_config=no_user_config) args += ["egg_info"] diff --git a/src/pip/_internal/utils/subprocess.py b/src/pip/_internal/utils/subprocess.py index cb2e23f007a..5f72c083fb8 100644 --- a/src/pip/_internal/utils/subprocess.py +++ b/src/pip/_internal/utils/subprocess.py @@ -2,7 +2,8 @@ import os import shlex import subprocess -from typing import Any, Callable, Iterable, List, Literal, Mapping, Optional, Union +from collections.abc import Iterable, Mapping +from typing import Any, Callable, Literal, Optional, Union from pip._vendor.rich.markup import escape @@ -11,7 +12,7 @@ from pip._internal.utils.logging import VERBOSE, subprocess_logger from pip._internal.utils.misc import HiddenText -CommandArgs = List[Union[str, HiddenText]] +CommandArgs = list[Union[str, HiddenText]] def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs: @@ -31,7 +32,7 @@ def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs: return command_args -def format_command_args(args: Union[List[str], CommandArgs]) -> str: +def format_command_args(args: Union[list[str], CommandArgs]) -> str: """ Format command arguments for display. """ @@ -46,7 +47,7 @@ def format_command_args(args: Union[List[str], CommandArgs]) -> str: ) -def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]: +def reveal_command_args(args: Union[list[str], CommandArgs]) -> list[str]: """ Return the arguments in their raw, unredacted form. """ @@ -54,7 +55,7 @@ def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]: def call_subprocess( - cmd: Union[List[str], CommandArgs], + cmd: Union[list[str], CommandArgs], show_stdout: bool = False, cwd: Optional[str] = None, on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise", @@ -229,7 +230,7 @@ def runner_with_spinner_message(message: str) -> Callable[..., None]: """ def runner( - cmd: List[str], + cmd: list[str], cwd: Optional[str] = None, extra_environ: Optional[Mapping[str, Any]] = None, ) -> None: diff --git a/src/pip/_internal/utils/temp_dir.py b/src/pip/_internal/utils/temp_dir.py index 06668e8ab2d..5b46bba4a42 100644 --- a/src/pip/_internal/utils/temp_dir.py +++ b/src/pip/_internal/utils/temp_dir.py @@ -4,14 +4,12 @@ import os.path import tempfile import traceback +from collections.abc import Generator from contextlib import ExitStack, contextmanager from pathlib import Path from typing import ( Any, Callable, - Dict, - Generator, - List, Optional, TypeVar, Union, @@ -51,7 +49,7 @@ class TempDirectoryTypeRegistry: """Manages temp directory behavior""" def __init__(self) -> None: - self._should_delete: Dict[str, bool] = {} + self._should_delete: dict[str, bool] = {} def set_delete(self, kind: str, value: bool) -> None: """Indicate whether a TempDirectory of the given kind should be @@ -184,7 +182,7 @@ def cleanup(self) -> None: if not os.path.exists(self._path): return - errors: List[BaseException] = [] + errors: list[BaseException] = [] def onerror( func: Callable[..., Any], diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py index 87a6d19ab5a..132846d908b 100644 --- a/src/pip/_internal/utils/unpacking.py +++ b/src/pip/_internal/utils/unpacking.py @@ -8,7 +8,8 @@ import sys import tarfile import zipfile -from typing import Iterable, List, Optional +from collections.abc import Iterable +from typing import Optional from zipfile import ZipInfo from pip._internal.exceptions import InstallationError @@ -48,7 +49,7 @@ def current_umask() -> int: return mask -def split_leading_dir(path: str) -> List[str]: +def split_leading_dir(path: str) -> list[str]: path = path.lstrip("/").lstrip("\\") if "/" in path and ( ("\\" in path and path.find("/") < path.find("\\")) or "\\" not in path diff --git a/src/pip/_internal/utils/virtualenv.py b/src/pip/_internal/utils/virtualenv.py index 882e36f5c1d..acccd96e0ac 100644 --- a/src/pip/_internal/utils/virtualenv.py +++ b/src/pip/_internal/utils/virtualenv.py @@ -3,7 +3,7 @@ import re import site import sys -from typing import List, Optional +from typing import Optional logger = logging.getLogger(__name__) _INCLUDE_SYSTEM_SITE_PACKAGES_REGEX = re.compile( @@ -33,7 +33,7 @@ def running_under_virtualenv() -> bool: return _running_under_venv() or _running_under_legacy_virtualenv() -def _get_pyvenv_cfg_lines() -> Optional[List[str]]: +def _get_pyvenv_cfg_lines() -> Optional[list[str]]: """Reads {sys.prefix}/pyvenv.cfg and returns its contents as list of lines Returns None, if it could not read/access the file. diff --git a/src/pip/_internal/utils/wheel.py b/src/pip/_internal/utils/wheel.py index f85aee8a3f9..346f2ce5a52 100644 --- a/src/pip/_internal/utils/wheel.py +++ b/src/pip/_internal/utils/wheel.py @@ -4,7 +4,6 @@ import logging from email.message import Message from email.parser import Parser -from typing import Tuple from zipfile import BadZipFile, ZipFile from pip._vendor.packaging.utils import canonicalize_name @@ -17,7 +16,7 @@ logger = logging.getLogger(__name__) -def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]: +def parse_wheel(wheel_zip: ZipFile, name: str) -> tuple[str, Message]: """Extract information from the provided wheel, ensuring it meets basic standards. @@ -94,7 +93,7 @@ def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message: return Parser().parsestr(wheel_text) -def wheel_version(wheel_data: Message) -> Tuple[int, ...]: +def wheel_version(wheel_data: Message) -> tuple[int, ...]: """Given WHEEL metadata, return the parsed Wheel-Version. Otherwise, raise UnsupportedWheel. """ @@ -110,7 +109,7 @@ def wheel_version(wheel_data: Message) -> Tuple[int, ...]: raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}") -def check_compatibility(version: Tuple[int, ...], name: str) -> None: +def check_compatibility(version: tuple[int, ...], name: str) -> None: """Raises errors or warns if called with an incompatible Wheel-Version. pip should refuse to install a Wheel-Version that's a major series diff --git a/src/pip/_internal/vcs/bazaar.py b/src/pip/_internal/vcs/bazaar.py index c754b7cc5c0..bbc45c936d4 100644 --- a/src/pip/_internal/vcs/bazaar.py +++ b/src/pip/_internal/vcs/bazaar.py @@ -1,5 +1,5 @@ import logging -from typing import List, Optional, Tuple +from typing import Optional from pip._internal.utils.misc import HiddenText, display_path from pip._internal.utils.subprocess import make_command @@ -30,7 +30,7 @@ class Bazaar(VersionControl): ) @staticmethod - def get_base_rev_args(rev: str) -> List[str]: + def get_base_rev_args(rev: str) -> list[str]: return ["-r", rev] def fetch_new( @@ -71,7 +71,7 @@ def update(self, dest: str, url: HiddenText, rev_options: RevOptions) -> None: self.run_command(cmd_args, cwd=dest) @classmethod - def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: + def get_url_rev_and_auth(cls, url: str) -> tuple[str, Optional[str], AuthInfo]: # hotfix the URL scheme after removing bzr+ from bzr+ssh:// re-add it url, rev, user_pass = super().get_url_rev_and_auth(url) if url.startswith("ssh://"): diff --git a/src/pip/_internal/vcs/git.py b/src/pip/_internal/vcs/git.py index 0425debb3ae..fea1339ad2d 100644 --- a/src/pip/_internal/vcs/git.py +++ b/src/pip/_internal/vcs/git.py @@ -5,7 +5,7 @@ import urllib.parse import urllib.request from dataclasses import replace -from typing import List, Optional, Tuple +from typing import Optional from pip._internal.exceptions import BadCommand, InstallationError from pip._internal.utils.misc import HiddenText, display_path, hide_url @@ -74,7 +74,7 @@ class Git(VersionControl): default_arg_rev = "HEAD" @staticmethod - def get_base_rev_args(rev: str) -> List[str]: + def get_base_rev_args(rev: str) -> list[str]: return [rev] def is_immutable_rev_checkout(self, url: str, dest: str) -> bool: @@ -91,7 +91,7 @@ def is_immutable_rev_checkout(self, url: str, dest: str) -> bool: is_tag_or_branch = bool(self.get_revision_sha(dest, rev_options.rev)[0]) return not is_tag_or_branch - def get_git_version(self) -> Tuple[int, ...]: + def get_git_version(self) -> tuple[int, ...]: version = self.run_command( ["version"], command_desc="git version", @@ -130,7 +130,7 @@ def get_current_branch(cls, location: str) -> Optional[str]: return None @classmethod - def get_revision_sha(cls, dest: str, rev: str) -> Tuple[Optional[str], bool]: + def get_revision_sha(cls, dest: str, rev: str) -> tuple[Optional[str], bool]: """ Return (sha_or_none, is_branch), where sha_or_none is a commit hash if the revision names a remote branch or tag, otherwise None. @@ -265,7 +265,7 @@ def fetch_new( rev_display = rev_options.to_display() logger.info("Cloning %s%s to %s", url, rev_display, display_path(dest)) if verbosity <= 0: - flags: Tuple[str, ...] = ("--quiet",) + flags: tuple[str, ...] = ("--quiet",) elif verbosity == 1: flags = () else: @@ -454,7 +454,7 @@ def get_subdirectory(cls, location: str) -> Optional[str]: return find_path_to_project_root_from_repo_root(location, repo_root) @classmethod - def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: + def get_url_rev_and_auth(cls, url: str) -> tuple[str, Optional[str], AuthInfo]: """ Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'. That's required because although they use SSH they sometimes don't diff --git a/src/pip/_internal/vcs/mercurial.py b/src/pip/_internal/vcs/mercurial.py index c183d41d09c..423e5b06d9c 100644 --- a/src/pip/_internal/vcs/mercurial.py +++ b/src/pip/_internal/vcs/mercurial.py @@ -1,7 +1,7 @@ import configparser import logging import os -from typing import List, Optional, Tuple +from typing import Optional from pip._internal.exceptions import BadCommand, InstallationError from pip._internal.utils.misc import HiddenText, display_path @@ -30,7 +30,7 @@ class Mercurial(VersionControl): ) @staticmethod - def get_base_rev_args(rev: str) -> List[str]: + def get_base_rev_args(rev: str) -> list[str]: return [f"--rev={rev}"] def fetch_new( @@ -44,7 +44,7 @@ def fetch_new( display_path(dest), ) if verbosity <= 0: - flags: Tuple[str, ...] = ("--quiet",) + flags: tuple[str, ...] = ("--quiet",) elif verbosity == 1: flags = () elif verbosity == 2: diff --git a/src/pip/_internal/vcs/subversion.py b/src/pip/_internal/vcs/subversion.py index f359266d9c0..63dcd72321c 100644 --- a/src/pip/_internal/vcs/subversion.py +++ b/src/pip/_internal/vcs/subversion.py @@ -1,7 +1,7 @@ import logging import os import re -from typing import List, Optional, Tuple +from typing import Optional from pip._internal.utils.misc import ( HiddenText, @@ -38,7 +38,7 @@ def should_add_vcs_url_prefix(cls, remote_url: str) -> bool: return True @staticmethod - def get_base_rev_args(rev: str) -> List[str]: + def get_base_rev_args(rev: str) -> list[str]: return ["-r", rev] @classmethod @@ -73,7 +73,7 @@ def get_revision(cls, location: str) -> str: @classmethod def get_netloc_and_auth( cls, netloc: str, scheme: str - ) -> Tuple[str, Tuple[Optional[str], Optional[str]]]: + ) -> tuple[str, tuple[Optional[str], Optional[str]]]: """ This override allows the auth information to be passed to svn via the --username and --password options instead of via the URL. @@ -86,7 +86,7 @@ def get_netloc_and_auth( return split_auth_from_netloc(netloc) @classmethod - def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: + def get_url_rev_and_auth(cls, url: str) -> tuple[str, Optional[str], AuthInfo]: # hotfix the URL scheme after removing svn+ from svn+ssh:// re-add it url, rev, user_pass = super().get_url_rev_and_auth(url) if url.startswith("ssh://"): @@ -130,7 +130,7 @@ def get_remote_url(cls, location: str) -> str: return url @classmethod - def _get_svn_url_rev(cls, location: str) -> Tuple[Optional[str], int]: + def _get_svn_url_rev(cls, location: str) -> tuple[Optional[str], int]: from pip._internal.exceptions import InstallationError entries_path = os.path.join(location, cls.dirname, "entries") @@ -194,11 +194,11 @@ def __init__(self, use_interactive: Optional[bool] = None) -> None: # Special value definitions: # None: Not evaluated yet. # Empty tuple: Could not parse version. - self._vcs_version: Optional[Tuple[int, ...]] = None + self._vcs_version: Optional[tuple[int, ...]] = None super().__init__() - def call_vcs_version(self) -> Tuple[int, ...]: + def call_vcs_version(self) -> tuple[int, ...]: """Query the version of the currently installed Subversion client. :return: A tuple containing the parts of the version information or @@ -226,7 +226,7 @@ def call_vcs_version(self) -> Tuple[int, ...]: return parsed_version - def get_vcs_version(self) -> Tuple[int, ...]: + def get_vcs_version(self) -> tuple[int, ...]: """Return the version of the currently installed Subversion client. If the version of the Subversion client has already been queried, diff --git a/src/pip/_internal/vcs/versioncontrol.py b/src/pip/_internal/vcs/versioncontrol.py index a4133165e9a..dfaeaadc6d0 100644 --- a/src/pip/_internal/vcs/versioncontrol.py +++ b/src/pip/_internal/vcs/versioncontrol.py @@ -5,18 +5,12 @@ import shutil import sys import urllib.parse +from collections.abc import Iterable, Iterator, Mapping from dataclasses import dataclass, field from typing import ( Any, - Dict, - Iterable, - Iterator, - List, Literal, - Mapping, Optional, - Tuple, - Type, Union, ) @@ -44,7 +38,7 @@ logger = logging.getLogger(__name__) -AuthInfo = Tuple[Optional[str], Optional[str]] +AuthInfo = tuple[Optional[str], Optional[str]] def is_url(name: str) -> bool: @@ -126,7 +120,7 @@ class RevOptions: extra_args: a list of extra options. """ - vc_class: Type["VersionControl"] + vc_class: type["VersionControl"] rev: Optional[str] = None extra_args: CommandArgs = field(default_factory=list) branch_name: Optional[str] = None @@ -170,7 +164,7 @@ def make_new(self, rev: str) -> "RevOptions": class VcsSupport: - _registry: Dict[str, "VersionControl"] = {} + _registry: dict[str, "VersionControl"] = {} schemes = ["ssh", "git", "hg", "bzr", "sftp", "svn"] def __init__(self) -> None: @@ -183,21 +177,21 @@ def __iter__(self) -> Iterator[str]: return self._registry.__iter__() @property - def backends(self) -> List["VersionControl"]: + def backends(self) -> list["VersionControl"]: return list(self._registry.values()) @property - def dirnames(self) -> List[str]: + def dirnames(self) -> list[str]: return [backend.dirname for backend in self.backends] @property - def all_schemes(self) -> List[str]: - schemes: List[str] = [] + def all_schemes(self) -> list[str]: + schemes: list[str] = [] for backend in self.backends: schemes.extend(backend.schemes) return schemes - def register(self, cls: Type["VersionControl"]) -> None: + def register(self, cls: type["VersionControl"]) -> None: if not hasattr(cls, "name"): logger.warning("Cannot register VCS %s", cls.__name__) return @@ -257,9 +251,9 @@ class VersionControl: dirname = "" repo_name = "" # List of supported schemes for this Version Control - schemes: Tuple[str, ...] = () + schemes: tuple[str, ...] = () # Iterable of environment variable names to pass to call_subprocess(). - unset_environ: Tuple[str, ...] = () + unset_environ: tuple[str, ...] = () default_arg_rev: Optional[str] = None @classmethod @@ -310,7 +304,7 @@ def get_src_requirement(cls, repo_dir: str, project_name: str) -> str: return req @staticmethod - def get_base_rev_args(rev: str) -> List[str]: + def get_base_rev_args(rev: str) -> list[str]: """ Return the base revision arguments for a vcs command. @@ -357,7 +351,7 @@ def _is_local_repository(cls, repo: str) -> bool: @classmethod def get_netloc_and_auth( cls, netloc: str, scheme: str - ) -> Tuple[str, Tuple[Optional[str], Optional[str]]]: + ) -> tuple[str, tuple[Optional[str], Optional[str]]]: """ Parse the repository URL's netloc, and return the new netloc to use along with auth information. @@ -376,7 +370,7 @@ def get_netloc_and_auth( return netloc, (None, None) @classmethod - def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: + def get_url_rev_and_auth(cls, url: str) -> tuple[str, Optional[str], AuthInfo]: """ Parse the repository URL to use, and return the URL, revision, and auth info to use. @@ -414,7 +408,7 @@ def make_rev_args( """ return [] - def get_url_rev_options(self, url: HiddenText) -> Tuple[HiddenText, RevOptions]: + def get_url_rev_options(self, url: HiddenText) -> tuple[HiddenText, RevOptions]: """ Return the URL and RevOptions object to use in obtain(), as a tuple (url, rev_options). @@ -608,7 +602,7 @@ def get_revision(cls, location: str) -> str: @classmethod def run_command( cls, - cmd: Union[List[str], CommandArgs], + cmd: Union[list[str], CommandArgs], show_stdout: bool = True, cwd: Optional[str] = None, on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise", diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py index 93f8e1f5b2f..c31b2548334 100644 --- a/src/pip/_internal/wheel_builder.py +++ b/src/pip/_internal/wheel_builder.py @@ -5,7 +5,8 @@ import os.path import re import shutil -from typing import Iterable, List, Optional, Tuple +from collections.abc import Iterable +from typing import Optional from pip._vendor.packaging.utils import canonicalize_name, canonicalize_version from pip._vendor.packaging.version import InvalidVersion, Version @@ -31,7 +32,7 @@ _egg_info_re = re.compile(r"([a-z0-9_.]+)-([a-z0-9_.!+-]+)", re.IGNORECASE) -BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]] +BuildResult = tuple[list[InstallRequirement], list[InstallRequirement]] def _contains_egg_info(s: str) -> bool: @@ -168,8 +169,8 @@ def _build_one( req: InstallRequirement, output_dir: str, verify: bool, - build_options: List[str], - global_options: List[str], + build_options: list[str], + global_options: list[str], editable: bool, ) -> Optional[str]: """Build one wheel. @@ -205,8 +206,8 @@ def _build_one( def _build_one_inside_env( req: InstallRequirement, output_dir: str, - build_options: List[str], - global_options: List[str], + build_options: list[str], + global_options: list[str], editable: bool, ) -> Optional[str]: with TempDirectory(kind="wheel") as temp_dir: @@ -273,7 +274,7 @@ def _build_one_inside_env( return None -def _clean_one_legacy(req: InstallRequirement, global_options: List[str]) -> bool: +def _clean_one_legacy(req: InstallRequirement, global_options: list[str]) -> bool: clean_args = make_setuptools_clean_args( req.setup_py_path, global_options=global_options, @@ -294,8 +295,8 @@ def build( requirements: Iterable[InstallRequirement], wheel_cache: WheelCache, verify: bool, - build_options: List[str], - global_options: List[str], + build_options: list[str], + global_options: list[str], ) -> BuildResult: """Build wheels. diff --git a/src/pip/_vendor/__init__.py b/src/pip/_vendor/__init__.py index 561089ccc0c..3d2810cb878 100644 --- a/src/pip/_vendor/__init__.py +++ b/src/pip/_vendor/__init__.py @@ -5,7 +5,6 @@ Files inside of pip._vendor should be considered immutable and should only be updated to versions from upstream. """ -from __future__ import absolute_import import glob import os.path @@ -27,7 +26,7 @@ # if the vendored ones do not exist. This idea of this was taken from # https://github.com/kennethreitz/requests/pull/2567. def vendored(modulename): - vendored_name = "{0}.{1}".format(__name__, modulename) + vendored_name = f"{__name__}.{modulename}" try: __import__(modulename, globals(), locals(), level=0) diff --git a/tests/conftest.py b/tests/conftest.py index d093eea462b..dd0fcf9c27e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,7 @@ import subprocess import sys import threading +from collections.abc import Iterable, Iterator from dataclasses import dataclass from enum import Enum from hashlib import sha256 @@ -19,14 +20,7 @@ AnyStr, Callable, ClassVar, - ContextManager, - Dict, - Iterable, - Iterator, - List, Optional, - Set, - Tuple, ) from unittest.mock import patch from zipfile import ZipFile @@ -104,7 +98,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_collection_modifyitems(config: Config, items: List[pytest.Function]) -> None: +def pytest_collection_modifyitems(config: Config, items: list[pytest.Function]) -> None: for item in items: if not hasattr(item, "module"): # e.g.: DoctestTextfile continue @@ -337,7 +331,9 @@ def scoped_global_tempdir_manager(request: pytest.FixtureRequest) -> Iterator[No temporary directories in the application. """ if "no_auto_tempdir_manager" in request.keywords: - ctx: Callable[[], ContextManager[None]] = contextlib.nullcontext + ctx: Callable[[], contextlib.AbstractContextManager[None]] = ( + contextlib.nullcontext + ) else: ctx = global_tempdir_manager @@ -347,7 +343,7 @@ def scoped_global_tempdir_manager(request: pytest.FixtureRequest) -> Iterator[No @pytest.fixture(scope="session") def pip_src(tmpdir_factory: pytest.TempPathFactory) -> Path: - def not_code_files_and_folders(path: str, names: List[str]) -> Iterable[str]: + def not_code_files_and_folders(path: str, names: list[str]) -> Iterable[str]: # In the root directory... if os.path.samefile(path, SRC_DIR): # ignore all folders except "src" @@ -380,7 +376,7 @@ def not_code_files_and_folders(path: str, names: List[str]) -> Iterable[str]: @pytest.fixture(scope="session") def pip_editable_parts( pip_src: Path, tmpdir_factory: pytest.TempPathFactory -) -> Tuple[Path, ...]: +) -> tuple[Path, ...]: pip_editable = tmpdir_factory.mktemp("pip") / "pip" shutil.copytree(pip_src, pip_editable, symlinks=True) # noxfile.py is Python 3 only @@ -474,7 +470,7 @@ def virtualenv_template( request: pytest.FixtureRequest, tmpdir_factory: pytest.TempPathFactory, pip_src: Path, - pip_editable_parts: Tuple[Path, ...], + pip_editable_parts: tuple[Path, ...], setuptools_install: Path, wheel_install: Path, coverage_install: Path, @@ -558,7 +554,7 @@ def script_factory( def factory( tmpdir: Path, virtualenv: Optional[VirtualEnvironment] = None, - environ: Optional[Dict[AnyStr, AnyStr]] = None, + environ: Optional[dict[AnyStr, AnyStr]] = None, ) -> PipTestEnvironment: kwargs = {} if environ: @@ -746,7 +742,7 @@ class FakePackage: filename: str metadata: MetadataKind # This will override any dependencies specified in the actual dist's METADATA. - requires_dist: Tuple[str, ...] = () + requires_dist: tuple[str, ...] = () # This will override the Name specified in the actual dist's METADATA. metadata_name: Optional[str] = None @@ -787,7 +783,7 @@ def generate_metadata(self) -> bytes: @pytest.fixture(scope="session") -def fake_packages() -> Dict[str, List[FakePackage]]: +def fake_packages() -> dict[str, list[FakePackage]]: """The package database we generate for testing PEP 658 support.""" return { "simple": [ @@ -880,7 +876,7 @@ def fake_packages() -> Dict[str, List[FakePackage]]: @pytest.fixture(scope="session") def html_index_for_packages( shared_data: TestData, - fake_packages: Dict[str, List[FakePackage]], + fake_packages: dict[str, list[FakePackage]], tmpdir_factory: pytest.TempPathFactory, ) -> Path: """Generate a PyPI HTML package index within a local directory pointing to @@ -915,7 +911,7 @@ def html_index_for_packages( pkg_subdir = html_dir / pkg pkg_subdir.mkdir() - download_links: List[str] = [] + download_links: list[str] = [] for package_link in links: # (3.1) Generate the tag which pip can crawl pointing to this # specific package version. @@ -960,7 +956,7 @@ class OneTimeDownloadHandler(http.server.SimpleHTTPRequestHandler): """Serve files from the current directory, but error if a file is downloaded more than once.""" - _seen_paths: ClassVar[Set[str]] = set() + _seen_paths: ClassVar[set[str]] = set() def do_GET(self) -> None: if self.path in self._seen_paths: @@ -994,7 +990,7 @@ def finish_request(self: "Self", request: Any, client_address: Any) -> None: ) class Handler(OneTimeDownloadHandler): - _seen_paths: ClassVar[Set[str]] = set() + _seen_paths: ClassVar[set[str]] = set() with InDirectoryServer(("", 8000), Handler) as httpd: server_thread = threading.Thread(target=httpd.serve_forever) diff --git a/tests/functional/test_broken_stdout.py b/tests/functional/test_broken_stdout.py index ae0fdc1ba84..089ee0713c9 100644 --- a/tests/functional/test_broken_stdout.py +++ b/tests/functional/test_broken_stdout.py @@ -1,14 +1,13 @@ import os import subprocess from pathlib import Path -from typing import List, Tuple _BROKEN_STDOUT_RETURN_CODE = 120 def setup_broken_stdout_test( - args: List[str], deprecated_python: bool -) -> Tuple[str, int]: + args: list[str], deprecated_python: bool +) -> tuple[str, int]: proc = subprocess.Popen( args, stdout=subprocess.PIPE, diff --git a/tests/functional/test_cache.py b/tests/functional/test_cache.py index 247bfcd4be0..bd1f75a4177 100644 --- a/tests/functional/test_cache.py +++ b/tests/functional/test_cache.py @@ -1,7 +1,7 @@ import os import shutil from glob import glob -from typing import Callable, List, Tuple +from typing import Callable import pytest @@ -29,7 +29,7 @@ def wheel_cache_dir(cache_dir: str) -> str: @pytest.fixture -def http_cache_files(http_cache_dir: str) -> List[str]: +def http_cache_files(http_cache_dir: str) -> list[str]: destination = os.path.join(http_cache_dir, "arbitrary", "pathname") if not os.path.exists(destination): @@ -40,7 +40,7 @@ def http_cache_files(http_cache_dir: str) -> List[str]: @pytest.fixture -def wheel_cache_files(wheel_cache_dir: str) -> List[str]: +def wheel_cache_files(wheel_cache_dir: str) -> list[str]: destination = os.path.join(wheel_cache_dir, "arbitrary", "pathname") if not os.path.exists(destination): @@ -51,7 +51,7 @@ def wheel_cache_files(wheel_cache_dir: str) -> List[str]: @pytest.fixture -def populate_http_cache(http_cache_dir: str) -> List[Tuple[str, str]]: +def populate_http_cache(http_cache_dir: str) -> list[tuple[str, str]]: destination = os.path.join(http_cache_dir, "arbitrary", "pathname") os.makedirs(destination) @@ -69,7 +69,7 @@ def populate_http_cache(http_cache_dir: str) -> List[Tuple[str, str]]: @pytest.fixture -def populate_wheel_cache(wheel_cache_dir: str) -> List[Tuple[str, str]]: +def populate_wheel_cache(wheel_cache_dir: str) -> list[tuple[str, str]]: destination = os.path.join(wheel_cache_dir, "arbitrary", "pathname") os.makedirs(destination) @@ -197,7 +197,7 @@ def test_cache_info( script: PipTestEnvironment, http_cache_dir: str, wheel_cache_dir: str, - wheel_cache_files: List[str], + wheel_cache_files: list[str], ) -> None: result = script.pip("cache", "info") @@ -383,8 +383,8 @@ def test_cache_purge( @pytest.mark.usefixtures("populate_http_cache", "populate_wheel_cache") def test_cache_purge_too_many_args( script: PipTestEnvironment, - http_cache_files: List[str], - wheel_cache_files: List[str], + http_cache_files: list[str], + wheel_cache_files: list[str], ) -> None: """Running `pip cache purge aaa` should raise an error and remove no cached http files or wheels.""" diff --git a/tests/functional/test_check.py b/tests/functional/test_check.py index f50f5593e5c..06ed1b08eac 100644 --- a/tests/functional/test_check.py +++ b/tests/functional/test_check.py @@ -1,4 +1,4 @@ -from typing import Collection +from collections.abc import Collection from tests.lib import ( PipTestEnvironment, diff --git a/tests/functional/test_completion.py b/tests/functional/test_completion.py index a52b135c8b0..9e5920e24c3 100644 --- a/tests/functional/test_completion.py +++ b/tests/functional/test_completion.py @@ -1,7 +1,7 @@ import os import sys from pathlib import Path -from typing import Protocol, Tuple, Union +from typing import Protocol, Union import pytest @@ -126,7 +126,7 @@ def __call__( cwd: Union[Path, str, None] = None, include_env: bool = True, expect_error: bool = True, - ) -> Tuple[TestPipResult, PipTestEnvironment]: ... + ) -> tuple[TestPipResult, PipTestEnvironment]: ... @pytest.fixture @@ -142,7 +142,7 @@ def do_autocomplete( cwd: Union[Path, str, None] = None, include_env: bool = True, expect_error: bool = True, - ) -> Tuple[TestPipResult, PipTestEnvironment]: + ) -> tuple[TestPipResult, PipTestEnvironment]: if include_env: autocomplete_script.environ["COMP_WORDS"] = words autocomplete_script.environ["COMP_CWORD"] = cword diff --git a/tests/functional/test_config_settings.py b/tests/functional/test_config_settings.py index 4e0b12ca185..bff3ab2394a 100644 --- a/tests/functional/test_config_settings.py +++ b/tests/functional/test_config_settings.py @@ -1,7 +1,7 @@ import json import tarfile from pathlib import Path -from typing import List, Optional, Tuple +from typing import Optional from zipfile import ZipFile from pip._internal.utils.urls import path_to_url @@ -92,8 +92,8 @@ def build_wheel( def make_project( - path: Path, name: str = "foo", dependencies: Optional[List[str]] = None -) -> Tuple[str, str, Path]: + path: Path, name: str = "foo", dependencies: Optional[list[str]] = None +) -> tuple[str, str, Path]: version = "1.0" project_dir = path / name backend = project_dir / "backend" diff --git a/tests/functional/test_debug.py b/tests/functional/test_debug.py index 82557299904..41893143814 100644 --- a/tests/functional/test_debug.py +++ b/tests/functional/test_debug.py @@ -1,5 +1,4 @@ import re -from typing import List import pytest @@ -61,7 +60,7 @@ def test_debug__library_versions(script: PipTestEnvironment) -> None: ["--verbose"], ], ) -def test_debug__tags(script: PipTestEnvironment, args: List[str]) -> None: +def test_debug__tags(script: PipTestEnvironment, args: list[str]) -> None: """ Check the compatible tag output. """ @@ -86,7 +85,7 @@ def test_debug__tags(script: PipTestEnvironment, args: List[str]) -> None: ], ) def test_debug__target_options( - script: PipTestEnvironment, args: List[str], expected: str + script: PipTestEnvironment, args: list[str], expected: str ) -> None: """ Check passing target-related options. diff --git a/tests/functional/test_download.py b/tests/functional/test_download.py index 3906885a19b..d91eb22a5e1 100644 --- a/tests/functional/test_download.py +++ b/tests/functional/test_download.py @@ -5,7 +5,7 @@ import textwrap from hashlib import sha256 from pathlib import Path -from typing import Callable, List, Tuple +from typing import Callable import pytest @@ -670,7 +670,7 @@ def test_download__python_version_used_for_python_requires( ) wheel_dir = os.path.dirname(wheel_path) - def make_args(python_version: str) -> List[str]: + def make_args(python_version: str) -> list[str]: return [ "download", "--no-index", @@ -1240,14 +1240,14 @@ def download_local_html_index( script: PipTestEnvironment, html_index_for_packages: Path, tmpdir: Path, -) -> Callable[..., Tuple[TestPipResult, Path]]: +) -> Callable[..., tuple[TestPipResult, Path]]: """Execute `pip download` against a generated PyPI index.""" download_dir = tmpdir / "download_dir" def run_for_generated_index( - args: List[str], + args: list[str], allow_error: bool = False, - ) -> Tuple[TestPipResult, Path]: + ) -> tuple[TestPipResult, Path]: """ Produce a PyPI directory structure pointing to the specified packages, then execute `pip download -i ...` pointing to our generated index. @@ -1271,14 +1271,14 @@ def download_server_html_index( script: PipTestEnvironment, tmpdir: Path, html_index_with_onetime_server: http.server.ThreadingHTTPServer, -) -> Callable[..., Tuple[TestPipResult, Path]]: +) -> Callable[..., tuple[TestPipResult, Path]]: """Execute `pip download` against a generated PyPI index.""" download_dir = tmpdir / "download_dir" def run_for_generated_index( - args: List[str], + args: list[str], allow_error: bool = False, - ) -> Tuple[TestPipResult, Path]: + ) -> tuple[TestPipResult, Path]: """ Produce a PyPI directory structure pointing to the specified packages, then execute `pip download -i ...` pointing to our generated index. @@ -1313,9 +1313,9 @@ def run_for_generated_index( ], ) def test_download_metadata( - download_local_html_index: Callable[..., Tuple[TestPipResult, Path]], + download_local_html_index: Callable[..., tuple[TestPipResult, Path]], requirement_to_download: str, - expected_outputs: List[str], + expected_outputs: list[str], ) -> None: """Verify that if a data-dist-info-metadata attribute is present, then it is used instead of the actual dist's METADATA.""" @@ -1350,9 +1350,9 @@ def test_download_metadata( ], ) def test_download_metadata_server( - download_server_html_index: Callable[..., Tuple[TestPipResult, Path]], + download_server_html_index: Callable[..., tuple[TestPipResult, Path]], requirement_to_download: str, - expected_outputs: List[str], + expected_outputs: list[str], doubled_path: str, ) -> None: """Verify that if a data-dist-info-metadata attribute is present, then it is used @@ -1390,7 +1390,7 @@ def test_download_metadata_server( ], ) def test_incorrect_metadata_hash( - download_local_html_index: Callable[..., Tuple[TestPipResult, Path]], + download_local_html_index: Callable[..., tuple[TestPipResult, Path]], requirement_to_download: str, real_hash: str, ) -> None: @@ -1415,7 +1415,7 @@ def test_incorrect_metadata_hash( ], ) def test_metadata_not_found( - download_local_html_index: Callable[..., Tuple[TestPipResult, Path]], + download_local_html_index: Callable[..., tuple[TestPipResult, Path]], requirement_to_download: str, expected_url: str, ) -> None: @@ -1435,7 +1435,7 @@ def test_metadata_not_found( def test_produces_error_for_mismatched_package_name_in_metadata( - download_local_html_index: Callable[..., Tuple[TestPipResult, Path]], + download_local_html_index: Callable[..., tuple[TestPipResult, Path]], ) -> None: """Verify that the package name from the metadata matches the requested package.""" result, _ = download_local_html_index( @@ -1458,7 +1458,7 @@ def test_produces_error_for_mismatched_package_name_in_metadata( ], ) def test_canonicalizes_package_name_before_verifying_metadata( - download_local_html_index: Callable[..., Tuple[TestPipResult, Path]], + download_local_html_index: Callable[..., tuple[TestPipResult, Path]], requirement: str, ) -> None: """Verify that the package name from the command line and the package's diff --git a/tests/functional/test_fast_deps.py b/tests/functional/test_fast_deps.py index 85c9bbd7072..f2c5fe5232f 100644 --- a/tests/functional/test_fast_deps.py +++ b/tests/functional/test_fast_deps.py @@ -3,8 +3,8 @@ import os import pathlib import re +from collections.abc import Iterable from os.path import basename -from typing import Iterable import pytest diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index 35d4e58b65e..9052de4546e 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -7,9 +7,10 @@ import sysconfig import tarfile import textwrap +from collections.abc import Iterable from os.path import curdir, join, pardir from pathlib import Path -from typing import Dict, Iterable, List, Optional, Tuple +from typing import Optional import pytest @@ -1347,7 +1348,7 @@ def test_install_package_with_prefix( def _test_install_editable_with_prefix( - script: PipTestEnvironment, files: Dict[str, str] + script: PipTestEnvironment, files: dict[str, str] ) -> TestPipResult: # make a dummy project pkga_path = script.scratch_path / "pkga" @@ -1746,7 +1747,7 @@ def test_install_builds_wheels(script: PipTestEnvironment, data: TestData) -> No to_install, expect_error=True, # error building wheelbroken ) - wheels: List[str] = [] + wheels: list[str] = [] for _, _, files in os.walk(wheels_cache): wheels.extend(f for f in files if f.endswith(".whl")) # Built wheel for upper @@ -2183,7 +2184,7 @@ def test_user_config_accepted(script: PipTestEnvironment) -> None: @pytest.mark.parametrize("use_module", [True, False]) def test_install_pip_does_not_modify_pip_when_satisfied( script: PipTestEnvironment, - install_args: List[str], + install_args: list[str], expected_message: str, use_module: bool, resolver_variant: ResolverVariant, @@ -2321,7 +2322,7 @@ def test_error_all_yanked_files_and_no_pin( ], ) def test_install_sends_client_cert( - install_args: Tuple[str, ...], + install_args: tuple[str, ...], script: PipTestEnvironment, cert_factory: CertFactory, data: TestData, diff --git a/tests/functional/test_install_check.py b/tests/functional/test_install_check.py index 8a8a7c93a80..a0598fe2703 100644 --- a/tests/functional/test_install_check.py +++ b/tests/functional/test_install_check.py @@ -1,4 +1,4 @@ -from typing import Iterable +from collections.abc import Iterable from tests.lib import PipTestEnvironment, create_test_package_with_setup diff --git a/tests/functional/test_install_config.py b/tests/functional/test_install_config.py index d111bc5f7bc..42a5bfb2665 100644 --- a/tests/functional/test_install_config.py +++ b/tests/functional/test_install_config.py @@ -3,7 +3,7 @@ import tempfile import textwrap from pathlib import Path -from typing import Callable, List +from typing import Callable import pytest @@ -368,7 +368,7 @@ def flags( auth_needed: bool, keyring_provider: str, keyring_provider_implementation: str, -) -> List[str]: +) -> list[str]: if ( keyring_provider not in [None, "auto"] and keyring_provider_implementation != keyring_provider @@ -393,7 +393,7 @@ def test_prompt_for_keyring_if_needed( data: TestData, cert_factory: CertFactory, auth_needed: bool, - flags: List[str], + flags: list[str], keyring_provider: str, keyring_provider_implementation: str, tmpdir: Path, diff --git a/tests/functional/test_install_report.py b/tests/functional/test_install_report.py index a25de64a3d1..3c1ec11858c 100644 --- a/tests/functional/test_install_report.py +++ b/tests/functional/test_install_report.py @@ -1,7 +1,7 @@ import json import textwrap from pathlib import Path -from typing import Any, Dict, Tuple +from typing import Any import pytest from packaging.utils import canonicalize_name @@ -9,7 +9,7 @@ from ..lib import PipTestEnvironment, TestData -def _install_dict(report: Dict[str, Any]) -> Dict[str, Any]: +def _install_dict(report: dict[str, Any]) -> dict[str, Any]: return {canonicalize_name(i["metadata"]["name"]): i for i in report["install"]} @@ -129,7 +129,7 @@ def test_skipped_yanked_version( ) @pytest.mark.network def test_install_report_index( - script: PipTestEnvironment, tmp_path: Path, specifiers: Tuple[str, ...] + script: PipTestEnvironment, tmp_path: Path, specifiers: tuple[str, ...] ) -> None: """Test report for sdist obtained from index.""" report_path = tmp_path / "report.json" diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py index 7ab8a0bb850..c094e675a30 100644 --- a/tests/functional/test_new_resolver.py +++ b/tests/functional/test_new_resolver.py @@ -2,7 +2,7 @@ import pathlib import sys import textwrap -from typing import TYPE_CHECKING, Callable, Dict, List, Protocol, Tuple +from typing import TYPE_CHECKING, Callable, Protocol import pytest from packaging.utils import canonicalize_name @@ -632,8 +632,8 @@ def test_new_resolver_force_reinstall(script: PipTestEnvironment) -> None: ) def test_new_resolver_handles_prerelease( script: PipTestEnvironment, - available_versions: List[str], - pip_args: List[str], + available_versions: list[str], + pip_args: list[str], expected_version: str, ) -> None: for version in available_versions: @@ -659,7 +659,7 @@ def test_new_resolver_handles_prerelease( ], ) def test_new_resolver_skips_marker( - script: PipTestEnvironment, pkg_deps: List[str], root_deps: List[str] + script: PipTestEnvironment, pkg_deps: list[str], root_deps: list[str] ) -> None: create_basic_wheel_for_package(script, "pkg", "1.0", depends=pkg_deps) create_basic_wheel_for_package(script, "dep", "1.0") @@ -686,7 +686,7 @@ def test_new_resolver_skips_marker( ], ) def test_new_resolver_constraints( - script: PipTestEnvironment, constraints: List[str] + script: PipTestEnvironment, constraints: list[str] ) -> None: create_basic_wheel_for_package(script, "pkg", "1.0") create_basic_wheel_for_package(script, "pkg", "2.0") @@ -952,8 +952,8 @@ def __call__( script: PipTestEnvironment, name: str, version: str, - requires: List[str], - extras: Dict[str, List[str]], + requires: list[str], + extras: dict[str, list[str]], ) -> str: ... @@ -961,8 +961,8 @@ def _local_with_setup( script: PipTestEnvironment, name: str, version: str, - requires: List[str], - extras: Dict[str, List[str]], + requires: list[str], + extras: dict[str, list[str]], ) -> str: """Create the package as a local source directory to install from path.""" path = create_test_package_with_setup( @@ -979,8 +979,8 @@ def _direct_wheel( script: PipTestEnvironment, name: str, version: str, - requires: List[str], - extras: Dict[str, List[str]], + requires: list[str], + extras: dict[str, list[str]], ) -> str: """Create the package as a wheel to install from path directly.""" path = create_basic_wheel_for_package( @@ -997,8 +997,8 @@ def _wheel_from_index( script: PipTestEnvironment, name: str, version: str, - requires: List[str], - extras: Dict[str, List[str]], + requires: list[str], + extras: dict[str, list[str]], ) -> str: """Create the package as a wheel to install from index.""" create_basic_wheel_for_package( @@ -1855,7 +1855,7 @@ def test_new_resolver_succeeds_on_matching_constraint_and_requirement( constraints_file = script.scratch_path / "constraints.txt" constraints_file.write_text(req_line) - last_args: Tuple[str, ...] + last_args: tuple[str, ...] if editable: last_args = ("-e", os.fspath(source_dir)) else: @@ -2320,7 +2320,7 @@ def test_new_resolver_dont_backtrack_on_extra_if_base_constrained_in_requirement script, "pkg", "2.0", extras={"ext1": ["dep"], "ext2": ["dep"]} ) - to_install: Tuple[str, str] = ( + to_install: tuple[str, str] = ( "pkg[ext1]", "pkg[ext2]==1.0" if two_extras else "pkg==1.0", ) @@ -2367,7 +2367,7 @@ def test_new_resolver_dont_backtrack_on_conflicting_constraints_on_extras( script, "pkg", "2.0", extras={"ext1": ["dep"], "ext2": ["dep"]} ) - to_install: Tuple[str, str] = ( + to_install: tuple[str, str] = ( "pkg[ext1]>1", "pkg[ext2]==1.0" if two_extras else "pkg==1.0", ) @@ -2531,7 +2531,7 @@ def test_new_resolver_comes_from_with_extra( create_basic_wheel_for_package(script, "dep", "1.0") create_basic_wheel_for_package(script, "pkg", "1.0", extras={"ext": ["dep"]}) - to_install: Tuple[str, str] = ("pkg", "pkg[ext]") + to_install: tuple[str, str] = ("pkg", "pkg[ext]") result = script.pip( "install", diff --git a/tests/functional/test_pep517.py b/tests/functional/test_pep517.py index fd9380d0eb6..77043ce95fc 100644 --- a/tests/functional/test_pep517.py +++ b/tests/functional/test_pep517.py @@ -1,6 +1,6 @@ import os from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Optional import pytest import tomli_w @@ -18,14 +18,14 @@ def make_project( tmpdir: Path, - requires: Optional[List[str]] = None, + requires: Optional[list[str]] = None, backend: Optional[str] = None, - backend_path: Optional[List[str]] = None, + backend_path: Optional[list[str]] = None, ) -> Path: requires = requires or [] project_dir = tmpdir / "project" project_dir.mkdir() - buildsys: Dict[str, Any] = {"requires": requires} + buildsys: dict[str, Any] = {"requires": requires} if backend: buildsys["build-backend"] = backend if backend_path: @@ -301,13 +301,13 @@ def test_pep517_install_with_no_cache_dir( def make_pyproject_with_setup( tmpdir: Path, build_system: bool = True, set_backend: bool = True -) -> Tuple[Path, str]: +) -> tuple[Path, str]: project_dir = tmpdir / "project" project_dir.mkdir() setup_script = "from setuptools import setup\n" expect_script_dir_on_path = True if build_system: - buildsys: Dict[str, Any] = { + buildsys: dict[str, Any] = { "requires": ["setuptools", "wheel"], } if set_backend: diff --git a/tests/functional/test_pep660.py b/tests/functional/test_pep660.py index d562d0750db..9cf3da31172 100644 --- a/tests/functional/test_pep660.py +++ b/tests/functional/test_pep660.py @@ -1,6 +1,6 @@ import os from pathlib import Path -from typing import Any, Dict +from typing import Any import tomli_w @@ -71,7 +71,7 @@ def _make_project( project_dir.joinpath("setup.py").write_text(SETUP_PY) if backend_code: assert with_pyproject - buildsys: Dict[str, Any] = {"requires": ["setuptools", "wheel"]} + buildsys: dict[str, Any] = {"requires": ["setuptools", "wheel"]} buildsys["build-backend"] = "test_backend" buildsys["backend-path"] = ["."] data = tomli_w.dumps({"build-system": buildsys}) @@ -89,7 +89,7 @@ def _assert_hook_called(project_dir: Path, hook: str) -> None: def _assert_hook_called_with_config_settings( - project_dir: Path, hook: str, config_settings: Dict[str, str] + project_dir: Path, hook: str, config_settings: dict[str, str] ) -> None: log = project_dir.joinpath("log.txt").read_text() assert f":{hook} called" in log, f"{hook} has not been called" diff --git a/tests/functional/test_pep668.py b/tests/functional/test_pep668.py index a4920dfce5e..5672e52d94b 100644 --- a/tests/functional/test_pep668.py +++ b/tests/functional/test_pep668.py @@ -1,7 +1,6 @@ import json import pathlib import textwrap -from typing import List import pytest @@ -37,7 +36,7 @@ def check_externally_managed(): ], ) @pytest.mark.usefixtures("patch_check_externally_managed") -def test_fails(script: PipTestEnvironment, arguments: List[str]) -> None: +def test_fails(script: PipTestEnvironment, arguments: list[str]) -> None: result = script.pip(*arguments, "pip", expect_error=True) assert "I am externally managed" in result.stderr @@ -52,7 +51,7 @@ def test_fails(script: PipTestEnvironment, arguments: List[str]) -> None: ) @pytest.mark.usefixtures("patch_check_externally_managed") def test_succeeds_when_overridden( - script: PipTestEnvironment, arguments: List[str] + script: PipTestEnvironment, arguments: list[str] ) -> None: result = script.pip(*arguments, "pip", "--break-system-packages") assert "I am externally managed" not in result.stderr @@ -69,7 +68,7 @@ def test_succeeds_when_overridden( @pytest.mark.usefixtures("patch_check_externally_managed") def test_allows_if_out_of_environment( script: PipTestEnvironment, - arguments: List[str], + arguments: list[str], ) -> None: wheel = create_basic_wheel_for_package(script, "foo", "1.0") result = script.pip(*arguments, script.scratch_path, wheel.as_uri()) diff --git a/tests/functional/test_proxy.py b/tests/functional/test_proxy.py index 52f7ea55d52..63e12cccd74 100644 --- a/tests/functional/test_proxy.py +++ b/tests/functional/test_proxy.py @@ -1,6 +1,6 @@ import ssl from pathlib import Path -from typing import Any, Dict +from typing import Any import proxy import pytest @@ -17,7 +17,7 @@ class AccessLogPlugin(HttpProxyBasePlugin): - def on_access_log(self, context: Dict[str, Any]) -> None: + def on_access_log(self, context: dict[str, Any]) -> None: print(context) @@ -25,10 +25,13 @@ def on_access_log(self, context: Dict[str, Any]) -> None: def test_proxy_overrides_env( script: PipTestEnvironment, capfd: pytest.CaptureFixture[str] ) -> None: - with proxy.Proxy( - port=8899, - num_acceptors=1, - ), proxy.Proxy(plugins=[AccessLogPlugin], port=8888, num_acceptors=1): + with ( + proxy.Proxy( + port=8899, + num_acceptors=1, + ), + proxy.Proxy(plugins=[AccessLogPlugin], port=8888, num_acceptors=1), + ): script.environ["http_proxy"] = "127.0.0.1:8888" script.environ["https_proxy"] = "127.0.0.1:8888" result = script.pip( diff --git a/tests/functional/test_search.py b/tests/functional/test_search.py index 9491b492400..e33a17bec8a 100644 --- a/tests/functional/test_search.py +++ b/tests/functional/test_search.py @@ -1,5 +1,5 @@ import logging -from typing import TYPE_CHECKING, Dict, List +from typing import TYPE_CHECKING from unittest import mock import pytest @@ -28,7 +28,7 @@ def test_pypi_xml_transformation() -> None: Test transformation of data structures (PyPI xmlrpc to custom list). """ - pypi_hits: List[Dict[str, str]] = [ + pypi_hits: list[dict[str, str]] = [ { "name": "foo", "summary": "foo summary", @@ -46,7 +46,7 @@ def test_pypi_xml_transformation() -> None: "version": "1.0", }, ] - expected: List[TransformedHit] = [ + expected: list[TransformedHit] = [ { "versions": ["1.0", "2.0"], "name": "foo", @@ -160,7 +160,7 @@ def test_latest_prerelease_install_message( """ Test documentation for installing pre-release packages is displayed """ - hits: List[TransformedHit] = [ + hits: list[TransformedHit] = [ { "name": "ni", "summary": "For knights who say Ni!", @@ -189,7 +189,7 @@ def test_search_print_results_should_contain_latest_versions( """ Test that printed search results contain the latest package versions """ - hits: List[TransformedHit] = [ + hits: list[TransformedHit] = [ { "name": "testlib1", "summary": "Test library 1.", diff --git a/tests/functional/test_uninstall.py b/tests/functional/test_uninstall.py index d86ba172002..8268b67820e 100644 --- a/tests/functional/test_uninstall.py +++ b/tests/functional/test_uninstall.py @@ -2,10 +2,11 @@ import os import sys import textwrap +from collections.abc import Iterator from os.path import join, normpath from pathlib import Path from tempfile import mkdtemp -from typing import Any, Iterator +from typing import Any from unittest.mock import Mock import pytest diff --git a/tests/functional/test_vcs_git.py b/tests/functional/test_vcs_git.py index f917fa8b39e..b2a241bd3db 100644 --- a/tests/functional/test_vcs_git.py +++ b/tests/functional/test_vcs_git.py @@ -5,7 +5,7 @@ import logging import os import pathlib -from typing import List, Optional, Tuple +from typing import Optional from unittest.mock import Mock, patch import pytest @@ -48,7 +48,7 @@ def do_commit(script: PipTestEnvironment, dest: str) -> str: return get_head_sha(script, dest) -def add_commits(script: PipTestEnvironment, dest: str, count: int) -> List[str]: +def add_commits(script: PipTestEnvironment, dest: str, count: int) -> list[str]: """Return a list of the commit hashes from oldest to newest.""" shas = [] for _ in range(count): @@ -58,7 +58,7 @@ def add_commits(script: PipTestEnvironment, dest: str, count: int) -> List[str]: return shas -def check_rev(repo_dir: str, rev: str, expected: Tuple[Optional[str], bool]) -> None: +def check_rev(repo_dir: str, rev: str, expected: tuple[Optional[str], bool]) -> None: assert Git.get_revision_sha(repo_dir, rev) == expected diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index 44fa4053b73..b7afaabec2e 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -8,6 +8,7 @@ import sys import textwrap from base64 import urlsafe_b64encode +from collections.abc import Iterable, Iterator, Mapping from contextlib import contextmanager from hashlib import sha256 from io import BytesIO, StringIO @@ -16,15 +17,9 @@ Any, AnyStr, Callable, - Dict, - Iterable, - Iterator, - List, Literal, - Mapping, Optional, Protocol, - Tuple, Union, cast, ) @@ -60,7 +55,7 @@ CURRENT_PY_VERSION_INFO = sys.version_info[:3] _Test = Callable[..., None] -_FilesState = Dict[str, Union[FoundDir, FoundFile]] +_FilesState = dict[str, Union[FoundDir, FoundFile]] def assert_paths_equal(actual: str, expected: str) -> None: @@ -80,8 +75,8 @@ def create_file(path: str, contents: Optional[str] = None) -> None: def make_test_search_scope( - find_links: Optional[List[str]] = None, - index_urls: Optional[List[str]] = None, + find_links: Optional[list[str]] = None, + index_urls: Optional[list[str]] = None, ) -> SearchScope: if find_links is None: find_links = [] @@ -96,8 +91,8 @@ def make_test_search_scope( def make_test_link_collector( - find_links: Optional[List[str]] = None, - index_urls: Optional[List[str]] = None, + find_links: Optional[list[str]] = None, + index_urls: Optional[list[str]] = None, session: Optional[PipSession] = None, ) -> LinkCollector: """ @@ -115,8 +110,8 @@ def make_test_link_collector( def make_test_finder( - find_links: Optional[List[str]] = None, - index_urls: Optional[List[str]] = None, + find_links: Optional[list[str]] = None, + index_urls: Optional[list[str]] = None, allow_all_prereleases: bool = False, session: Optional[PipSession] = None, target_python: Optional[TargetPython] = None, @@ -305,7 +300,7 @@ def files_updated(self) -> FoundFiles: def files_deleted(self) -> FoundFiles: return FoundFiles(self._impl.files_deleted) - def _get_egg_link_path_created(self, egg_link_paths: List[str]) -> Optional[str]: + def _get_egg_link_path_created(self, egg_link_paths: list[str]) -> Optional[str]: for egg_link_path in egg_link_paths: if egg_link_path in self.files_created: return egg_link_path @@ -315,8 +310,8 @@ def assert_installed( self, pkg_name: str, editable: bool = True, - with_files: Optional[List[str]] = None, - without_files: Optional[List[str]] = None, + with_files: Optional[list[str]] = None, + without_files: Optional[list[str]] = None, without_egg_link: bool = False, use_user_site: bool = False, sub_dir: Optional[str] = None, @@ -617,7 +612,7 @@ def _ignore_file(self, fn: str) -> bool: result = super()._ignore_file(fn) return result - def _find_traverse(self, path: str, result: Dict[str, FoundDir]) -> None: + def _find_traverse(self, path: str, result: dict[str, FoundDir]) -> None: # Ignore symlinked directories to avoid duplicates in `run()` # results because of venv `lib64 -> lib/` symlink on Linux. full = os.path.join(self.base_path, path) @@ -777,7 +772,7 @@ def assert_not_installed(self, *args: str) -> None: # multiple commands. Maybe should be rolled into ScriptTest? def diff_states( start: _FilesState, end: _FilesState, ignore: Iterable[StrPath] = () -) -> Dict[str, _FilesState]: +) -> dict[str, _FilesState]: """ Differences two "filesystem states" as represented by dictionaries of FoundFile and FoundDir objects. @@ -823,8 +818,8 @@ def prefix_match(path: str, prefix_path: StrPath) -> bool: def assert_all_changes( start_state: Union[_FilesState, TestPipResult], end_state: Union[_FilesState, TestPipResult], - expected_changes: List[StrPath], -) -> Dict[str, _FilesState]: + expected_changes: list[StrPath], +) -> dict[str, _FilesState]: """ Fails if anything changed that isn't listed in the expected_changes. @@ -1177,10 +1172,10 @@ def create_basic_wheel_for_package( script: PipTestEnvironment, name: str, version: str, - depends: Optional[List[str]] = None, - extras: Optional[Dict[str, List[str]]] = None, + depends: Optional[list[str]] = None, + extras: Optional[dict[str, list[str]]] = None, requires_python: Optional[str] = None, - extra_files: Optional[Dict[str, Union[bytes, str]]] = None, + extra_files: Optional[dict[str, Union[bytes, str]]] = None, ) -> pathlib.Path: if depends is None: depends = [] @@ -1211,7 +1206,7 @@ def hello(): for package in packages ] - metadata_updates: Dict[str, Any] = { + metadata_updates: dict[str, Any] = { "Provides-Extra": list(extras), "Requires-Dist": requires_dist, } @@ -1237,11 +1232,11 @@ def create_basic_sdist_for_package( script: PipTestEnvironment, name: str, version: str, - extra_files: Optional[Dict[str, str]] = None, + extra_files: Optional[dict[str, str]] = None, *, fails_egg_info: bool = False, fails_bdist_wheel: bool = False, - depends: Optional[List[str]] = None, + depends: Optional[list[str]] = None, setup_py_prelude: str = "", ) -> pathlib.Path: files = { @@ -1301,7 +1296,7 @@ def create_basic_sdist_for_package( return retval -def need_executable(name: str, check_cmd: Tuple[str, ...]) -> Callable[[_Test], _Test]: +def need_executable(name: str, check_cmd: tuple[str, ...]) -> Callable[[_Test], _Test]: def wrapper(fn: _Test) -> _Test: try: subprocess.check_output(check_cmd) @@ -1374,7 +1369,7 @@ def __call__( self, tmpdir: pathlib.Path, virtualenv: Optional[VirtualEnvironment] = None, - environ: Optional[Dict[AnyStr, AnyStr]] = None, + environ: Optional[dict[AnyStr, AnyStr]] = None, ) -> PipTestEnvironment: ... diff --git a/tests/lib/certs.py b/tests/lib/certs.py index 6f899acfe48..3f2fffc281e 100644 --- a/tests/lib/certs.py +++ b/tests/lib/certs.py @@ -1,5 +1,4 @@ from datetime import datetime, timedelta, timezone -from typing import Tuple from cryptography import x509 from cryptography.hazmat.backends import default_backend @@ -8,7 +7,7 @@ from cryptography.x509.oid import ExtendedKeyUsageOID, NameOID -def make_tls_cert(hostname: str) -> Tuple[x509.Certificate, rsa.RSAPrivateKey]: +def make_tls_cert(hostname: str) -> tuple[x509.Certificate, rsa.RSAPrivateKey]: key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend() ) diff --git a/tests/lib/compat.py b/tests/lib/compat.py index 866ac7a7734..186aa05421f 100644 --- a/tests/lib/compat.py +++ b/tests/lib/compat.py @@ -2,13 +2,16 @@ import contextlib import signal -from typing import Callable, ContextManager, Iterable, Iterator +from collections.abc import Iterable, Iterator +from typing import Callable # Applies on Windows. if not hasattr(signal, "pthread_sigmask"): # We're not relying on this behavior anywhere currently, it's just best # practice. - blocked_signals: Callable[[], ContextManager[None]] = contextlib.nullcontext + blocked_signals: Callable[[], contextlib.AbstractContextManager[None]] = ( + contextlib.nullcontext + ) else: @contextlib.contextmanager diff --git a/tests/lib/configuration_helpers.py b/tests/lib/configuration_helpers.py index b6e398c5bf1..4015cc3036a 100644 --- a/tests/lib/configuration_helpers.py +++ b/tests/lib/configuration_helpers.py @@ -6,7 +6,8 @@ import os import tempfile import textwrap -from typing import Any, Dict, Iterator +from collections.abc import Iterator +from typing import Any import pip._internal.configuration from pip._internal.utils.misc import ensure_dir @@ -22,7 +23,7 @@ def setup_method(self) -> None: isolated=False, ) - def patch_configuration(self, variant: Kind, di: Dict[str, Any]) -> None: + def patch_configuration(self, variant: Kind, di: dict[str, Any]) -> None: old = self.configuration._load_config_files @functools.wraps(old) diff --git a/tests/lib/filesystem.py b/tests/lib/filesystem.py index e05e2703e9c..780f113689d 100644 --- a/tests/lib/filesystem.py +++ b/tests/lib/filesystem.py @@ -2,15 +2,16 @@ """ import os +from collections.abc import Iterator from contextlib import contextmanager from functools import partial from itertools import chain from pathlib import Path -from typing import Iterator, List, Set, Union +from typing import Union -def get_filelist(base: str) -> Set[str]: - def join(dirpath: str, dirnames: List[str], filenames: List[str]) -> Iterator[str]: +def get_filelist(base: str) -> set[str]: + def join(dirpath: str, dirnames: list[str], filenames: list[str]) -> Iterator[str]: relative_dirpath = os.path.relpath(dirpath, base) join_dirpath = partial(os.path.join, relative_dirpath) return chain( diff --git a/tests/lib/git_submodule_helpers.py b/tests/lib/git_submodule_helpers.py index 12b40c60768..5620486b378 100644 --- a/tests/lib/git_submodule_helpers.py +++ b/tests/lib/git_submodule_helpers.py @@ -1,7 +1,6 @@ import os import textwrap from pathlib import Path -from typing import Tuple from tests.lib import PipTestEnvironment, _create_main_file, _git_commit @@ -41,7 +40,7 @@ def _pull_in_submodule_changes_to_module( def _create_test_package_with_submodule( env: PipTestEnvironment, rel_path: str -) -> Tuple[Path, Path]: +) -> tuple[Path, Path]: """ Args: rel_path: the location of the submodule relative to the superproject. diff --git a/tests/lib/options_helpers.py b/tests/lib/options_helpers.py index 4444fa3e97b..988dea1e44a 100644 --- a/tests/lib/options_helpers.py +++ b/tests/lib/options_helpers.py @@ -2,7 +2,6 @@ """ from optparse import Values -from typing import List, Tuple from pip._internal.cli import cmdoptions from pip._internal.cli.base_command import Command @@ -11,8 +10,8 @@ class FakeCommand(Command): def main( # type: ignore[override] - self, args: List[str] - ) -> Tuple[Values, List[str]]: + self, args: list[str] + ) -> tuple[Values, list[str]]: index_opts = cmdoptions.make_option_group( cmdoptions.index_group, self.parser, diff --git a/tests/lib/requests_mocks.py b/tests/lib/requests_mocks.py index a70a9b2b048..5795cb8f2b1 100644 --- a/tests/lib/requests_mocks.py +++ b/tests/lib/requests_mocks.py @@ -1,8 +1,9 @@ """Helper classes as mocks for requests objects. """ +from collections.abc import Iterator from io import BytesIO -from typing import Any, Callable, Dict, Iterator, List, Optional +from typing import Any, Callable, Optional _Hook = Callable[["MockResponse"], None] @@ -34,7 +35,7 @@ def __init__(self, contents: bytes) -> None: self.reason = "OK" self.status_code = 200 self.headers = {"Content-Length": str(len(contents))} - self.history: List[MockResponse] = [] + self.history: list[MockResponse] = [] self.from_cache = False @@ -52,8 +53,8 @@ def send(self, req: "MockRequest", **kwargs: Any) -> MockResponse: class MockRequest: def __init__(self, url: str) -> None: self.url = url - self.headers: Dict[str, str] = {} - self.hooks: Dict[str, List[_Hook]] = {} + self.headers: dict[str, str] = {} + self.hooks: dict[str, list[_Hook]] = {} def register_hook(self, event_name: str, callback: _Hook) -> None: self.hooks.setdefault(event_name, []).append(callback) diff --git a/tests/lib/server.py b/tests/lib/server.py index 96ac5930dc9..f49c73172f2 100644 --- a/tests/lib/server.py +++ b/tests/lib/server.py @@ -2,9 +2,10 @@ import ssl import threading from base64 import b64encode +from collections.abc import Iterable, Iterator from contextlib import ExitStack, contextmanager from textwrap import dedent -from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List +from typing import TYPE_CHECKING, Any, Callable from unittest.mock import Mock from werkzeug.serving import BaseWSGIServer, WSGIRequestHandler @@ -23,7 +24,7 @@ class _MockServer(BaseWSGIServer): class _RequestHandler(WSGIRequestHandler): - def make_environ(self) -> Dict[str, Any]: + def make_environ(self) -> dict[str, Any]: environ = super().make_environ() # From pallets/werkzeug#1469, will probably be in release after @@ -150,7 +151,7 @@ def html5_page(text: str) -> str: ) -def package_page(spec: Dict[str, str]) -> "WSGIApplication": +def package_page(spec: dict[str, str]) -> "WSGIApplication": def link(name: str, value: str) -> str: return f'{name}' @@ -226,7 +227,7 @@ def stop(self) -> None: assert self._running, "idle server cannot be stopped" self.context.close() - def get_requests(self) -> List[Dict[str, str]]: + def get_requests(self) -> list[dict[str, str]]: """Get environ for each received request.""" assert not self._running, "cannot get mock from running server" # Legacy: replace call[0][0] with call.args[0] diff --git a/tests/lib/test_lib.py b/tests/lib/test_lib.py index dba55a82809..c94750d0f2e 100644 --- a/tests/lib/test_lib.py +++ b/tests/lib/test_lib.py @@ -4,9 +4,10 @@ import pathlib import re import sys +from collections.abc import Iterator from contextlib import contextmanager from os.path import isdir, join -from typing import Any, Dict, Iterator, Type +from typing import Any import pytest @@ -15,7 +16,7 @@ @contextmanager def assert_error_startswith( - exc_type: Type[Exception], expected_start: str + exc_type: type[Exception], expected_start: str ) -> Iterator[None]: """ Assert that an exception is raised starting with a certain message. @@ -238,7 +239,7 @@ def test_run__allow_stderr_warning_false_error( """ Test passing allow_stderr_warning=False when it is not allowed. """ - kwargs: Dict[str, Any] = {"allow_stderr_warning": False, arg_name: True} + kwargs: dict[str, Any] = {"allow_stderr_warning": False, arg_name: True} expected_start = ( "cannot pass allow_stderr_warning=False with allow_stderr_error=True" ) diff --git a/tests/lib/venv.py b/tests/lib/venv.py index fac54d3bd2c..bd38280a66e 100644 --- a/tests/lib/venv.py +++ b/tests/lib/venv.py @@ -7,7 +7,7 @@ import textwrap import venv as _venv from pathlib import Path -from typing import Dict, Literal, Optional, Union +from typing import Literal, Optional, Union import virtualenv as _virtualenv @@ -196,7 +196,7 @@ def _customize_site(self) -> None: # Make sure bytecode is up-to-date too. assert compileall.compile_file(str(sitecustomize), quiet=1, force=True) - def _rewrite_pyvenv_cfg(self, replacements: Dict[str, str]) -> None: + def _rewrite_pyvenv_cfg(self, replacements: dict[str, str]) -> None: pyvenv_cfg = self.location.joinpath("pyvenv.cfg") lines = pyvenv_cfg.read_text(encoding="utf-8").splitlines() diff --git a/tests/lib/wheel.py b/tests/lib/wheel.py index e2634e72c86..5f0ae4ac31f 100644 --- a/tests/lib/wheel.py +++ b/tests/lib/wheel.py @@ -5,6 +5,7 @@ import itertools from base64 import urlsafe_b64encode from collections import namedtuple +from collections.abc import Iterable, Sequence from copy import deepcopy from email.message import Message from enum import Enum @@ -14,12 +15,7 @@ from pathlib import Path from typing import ( AnyStr, - Dict, - Iterable, - List, Optional, - Sequence, - Tuple, TypeVar, Union, ) @@ -30,7 +26,7 @@ from pip._internal.metadata import BaseDistribution, MemoryWheel, get_wheel_distribution # As would be used in metadata -HeaderValue = Union[str, List[str]] +HeaderValue = Union[str, list[str]] File = namedtuple("File", ["name", "contents"]) @@ -55,7 +51,7 @@ def ensure_binary(value: Union[bytes, str]) -> bytes: return value.encode() -def message_from_dict(headers: Dict[str, HeaderValue]) -> Message: +def message_from_dict(headers: dict[str, HeaderValue]) -> Message: """Plain key-value pairs are set in the returned message. List values are converted into repeated headers in the result. @@ -78,7 +74,7 @@ def make_metadata_file( name: str, version: str, value: Defaulted[Optional[AnyStr]], - updates: Defaulted[Dict[str, HeaderValue]], + updates: Defaulted[dict[str, HeaderValue]], body: Defaulted[AnyStr], ) -> Optional[File]: if value is None: @@ -110,8 +106,8 @@ def make_wheel_metadata_file( name: str, version: str, value: Defaulted[Union[bytes, str, None]], - tags: Sequence[Tuple[str, str, str]], - updates: Defaulted[Dict[str, HeaderValue]], + tags: Sequence[tuple[str, str, str]], + updates: Defaulted[dict[str, HeaderValue]], ) -> Optional[File]: if value is None: return None @@ -139,8 +135,8 @@ def make_wheel_metadata_file( def make_entry_points_file( name: str, version: str, - entry_points: Defaulted[Dict[str, List[str]]], - console_scripts: Defaulted[List[str]], + entry_points: Defaulted[dict[str, list[str]]], + console_scripts: Defaulted[list[str]], ) -> Optional[File]: if entry_points is _default and console_scripts is _default: return None @@ -164,13 +160,13 @@ def make_entry_points_file( ) -def make_files(files: Dict[str, Union[bytes, str]]) -> List[File]: +def make_files(files: dict[str, Union[bytes, str]]) -> list[File]: return [File(name, ensure_binary(contents)) for name, contents in files.items()] def make_metadata_files( - name: str, version: str, files: Dict[str, AnyStr] -) -> List[File]: + name: str, version: str, files: dict[str, AnyStr] +) -> list[File]: get_path = partial(dist_info_path, name, version) return [ File(get_path(name), ensure_binary(contents)) @@ -178,7 +174,7 @@ def make_metadata_files( ] -def make_data_files(name: str, version: str, files: Dict[str, AnyStr]) -> List[File]: +def make_data_files(name: str, version: str, files: dict[str, AnyStr]) -> list[File]: data_dir = f"{name}-{version}.data" return [ File(f"{data_dir}/{name}", ensure_binary(contents)) @@ -200,7 +196,7 @@ def record_file_maker_wrapper( files: Iterable[File], record: Defaulted[Optional[AnyStr]], ) -> Iterable[File]: - records: List[Record] = [] + records: list[Record] = [] for file in files: records.append( Record(file.name, digest(file.contents), str(len(file.contents))) @@ -292,15 +288,15 @@ def make_wheel( name: str, version: str, wheel_metadata: Defaulted[Optional[AnyStr]] = _default, - wheel_metadata_updates: Defaulted[Dict[str, HeaderValue]] = _default, + wheel_metadata_updates: Defaulted[dict[str, HeaderValue]] = _default, metadata: Defaulted[Optional[AnyStr]] = _default, metadata_body: Defaulted[AnyStr] = _default, - metadata_updates: Defaulted[Dict[str, HeaderValue]] = _default, - extra_files: Defaulted[Dict[str, Union[bytes, str]]] = _default, - extra_metadata_files: Defaulted[Dict[str, AnyStr]] = _default, - extra_data_files: Defaulted[Dict[str, AnyStr]] = _default, - console_scripts: Defaulted[List[str]] = _default, - entry_points: Defaulted[Dict[str, List[str]]] = _default, + metadata_updates: Defaulted[dict[str, HeaderValue]] = _default, + extra_files: Defaulted[dict[str, Union[bytes, str]]] = _default, + extra_metadata_files: Defaulted[dict[str, AnyStr]] = _default, + extra_data_files: Defaulted[dict[str, AnyStr]] = _default, + console_scripts: Defaulted[list[str]] = _default, + entry_points: Defaulted[dict[str, list[str]]] = _default, record: Defaulted[Optional[AnyStr]] = _default, ) -> WheelBuilder: """ diff --git a/tests/unit/metadata/test_metadata_pkg_resources.py b/tests/unit/metadata/test_metadata_pkg_resources.py index 6b3a302fb3c..ccb0b7dcf0f 100644 --- a/tests/unit/metadata/test_metadata_pkg_resources.py +++ b/tests/unit/metadata/test_metadata_pkg_resources.py @@ -1,6 +1,6 @@ import email.message import itertools -from typing import List, cast +from typing import cast from unittest import mock import pytest @@ -34,7 +34,7 @@ def patch_distribution_lookups(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(Distribution, "in_usersite", property(_dist_in_usersite)) -class _MockWorkingSet(List[mock.Mock]): +class _MockWorkingSet(list[mock.Mock]): def require(self, name: str) -> None: pass diff --git a/tests/unit/resolution_resolvelib/conftest.py b/tests/unit/resolution_resolvelib/conftest.py index 6aa5f505dbb..ee2633afac9 100644 --- a/tests/unit/resolution_resolvelib/conftest.py +++ b/tests/unit/resolution_resolvelib/conftest.py @@ -1,4 +1,4 @@ -from typing import Iterator +from collections.abc import Iterator import pytest diff --git a/tests/unit/resolution_resolvelib/test_provider.py b/tests/unit/resolution_resolvelib/test_provider.py index 5f30e2bc1dd..9911193ca83 100644 --- a/tests/unit/resolution_resolvelib/test_provider.py +++ b/tests/unit/resolution_resolvelib/test_provider.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, Optional from pip._vendor.resolvelib.resolvers import RequirementInformation @@ -15,7 +15,7 @@ def build_requirement_information( name: str, parent: Optional[InstallationCandidate] -) -> List["PreferenceInformation"]: +) -> list["PreferenceInformation"]: install_requirement = install_req_from_req_string(name) # RequirementInformation is typed as a tuple, but it is a namedtupled. # https://github.com/sarugaku/resolvelib/blob/7bc025aa2a4e979597c438ad7b17d2e8a08a364e/src/resolvelib/resolvers.pyi#L20-L22 diff --git a/tests/unit/resolution_resolvelib/test_requirement.py b/tests/unit/resolution_resolvelib/test_requirement.py index cde6256a206..f5c737118cd 100644 --- a/tests/unit/resolution_resolvelib/test_requirement.py +++ b/tests/unit/resolution_resolvelib/test_requirement.py @@ -1,6 +1,5 @@ import os from pathlib import Path -from typing import List, Tuple import pytest @@ -34,7 +33,7 @@ def _is_satisfied_by(requirement: Requirement, candidate: Candidate) -> bool: @pytest.fixture -def test_cases(data: TestData) -> List[Tuple[str, str, int]]: +def test_cases(data: TestData) -> list[tuple[str, str, int]]: def _data_file(name: str) -> Path: return data.packages.joinpath(name) @@ -67,7 +66,7 @@ def data_url(name: str) -> str: def test_new_resolver_requirement_has_name( - test_cases: List[Tuple[str, str, int]], factory: Factory + test_cases: list[tuple[str, str, int]], factory: Factory ) -> None: """All requirements should have a name""" for spec, name, _ in test_cases: @@ -77,7 +76,7 @@ def test_new_resolver_requirement_has_name( def test_new_resolver_correct_number_of_matches( - test_cases: List[Tuple[str, str, int]], factory: Factory + test_cases: list[tuple[str, str, int]], factory: Factory ) -> None: """Requirements should return the correct number of candidates""" for spec, _, match_count in test_cases: @@ -96,7 +95,7 @@ def test_new_resolver_correct_number_of_matches( def test_new_resolver_candidates_match_requirement( - test_cases: List[Tuple[str, str, int]], factory: Factory + test_cases: list[tuple[str, str, int]], factory: Factory ) -> None: """Candidates returned from find_candidates should satisfy the requirement""" for spec, _, _ in test_cases: diff --git a/tests/unit/resolution_resolvelib/test_resolver.py b/tests/unit/resolution_resolvelib/test_resolver.py index 18238eef134..2aa61f59963 100644 --- a/tests/unit/resolution_resolvelib/test_resolver.py +++ b/tests/unit/resolution_resolvelib/test_resolver.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional, Set, Tuple, cast +from typing import Optional, cast from unittest import mock import pytest @@ -35,7 +35,7 @@ def resolver(preparer: RequirementPreparer, finder: PackageFinder) -> Resolver: def _make_graph( - edges: List[Tuple[Optional[str], Optional[str]]] + edges: list[tuple[Optional[str], Optional[str]]] ) -> "DirectedGraph[Optional[str]]": """Build graph from edge declarations.""" @@ -84,8 +84,8 @@ def _make_graph( ) def test_new_resolver_get_installation_order( resolver: Resolver, - edges: List[Tuple[Optional[str], Optional[str]]], - ordered_reqs: List[str], + edges: list[tuple[Optional[str], Optional[str]]], + ordered_reqs: list[str], ) -> None: graph = _make_graph(edges) @@ -290,9 +290,9 @@ def test_new_resolver_get_installation_order( ) def test_new_resolver_topological_weights( name: str, - edges: List[Tuple[Optional[str], Optional[str]]], - requirement_keys: Set[str], - expected_weights: Dict[Optional[str], int], + edges: list[tuple[Optional[str], Optional[str]]], + requirement_keys: set[str], + expected_weights: dict[Optional[str], int], ) -> None: graph = _make_graph(edges) diff --git a/tests/unit/test_base_command.py b/tests/unit/test_base_command.py index f9fae651422..84964a7cb4b 100644 --- a/tests/unit/test_base_command.py +++ b/tests/unit/test_base_command.py @@ -1,9 +1,10 @@ import logging import os import time +from collections.abc import Iterator from optparse import Values from pathlib import Path -from typing import Callable, Iterator, List, NoReturn, Optional +from typing import Callable, NoReturn, Optional from unittest.mock import Mock, patch import pytest @@ -39,11 +40,11 @@ def run_func() -> int: self.run_func = run_func super().__init__(self._name, self._name) - def main(self, args: List[str]) -> int: + def main(self, args: list[str]) -> int: args.append("--disable-pip-version-check") return super().main(args) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: logging.getLogger("pip.tests").info("fake") # Return SUCCESS from run if run_func is not provided if self.run_func: @@ -55,14 +56,14 @@ def run(self, options: Values, args: List[str]) -> int: class FakeCommandWithUnicode(FakeCommand): _name = "fake_unicode" - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: logging.getLogger("pip.tests").info(b"bytes here \xE9") logging.getLogger("pip.tests").info(b"unicode here \xC3\xA9".decode("utf-8")) return SUCCESS class TestCommand: - def call_main(self, capsys: pytest.CaptureFixture[str], args: List[str]) -> str: + def call_main(self, capsys: pytest.CaptureFixture[str], args: list[str]) -> str: """ Call command.main(), and return the command's stderr. """ @@ -148,7 +149,7 @@ def test_base_command_provides_tempdir_helpers() -> None: assert temp_dir._tempdir_manager is None assert temp_dir._tempdir_registry is None - def assert_helpers_set(options: Values, args: List[str]) -> int: + def assert_helpers_set(options: Values, args: list[str]) -> int: assert temp_dir._tempdir_manager is not None assert temp_dir._tempdir_registry is not None return SUCCESS @@ -172,7 +173,7 @@ def test_base_command_global_tempdir_cleanup(kind: str, exists: bool) -> None: class Holder: value: str - def create_temp_dirs(options: Values, args: List[str]) -> int: + def create_temp_dirs(options: Values, args: list[str]) -> int: assert c.tempdir_registry is not None c.tempdir_registry.set_delete(not_deleted, False) Holder.value = TempDirectory(kind=kind, globally_managed=True).path @@ -192,7 +193,7 @@ def test_base_command_local_tempdir_cleanup(kind: str, exists: bool) -> None: assert temp_dir._tempdir_manager is None assert temp_dir._tempdir_registry is None - def create_temp_dirs(options: Values, args: List[str]) -> int: + def create_temp_dirs(options: Values, args: list[str]) -> int: assert c.tempdir_registry is not None c.tempdir_registry.set_delete(not_deleted, False) diff --git a/tests/unit/test_cmdoptions.py b/tests/unit/test_cmdoptions.py index 8c33ca8c18d..08bd932c8fc 100644 --- a/tests/unit/test_cmdoptions.py +++ b/tests/unit/test_cmdoptions.py @@ -1,6 +1,6 @@ import os from pathlib import Path -from typing import Optional, Tuple +from typing import Optional from venv import EnvBuilder import pytest @@ -29,7 +29,7 @@ ], ) def test_convert_python_version( - value: str, expected: Tuple[Optional[Tuple[int, ...]], Optional[str]] + value: str, expected: tuple[Optional[tuple[int, ...]], Optional[str]] ) -> None: actual = _convert_python_version(value) assert actual == expected, f"actual: {actual!r}" diff --git a/tests/unit/test_collector.py b/tests/unit/test_collector.py index c7760a238f2..0077f8f16d0 100644 --- a/tests/unit/test_collector.py +++ b/tests/unit/test_collector.py @@ -6,7 +6,7 @@ import uuid from pathlib import Path from textwrap import dedent -from typing import Dict, List, Optional, Tuple +from typing import Optional from unittest import mock import pytest @@ -685,7 +685,7 @@ def test_parse_links__yanked_reason(anchor_html: str, expected: Optional[str]) - def test_parse_links__metadata_file_data( anchor_html: str, expected: Optional[str], - hashes: Dict[str, str], + hashes: dict[str, str], ) -> None: link = _test_parse_links_data_attribute(anchor_html, "metadata_file_data", expected) assert link._hashes == hashes @@ -954,7 +954,7 @@ def test_collect_sources__non_existing_path() -> None: assert sources.find_links == [None], "Nothing should have been found" -def check_links_include(links: List[Link], names: List[str]) -> None: +def check_links_include(links: list[Link], names: list[str]) -> None: """ Assert that the given list of Link objects includes, for each of the given names, a link whose URL has a base name matching that name. @@ -1099,10 +1099,10 @@ def test_collect_file_sources( ], ) def test_link_collector_create( - find_links: List[str], + find_links: list[str], no_index: bool, suppress_no_index: bool, - expected: Tuple[List[str], List[str]], + expected: tuple[list[str], list[str]], ) -> None: """ :param expected: the expected (find_links, index_urls) values. @@ -1216,7 +1216,7 @@ def test_link_hash_parsing(url: str, result: Optional[LinkHash]) -> None: def test_metadata_file_info_parsing_html( metadata_attrib: str, expected: Optional[MetadataFile] ) -> None: - attribs: Dict[str, Optional[str]] = { + attribs: dict[str, Optional[str]] = { "href": "something", "data-dist-info-metadata": metadata_attrib, } diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py index 9d5aefec298..81229a402c6 100644 --- a/tests/unit/test_commands.py +++ b/tests/unit/test_commands.py @@ -1,5 +1,5 @@ import os -from typing import Callable, List +from typing import Callable from unittest import mock import pytest @@ -17,7 +17,7 @@ EXPECTED_INDEX_GROUP_COMMANDS = ["download", "index", "install", "list", "wheel"] -def check_commands(pred: Callable[[Command], bool], expected: List[str]) -> None: +def check_commands(pred: Callable[[Command], bool], expected: list[str]) -> None: """ Check the commands satisfying a predicate. """ diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py index 90a44348f0a..f1a491783c7 100644 --- a/tests/unit/test_exceptions.py +++ b/tests/unit/test_exceptions.py @@ -6,7 +6,7 @@ import pathlib import sys import textwrap -from typing import Optional, Tuple +from typing import Optional import pytest @@ -492,7 +492,7 @@ class TestExternallyManagedEnvironment: def patch_locale(self, monkeypatch: pytest.MonkeyPatch) -> None: orig_getlocal = locale.getlocale - def fake_getlocale(category: int) -> Tuple[Optional[str], Optional[str]]: + def fake_getlocale(category: int) -> tuple[Optional[str], Optional[str]]: """Fake getlocale() that always reports zh_Hant for LC_MESSASGES.""" result = orig_getlocal(category) if category == getattr(locale, "LC_MESSAGES", None): diff --git a/tests/unit/test_finder.py b/tests/unit/test_finder.py index 8c923dcd36f..38697a8621a 100644 --- a/tests/unit/test_finder.py +++ b/tests/unit/test_finder.py @@ -1,5 +1,5 @@ import logging -from typing import Iterable +from collections.abc import Iterable from unittest.mock import Mock, patch import pytest diff --git a/tests/unit/test_format_control.py b/tests/unit/test_format_control.py index 33a03729db5..f3b714d9134 100644 --- a/tests/unit/test_format_control.py +++ b/tests/unit/test_format_control.py @@ -1,5 +1,4 @@ from optparse import Values -from typing import FrozenSet, List, Set import pytest @@ -17,7 +16,7 @@ def add_options(self) -> None: self.cmd_opts.add_option(cmdoptions.no_binary()) self.cmd_opts.add_option(cmdoptions.only_binary()) - def run(self, options: Values, args: List[str]) -> int: + def run(self, options: Values, args: list[str]) -> int: self.options = options return SUCCESS @@ -67,7 +66,7 @@ def test_comma_separated_values() -> None: ], ) def test_fmt_ctl_matches( - no_binary: Set[str], only_binary: Set[str], argument: str, expected: FrozenSet[str] + no_binary: set[str], only_binary: set[str], argument: str, expected: frozenset[str] ) -> None: fmt = FormatControl(no_binary, only_binary) assert fmt.get_allowed_formats(argument) == expected diff --git a/tests/unit/test_index.py b/tests/unit/test_index.py index cbbca03817e..b29f89c399b 100644 --- a/tests/unit/test_index.py +++ b/tests/unit/test_index.py @@ -1,5 +1,5 @@ import logging -from typing import FrozenSet, List, Optional, Set, Tuple +from typing import Optional import pytest @@ -82,7 +82,7 @@ def check_caplog( def test_check_link_requires_python__incompatible_python( caplog: pytest.LogCaptureFixture, ignore_requires_python: bool, - expected: Tuple[bool, str, str], + expected: tuple[bool, str, str], ) -> None: """ Test an incompatible Python. @@ -146,9 +146,9 @@ class TestLinkEvaluator: ) def test_evaluate_link( self, - py_version_info: Tuple[int, int, int], + py_version_info: tuple[int, int, int], ignore_requires_python: bool, - expected: Tuple[LinkType, str], + expected: tuple[LinkType, str], ) -> None: target_python = TargetPython(py_version_info=py_version_info) evaluator = LinkEvaluator( @@ -199,7 +199,7 @@ def test_evaluate_link__allow_yanked( self, yanked_reason: str, allow_yanked: bool, - expected: Tuple[LinkType, str], + expected: tuple[LinkType, str], ) -> None: target_python = TargetPython(py_version_info=(3, 6, 4)) evaluator = LinkEvaluator( @@ -248,7 +248,7 @@ def test_evaluate_link__incompatible_wheel(self) -> None: (64 * "c", ["1.0", "1.1", "1.2"]), ], ) -def test_filter_unallowed_hashes(hex_digest: str, expected_versions: List[str]) -> None: +def test_filter_unallowed_hashes(hex_digest: str, expected_versions: list[str]) -> None: candidates = [ make_mock_candidate("1.0"), make_mock_candidate("1.1", hex_digest=(64 * "a")), @@ -434,7 +434,7 @@ def test_get_applicable_candidates(self) -> None: def test_get_applicable_candidates__hashes( self, specifier: SpecifierSet, - expected_versions: List[str], + expected_versions: list[str], ) -> None: """ Test a non-None hashes value. @@ -736,8 +736,8 @@ def test_make_link_evaluator( self, allow_yanked: bool, ignore_requires_python: bool, - only_binary: Set[str], - expected_formats: FrozenSet[str], + only_binary: set[str], + expected_formats: frozenset[str], ) -> None: # Create a test TargetPython that we can check for. target_python = TargetPython(py_version_info=(3, 7)) diff --git a/tests/unit/test_locations.py b/tests/unit/test_locations.py index 884e0dd51e2..485d27476be 100644 --- a/tests/unit/test_locations.py +++ b/tests/unit/test_locations.py @@ -10,7 +10,7 @@ import sysconfig import tempfile from pathlib import Path -from typing import Any, Dict +from typing import Any from unittest.mock import Mock import pytest @@ -23,7 +23,7 @@ import pwd -def _get_scheme_dict(*args: Any, **kwargs: Any) -> Dict[str, str]: +def _get_scheme_dict(*args: Any, **kwargs: Any) -> dict[str, str]: scheme = get_scheme(*args, **kwargs) return {k: getattr(scheme, k) for k in SCHEME_KEYS} diff --git a/tests/unit/test_network_auth.py b/tests/unit/test_network_auth.py index aec5e513ba5..0372d32c7f5 100644 --- a/tests/unit/test_network_auth.py +++ b/tests/unit/test_network_auth.py @@ -2,7 +2,8 @@ import os import subprocess import sys -from typing import Any, Dict, Iterable, List, Optional, Tuple +from collections.abc import Iterable +from typing import Any, Optional import pytest @@ -163,7 +164,7 @@ class KeyringModuleV1: """ def __init__(self) -> None: - self.saved_passwords: List[Tuple[str, str, str]] = [] + self.saved_passwords: list[tuple[str, str, str]] = [] def get_password(self, system: str, username: str) -> Optional[str]: if system == "example.com" and username: @@ -191,7 +192,7 @@ def set_password(self, system: str, username: str, password: str) -> None: def test_keyring_get_password( monkeypatch: pytest.MonkeyPatch, url: str, - expect: Tuple[Optional[str], Optional[str]], + expect: tuple[Optional[str], Optional[str]], ) -> None: keyring = KeyringModuleV1() monkeypatch.setitem(sys.modules, "keyring", keyring) @@ -275,7 +276,7 @@ def test_keyring_get_password_username_in_index( def test_keyring_set_password( monkeypatch: pytest.MonkeyPatch, response_status: int, - creds: Tuple[str, str, bool], + creds: tuple[str, str, bool], expect_save: bool, ) -> None: keyring = KeyringModuleV1() @@ -353,7 +354,7 @@ def get_credential(self, system: str, username: str) -> Optional[Credential]: ], ) def test_keyring_get_credential( - monkeypatch: pytest.MonkeyPatch, url: str, expect: Tuple[str, str] + monkeypatch: pytest.MonkeyPatch, url: str, expect: tuple[str, str] ) -> None: monkeypatch.setitem(sys.modules, "keyring", KeyringModuleV2()) auth = MultiDomainBasicAuth( @@ -401,9 +402,9 @@ class KeyringSubprocessResult(KeyringModuleV1): def __call__( self, - cmd: List[str], + cmd: list[str], *, - env: Dict[str, str], + env: dict[str, str], stdin: Optional[Any] = None, stdout: Optional[Any] = None, input: Optional[bytes] = None, @@ -456,7 +457,7 @@ def check_returncode(self) -> None: def test_keyring_cli_get_password( monkeypatch: pytest.MonkeyPatch, url: str, - expect: Tuple[Optional[str], Optional[str]], + expect: tuple[Optional[str], Optional[str]], ) -> None: monkeypatch.setattr(pip._internal.network.auth.shutil, "which", lambda x: "keyring") monkeypatch.setattr( @@ -490,7 +491,7 @@ def test_keyring_cli_get_password( def test_keyring_cli_set_password( monkeypatch: pytest.MonkeyPatch, response_status: int, - creds: Tuple[str, str, bool], + creds: tuple[str, str, bool], expect_save: bool, ) -> None: monkeypatch.setattr(pip._internal.network.auth.shutil, "which", lambda x: "keyring") diff --git a/tests/unit/test_network_download.py b/tests/unit/test_network_download.py index 14998d229bf..b7958773f63 100644 --- a/tests/unit/test_network_download.py +++ b/tests/unit/test_network_download.py @@ -1,6 +1,5 @@ import logging import sys -from typing import Dict import pytest @@ -53,7 +52,7 @@ def test_prepare_download__log( caplog: pytest.LogCaptureFixture, url: str, - headers: Dict[str, str], + headers: dict[str, str], from_cache: bool, expected: str, ) -> None: diff --git a/tests/unit/test_network_lazy_wheel.py b/tests/unit/test_network_lazy_wheel.py index 5d97e9e3202..b7cd9d42444 100644 --- a/tests/unit/test_network_lazy_wheel.py +++ b/tests/unit/test_network_lazy_wheel.py @@ -1,4 +1,4 @@ -from typing import Iterator +from collections.abc import Iterator import pytest diff --git a/tests/unit/test_network_session.py b/tests/unit/test_network_session.py index fd00d5c606c..69217053d9a 100644 --- a/tests/unit/test_network_session.py +++ b/tests/unit/test_network_session.py @@ -1,7 +1,7 @@ import logging import os from pathlib import Path -from typing import Any, List, Optional +from typing import Any, Optional from urllib.parse import urlparse from urllib.request import getproxies @@ -231,7 +231,7 @@ def test_is_secure_origin( self, caplog: pytest.LogCaptureFixture, location: str, - trusted: List[str], + trusted: list[str], expected: bool, ) -> None: class MockLogger: diff --git a/tests/unit/test_operations_prepare.py b/tests/unit/test_operations_prepare.py index 86e26c11801..59a2e82206e 100644 --- a/tests/unit/test_operations_prepare.py +++ b/tests/unit/test_operations_prepare.py @@ -3,7 +3,7 @@ from pathlib import Path from shutil import rmtree from tempfile import mkdtemp -from typing import Any, Dict +from typing import Any from unittest.mock import Mock, patch import pytest @@ -25,7 +25,7 @@ def test_unpack_url_with_urllib_response_without_content_type(data: TestData) -> """ _real_session = PipSession() - def _fake_session_get(*args: Any, **kwargs: Any) -> Dict[str, str]: + def _fake_session_get(*args: Any, **kwargs: Any) -> dict[str, str]: resp = _real_session.get(*args, **kwargs) del resp.headers["Content-Type"] return resp diff --git a/tests/unit/test_options.py b/tests/unit/test_options.py index 8f3cf7de6a6..cb23620ff10 100644 --- a/tests/unit/test_options.py +++ b/tests/unit/test_options.py @@ -1,8 +1,9 @@ import os +from collections.abc import Iterator from contextlib import contextmanager from optparse import Values from tempfile import NamedTemporaryFile -from typing import Any, Dict, Iterator, List, Tuple, Type, Union, cast +from typing import Any, Union, cast import pytest @@ -46,15 +47,15 @@ class TestOptionPrecedence(AddFakeCommandMixin): defaults """ - def get_config_section(self, section: str) -> List[Tuple[str, str]]: + def get_config_section(self, section: str) -> list[tuple[str, str]]: config = { "global": [("timeout", "-3")], "fake": [("timeout", "-2")], } return config[section] - def get_config_section_global(self, section: str) -> List[Tuple[str, str]]: - config: Dict[str, List[Tuple[str, str]]] = { + def get_config_section_global(self, section: str) -> list[tuple[str, str]]: + config: dict[str, list[tuple[str, str]]] = { "global": [("timeout", "-3")], "fake": [], } @@ -66,31 +67,31 @@ def test_env_override_default_int(self, monkeypatch: pytest.MonkeyPatch) -> None """ monkeypatch.setenv("PIP_TIMEOUT", "-1") # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) assert options.timeout == -1 @pytest.mark.parametrize("values", [["F1"], ["F1", "F2"]]) def test_env_override_default_append( - self, values: List[str], monkeypatch: pytest.MonkeyPatch + self, values: list[str], monkeypatch: pytest.MonkeyPatch ) -> None: """ Test that environment variable overrides an append option default. """ monkeypatch.setenv("PIP_FIND_LINKS", " ".join(values)) # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) assert options.find_links == values @pytest.mark.parametrize("choices", [["w"], ["s", "w"]]) def test_env_override_default_choice( - self, choices: List[str], monkeypatch: pytest.MonkeyPatch + self, choices: list[str], monkeypatch: pytest.MonkeyPatch ) -> None: """ Test that environment variable overrides a choice option default. """ monkeypatch.setenv("PIP_EXISTS_ACTION", " ".join(choices)) # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) assert options.exists_action == choices @pytest.mark.parametrize("name", ["PIP_LOG_FILE", "PIP_LOCAL_LOG"]) @@ -104,7 +105,7 @@ def test_env_alias_override_default( """ monkeypatch.setenv(name, "override.log") # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) assert options.log == "override.log" def test_cli_override_environment(self, monkeypatch: pytest.MonkeyPatch) -> None: @@ -114,7 +115,7 @@ def test_cli_override_environment(self, monkeypatch: pytest.MonkeyPatch) -> None monkeypatch.setenv("PIP_TIMEOUT", "-1") # FakeCommand intentionally returns the wrong type. options, args = cast( - Tuple[Values, List[str]], main(["fake", "--timeout", "-2"]) + tuple[Values, list[str]], main(["fake", "--timeout", "-2"]) ) assert options.timeout == -2 @@ -143,7 +144,7 @@ def test_cache_dir__PIP_NO_CACHE_DIR( """ monkeypatch.setenv("PIP_NO_CACHE_DIR", pip_no_cache_dir) # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) assert options.cache_dir is False @pytest.mark.parametrize("pip_no_cache_dir", ["yes", "no"]) @@ -159,7 +160,7 @@ def test_cache_dir__PIP_NO_CACHE_DIR__with_cache_dir( monkeypatch.setenv("PIP_NO_CACHE_DIR", pip_no_cache_dir) # FakeCommand intentionally returns the wrong type. options, args = cast( - Tuple[Values, List[str]], main(["--cache-dir", "/cache/dir", "fake"]) + tuple[Values, list[str]], main(["--cache-dir", "/cache/dir", "fake"]) ) # The command-line flag takes precedence. assert options.cache_dir == "/cache/dir" @@ -175,7 +176,7 @@ def test_cache_dir__PIP_NO_CACHE_DIR__with_no_cache_dir( """ monkeypatch.setenv("PIP_NO_CACHE_DIR", pip_no_cache_dir) # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["--no-cache-dir", "fake"])) + options, args = cast(tuple[Values, list[str]], main(["--no-cache-dir", "fake"])) # The command-line flag should take precedence (which has the same # value in this case). assert options.cache_dir is False @@ -200,7 +201,7 @@ class TestUsePEP517Options: Test options related to using --use-pep517. """ - def parse_args(self, args: List[str]) -> Values: + def parse_args(self, args: list[str]) -> Values: # We use DownloadCommand since that is one of the few Command # classes with the use_pep517 options. command = create_command("download") @@ -284,20 +285,20 @@ class TestOptionsInterspersed(AddFakeCommandMixin): def test_general_option_after_subcommand(self) -> None: # FakeCommand intentionally returns the wrong type. options, args = cast( - Tuple[Values, List[str]], main(["fake", "--timeout", "-1"]) + tuple[Values, list[str]], main(["fake", "--timeout", "-1"]) ) assert options.timeout == -1 def test_option_after_subcommand_arg(self) -> None: # FakeCommand intentionally returns the wrong type. options, args = cast( - Tuple[Values, List[str]], main(["fake", "arg", "--timeout", "-1"]) + tuple[Values, list[str]], main(["fake", "arg", "--timeout", "-1"]) ) assert options.timeout == -1 def test_additive_before_after_subcommand(self) -> None: # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["-v", "fake", "-v"])) + options, args = cast(tuple[Values, list[str]], main(["-v", "fake", "-v"])) assert options.verbose == 2 def test_subcommand_option_before_subcommand_fails(self) -> None: @@ -322,8 +323,8 @@ class TestCountOptions(AddFakeCommandMixin): def test_cli_long(self, option: str, value: int) -> None: flags = [f"--{option}"] * value # FakeCommand intentionally returns the wrong type. - opt1, args1 = cast(Tuple[Values, List[str]], main(flags + ["fake"])) - opt2, args2 = cast(Tuple[Values, List[str]], main(["fake"] + flags)) + opt1, args1 = cast(tuple[Values, list[str]], main(flags + ["fake"])) + opt2, args2 = cast(tuple[Values, list[str]], main(["fake"] + flags)) assert getattr(opt1, option) == getattr(opt2, option) == value @pytest.mark.parametrize("option", ["verbose", "quiet"]) @@ -331,8 +332,8 @@ def test_cli_long(self, option: str, value: int) -> None: def test_cli_short(self, option: str, value: int) -> None: flag = "-" + option[0] * value # FakeCommand intentionally returns the wrong type. - opt1, args1 = cast(Tuple[Values, List[str]], main([flag, "fake"])) - opt2, args2 = cast(Tuple[Values, List[str]], main(["fake", flag])) + opt1, args1 = cast(tuple[Values, list[str]], main([flag, "fake"])) + opt2, args2 = cast(tuple[Values, list[str]], main(["fake", flag])) assert getattr(opt1, option) == getattr(opt2, option) == value @pytest.mark.parametrize("option", ["verbose", "quiet"]) @@ -342,7 +343,7 @@ def test_env_var( ) -> None: monkeypatch.setenv("PIP_" + option.upper(), str(value)) # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) assert getattr(options, option) == value @pytest.mark.parametrize("option", ["verbose", "quiet"]) @@ -352,7 +353,7 @@ def test_env_var_integrate_cli( ) -> None: monkeypatch.setenv("PIP_" + option.upper(), str(value)) # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake", "--" + option])) + options, args = cast(tuple[Values, list[str]], main(["fake", "--" + option])) assert getattr(options, option) == value + 1 @pytest.mark.parametrize("option", ["verbose", "quiet"]) @@ -376,7 +377,7 @@ def test_env_var_false( ) -> None: monkeypatch.setenv("PIP_" + option.upper(), str(value)) # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) assert getattr(options, option) == 0 # Undocumented, support for backward compatibility @@ -387,7 +388,7 @@ def test_env_var_true( ) -> None: monkeypatch.setenv("PIP_" + option.upper(), str(value)) # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) assert getattr(options, option) == 1 @pytest.mark.parametrize("option", ["verbose", "quiet"]) @@ -398,7 +399,7 @@ def test_config_file( with tmpconfig(option, value) as name: monkeypatch.setenv("PIP_CONFIG_FILE", name) # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) assert getattr(options, option) == value @pytest.mark.parametrize("option", ["verbose", "quiet"]) @@ -410,7 +411,7 @@ def test_config_file_integrate_cli( monkeypatch.setenv("PIP_CONFIG_FILE", name) # FakeCommand intentionally returns the wrong type. options, args = cast( - Tuple[Values, List[str]], main(["fake", "--" + option]) + tuple[Values, list[str]], main(["fake", "--" + option]) ) assert getattr(options, option) == value + 1 @@ -437,7 +438,7 @@ def test_config_file_false( with tmpconfig(option, value) as name: monkeypatch.setenv("PIP_CONFIG_FILE", name) # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) assert getattr(options, option) == 0 # Undocumented, support for backward compatibility @@ -449,7 +450,7 @@ def test_config_file_true( with tmpconfig(option, value) as name: monkeypatch.setenv("PIP_CONFIG_FILE", name) # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) assert getattr(options, option) == 1 @@ -459,29 +460,29 @@ class TestGeneralOptions(AddFakeCommandMixin): def test_cache_dir__default(self) -> None: # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["fake"])) + options, args = cast(tuple[Values, list[str]], main(["fake"])) # With no options the default cache dir should be used. assert_is_default_cache_dir(options.cache_dir) def test_cache_dir__provided(self) -> None: # FakeCommand intentionally returns the wrong type. options, args = cast( - Tuple[Values, List[str]], main(["--cache-dir", "/cache/dir", "fake"]) + tuple[Values, list[str]], main(["--cache-dir", "/cache/dir", "fake"]) ) assert options.cache_dir == "/cache/dir" def test_no_cache_dir__provided(self) -> None: # FakeCommand intentionally returns the wrong type. - options, args = cast(Tuple[Values, List[str]], main(["--no-cache-dir", "fake"])) + options, args = cast(tuple[Values, list[str]], main(["--no-cache-dir", "fake"])) assert options.cache_dir is False def test_require_virtualenv(self) -> None: # FakeCommand intentionally returns the wrong type. options1, args1 = cast( - Tuple[Values, List[str]], main(["--require-virtualenv", "fake"]) + tuple[Values, list[str]], main(["--require-virtualenv", "fake"]) ) options2, args2 = cast( - Tuple[Values, List[str]], main(["fake", "--require-virtualenv"]) + tuple[Values, list[str]], main(["fake", "--require-virtualenv"]) ) assert options1.require_venv assert options2.require_venv @@ -489,87 +490,87 @@ def test_require_virtualenv(self) -> None: def test_log(self) -> None: # FakeCommand intentionally returns the wrong type. options1, args1 = cast( - Tuple[Values, List[str]], main(["--log", "path", "fake"]) + tuple[Values, list[str]], main(["--log", "path", "fake"]) ) options2, args2 = cast( - Tuple[Values, List[str]], main(["fake", "--log", "path"]) + tuple[Values, list[str]], main(["fake", "--log", "path"]) ) assert options1.log == options2.log == "path" def test_local_log(self) -> None: # FakeCommand intentionally returns the wrong type. options1, args1 = cast( - Tuple[Values, List[str]], main(["--local-log", "path", "fake"]) + tuple[Values, list[str]], main(["--local-log", "path", "fake"]) ) options2, args2 = cast( - Tuple[Values, List[str]], main(["fake", "--local-log", "path"]) + tuple[Values, list[str]], main(["fake", "--local-log", "path"]) ) assert options1.log == options2.log == "path" def test_no_input(self) -> None: # FakeCommand intentionally returns the wrong type. - options1, args1 = cast(Tuple[Values, List[str]], main(["--no-input", "fake"])) - options2, args2 = cast(Tuple[Values, List[str]], main(["fake", "--no-input"])) + options1, args1 = cast(tuple[Values, list[str]], main(["--no-input", "fake"])) + options2, args2 = cast(tuple[Values, list[str]], main(["fake", "--no-input"])) assert options1.no_input assert options2.no_input def test_proxy(self) -> None: # FakeCommand intentionally returns the wrong type. options1, args1 = cast( - Tuple[Values, List[str]], main(["--proxy", "path", "fake"]) + tuple[Values, list[str]], main(["--proxy", "path", "fake"]) ) options2, args2 = cast( - Tuple[Values, List[str]], main(["fake", "--proxy", "path"]) + tuple[Values, list[str]], main(["fake", "--proxy", "path"]) ) assert options1.proxy == options2.proxy == "path" def test_retries(self) -> None: # FakeCommand intentionally returns the wrong type. options1, args1 = cast( - Tuple[Values, List[str]], main(["--retries", "-1", "fake"]) + tuple[Values, list[str]], main(["--retries", "-1", "fake"]) ) options2, args2 = cast( - Tuple[Values, List[str]], main(["fake", "--retries", "-1"]) + tuple[Values, list[str]], main(["fake", "--retries", "-1"]) ) assert options1.retries == options2.retries == -1 def test_timeout(self) -> None: # FakeCommand intentionally returns the wrong type. options1, args1 = cast( - Tuple[Values, List[str]], main(["--timeout", "-1", "fake"]) + tuple[Values, list[str]], main(["--timeout", "-1", "fake"]) ) options2, args2 = cast( - Tuple[Values, List[str]], main(["fake", "--timeout", "-1"]) + tuple[Values, list[str]], main(["fake", "--timeout", "-1"]) ) assert options1.timeout == options2.timeout == -1 def test_exists_action(self) -> None: # FakeCommand intentionally returns the wrong type. options1, args1 = cast( - Tuple[Values, List[str]], main(["--exists-action", "w", "fake"]) + tuple[Values, list[str]], main(["--exists-action", "w", "fake"]) ) options2, args2 = cast( - Tuple[Values, List[str]], main(["fake", "--exists-action", "w"]) + tuple[Values, list[str]], main(["fake", "--exists-action", "w"]) ) assert options1.exists_action == options2.exists_action == ["w"] def test_cert(self) -> None: # FakeCommand intentionally returns the wrong type. options1, args1 = cast( - Tuple[Values, List[str]], main(["--cert", "path", "fake"]) + tuple[Values, list[str]], main(["--cert", "path", "fake"]) ) options2, args2 = cast( - Tuple[Values, List[str]], main(["fake", "--cert", "path"]) + tuple[Values, list[str]], main(["fake", "--cert", "path"]) ) assert options1.cert == options2.cert == "path" def test_client_cert(self) -> None: # FakeCommand intentionally returns the wrong type. options1, args1 = cast( - Tuple[Values, List[str]], main(["--client-cert", "path", "fake"]) + tuple[Values, list[str]], main(["--client-cert", "path", "fake"]) ) options2, args2 = cast( - Tuple[Values, List[str]], main(["fake", "--client-cert", "path"]) + tuple[Values, list[str]], main(["fake", "--client-cert", "path"]) ) assert options1.client_cert == options2.client_cert == "path" @@ -604,8 +605,8 @@ def test_venv_config_file_found(self, monkeypatch: pytest.MonkeyPatch) -> None: def test_config_file_options( self, monkeypatch: pytest.MonkeyPatch, - args: List[str], - expect: Union[None, str, Type[PipError]], + args: list[str], + expect: Union[None, str, type[PipError]], ) -> None: cmd = cast(ConfigurationCommand, create_command("config")) # Replace a handler with a no-op to avoid side effects @@ -623,34 +624,34 @@ class TestOptionsExpandUser(AddFakeCommandMixin): def test_cache_dir(self) -> None: # FakeCommand intentionally returns the wrong type. options, args = cast( - Tuple[Values, List[str]], main(["--cache-dir", "~/cache/dir", "fake"]) + tuple[Values, list[str]], main(["--cache-dir", "~/cache/dir", "fake"]) ) assert options.cache_dir == os.path.expanduser("~/cache/dir") def test_log(self) -> None: # FakeCommand intentionally returns the wrong type. options, args = cast( - Tuple[Values, List[str]], main(["--log", "~/path", "fake"]) + tuple[Values, list[str]], main(["--log", "~/path", "fake"]) ) assert options.log == os.path.expanduser("~/path") def test_local_log(self) -> None: # FakeCommand intentionally returns the wrong type. options, args = cast( - Tuple[Values, List[str]], main(["--local-log", "~/path", "fake"]) + tuple[Values, list[str]], main(["--local-log", "~/path", "fake"]) ) assert options.log == os.path.expanduser("~/path") def test_cert(self) -> None: # FakeCommand intentionally returns the wrong type. options, args = cast( - Tuple[Values, List[str]], main(["--cert", "~/path", "fake"]) + tuple[Values, list[str]], main(["--cert", "~/path", "fake"]) ) assert options.cert == os.path.expanduser("~/path") def test_client_cert(self) -> None: # FakeCommand intentionally returns the wrong type. options, args = cast( - Tuple[Values, List[str]], main(["--client-cert", "~/path", "fake"]) + tuple[Values, list[str]], main(["--client-cert", "~/path", "fake"]) ) assert options.client_cert == os.path.expanduser("~/path") diff --git a/tests/unit/test_packaging.py b/tests/unit/test_packaging.py index 6b8c4cd37d8..30008f9ea58 100644 --- a/tests/unit/test_packaging.py +++ b/tests/unit/test_packaging.py @@ -1,4 +1,4 @@ -from typing import Optional, Tuple +from typing import Optional import pytest @@ -17,7 +17,7 @@ ], ) def test_check_requires_python( - version_info: Tuple[int, int, int], requires_python: Optional[str], expected: bool + version_info: tuple[int, int, int], requires_python: Optional[str], expected: bool ) -> None: actual = check_requires_python(requires_python, version_info) assert actual == expected diff --git a/tests/unit/test_pep517.py b/tests/unit/test_pep517.py index 4264bbdcac8..27008de79c2 100644 --- a/tests/unit/test_pep517.py +++ b/tests/unit/test_pep517.py @@ -1,7 +1,6 @@ import os from pathlib import Path from textwrap import dedent -from typing import Tuple import pytest @@ -75,7 +74,7 @@ def test_disabling_pep517_invalid(shared_data: TestData, source: str, msg: str) @pytest.mark.parametrize( "spec", [("./foo",), ("git+https://example.com/pkg@dev#egg=myproj",)] ) -def test_pep517_parsing_checks_requirements(tmpdir: Path, spec: Tuple[str]) -> None: +def test_pep517_parsing_checks_requirements(tmpdir: Path, spec: tuple[str]) -> None: tmpdir.joinpath("pyproject.toml").write_text( dedent( f""" diff --git a/tests/unit/test_pyproject_config.py b/tests/unit/test_pyproject_config.py index d385cfb515d..218215442e9 100644 --- a/tests/unit/test_pyproject_config.py +++ b/tests/unit/test_pyproject_config.py @@ -1,5 +1,3 @@ -from typing import Dict, List - import pytest from pip._internal.commands import create_command @@ -45,7 +43,7 @@ def test_set_config_empty_value() -> None: (["x=hello", "x=world", "x=other"], {"x": ["hello", "world", "other"]}), ], ) -def test_multiple_config_values(passed: List[str], expected: Dict[str, str]) -> None: +def test_multiple_config_values(passed: list[str], expected: dict[str, str]) -> None: i = create_command("install") options, _ = i.parse_args( ["xxx", *(f"--config-settings={option}" for option in passed)] diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index e243a718725..c564e18fc08 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -4,9 +4,10 @@ import shutil import sys import tempfile +from collections.abc import Iterator from functools import partial from pathlib import Path -from typing import Iterator, Optional, Set, Tuple, cast +from typing import Optional, cast from unittest import mock import pytest @@ -815,7 +816,7 @@ def test_install_req_drop_extras(self, inp: str, out: str) -> None: ], ) def test_install_req_extend_extras( - self, inp: str, extras: Set[str], out: str + self, inp: str, extras: set[str], out: str ) -> None: """ Test behavior of install_req_extend_extras @@ -996,8 +997,8 @@ def test_looks_like_path_win(args: str, expected: bool) -> None: def test_get_url_from_path( isdir_mock: mock.Mock, isfile_mock: mock.Mock, - args: Tuple[str, str], - mock_returns: Tuple[bool, bool], + args: tuple[str, str], + mock_returns: tuple[bool, bool], expected: None, ) -> None: isdir_mock.return_value = mock_returns[0] diff --git a/tests/unit/test_req_file.py b/tests/unit/test_req_file.py index 60b14940d27..31119ced822 100644 --- a/tests/unit/test_req_file.py +++ b/tests/unit/test_req_file.py @@ -4,9 +4,10 @@ import os import re import textwrap +from collections.abc import Iterator from optparse import Values from pathlib import Path -from typing import Any, Iterator, List, Optional, Protocol, Tuple, Union +from typing import Any, Optional, Protocol, Union from unittest import mock import pytest @@ -201,7 +202,7 @@ def __call__( options: Optional[Values] = None, session: Optional[PipSession] = None, constraint: bool = False, - ) -> List[InstallRequirement]: ... + ) -> list[InstallRequirement]: ... @pytest.fixture @@ -214,7 +215,7 @@ def process_line( options: Optional[Values] = None, session: Optional[PipSession] = None, constraint: bool = False, - ) -> List[InstallRequirement]: + ) -> list[InstallRequirement]: if session is None: session = PipSession() @@ -587,7 +588,7 @@ def test_relative_http_nested_req_files( def get_file_content( filename: str, *args: Any, **kwargs: Any - ) -> Tuple[None, str]: + ) -> tuple[None, str]: if filename == req_file: return None, "-r reqs.txt" elif filename == "http://me.com/me/reqs.txt": @@ -656,7 +657,7 @@ def test_absolute_http_nested_req_file_in_local( def get_file_content( filename: str, *args: Any, **kwargs: Any - ) -> Tuple[None, str]: + ) -> tuple[None, str]: if filename == str(req_file): return None, f"-r {nested_req_file}" elif filename == nested_req_file: @@ -1033,8 +1034,9 @@ def test_warns_and_fallsback_to_locale_on_utf8_decode_fail( # it's hard to rely on a locale definitely existing for testing # so patch things out for simplicity - with caplog.at_level(logging.WARNING), mock.patch( - "locale.getpreferredencoding", return_value=locale_encoding + with ( + caplog.at_level(logging.WARNING), + mock.patch("locale.getpreferredencoding", return_value=locale_encoding), ): reqs = tuple(parse_reqfile(req_file.resolve(), session=session)) @@ -1065,7 +1067,8 @@ def test_errors_on_non_decodable_data( req_file = tmpdir / "requirements.txt" req_file.write_bytes(data) - with pytest.raises(UnicodeDecodeError), mock.patch( - "locale.getpreferredencoding", return_value=encoding + with ( + pytest.raises(UnicodeDecodeError), + mock.patch("locale.getpreferredencoding", return_value=encoding), ): next(parse_reqfile(req_file.resolve(), session=session)) diff --git a/tests/unit/test_req_uninstall.py b/tests/unit/test_req_uninstall.py index 0372eac9bd9..8f8a8380765 100644 --- a/tests/unit/test_req_uninstall.py +++ b/tests/unit/test_req_uninstall.py @@ -1,7 +1,8 @@ import os import sys +from collections.abc import Iterator from pathlib import Path -from typing import Iterator, List, Optional, Tuple +from typing import Optional from unittest.mock import Mock import pytest @@ -56,7 +57,7 @@ def iter_declared_entries(self) -> Optional[Iterator[str]]: def test_compressed_listing(tmpdir: Path) -> None: - def in_tmpdir(paths: List[str]) -> List[str]: + def in_tmpdir(paths: list[str]) -> list[str]: return [ str(os.path.join(tmpdir, path.replace("/", os.path.sep))) for path in paths ] @@ -239,7 +240,7 @@ def test_detect_symlink_dirs( class TestStashedUninstallPathSet: - WALK_RESULT: List[Tuple[str, List[str], List[str]]] = [ + WALK_RESULT: list[tuple[str, list[str], list[str]]] = [ ("A", ["B", "C"], ["a.py"]), ("A/B", ["D"], ["b.py"]), ("A/B/D", [], ["c.py"]), @@ -251,7 +252,7 @@ class TestStashedUninstallPathSet: ] @classmethod - def mock_walk(cls, root: str) -> Iterator[Tuple[str, List[str], List[str]]]: + def mock_walk(cls, root: str) -> Iterator[tuple[str, list[str], list[str]]]: for dirname, subdirs, files in cls.WALK_RESULT: dirname = os.path.sep.join(dirname.split("/")) if dirname.startswith(root): @@ -286,8 +287,8 @@ def test_compress_for_rename(self, monkeypatch: pytest.MonkeyPatch) -> None: @classmethod def make_stash( - cls, tmpdir: Path, paths: List[str] - ) -> Tuple[StashedUninstallPathSet, List[Tuple[str, str]]]: + cls, tmpdir: Path, paths: list[str] + ) -> tuple[StashedUninstallPathSet, list[tuple[str, str]]]: for dirname, subdirs, files in cls.WALK_RESULT: root = os.path.join(tmpdir, *dirname.split("/")) if not os.path.exists(root): diff --git a/tests/unit/test_resolution_legacy_resolver.py b/tests/unit/test_resolution_legacy_resolver.py index 489f678c561..8f8afe25918 100644 --- a/tests/unit/test_resolution_legacy_resolver.py +++ b/tests/unit/test_resolution_legacy_resolver.py @@ -1,7 +1,7 @@ import email.message import logging import os -from typing import List, Optional, Type, TypeVar, cast +from typing import Optional, TypeVar, cast from unittest import mock import pytest @@ -47,7 +47,7 @@ def metadata(self) -> email.message.Message: def make_fake_dist( - *, klass: Type[BaseDistribution] = FakeDist, requires_python: Optional[str] = None + *, klass: type[BaseDistribution] = FakeDist, requires_python: Optional[str] = None ) -> BaseDistribution: metadata = email.message.Message() metadata["Name"] = "my-project" @@ -60,9 +60,9 @@ def make_fake_dist( def make_test_resolver( monkeypatch: pytest.MonkeyPatch, - mock_candidates: List[InstallationCandidate], + mock_candidates: list[InstallationCandidate], ) -> Resolver: - def _find_candidates(project_name: str) -> List[InstallationCandidate]: + def _find_candidates(project_name: str) -> list[InstallationCandidate]: return mock_candidates finder = make_test_finder() diff --git a/tests/unit/test_target_python.py b/tests/unit/test_target_python.py index 63e77a3d8a9..b3799f489e1 100644 --- a/tests/unit/test_target_python.py +++ b/tests/unit/test_target_python.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Tuple +from typing import Any, Optional from unittest import mock import pytest @@ -25,8 +25,8 @@ class TestTargetPython: ) def test_init__py_version_info( self, - py_version_info: Tuple[int, ...], - expected: Tuple[Tuple[int, int, int], str], + py_version_info: tuple[int, ...], + expected: tuple[tuple[int, int, int], str], ) -> None: """ Test passing the py_version_info argument. @@ -75,7 +75,7 @@ def test_init__py_version_info_none(self) -> None: ), ], ) - def test_format_given(self, kwargs: Dict[str, Any], expected: str) -> None: + def test_format_given(self, kwargs: dict[str, Any], expected: str) -> None: target_python = TargetPython(**kwargs) actual = target_python.format_given() assert actual == expected @@ -98,7 +98,7 @@ def test_format_given(self, kwargs: Dict[str, Any], expected: str) -> None: def test_get_sorted_tags( self, mock_get_supported: mock.Mock, - py_version_info: Optional[Tuple[int, ...]], + py_version_info: Optional[tuple[int, ...]], expected_version: Optional[str], ) -> None: dummy_tags = [Tag("py4", "none", "any"), Tag("py5", "none", "any")] diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index e2d1710739a..5e8671e9609 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -8,9 +8,10 @@ import stat import sys import time +from collections.abc import Iterator from io import BytesIO from pathlib import Path -from typing import Any, Callable, Iterator, List, NoReturn, Optional, Tuple, Type +from typing import Any, Callable, NoReturn, Optional from unittest.mock import Mock import pytest @@ -443,7 +444,7 @@ def test_has_one_of(self) -> None: assert not empty_hashes.has_one_of({"sha256": "xyzt"}) -def raises(error: Type[Exception]) -> NoReturn: +def raises(error: type[Exception]) -> NoReturn: raise error @@ -508,7 +509,7 @@ def test_glibc_version_string_ctypes_missing( ], ) def test_normalize_version_info( - version_info: Tuple[int, ...], expected: Tuple[int, int, int] + version_info: tuple[int, ...], expected: tuple[int, int, int] ) -> None: actual = normalize_version_info(version_info) assert actual == expected @@ -548,7 +549,7 @@ def test_get_prog( ], ) def test_build_netloc( - host_port: Tuple[str, Optional[int]], expected_netloc: str + host_port: tuple[str, Optional[int]], expected_netloc: str ) -> None: assert build_netloc(*host_port) == expected_netloc @@ -577,7 +578,7 @@ def test_build_netloc( def test_build_url_from_netloc_and_parse_netloc( netloc: str, expected_url: str, - expected_host_port: Tuple[str, Optional[int]], + expected_host_port: tuple[str, Optional[int]], ) -> None: assert build_url_from_netloc(netloc) == expected_url assert parse_netloc(netloc) == expected_host_port @@ -603,7 +604,7 @@ def test_build_url_from_netloc_and_parse_netloc( ], ) def test_split_auth_from_netloc( - netloc: str, expected: Tuple[str, Tuple[Optional[str], Optional[str]]] + netloc: str, expected: tuple[str, tuple[Optional[str], Optional[str]]] ) -> None: actual = split_auth_from_netloc(netloc) assert actual == expected @@ -650,7 +651,7 @@ def test_split_auth_from_netloc( ], ) def test_split_auth_netloc_from_url( - url: str, expected: Tuple[str, str, Tuple[Optional[str], Optional[str]]] + url: str, expected: tuple[str, str, tuple[Optional[str], Optional[str]]] ) -> None: actual = split_auth_netloc_from_url(url) assert actual == expected @@ -952,7 +953,7 @@ def test_make_setuptools_shim_args() -> None: @pytest.mark.parametrize("global_options", [None, [], ["--some", "--option"]]) def test_make_setuptools_shim_args__global_options( - global_options: Optional[List[str]], + global_options: Optional[list[str]], ) -> None: args = make_setuptools_shim_args( "/dir/path/setup.py", @@ -1041,5 +1042,5 @@ def test_format_size(size: int, expected: str) -> None: ), ], ) -def test_tabulate(rows: List[Tuple[str]], table: List[str], sizes: List[int]) -> None: +def test_tabulate(rows: list[tuple[str]], table: list[str], sizes: list[int]) -> None: assert tabulate(rows) == (table, sizes) diff --git a/tests/unit/test_utils_compatibility_tags.py b/tests/unit/test_utils_compatibility_tags.py index f09c451b8ee..56f386cbb49 100644 --- a/tests/unit/test_utils_compatibility_tags.py +++ b/tests/unit/test_utils_compatibility_tags.py @@ -1,5 +1,5 @@ import sysconfig -from typing import Any, Callable, Dict, List, Tuple +from typing import Any, Callable from unittest.mock import patch import pytest @@ -20,7 +20,7 @@ ((3, 10), "310"), ], ) -def test_version_info_to_nodot(version_info: Tuple[int], expected: str) -> None: +def test_version_info_to_nodot(version_info: tuple[int], expected: str) -> None: actual = compatibility_tags.version_info_to_nodot(version_info) assert actual == expected @@ -70,7 +70,7 @@ def test_manylinux2010_implies_manylinux1( """ Specifying manylinux2010 implies manylinux1. """ - groups: Dict[Tuple[str, str], List[str]] = {} + groups: dict[tuple[str, str], list[str]] = {} supported = compatibility_tags.get_supported(platforms=[manylinux2010]) for tag in supported: groups.setdefault((tag.interpreter, tag.abi), []).append(tag.platform) @@ -90,12 +90,12 @@ class TestManylinux2014Tags: ], ) def test_manylinuxA_implies_manylinuxB( - self, manylinuxA: str, manylinuxB: List[str] + self, manylinuxA: str, manylinuxB: list[str] ) -> None: """ Specifying manylinux2014 implies manylinux2010/manylinux1. """ - groups: Dict[Tuple[str, str], List[str]] = {} + groups: dict[tuple[str, str], list[str]] = {} supported = compatibility_tags.get_supported(platforms=[manylinuxA]) for tag in supported: groups.setdefault((tag.interpreter, tag.abi), []).append(tag.platform) diff --git a/tests/unit/test_utils_retry.py b/tests/unit/test_utils_retry.py index bdb2f23b480..ba9ffda88fa 100644 --- a/tests/unit/test_utils_retry.py +++ b/tests/unit/test_utils_retry.py @@ -1,7 +1,7 @@ import random import sys from time import perf_counter, sleep -from typing import List, NoReturn, Tuple, Type +from typing import NoReturn from unittest.mock import Mock import pytest @@ -50,7 +50,7 @@ def _raise_error() -> NoReturn: @pytest.mark.parametrize("exc", [KeyboardInterrupt, SystemExit]) -def test_retry_ignores_base_exception(exc: Type[BaseException]) -> None: +def test_retry_ignores_base_exception(exc: type[BaseException]) -> None: function = Mock(side_effect=exc()) wrapped = retry(wait=0, stop_after_delay=0.01)(function) with pytest.raises(exc): @@ -58,7 +58,7 @@ def test_retry_ignores_base_exception(exc: Type[BaseException]) -> None: function.assert_called_once() -def create_timestamped_callable(sleep_per_call: float = 0) -> Tuple[Mock, List[float]]: +def create_timestamped_callable(sleep_per_call: float = 0) -> tuple[Mock, list[float]]: timestamps = [] def _raise_error() -> NoReturn: diff --git a/tests/unit/test_utils_subprocess.py b/tests/unit/test_utils_subprocess.py index 65e7d6fdca9..f4a6543734e 100644 --- a/tests/unit/test_utils_subprocess.py +++ b/tests/unit/test_utils_subprocess.py @@ -1,7 +1,7 @@ import locale import sys from logging import DEBUG, ERROR, INFO, WARNING -from typing import List, Optional, Tuple, Type +from typing import Optional import pytest @@ -49,7 +49,7 @@ def test_call_subprocess_stdout_only( capfd: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch, stdout_only: bool, - expected: Tuple[str, ...], + expected: tuple[str, ...], ) -> None: log = [] monkeypatch.setattr( @@ -100,8 +100,8 @@ def check_result( log_level: int, spinner: FakeSpinner, result: Optional[str], - expected: Tuple[Optional[List[str]], List[Tuple[str, int, str]]], - expected_spinner: Tuple[int, Optional[str]], + expected: tuple[Optional[list[str]], list[tuple[str, int, str]]], + expected_spinner: tuple[int, Optional[str]], ) -> None: """ Check the result of calling call_subprocess(). @@ -153,7 +153,7 @@ def prepare_call( caplog: pytest.LogCaptureFixture, log_level: int, command: Optional[str] = None, - ) -> Tuple[List[str], FakeSpinner]: + ) -> tuple[list[str], FakeSpinner]: if command is None: command = 'print("Hello"); print("world")' @@ -211,7 +211,7 @@ def test_info_logging( spinner=spinner, ) - expected: Tuple[List[str], List[Tuple[str, int, str]]] = ( + expected: tuple[list[str], list[tuple[str, int, str]]] = ( ["Hello", "world"], [], ) @@ -337,10 +337,10 @@ def test_spinner_finish( self, exit_status: int, show_stdout: bool, - extra_ok_returncodes: Optional[Tuple[int, ...]], + extra_ok_returncodes: Optional[tuple[int, ...]], log_level: int, caplog: pytest.LogCaptureFixture, - expected: Tuple[Optional[Type[Exception]], Optional[str], int], + expected: tuple[Optional[type[Exception]], Optional[str], int], ) -> None: """ Test that the spinner finishes correctly. @@ -351,7 +351,7 @@ def test_spinner_finish( command = f'print("Hello"); print("world"); exit({exit_status})' args, spinner = self.prepare_call(caplog, log_level, command=command) - exc_type: Optional[Type[Exception]] + exc_type: Optional[type[Exception]] try: call_subprocess( args, diff --git a/tests/unit/test_utils_temp_dir.py b/tests/unit/test_utils_temp_dir.py index a6cd0d0e5af..4d301b18f93 100644 --- a/tests/unit/test_utils_temp_dir.py +++ b/tests/unit/test_utils_temp_dir.py @@ -2,8 +2,9 @@ import os import stat import tempfile +from collections.abc import Iterator from pathlib import Path -from typing import Any, Iterator, Optional, Union +from typing import Any, Optional, Union from unittest import mock import pytest diff --git a/tests/unit/test_utils_unpacking.py b/tests/unit/test_utils_unpacking.py index 5f89751311a..6f373b1acad 100644 --- a/tests/unit/test_utils_unpacking.py +++ b/tests/unit/test_utils_unpacking.py @@ -8,7 +8,6 @@ import time import zipfile from pathlib import Path -from typing import List, Tuple import pytest @@ -81,7 +80,7 @@ def confirm_files(self) -> None: mode == expected_mode ), f"mode: {mode}, expected mode: {expected_mode}" - def make_zip_file(self, filename: str, file_list: List[str]) -> str: + def make_zip_file(self, filename: str, file_list: list[str]) -> str: """ Create a zip file for test case """ @@ -91,7 +90,7 @@ def make_zip_file(self, filename: str, file_list: List[str]) -> str: myzip.writestr(item, "file content") return test_zip - def make_tar_file(self, filename: str, file_list: List[str]) -> str: + def make_tar_file(self, filename: str, file_list: list[str]) -> str: """ Create a tar file for test case """ @@ -272,6 +271,6 @@ def test_unpack_tar_unicode(tmpdir: Path) -> None: (("parent/", "parent/../sub"), False), ], ) -def test_is_within_directory(args: Tuple[str, str], expected: bool) -> None: +def test_is_within_directory(args: tuple[str, str], expected: bool) -> None: result = is_within_directory(*args) assert result == expected diff --git a/tests/unit/test_utils_virtualenv.py b/tests/unit/test_utils_virtualenv.py index 94461c6d89e..3d32e72a213 100644 --- a/tests/unit/test_utils_virtualenv.py +++ b/tests/unit/test_utils_virtualenv.py @@ -3,7 +3,7 @@ import site import sys from pathlib import Path -from typing import List, Optional +from typing import Optional import pytest @@ -102,7 +102,7 @@ def test_virtualenv_no_global_with_regular_virtualenv( def test_virtualenv_no_global_with_pep_405_virtual_environment( monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture, - pyvenv_cfg_lines: Optional[List[str]], + pyvenv_cfg_lines: Optional[list[str]], under_venv: bool, expect_no_global: bool, expect_warning: bool, @@ -136,7 +136,7 @@ def test_get_pyvenv_cfg_lines_for_pep_405_virtual_environment( monkeypatch: pytest.MonkeyPatch, tmpdir: Path, contents: Optional[str], - expected: Optional[List[str]], + expected: Optional[list[str]], ) -> None: monkeypatch.setattr(sys, "prefix", str(tmpdir)) if contents is not None: diff --git a/tests/unit/test_utils_wheel.py b/tests/unit/test_utils_wheel.py index b31b51ccae0..958cf0baa3e 100644 --- a/tests/unit/test_utils_wheel.py +++ b/tests/unit/test_utils_wheel.py @@ -1,9 +1,10 @@ import os +from collections.abc import Iterator from contextlib import ExitStack from email import message_from_string from io import BytesIO from pathlib import Path -from typing import Callable, Iterator +from typing import Callable from zipfile import ZipFile import pytest diff --git a/tests/unit/test_vcs.py b/tests/unit/test_vcs.py index c9a026968f4..6d6568b50e0 100644 --- a/tests/unit/test_vcs.py +++ b/tests/unit/test_vcs.py @@ -1,6 +1,6 @@ import os import pathlib -from typing import Any, Dict, List, Optional, Tuple, Type +from typing import Any, Optional from unittest import TestCase, mock import pytest @@ -51,7 +51,7 @@ def test_ensure_svn_available() -> None: ), ], ) -def test_make_vcs_requirement_url(args: Tuple[Any, ...], expected: str) -> None: +def test_make_vcs_requirement_url(args: tuple[Any, ...], expected: str) -> None: actual = make_vcs_requirement_url(*args) assert actual == expected @@ -79,10 +79,10 @@ def test_rev_options_repr() -> None: ], ) def test_rev_options_to_args( - vc_class: Type[VersionControl], - expected1: List[str], - expected2: List[str], - kwargs: Dict[str, Any], + vc_class: type[VersionControl], + expected1: list[str], + expected2: list[str], + kwargs: dict[str, Any], ) -> None: """ Test RevOptions.to_args(). @@ -150,7 +150,7 @@ def test_looks_like_hash(sha: str, expected: bool) -> None: ], ) def test_should_add_vcs_url_prefix( - vcs_cls: Type[VersionControl], remote_url: str, expected: bool + vcs_cls: type[VersionControl], remote_url: str, expected: bool ) -> None: actual = vcs_cls.should_add_vcs_url_prefix(remote_url) assert actual == expected @@ -324,7 +324,7 @@ def test_git_is_commit_id_equal( ], ) def test_git__get_netloc_and_auth( - args: Tuple[str, str], expected: Tuple[str, Tuple[None, None]] + args: tuple[str, str], expected: tuple[str, tuple[None, None]] ) -> None: """ Test VersionControl.get_netloc_and_auth(). @@ -353,7 +353,7 @@ def test_git__get_netloc_and_auth( ], ) def test_subversion__get_netloc_and_auth( - args: Tuple[str, str], expected: Tuple[str, Tuple[Optional[str], Optional[str]]] + args: tuple[str, str], expected: tuple[str, tuple[Optional[str], Optional[str]]] ) -> None: """ Test Subversion.get_netloc_and_auth(). @@ -393,7 +393,7 @@ def test_git__get_url_rev__idempotent() -> None: ], ) def test_version_control__get_url_rev_and_auth( - url: str, expected: Tuple[str, None, Tuple[None, None]] + url: str, expected: tuple[str, None, tuple[None, None]] ) -> None: """ Test the basic case of VersionControl.get_url_rev_and_auth(). @@ -450,7 +450,7 @@ def test_version_control__get_url_rev_and_auth__no_revision(url: str) -> None: ids=["FileNotFoundError", "PermissionError", "NotADirectoryError"], ) def test_version_control__run_command__fails( - vcs_cls: Type[VersionControl], exc_cls: Type[Exception], msg_re: str + vcs_cls: type[VersionControl], exc_cls: type[Exception], msg_re: str ) -> None: """ Test that ``VersionControl.run_command()`` raises ``BadCommand`` @@ -529,7 +529,7 @@ def test_bazaar__get_url_rev_and_auth(url: str, expected: str) -> None: ], ) def test_subversion__get_url_rev_and_auth( - url: str, expected: Tuple[str, None, Tuple[Optional[str], Optional[str]]] + url: str, expected: tuple[str, None, tuple[Optional[str], Optional[str]]] ) -> None: """ Test Subversion.get_url_rev_and_auth(). @@ -609,7 +609,7 @@ def test_get_git_version() -> None: ("git version 2.GIT", ()), # invalid version ], ) -def test_get_git_version_parser(version: str, expected: Tuple[int, int]) -> None: +def test_get_git_version_parser(version: str, expected: tuple[int, int]) -> None: with mock.patch("pip._internal.vcs.git.Git.run_command", return_value=version): assert Git().get_git_version() == expected @@ -675,7 +675,7 @@ def test_subversion__call_vcs_version() -> None: ) @mock.patch("pip._internal.vcs.subversion.Subversion.run_command") def test_subversion__call_vcs_version_patched( - mock_run_command: mock.Mock, svn_output: str, expected_version: Tuple[int, ...] + mock_run_command: mock.Mock, svn_output: str, expected_version: tuple[int, ...] ) -> None: """ Test Subversion.call_vcs_version() against patched output. @@ -706,7 +706,7 @@ def test_subversion__call_vcs_version_svn_not_installed( (1, 8, 0), ], ) -def test_subversion__get_vcs_version_cached(version: Tuple[int, ...]) -> None: +def test_subversion__get_vcs_version_cached(version: tuple[int, ...]) -> None: """ Test Subversion.get_vcs_version() with previously cached result. """ @@ -725,7 +725,7 @@ def test_subversion__get_vcs_version_cached(version: Tuple[int, ...]) -> None: ) @mock.patch("pip._internal.vcs.subversion.Subversion.call_vcs_version") def test_subversion__get_vcs_version_call_vcs( - mock_call_vcs: mock.Mock, vcs_version: Tuple[int, ...] + mock_call_vcs: mock.Mock, vcs_version: tuple[int, ...] ) -> None: """ Test Subversion.get_vcs_version() with mocked output from @@ -751,7 +751,7 @@ def test_subversion__get_vcs_version_call_vcs( ], ) def test_subversion__get_remote_call_options( - use_interactive: bool, vcs_version: Tuple[int, ...], expected_options: List[str] + use_interactive: bool, vcs_version: tuple[int, ...], expected_options: list[str] ) -> None: """ Test Subversion.get_remote_call_options(). diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py index 7b44a59e4a4..aa50f19afc0 100644 --- a/tests/unit/test_wheel.py +++ b/tests/unit/test_wheel.py @@ -8,7 +8,7 @@ import textwrap from email import message_from_string from pathlib import Path -from typing import Dict, List, Optional, Tuple, cast +from typing import Optional, cast from unittest.mock import patch import pytest @@ -39,7 +39,7 @@ def call_get_legacy_build_wheel_path( - caplog: pytest.LogCaptureFixture, names: List[str] + caplog: pytest.LogCaptureFixture, names: list[str] ) -> Optional[str]: wheel_path = get_legacy_build_wheel_path( names=names, @@ -172,19 +172,19 @@ def test_get_entrypoints_no_entrypoints(tmp_path: pathlib.Path) -> None: ], ) def test_normalized_outrows( - outrows: List[Tuple[RecordPath, str, str]], expected: List[Tuple[str, str, str]] + outrows: list[tuple[RecordPath, str, str]], expected: list[tuple[str, str, str]] ) -> None: actual = wheel._normalized_outrows(outrows) assert actual == expected -def call_get_csv_rows_for_installed(tmpdir: Path, text: str) -> List[InstalledCSVRow]: +def call_get_csv_rows_for_installed(tmpdir: Path, text: str) -> list[InstalledCSVRow]: path = tmpdir.joinpath("temp.txt") path.write_text(text) # Test that an installed file appearing in RECORD has its filename # updated in the new RECORD file. - installed = cast(Dict[RecordPath, RecordPath], {"a": "z"}) + installed = cast(dict[RecordPath, RecordPath], {"a": "z"}) lib_dir = "/lib/dir" with open(path, **wheel.csv_io_kwargs("r")) as f: @@ -524,7 +524,7 @@ class TestMessageAboutScriptsNotOnPATH: "which may not be expanded by all applications." ) - def _template(self, paths: List[str], scripts: List[str]) -> Optional[str]: + def _template(self, paths: list[str], scripts: list[str]) -> Optional[str]: with patch.dict("os.environ", {"PATH": os.pathsep.join(paths)}): return wheel.message_about_scripts_not_on_PATH(scripts) diff --git a/tools/protected_pip.py b/tools/protected_pip.py index 48230719ee2..57339bb1e17 100644 --- a/tools/protected_pip.py +++ b/tools/protected_pip.py @@ -3,8 +3,9 @@ import shutil import subprocess import sys +from collections.abc import Iterable from glob import glob -from typing import Iterable, Union +from typing import Union VIRTUAL_ENV = os.environ["VIRTUAL_ENV"] TOX_PIP_DIR = os.path.join(VIRTUAL_ENV, "pip") diff --git a/tools/release/__init__.py b/tools/release/__init__.py index 9e0601683d6..93cad529c61 100644 --- a/tools/release/__init__.py +++ b/tools/release/__init__.py @@ -9,7 +9,8 @@ import subprocess import tempfile import unicodedata -from typing import Iterator, List, Optional, Set +from collections.abc import Iterator +from typing import Optional from nox.sessions import Session @@ -74,7 +75,7 @@ def strip_rtl_ltr_overrides(a: str) -> str: return combined -def get_author_list() -> List[str]: +def get_author_list() -> list[str]: """Get the list of authors from Git commits.""" # subprocess because session.run doesn't give us stdout # only use names in list of Authors @@ -86,7 +87,7 @@ def get_author_list() -> List[str]: # Create a unique list. authors = [] - seen_authors: Set[str] = set() + seen_authors: set[str] = set() for author in result.stdout.splitlines(): author = author.strip() author = strip_rtl_ltr_overrides(author) diff --git a/tools/update-rtd-redirects.py b/tools/update-rtd-redirects.py index 2aa90e467e3..9252a9f7053 100644 --- a/tools/update-rtd-redirects.py +++ b/tools/update-rtd-redirects.py @@ -7,7 +7,6 @@ import os import sys from pathlib import Path -from typing import Dict, List import httpx import rich @@ -86,8 +85,8 @@ def get_rtd_api() -> httpx.Client: next_step("Compare and determine modifications.") -redirects_to_remove: List[int] = [] -redirects_to_add: Dict[str, str] = {} +redirects_to_remove: list[int] = [] +redirects_to_add: dict[str, str] = {} for redirect in rtd_redirects["results"]: if redirect["type"] != "exact":