diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 1b62b864bb4..6b18de7bd1f 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -4,27 +4,30 @@ about: Something is not working correctly. title: "" labels: "S: needs triage, type: bug" issue_body: true # default: true, adds a classic WSYWIG textarea, if on -inputs: -- type: description +body: +- type: markdown attributes: value: | ⚠ If you're reporting an issue for `--use-feature=2020-resolver`, use the "Dependency resolver failures / errors" template instead. -- type: description +- type: markdown attributes: value: "**Environment**" - type: input attributes: label: pip version + validations: required: true - type: input attributes: label: Python version + validations: required: true - type: input attributes: label: OS + validations: required: true - type: textarea attributes: @@ -72,7 +75,7 @@ inputs: Read the [PSF Code of Conduct][CoC] first. [CoC]: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md - choices: + options: - label: I agree to follow the PSF Code of Conduct required: true ... diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 164de60b05d..0afa776e702 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,16 +23,12 @@ repos: exclude: | (?x) ^docs/| - ^src/pip/_internal/cli| ^src/pip/_internal/commands| - ^src/pip/_internal/distributions| ^src/pip/_internal/index| ^src/pip/_internal/models| ^src/pip/_internal/network| ^src/pip/_internal/operations| ^src/pip/_internal/req| - ^src/pip/_internal/resolution| - ^src/pip/_internal/utils| ^src/pip/_internal/vcs| ^src/pip/_internal/\w+\.py$| ^src/pip/__main__.py$| @@ -47,7 +43,6 @@ repos: ^tests/functional/test_install| # Files in the root of the repository ^setup.py| - ^noxfile.py| # A blank ignore, to avoid merge conflicts later. ^$ @@ -74,6 +69,7 @@ repos: - id: mypy exclude: docs|tests args: ["--pretty"] + additional_dependencies: ['nox==2020.12.31'] - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.7.0 diff --git a/docs/docs_feedback_sphinxext.py b/docs/docs_feedback_sphinxext.py index a8ab94e5cbd..d0ff1f03da1 100644 --- a/docs/docs_feedback_sphinxext.py +++ b/docs/docs_feedback_sphinxext.py @@ -3,13 +3,9 @@ from __future__ import annotations from itertools import chain -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Dict, List, Union - - from sphinx.application import Sphinx +from typing import Dict, List, Union +from sphinx.application import Sphinx DEFAULT_DOC_LINES_THRESHOLD = 250 RST_INDENT = 4 diff --git a/docs/html/reference/pip_install.rst b/docs/html/reference/pip_install.rst index 81e315ebaa2..742c4ddb3c6 100644 --- a/docs/html/reference/pip_install.rst +++ b/docs/html/reference/pip_install.rst @@ -808,7 +808,15 @@ You can install local projects by specifying the project path to pip: During regular installation, pip will copy the entire project directory to a temporary location and install from there. The exception is that pip will exclude .tox and .nox directories present in the top level of the project from -being copied. +being copied. This approach is the cause of several performance and correctness +issues, so it is planned that pip 21.3 will change to install directly from the +local project directory. Depending on the build backend used by the project, +this may generate secondary build artifacts in the project directory, such as +the ``.egg-info`` and ``build`` directories in the case of the setuptools +backend. + +To opt in to the future behavior, specify the ``--use-feature=in-tree-build`` +option in pip's command line. .. _`editable-installs`: diff --git a/docs/pip_sphinxext.py b/docs/pip_sphinxext.py index df4390d8103..1ce526e0d6c 100644 --- a/docs/pip_sphinxext.py +++ b/docs/pip_sphinxext.py @@ -50,10 +50,10 @@ class PipOptions(rst.Directive): def _format_option(self, option, cmd_name=None): bookmark_line = ( - ".. _`{cmd_name}_{option._long_opts[0]}`:" + f".. _`{cmd_name}_{option._long_opts[0]}`:" if cmd_name else - ".. _`{option._long_opts[0]}`:" - ).format(**locals()) + f".. _`{option._long_opts[0]}`:" + ) line = ".. option:: " if option._short_opts: line += option._short_opts[0] diff --git a/news/0a741827-049c-4d5d-b44d-daea0c2fd01a.trivial.rst b/news/0a741827-049c-4d5d-b44d-daea0c2fd01a.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/news/11e1b2eb-6433-4f15-b70d-c2c514f72ebd.trivial.rst b/news/11e1b2eb-6433-4f15-b70d-c2c514f72ebd.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/news/151a1e46-d005-46ca-b1ae-a3811357dba3.trivial.rst b/news/151a1e46-d005-46ca-b1ae-a3811357dba3.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/news/40711960-12d9-4e58-8322-21e5975a804e.trivial.rst b/news/40711960-12d9-4e58-8322-21e5975a804e.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/news/5be04056-e1d6-4f9a-bf46-8938d1936d9e.trivial.rst b/news/5be04056-e1d6-4f9a-bf46-8938d1936d9e.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/news/9091.feature.rst b/news/9091.feature.rst new file mode 100644 index 00000000000..8147e79c5e8 --- /dev/null +++ b/news/9091.feature.rst @@ -0,0 +1,4 @@ +Add a feature ``--use-feature=in-tree-build`` to build local projects in-place +when installing. This is expected to become the default behavior in pip 21.3; +see `Installing from local packages `_ +for more information. diff --git a/news/917ab6ff-72ea-4db5-846a-30273dac1c0c.trivial.rst b/news/917ab6ff-72ea-4db5-846a-30273dac1c0c.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/news/a06e528d-1172-4012-a0a5-0fc42264a70d.trivial.rst b/news/a06e528d-1172-4012-a0a5-0fc42264a70d.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/news/adba2b7a-af01-49c9-9b72-2d3d4a89b11a.trivial.rst b/news/adba2b7a-af01-49c9-9b72-2d3d4a89b11a.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/news/d0935419-2486-45dc-b8dc-d2a5b9197ca4.trivial.rst b/news/d0935419-2486-45dc-b8dc-d2a5b9197ca4.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/news/dfaa54d4-21e2-460f-9d80-455ff318c713.trivial.rst b/news/dfaa54d4-21e2-460f-9d80-455ff318c713.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/news/fc6b6951-9a1a-453e-af98-bbb35f7c3e66.trivial.rst b/news/fc6b6951-9a1a-453e-af98-bbb35f7c3e66.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/news/fd62a11c-018c-4fde-ac8d-f674c6d9d190.trivial.rst b/news/fd62a11c-018c-4fde-ac8d-f674c6d9d190.trivial.rst new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noxfile.py b/noxfile.py index e89e73a8d0f..165a430d14b 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,20 +1,20 @@ """Automation using nox. """ -# The following comment should be removed at some point in the future. -# mypy: disallow-untyped-defs=False - import glob import os import shutil import sys from pathlib import Path +from typing import Iterator, List, Tuple import nox +# fmt: off sys.path.append(".") from tools.automation import release # isort:skip # noqa sys.path.pop() +# fmt: on nox.options.reuse_existing_virtualenvs = True nox.options.sessions = ["lint"] @@ -34,20 +34,22 @@ def run_with_protected_pip(session, *arguments): + # type: (nox.Session, *str) -> None """Do a session.run("pip", *arguments), using a "protected" pip. This invokes a wrapper script, that forwards calls to original virtualenv (stable) version, and not the code being tested. This ensures pip being used is not the code being tested. """ - env = {"VIRTUAL_ENV": session.virtualenv.location} + # https://github.com/theacodes/nox/pull/377 + env = {"VIRTUAL_ENV": session.virtualenv.location} # type: ignore command = ("python", LOCATIONS["protected-pip"]) + arguments - kwargs = {"env": env, "silent": True} - session.run(*command, **kwargs) + session.run(*command, env=env, silent=True) def should_update_common_wheels(): + # type: () -> bool # If the cache hasn't been created, create it. if not os.path.exists(LOCATIONS["common-wheels"]): return True @@ -72,30 +74,34 @@ def should_update_common_wheels(): # ----------------------------------------------------------------------------- @nox.session(python=["3.6", "3.7", "3.8", "3.9", "pypy3"]) def test(session): + # type: (nox.Session) -> None # Get the common wheels. if should_update_common_wheels(): + # fmt: off run_with_protected_pip( session, "wheel", "-w", LOCATIONS["common-wheels"], "-r", REQUIREMENTS["common-wheels"], ) + # fmt: on else: - msg = ( - "Re-using existing common-wheels at {}." - .format(LOCATIONS["common-wheels"]) - ) + msg = f"Re-using existing common-wheels at {LOCATIONS['common-wheels']}." session.log(msg) # Build source distribution - sdist_dir = os.path.join(session.virtualenv.location, "sdist") + # https://github.com/theacodes/nox/pull/377 + sdist_dir = os.path.join(session.virtualenv.location, "sdist") # type: ignore if os.path.exists(sdist_dir): shutil.rmtree(sdist_dir, ignore_errors=True) + + # fmt: off session.run( - "python", "setup.py", "sdist", - "--formats=zip", "--dist-dir", sdist_dir, + "python", "setup.py", "sdist", "--formats=zip", "--dist-dir", sdist_dir, silent=True, ) + # fmt: on + generated_files = os.listdir(sdist_dir) assert len(generated_files) == 1 generated_sdist = os.path.join(sdist_dir, generated_files[0]) @@ -117,14 +123,17 @@ def test(session): @nox.session def docs(session): + # type: (nox.Session) -> None session.install("-e", ".") session.install("-r", REQUIREMENTS["docs"]) def get_sphinx_build_command(kind): + # type: (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. # See https://github.com/rtfd/readthedocs.org/issues/1543. + # fmt: off return [ "sphinx-build", "-W", @@ -134,6 +143,7 @@ def get_sphinx_build_command(kind): "docs/" + kind, "docs/build/" + kind, ] + # fmt: on session.run(*get_sphinx_build_command("html")) session.run(*get_sphinx_build_command("man")) @@ -141,6 +151,7 @@ def get_sphinx_build_command(kind): @nox.session def lint(session): + # type: (nox.Session) -> None session.install("pre-commit") if session.posargs: @@ -154,6 +165,7 @@ def lint(session): @nox.session def vendoring(session): + # type: (nox.Session) -> None session.install("vendoring>=0.3.0") if "--upgrade" not in session.posargs: @@ -161,11 +173,15 @@ def vendoring(session): return def pinned_requirements(path): - for line in path.read_text().splitlines(): - one, two = line.split("==", 1) + # type: (Path) -> Iterator[Tuple[str, str]] + for line in path.read_text().splitlines(keepends=False): + one, sep, two = line.partition("==") + if not sep: + continue name = one.strip() - version = two.split("#")[0].strip() - yield name, version + version = two.split("#", 1)[0].strip() + if name and version: + yield name, version vendor_txt = Path("src/pip/_vendor/vendor.txt") for name, old_version in pinned_requirements(vendor_txt): @@ -208,6 +224,7 @@ def pinned_requirements(path): # ----------------------------------------------------------------------------- @nox.session(name="prepare-release") def prepare_release(session): + # type: (nox.Session) -> None version = release.get_version_from_arguments(session) if not version: session.error("Usage: nox -s prepare-release -- ") @@ -219,9 +236,7 @@ def prepare_release(session): session.log(f"# Updating {AUTHORS_FILE}") release.generate_authors(AUTHORS_FILE) if release.modified_files_in_git(): - release.commit_file( - session, AUTHORS_FILE, message=f"Update {AUTHORS_FILE}", - ) + release.commit_file(session, AUTHORS_FILE, message=f"Update {AUTHORS_FILE}") else: session.log(f"# No changes to {AUTHORS_FILE}") @@ -243,6 +258,7 @@ def prepare_release(session): @nox.session(name="build-release") def build_release(session): + # type: (nox.Session) -> None version = release.get_version_from_arguments(session) if not version: session.error("Usage: nox -s build-release -- YY.N[.P]") @@ -267,13 +283,14 @@ def build_release(session): tmp_dist_paths = (build_dir / p for p in tmp_dists) session.log(f"# Copying dists from {build_dir}") - os.makedirs('dist', exist_ok=True) + os.makedirs("dist", exist_ok=True) for dist, final in zip(tmp_dist_paths, tmp_dists): session.log(f"# Copying {dist} to {final}") shutil.copy(dist, final) def build_dists(session): + # type: (nox.Session) -> List[str] """Return dists with valid metadata.""" session.log( "# Check if there's any Git-untracked files before building the wheel", @@ -281,7 +298,7 @@ def build_dists(session): has_forbidden_git_untracked_files = any( # Don't report the environment this session is running in - not untracked_file.startswith('.nox/build-release/') + not untracked_file.startswith(".nox/build-release/") for untracked_file in release.get_git_untracked_files() ) if has_forbidden_git_untracked_files: @@ -302,6 +319,7 @@ def build_dists(session): @nox.session(name="upload-release") def upload_release(session): + # type: (nox.Session) -> None version = release.get_version_from_arguments(session) if not version: session.error("Usage: nox -s upload-release -- YY.N[.P]") @@ -320,15 +338,13 @@ def upload_release(session): f"Remove dist/ and run 'nox -s build-release -- {version}'" ) # Sanity check: Make sure the files are correctly named. - distfile_names = map(os.path.basename, distribution_files) + distfile_names = (os.path.basename(fn) for fn in distribution_files) expected_distribution_files = [ f"pip-{version}-py3-none-any.whl", f"pip-{version}.tar.gz", ] if sorted(distfile_names) != sorted(expected_distribution_files): - session.error( - f"Distribution files do not seem to be for {version} release." - ) + session.error(f"Distribution files do not seem to be for {version} release.") session.log("# Upload distributions") session.run("twine", "upload", *distribution_files) diff --git a/setup.cfg b/setup.cfg index a96614dc199..1d851d94929 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,7 +31,6 @@ per-file-ignores = tests/*: B011 [mypy] -follow_imports = silent ignore_missing_imports = True disallow_untyped_defs = True disallow_any_generics = True diff --git a/setup.py b/setup.py index 66820387bb9..26056f280aa 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,3 @@ -# The following comment should be removed at some point in the future. -# mypy: disallow-untyped-defs=False - import os import sys @@ -8,14 +5,16 @@ def read(rel_path): + # type: (str) -> str here = os.path.abspath(os.path.dirname(__file__)) # intentionally *not* adding an encoding option to open, See: # https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690 - with open(os.path.join(here, rel_path), 'r') as fp: + with open(os.path.join(here, rel_path)) as fp: return fp.read() def get_version(rel_path): + # type: (str) -> str for line in read(rel_path).splitlines(): if line.startswith('__version__'): # __version__ = "0.9" diff --git a/src/pip/__init__.py b/src/pip/__init__.py index 97b5e2f8839..ada5d647123 100644 --- a/src/pip/__init__.py +++ b/src/pip/__init__.py @@ -1,8 +1,4 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import List, Optional - +from typing import List, Optional __version__ = "21.1.dev0" diff --git a/src/pip/_internal/__init__.py b/src/pip/_internal/__init__.py index 23652fadc5e..41071cd8608 100755 --- a/src/pip/_internal/__init__.py +++ b/src/pip/_internal/__init__.py @@ -1,10 +1,7 @@ -from typing import TYPE_CHECKING +from typing import List, Optional import pip._internal.utils.inject_securetransport # noqa -if TYPE_CHECKING: - from typing import List, Optional - def main(args=None): # type: (Optional[List[str]]) -> int diff --git a/src/pip/_internal/build_env.py b/src/pip/_internal/build_env.py index 9df467f309a..b1c877cfd9b 100644 --- a/src/pip/_internal/build_env.py +++ b/src/pip/_internal/build_env.py @@ -8,7 +8,8 @@ from collections import OrderedDict from distutils.sysconfig import get_python_lib from sysconfig import get_paths -from typing import TYPE_CHECKING +from types import TracebackType +from typing import TYPE_CHECKING, Iterable, List, Optional, Set, Tuple, Type from pip._vendor.pkg_resources import Requirement, VersionConflict, WorkingSet @@ -18,9 +19,6 @@ from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds if TYPE_CHECKING: - from types import TracebackType - from typing import Iterable, List, Optional, Set, Tuple, Type - from pip._internal.index.package_finder import PackageFinder logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/cache.py b/src/pip/_internal/cache.py index 83ea57ad4b9..7ef51b92e1d 100644 --- a/src/pip/_internal/cache.py +++ b/src/pip/_internal/cache.py @@ -5,24 +5,18 @@ import json import logging import os -from typing import TYPE_CHECKING +from typing import Any, Dict, List, Optional, Set -from pip._vendor.packaging.tags import interpreter_name, interpreter_version +from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version from pip._vendor.packaging.utils import canonicalize_name from pip._internal.exceptions import InvalidWheelFilename +from pip._internal.models.format_control import FormatControl from pip._internal.models.link import Link from pip._internal.models.wheel import Wheel from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds from pip._internal.utils.urls import path_to_url -if TYPE_CHECKING: - from typing import Any, Dict, List, Optional, Set - - from pip._vendor.packaging.tags import Tag - - from pip._internal.models.format_control import FormatControl - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/cli/autocompletion.py b/src/pip/_internal/cli/autocompletion.py index 3b4fc339e8a..3b1d2ac9b11 100644 --- a/src/pip/_internal/cli/autocompletion.py +++ b/src/pip/_internal/cli/autocompletion.py @@ -5,29 +5,25 @@ import os import sys from itertools import chain -from typing import TYPE_CHECKING +from typing import Any, Iterable, List, Optional from pip._internal.cli.main_parser import create_main_parser from pip._internal.commands import commands_dict, create_command from pip._internal.utils.misc import get_installed_distributions -if TYPE_CHECKING: - from typing import Any, Iterable, List, Optional - def autocomplete(): # type: () -> None - """Entry Point for completion of main and subcommand options. - """ + """Entry Point for completion of main and subcommand options.""" # Don't complete if user hasn't sourced bash_completion file. - if 'PIP_AUTO_COMPLETE' not in os.environ: + if "PIP_AUTO_COMPLETE" not in os.environ: return - cwords = os.environ['COMP_WORDS'].split()[1:] - cword = int(os.environ['COMP_CWORD']) + cwords = os.environ["COMP_WORDS"].split()[1:] + cword = int(os.environ["COMP_CWORD"]) try: current = cwords[cword - 1] except IndexError: - current = '' + current = "" parser = create_main_parser() subcommands = list(commands_dict) @@ -42,19 +38,20 @@ def autocomplete(): # subcommand options if subcommand_name is not None: # special case: 'help' subcommand has no options - if subcommand_name == 'help': + if subcommand_name == "help": sys.exit(1) # special case: list locally installed dists for show and uninstall - should_list_installed = ( - subcommand_name in ['show', 'uninstall'] and - not current.startswith('-') - ) + should_list_installed = not current.startswith("-") and subcommand_name in [ + "show", + "uninstall", + ] if should_list_installed: - installed = [] lc = current.lower() - for dist in get_installed_distributions(local_only=True): - if dist.key.startswith(lc) and dist.key not in cwords[1:]: - installed.append(dist.key) + installed = [ + dist.key + for dist in get_installed_distributions(local_only=True) + if dist.key.startswith(lc) and dist.key not in cwords[1:] + ] # if there are no dists installed, fall back to option completion if installed: for dist in installed: @@ -69,13 +66,15 @@ def autocomplete(): options.append((opt_str, opt.nargs)) # filter out previously specified options from available options - prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]] + prev_opts = [x.split("=")[0] for x in cwords[1 : cword - 1]] options = [(x, v) for (x, v) in options if x not in prev_opts] # filter options by current input options = [(k, v) for k, v in options if k.startswith(current)] # get completion type given cwords and available subcommand options completion_type = get_path_completion_type( - cwords, cword, subcommand.parser.option_list_all, + cwords, + cword, + subcommand.parser.option_list_all, ) # get completion files and directories if ``completion_type`` is # ````, ```` or ```` @@ -86,7 +85,7 @@ def autocomplete(): opt_label = option[0] # append '=' to options which require args if option[1] and option[0][:2] == "--": - opt_label += '=' + opt_label += "=" print(opt_label) else: # show main parser options only when necessary @@ -94,19 +93,17 @@ def autocomplete(): opts = [i.option_list for i in parser.option_groups] opts.append(parser.option_list) flattened_opts = chain.from_iterable(opts) - if current.startswith('-'): + if current.startswith("-"): for opt in flattened_opts: if opt.help != optparse.SUPPRESS_HELP: subcommands += opt._long_opts + opt._short_opts else: # get completion type given cwords and all available options - completion_type = get_path_completion_type(cwords, cword, - flattened_opts) + completion_type = get_path_completion_type(cwords, cword, flattened_opts) if completion_type: - subcommands = list(auto_complete_paths(current, - completion_type)) + subcommands = list(auto_complete_paths(current, completion_type)) - print(' '.join([x for x in subcommands if x.startswith(current)])) + print(" ".join([x for x in subcommands if x.startswith(current)])) sys.exit(1) @@ -119,16 +116,16 @@ def get_path_completion_type(cwords, cword, opts): :param opts: The available options to check :return: path completion type (``file``, ``dir``, ``path`` or None) """ - if cword < 2 or not cwords[cword - 2].startswith('-'): + if cword < 2 or not cwords[cword - 2].startswith("-"): return None for opt in opts: if opt.help == optparse.SUPPRESS_HELP: continue - for o in str(opt).split('/'): - if cwords[cword - 2].split('=')[0] == o: + for o in str(opt).split("/"): + if cwords[cword - 2].split("=")[0] == o: if not opt.metavar or any( - x in ('path', 'file', 'dir') - for x in opt.metavar.split('/')): + x in ("path", "file", "dir") for x in opt.metavar.split("/") + ): return opt.metavar return None @@ -150,15 +147,16 @@ def auto_complete_paths(current, completion_type): return filename = os.path.normcase(filename) # list all files that start with ``filename`` - file_list = (x for x in os.listdir(current_path) - if os.path.normcase(x).startswith(filename)) + file_list = ( + x for x in os.listdir(current_path) if os.path.normcase(x).startswith(filename) + ) for f in file_list: opt = os.path.join(current_path, f) comp_file = os.path.normcase(os.path.join(directory, f)) # complete regular files when there is not ```` after option # complete directories when there is ````, ```` or # ````after option - if completion_type != 'dir' and os.path.isfile(opt): + if completion_type != "dir" and os.path.isfile(opt): yield comp_file elif os.path.isdir(opt): - yield os.path.join(comp_file, '') + yield os.path.join(comp_file, "") diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py index 380ba8a7163..6d5798a0bdc 100644 --- a/src/pip/_internal/cli/base_command.py +++ b/src/pip/_internal/cli/base_command.py @@ -6,7 +6,8 @@ import os import sys import traceback -from typing import TYPE_CHECKING +from optparse import Values +from typing import Any, List, Optional, Tuple from pip._internal.cli import cmdoptions from pip._internal.cli.command_context import CommandContextMixIn @@ -29,18 +30,11 @@ from pip._internal.utils.filesystem import check_path_owner from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging from pip._internal.utils.misc import get_prog, normalize_path +from pip._internal.utils.temp_dir import TempDirectoryTypeRegistry as TempDirRegistry from pip._internal.utils.temp_dir import global_tempdir_manager, tempdir_registry from pip._internal.utils.virtualenv import running_under_virtualenv -if TYPE_CHECKING: - from optparse import Values - from typing import Any, List, Optional, Tuple - - from pip._internal.utils.temp_dir import ( - TempDirectoryTypeRegistry as TempDirRegistry, - ) - -__all__ = ['Command'] +__all__ = ["Command"] logger = logging.getLogger(__name__) @@ -57,7 +51,7 @@ def __init__(self, name, summary, isolated=False): self.summary = summary self.parser = ConfigOptionParser( usage=self.usage, - prog=f'{get_prog()} {name}', + prog=f"{get_prog()} {name}", formatter=UpdatingDefaultsHelpFormatter(), add_help_option=False, name=name, @@ -68,7 +62,7 @@ def __init__(self, name, summary, isolated=False): self.tempdir_registry = None # type: Optional[TempDirRegistry] # Commands should add options to this option group - optgroup_name = f'{self.name.capitalize()} Options' + optgroup_name = f"{self.name.capitalize()} Options" self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name) # Add the general options @@ -92,7 +86,7 @@ def handle_pip_version_check(self, options): """ # Make sure we do the pip version check if the index_group options # are present. - assert not hasattr(options, 'no_index') + assert not hasattr(options, "no_index") def run(self, options, args): # type: (Values, List[Any]) -> int @@ -137,17 +131,15 @@ def _main(self, args): # This also affects isolated builds and it should. if options.no_input: - os.environ['PIP_NO_INPUT'] = '1' + os.environ["PIP_NO_INPUT"] = "1" if options.exists_action: - os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action) + os.environ["PIP_EXISTS_ACTION"] = " ".join(options.exists_action) if options.require_venv and not self.ignore_require_venv: # If a venv is required check if it can really be found if not running_under_virtualenv(): - logger.critical( - 'Could not find an activated virtualenv (required).' - ) + logger.critical("Could not find an activated virtualenv (required).") sys.exit(VIRTUALENV_NOT_FOUND) if options.cache_dir: @@ -177,7 +169,7 @@ def _main(self, args): issue=8333, ) - if '2020-resolver' in options.features_enabled: + if "2020-resolver" in options.features_enabled: logger.warning( "--use-feature=2020-resolver no longer has any effect, " "since it is now the default dependency resolver in pip. " @@ -190,35 +182,39 @@ def _main(self, args): return status except PreviousBuildDirError as exc: logger.critical(str(exc)) - logger.debug('Exception information:', exc_info=True) + logger.debug("Exception information:", exc_info=True) return PREVIOUS_BUILD_DIR_ERROR - except (InstallationError, UninstallationError, BadCommand, - NetworkConnectionError) as exc: + except ( + InstallationError, + UninstallationError, + BadCommand, + NetworkConnectionError, + ) as exc: logger.critical(str(exc)) - logger.debug('Exception information:', exc_info=True) + logger.debug("Exception information:", exc_info=True) return ERROR except CommandError as exc: - logger.critical('%s', exc) - logger.debug('Exception information:', exc_info=True) + logger.critical("%s", exc) + logger.debug("Exception information:", exc_info=True) return ERROR except BrokenStdoutLoggingError: # Bypass our logger and write any remaining messages to stderr # because stdout no longer works. - print('ERROR: Pipe to stdout was broken', file=sys.stderr) + print("ERROR: Pipe to stdout was broken", file=sys.stderr) if level_number <= logging.DEBUG: traceback.print_exc(file=sys.stderr) return ERROR except KeyboardInterrupt: - logger.critical('Operation cancelled by user') - logger.debug('Exception information:', exc_info=True) + logger.critical("Operation cancelled by user") + logger.debug("Exception information:", exc_info=True) return ERROR except BaseException: - logger.critical('Exception:', exc_info=True) + logger.critical("Exception:", exc_info=True) return UNKNOWN_ERROR finally: diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index b7a18debb9a..7f035426f18 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -14,12 +14,13 @@ import textwrap import warnings from functools import partial -from optparse import SUPPRESS_HELP, Option, OptionGroup +from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values from textwrap import dedent -from typing import TYPE_CHECKING +from typing import Any, Callable, Dict, Optional, Tuple from pip._vendor.packaging.utils import canonicalize_name +from pip._internal.cli.parser import ConfigOptionParser from pip._internal.cli.progress_bars import BAR_TYPES from pip._internal.exceptions import CommandError from pip._internal.locations import USER_CACHE_DIR, get_src_prefix @@ -29,12 +30,6 @@ from pip._internal.utils.hashes import STRONG_HASHES from pip._internal.utils.misc import strtobool -if TYPE_CHECKING: - from optparse import OptionParser, Values - from typing import Any, Callable, Dict, Optional, Tuple - - from pip._internal.cli.parser import ConfigOptionParser - def raise_option_error(parser, option, msg): # type: (OptionParser, Option, str) -> None @@ -46,8 +41,8 @@ def raise_option_error(parser, option, msg): option: an Option instance. msg: the error text. """ - msg = f'{option} error: {msg}' - msg = textwrap.fill(' '.join(msg.split())) + msg = f"{option} error: {msg}" + msg = textwrap.fill(" ".join(msg.split())) parser.error(msg) @@ -58,8 +53,8 @@ def make_option_group(group, parser): group -- assumed to be dict with 'name' and 'options' keys parser -- an optparse Parser """ - option_group = OptionGroup(parser, group['name']) - for option in group['options']: + option_group = OptionGroup(parser, group["name"]) + for option in group["options"]: option_group.add_option(option()) return option_group @@ -78,13 +73,15 @@ def check_install_build_global(options, check_options=None): def getname(n): # type: (str) -> Optional[Any] return getattr(check_options, n, None) + names = ["build_options", "global_options", "install_options"] if any(map(getname, names)): control = options.format_control control.disallow_binaries() warnings.warn( - 'Disabling all use of wheels due to the use of --build-option ' - '/ --global-option / --install-option.', stacklevel=2, + "Disabling all use of wheels due to the use of --build-option " + "/ --global-option / --install-option.", + stacklevel=2, ) @@ -95,17 +92,18 @@ def check_dist_restriction(options, check_target=False): :param options: The OptionParser options. :param check_target: Whether or not to check if --target is being used. """ - dist_restriction_set = any([ - options.python_version, - options.platforms, - options.abis, - options.implementation, - ]) - - binary_only = FormatControl(set(), {':all:'}) + dist_restriction_set = any( + [ + options.python_version, + options.platforms, + options.abis, + options.implementation, + ] + ) + + binary_only = FormatControl(set(), {":all:"}) sdist_dependencies_allowed = ( - options.format_control != binary_only and - not options.ignore_dependencies + options.format_control != binary_only and not options.ignore_dependencies ) # Installations or downloads using dist restrictions must not combine @@ -151,10 +149,11 @@ class PipOption(Option): help_ = partial( Option, - '-h', '--help', - dest='help', - action='help', - help='Show help.', + "-h", + "--help", + dest="help", + action="help", + help="Show help.", ) # type: Callable[..., Option] isolated_mode = partial( @@ -172,111 +171,119 @@ class PipOption(Option): require_virtualenv = partial( Option, # Run only if inside a virtualenv, bail if not. - '--require-virtualenv', '--require-venv', - dest='require_venv', - action='store_true', + "--require-virtualenv", + "--require-venv", + dest="require_venv", + action="store_true", default=False, - help=SUPPRESS_HELP + help=SUPPRESS_HELP, ) # type: Callable[..., Option] verbose = partial( Option, - '-v', '--verbose', - dest='verbose', - action='count', + "-v", + "--verbose", + dest="verbose", + action="count", default=0, - help='Give more output. Option is additive, and can be used up to 3 times.' + help="Give more output. Option is additive, and can be used up to 3 times.", ) # type: Callable[..., Option] no_color = partial( Option, - '--no-color', - dest='no_color', - action='store_true', + "--no-color", + dest="no_color", + action="store_true", default=False, help="Suppress colored output.", ) # type: Callable[..., Option] version = partial( Option, - '-V', '--version', - dest='version', - action='store_true', - help='Show version and exit.', + "-V", + "--version", + dest="version", + action="store_true", + help="Show version and exit.", ) # type: Callable[..., Option] quiet = partial( Option, - '-q', '--quiet', - dest='quiet', - action='count', + "-q", + "--quiet", + dest="quiet", + action="count", default=0, help=( - 'Give less output. Option is additive, and can be used up to 3' - ' times (corresponding to WARNING, ERROR, and CRITICAL logging' - ' levels).' + "Give less output. Option is additive, and can be used up to 3" + " times (corresponding to WARNING, ERROR, and CRITICAL logging" + " levels)." ), ) # type: Callable[..., Option] progress_bar = partial( Option, - '--progress-bar', - dest='progress_bar', - type='choice', + "--progress-bar", + dest="progress_bar", + type="choice", choices=list(BAR_TYPES.keys()), - default='on', + default="on", help=( - 'Specify type of progress to be displayed [' + - '|'.join(BAR_TYPES.keys()) + '] (default: %default)' + "Specify type of progress to be displayed [" + + "|".join(BAR_TYPES.keys()) + + "] (default: %default)" ), ) # type: Callable[..., Option] log = partial( PipOption, - "--log", "--log-file", "--local-log", + "--log", + "--log-file", + "--local-log", dest="log", metavar="path", type="path", - help="Path to a verbose appending log." + help="Path to a verbose appending log.", ) # type: Callable[..., Option] no_input = partial( Option, # Don't ask for input - '--no-input', - dest='no_input', - action='store_true', + "--no-input", + dest="no_input", + action="store_true", default=False, - help="Disable prompting for input." + help="Disable prompting for input.", ) # type: Callable[..., Option] proxy = partial( Option, - '--proxy', - dest='proxy', - type='str', - default='', - help="Specify a proxy in the form [user:passwd@]proxy.server:port." + "--proxy", + dest="proxy", + type="str", + default="", + help="Specify a proxy in the form [user:passwd@]proxy.server:port.", ) # type: Callable[..., Option] retries = partial( Option, - '--retries', - dest='retries', - type='int', + "--retries", + dest="retries", + type="int", default=5, help="Maximum number of retries each connection should attempt " - "(default %default times).", + "(default %default times).", ) # type: Callable[..., Option] timeout = partial( Option, - '--timeout', '--default-timeout', - metavar='sec', - dest='timeout', - type='float', + "--timeout", + "--default-timeout", + metavar="sec", + dest="timeout", + type="float", default=15, - help='Set the socket timeout (default %default seconds).', + help="Set the socket timeout (default %default seconds).", ) # type: Callable[..., Option] @@ -284,88 +291,91 @@ def exists_action(): # type: () -> Option return Option( # Option when path already exist - '--exists-action', - dest='exists_action', - type='choice', - choices=['s', 'i', 'w', 'b', 'a'], + "--exists-action", + dest="exists_action", + type="choice", + choices=["s", "i", "w", "b", "a"], default=[], - action='append', - metavar='action', + action="append", + metavar="action", help="Default action when a path already exists: " - "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.", + "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.", ) cert = partial( PipOption, - '--cert', - dest='cert', - type='path', - metavar='path', + "--cert", + dest="cert", + type="path", + metavar="path", help="Path to alternate CA bundle.", ) # type: Callable[..., Option] client_cert = partial( PipOption, - '--client-cert', - dest='client_cert', - type='path', + "--client-cert", + dest="client_cert", + type="path", default=None, - metavar='path', + metavar="path", help="Path to SSL client certificate, a single file containing the " - "private key and the certificate in PEM format.", + "private key and the certificate in PEM format.", ) # type: Callable[..., Option] index_url = partial( Option, - '-i', '--index-url', '--pypi-url', - dest='index_url', - metavar='URL', + "-i", + "--index-url", + "--pypi-url", + dest="index_url", + metavar="URL", default=PyPI.simple_url, help="Base URL of the Python Package Index (default %default). " - "This should point to a repository compliant with PEP 503 " - "(the simple repository API) or a local directory laid out " - "in the same format.", + "This should point to a repository compliant with PEP 503 " + "(the simple repository API) or a local directory laid out " + "in the same format.", ) # type: Callable[..., Option] def extra_index_url(): # type: () -> Option return Option( - '--extra-index-url', - dest='extra_index_urls', - metavar='URL', - action='append', + "--extra-index-url", + dest="extra_index_urls", + metavar="URL", + action="append", default=[], help="Extra URLs of package indexes to use in addition to " - "--index-url. Should follow the same rules as " - "--index-url.", + "--index-url. Should follow the same rules as " + "--index-url.", ) no_index = partial( Option, - '--no-index', - dest='no_index', - action='store_true', + "--no-index", + dest="no_index", + action="store_true", default=False, - help='Ignore package index (only looking at --find-links URLs instead).', + help="Ignore package index (only looking at --find-links URLs instead).", ) # type: Callable[..., Option] def find_links(): # type: () -> Option return Option( - '-f', '--find-links', - dest='find_links', - action='append', + "-f", + "--find-links", + dest="find_links", + action="append", default=[], - metavar='url', + metavar="url", help="If a URL or path to an html file, then parse for links to " - "archives such as sdist (.tar.gz) or wheel (.whl) files. " - "If a local path or file:// URL that's a directory, " - "then look for archives in the directory listing. " - "Links to VCS project URLs are not supported.", + "archives such as sdist (.tar.gz) or wheel (.whl) files. " + "If a local path or file:// URL that's a directory, " + "then look for archives in the directory listing. " + "Links to VCS project URLs are not supported.", ) @@ -378,46 +388,51 @@ def trusted_host(): metavar="HOSTNAME", default=[], help="Mark this host or host:port pair as trusted, even though it " - "does not have valid or any HTTPS.", + "does not have valid or any HTTPS.", ) def constraints(): # type: () -> Option return Option( - '-c', '--constraint', - dest='constraints', - action='append', + "-c", + "--constraint", + dest="constraints", + action="append", default=[], - metavar='file', - help='Constrain versions using the given constraints file. ' - 'This option can be used multiple times.' + metavar="file", + help="Constrain versions using the given constraints file. " + "This option can be used multiple times.", ) def requirements(): # type: () -> Option return Option( - '-r', '--requirement', - dest='requirements', - action='append', + "-r", + "--requirement", + dest="requirements", + action="append", default=[], - metavar='file', - help='Install from the given requirements file. ' - 'This option can be used multiple times.' + metavar="file", + help="Install from the given requirements file. " + "This option can be used multiple times.", ) def editable(): # type: () -> Option return Option( - '-e', '--editable', - dest='editables', - action='append', + "-e", + "--editable", + dest="editables", + action="append", default=[], - metavar='path/url', - help=('Install a project in editable mode (i.e. setuptools ' - '"develop mode") from a local project path or a VCS url.'), + metavar="path/url", + help=( + "Install a project in editable mode (i.e. setuptools " + '"develop mode") from a local project path or a VCS url.' + ), ) @@ -429,16 +444,19 @@ def _handle_src(option, opt_str, value, parser): src = partial( PipOption, - '--src', '--source', '--source-dir', '--source-directory', - dest='src_dir', - type='path', - metavar='dir', + "--src", + "--source", + "--source-dir", + "--source-directory", + dest="src_dir", + type="path", + metavar="dir", default=get_src_prefix(), - action='callback', + action="callback", callback=_handle_src, - help='Directory to check out editable projects into. ' + help="Directory to check out editable projects into. " 'The default in a virtualenv is "/src". ' - 'The default for global installs is "/src".' + 'The default for global installs is "/src".', ) # type: Callable[..., Option] @@ -452,7 +470,9 @@ def _handle_no_binary(option, opt_str, value, parser): # type: (Option, str, str, OptionParser) -> None existing = _get_format_control(parser.values, option) FormatControl.handle_mutual_excludes( - value, existing.no_binary, existing.only_binary, + value, + existing.no_binary, + existing.only_binary, ) @@ -460,7 +480,9 @@ def _handle_only_binary(option, opt_str, value, parser): # type: (Option, str, str, OptionParser) -> None existing = _get_format_control(parser.values, option) FormatControl.handle_mutual_excludes( - value, existing.only_binary, existing.no_binary, + value, + existing.only_binary, + existing.no_binary, ) @@ -468,15 +490,18 @@ def no_binary(): # type: () -> Option format_control = FormatControl(set(), set()) return Option( - "--no-binary", dest="format_control", action="callback", - callback=_handle_no_binary, type="str", + "--no-binary", + dest="format_control", + action="callback", + callback=_handle_no_binary, + type="str", default=format_control, - help='Do not use binary packages. Can be supplied multiple times, and ' - 'each time adds to the existing value. Accepts either ":all:" to ' - 'disable all binary packages, ":none:" to empty the set (notice ' - 'the colons), or one or more package names with commas between ' - 'them (no colons). Note that some packages are tricky to compile ' - 'and may fail to install when this option is used on them.', + help="Do not use binary packages. Can be supplied multiple times, and " + 'each time adds to the existing value. Accepts either ":all:" to ' + 'disable all binary packages, ":none:" to empty the set (notice ' + "the colons), or one or more package names with commas between " + "them (no colons). Note that some packages are tricky to compile " + "and may fail to install when this option is used on them.", ) @@ -484,28 +509,33 @@ def only_binary(): # type: () -> Option format_control = FormatControl(set(), set()) return Option( - "--only-binary", dest="format_control", action="callback", - callback=_handle_only_binary, type="str", + "--only-binary", + dest="format_control", + action="callback", + callback=_handle_only_binary, + type="str", default=format_control, - help='Do not use source packages. Can be supplied multiple times, and ' - 'each time adds to the existing value. Accepts either ":all:" to ' - 'disable all source packages, ":none:" to empty the set, or one ' - 'or more package names with commas between them. Packages ' - 'without binary distributions will fail to install when this ' - 'option is used on them.', + help="Do not use source packages. Can be supplied multiple times, and " + 'each time adds to the existing value. Accepts either ":all:" to ' + 'disable all source packages, ":none:" to empty the set, or one ' + "or more package names with commas between them. Packages " + "without binary distributions will fail to install when this " + "option is used on them.", ) platforms = partial( Option, - '--platform', - dest='platforms', - metavar='platform', - action='append', + "--platform", + dest="platforms", + metavar="platform", + action="append", default=None, - help=("Only use wheels compatible with . Defaults to the " - "platform of the running system. Use this option multiple times to " - "specify multiple platforms supported by the target interpreter."), + help=( + "Only use wheels compatible with . Defaults to the " + "platform of the running system. Use this option multiple times to " + "specify multiple platforms supported by the target interpreter." + ), ) # type: Callable[..., Option] @@ -522,9 +552,9 @@ def _convert_python_version(value): # The empty string is the same as not providing a value. return (None, None) - parts = value.split('.') + parts = value.split(".") if len(parts) > 3: - return ((), 'at most three version parts are allowed') + return ((), "at most three version parts are allowed") if len(parts) == 1: # Then we are in the case of "3" or "37". @@ -535,7 +565,7 @@ def _convert_python_version(value): try: version_info = tuple(int(part) for part in parts) except ValueError: - return ((), 'each version part must be an integer') + return ((), "each version part must be an integer") return (version_info, None) @@ -547,10 +577,9 @@ def _handle_python_version(option, opt_str, value, parser): """ version_info, error_msg = _convert_python_version(value) if error_msg is not None: - msg = ( - 'invalid --python-version value: {!r}: {}'.format( - value, error_msg, - ) + msg = "invalid --python-version value: {!r}: {}".format( + value, + error_msg, ) raise_option_error(parser, option=option, msg=msg) @@ -559,49 +588,56 @@ def _handle_python_version(option, opt_str, value, parser): python_version = partial( Option, - '--python-version', - dest='python_version', - metavar='python_version', - action='callback', - callback=_handle_python_version, type='str', + "--python-version", + dest="python_version", + metavar="python_version", + action="callback", + callback=_handle_python_version, + type="str", default=None, - help=dedent("""\ + help=dedent( + """\ The Python interpreter version to use for wheel and "Requires-Python" compatibility checks. Defaults to a version derived from the running interpreter. The version can be specified using up to three dot-separated integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor version can also be given as a string without dots (e.g. "37" for 3.7.0). - """), + """ + ), ) # type: Callable[..., Option] implementation = partial( Option, - '--implementation', - dest='implementation', - metavar='implementation', + "--implementation", + dest="implementation", + metavar="implementation", default=None, - help=("Only use wheels compatible with Python " - "implementation , e.g. 'pp', 'jy', 'cp', " - " or 'ip'. If not specified, then the current " - "interpreter implementation is used. Use 'py' to force " - "implementation-agnostic wheels."), + help=( + "Only use wheels compatible with Python " + "implementation , e.g. 'pp', 'jy', 'cp', " + " or 'ip'. If not specified, then the current " + "interpreter implementation is used. Use 'py' to force " + "implementation-agnostic wheels." + ), ) # type: Callable[..., Option] abis = partial( Option, - '--abi', - dest='abis', - metavar='abi', - action='append', + "--abi", + dest="abis", + metavar="abi", + action="append", default=None, - help=("Only use wheels compatible with Python abi , e.g. 'pypy_41'. " - "If not specified, then the current interpreter abi tag is used. " - "Use this option multiple times to specify multiple abis supported " - "by the target interpreter. Generally you will need to specify " - "--implementation, --platform, and --python-version when using this " - "option."), + help=( + "Only use wheels compatible with Python abi , e.g. 'pypy_41'. " + "If not specified, then the current interpreter abi tag is used. " + "Use this option multiple times to specify multiple abis supported " + "by the target interpreter. Generally you will need to specify " + "--implementation, --platform, and --python-version when using this " + "option." + ), ) # type: Callable[..., Option] @@ -632,7 +668,7 @@ def prefer_binary(): dest="prefer_binary", action="store_true", default=False, - help="Prefer older binary packages over newer source packages." + help="Prefer older binary packages over newer source packages.", ) @@ -642,8 +678,8 @@ def prefer_binary(): dest="cache_dir", default=USER_CACHE_DIR, metavar="dir", - type='path', - help="Store the cache data in ." + type="path", + help="Store the cache data in .", ) # type: Callable[..., Option] @@ -686,39 +722,43 @@ def _handle_no_cache_dir(option, opt, value, parser): no_deps = partial( Option, - '--no-deps', '--no-dependencies', - dest='ignore_dependencies', - action='store_true', + "--no-deps", + "--no-dependencies", + dest="ignore_dependencies", + action="store_true", default=False, help="Don't install package dependencies.", ) # type: Callable[..., Option] build_dir = partial( PipOption, - '-b', '--build', '--build-dir', '--build-directory', - dest='build_dir', - type='path', - metavar='dir', + "-b", + "--build", + "--build-dir", + "--build-directory", + dest="build_dir", + type="path", + metavar="dir", help=SUPPRESS_HELP, ) # type: Callable[..., Option] ignore_requires_python = partial( Option, - '--ignore-requires-python', - dest='ignore_requires_python', - action='store_true', - help='Ignore the Requires-Python information.' + "--ignore-requires-python", + dest="ignore_requires_python", + action="store_true", + help="Ignore the Requires-Python information.", ) # type: Callable[..., Option] no_build_isolation = partial( Option, - '--no-build-isolation', - dest='build_isolation', - action='store_false', + "--no-build-isolation", + dest="build_isolation", + action="store_false", default=True, - help='Disable isolation when building a modern source distribution. ' - 'Build dependencies specified by PEP 518 must be already installed ' - 'if this option is used.' + help="Disable isolation when building a modern source distribution. " + "Build dependencies specified by PEP 518 must be already installed " + "if this option is used.", ) # type: Callable[..., Option] @@ -748,62 +788,62 @@ def _handle_no_use_pep517(option, opt, value, parser): use_pep517 = partial( Option, - '--use-pep517', - dest='use_pep517', - action='store_true', + "--use-pep517", + dest="use_pep517", + action="store_true", default=None, - help='Use PEP 517 for building source distributions ' - '(use --no-use-pep517 to force legacy behaviour).' + help="Use PEP 517 for building source distributions " + "(use --no-use-pep517 to force legacy behaviour).", ) # type: Any no_use_pep517 = partial( Option, - '--no-use-pep517', - dest='use_pep517', - action='callback', + "--no-use-pep517", + dest="use_pep517", + action="callback", callback=_handle_no_use_pep517, default=None, - help=SUPPRESS_HELP + help=SUPPRESS_HELP, ) # type: Any install_options = partial( Option, - '--install-option', - dest='install_options', - action='append', - metavar='options', + "--install-option", + dest="install_options", + action="append", + metavar="options", help="Extra arguments to be supplied to the setup.py install " - "command (use like --install-option=\"--install-scripts=/usr/local/" - "bin\"). Use multiple --install-option options to pass multiple " - "options to setup.py install. If you are using an option with a " - "directory path, be sure to use absolute path.", + 'command (use like --install-option="--install-scripts=/usr/local/' + 'bin"). Use multiple --install-option options to pass multiple ' + "options to setup.py install. If you are using an option with a " + "directory path, be sure to use absolute path.", ) # type: Callable[..., Option] global_options = partial( Option, - '--global-option', - dest='global_options', - action='append', - metavar='options', + "--global-option", + dest="global_options", + action="append", + metavar="options", help="Extra global options to be supplied to the setup.py " - "call before the install command.", + "call before the install command.", ) # type: Callable[..., Option] no_clean = partial( Option, - '--no-clean', - action='store_true', + "--no-clean", + action="store_true", default=False, - help="Don't clean up build directories." + help="Don't clean up build directories.", ) # type: Callable[..., Option] pre = partial( Option, - '--pre', - action='store_true', + "--pre", + action="store_true", default=False, help="Include pre-release and development versions. By default, " - "pip only finds stable versions.", + "pip only finds stable versions.", ) # type: Callable[..., Option] disable_pip_version_check = partial( @@ -813,7 +853,7 @@ def _handle_no_use_pep517(option, opt, value, parser): action="store_true", default=False, help="Don't periodically check PyPI to determine whether a new version " - "of pip is available for download. Implied with --no-index.", + "of pip is available for download. Implied with --no-index.", ) # type: Callable[..., Option] @@ -824,105 +864,106 @@ def _handle_merge_hash(option, opt_str, value, parser): if not parser.values.hashes: parser.values.hashes = {} try: - algo, digest = value.split(':', 1) + algo, digest = value.split(":", 1) except ValueError: - parser.error('Arguments to {} must be a hash name ' # noqa - 'followed by a value, like --hash=sha256:' - 'abcde...'.format(opt_str)) + parser.error( + "Arguments to {} must be a hash name " # noqa + "followed by a value, like --hash=sha256:" + "abcde...".format(opt_str) + ) if algo not in STRONG_HASHES: - parser.error('Allowed hash algorithms for {} are {}.'.format( # noqa - opt_str, ', '.join(STRONG_HASHES))) + parser.error( + "Allowed hash algorithms for {} are {}.".format( # noqa + opt_str, ", ".join(STRONG_HASHES) + ) + ) parser.values.hashes.setdefault(algo, []).append(digest) hash = partial( Option, - '--hash', + "--hash", # Hash values eventually end up in InstallRequirement.hashes due to # __dict__ copying in process_line(). - dest='hashes', - action='callback', + dest="hashes", + action="callback", callback=_handle_merge_hash, - type='string', + type="string", help="Verify that the package's archive matches this " - 'hash before installing. Example: --hash=sha256:abcdef...', + "hash before installing. Example: --hash=sha256:abcdef...", ) # type: Callable[..., Option] require_hashes = partial( Option, - '--require-hashes', - dest='require_hashes', - action='store_true', + "--require-hashes", + dest="require_hashes", + action="store_true", default=False, - help='Require a hash to check each requirement against, for ' - 'repeatable installs. This option is implied when any package in a ' - 'requirements file has a --hash option.', + help="Require a hash to check each requirement against, for " + "repeatable installs. This option is implied when any package in a " + "requirements file has a --hash option.", ) # type: Callable[..., Option] list_path = partial( PipOption, - '--path', - dest='path', - type='path', - action='append', - help='Restrict to the specified installation path for listing ' - 'packages (can be used multiple times).' + "--path", + dest="path", + type="path", + action="append", + help="Restrict to the specified installation path for listing " + "packages (can be used multiple times).", ) # type: Callable[..., Option] def check_list_path_option(options): # type: (Values) -> None if options.path and (options.user or options.local): - raise CommandError( - "Cannot combine '--path' with '--user' or '--local'" - ) + raise CommandError("Cannot combine '--path' with '--user' or '--local'") list_exclude = partial( PipOption, - '--exclude', - dest='excludes', - action='append', - metavar='package', - type='package_name', + "--exclude", + dest="excludes", + action="append", + metavar="package", + type="package_name", help="Exclude specified package from the output", ) # type: Callable[..., Option] no_python_version_warning = partial( Option, - '--no-python-version-warning', - dest='no_python_version_warning', - action='store_true', + "--no-python-version-warning", + dest="no_python_version_warning", + action="store_true", default=False, - help='Silence deprecation warnings for upcoming unsupported Pythons.', + help="Silence deprecation warnings for upcoming unsupported Pythons.", ) # type: Callable[..., Option] use_new_feature = partial( Option, - '--use-feature', - dest='features_enabled', - metavar='feature', - action='append', + "--use-feature", + dest="features_enabled", + metavar="feature", + action="append", default=[], - choices=['2020-resolver', 'fast-deps'], - help='Enable new functionality, that may be backward incompatible.', + choices=["2020-resolver", "fast-deps", "in-tree-build"], + help="Enable new functionality, that may be backward incompatible.", ) # type: Callable[..., Option] use_deprecated_feature = partial( Option, - '--use-deprecated', - dest='deprecated_features_enabled', - metavar='feature', - action='append', + "--use-deprecated", + dest="deprecated_features_enabled", + metavar="feature", + action="append", default=[], choices=[], - help=( - 'Enable deprecated functionality, that will be removed in the future.' - ), + help=("Enable deprecated functionality, that will be removed in the future."), ) # type: Callable[..., Option] @@ -931,8 +972,8 @@ def check_list_path_option(options): ########## general_group = { - 'name': 'General Options', - 'options': [ + "name": "General Options", + "options": [ help_, isolated_mode, require_virtualenv, @@ -955,15 +996,15 @@ def check_list_path_option(options): no_python_version_warning, use_new_feature, use_deprecated_feature, - ] + ], } # type: Dict[str, Any] index_group = { - 'name': 'Package Index Options', - 'options': [ + "name": "Package Index Options", + "options": [ index_url, extra_index_url, no_index, find_links, - ] + ], } # type: Dict[str, Any] diff --git a/src/pip/_internal/cli/command_context.py b/src/pip/_internal/cli/command_context.py index b8eb9ecbb85..375a2e3660b 100644 --- a/src/pip/_internal/cli/command_context.py +++ b/src/pip/_internal/cli/command_context.py @@ -1,10 +1,7 @@ from contextlib import ExitStack, contextmanager -from typing import TYPE_CHECKING +from typing import ContextManager, Iterator, TypeVar -if TYPE_CHECKING: - from typing import ContextManager, Iterator, TypeVar - - _T = TypeVar('_T', covariant=True) +_T = TypeVar("_T", covariant=True) class CommandContextMixIn: diff --git a/src/pip/_internal/cli/main.py b/src/pip/_internal/cli/main.py index 64210aeba6a..7ae074b59d5 100644 --- a/src/pip/_internal/cli/main.py +++ b/src/pip/_internal/cli/main.py @@ -4,7 +4,7 @@ import logging import os import sys -from typing import TYPE_CHECKING +from typing import List, Optional from pip._internal.cli.autocompletion import autocomplete from pip._internal.cli.main_parser import parse_command @@ -12,9 +12,6 @@ from pip._internal.exceptions import PipError from pip._internal.utils import deprecation -if TYPE_CHECKING: - from typing import List, Optional - logger = logging.getLogger(__name__) @@ -44,6 +41,7 @@ # call to main. As it is not safe to do any processing after calling # main, this should not be an issue in practice. + def main(args=None): # type: (Optional[List[str]]) -> int if args is None: @@ -64,7 +62,7 @@ def main(args=None): # Needed for locale.getpreferredencoding(False) to work # in pip._internal.utils.encoding.auto_decode try: - locale.setlocale(locale.LC_ALL, '') + locale.setlocale(locale.LC_ALL, "") except locale.Error as e: # setlocale can apparently crash if locale are uninitialized logger.debug("Ignoring error %s when setting locale", e) diff --git a/src/pip/_internal/cli/main_parser.py b/src/pip/_internal/cli/main_parser.py index 1ad8d7de3ca..d0f58fe421b 100644 --- a/src/pip/_internal/cli/main_parser.py +++ b/src/pip/_internal/cli/main_parser.py @@ -3,7 +3,7 @@ import os import sys -from typing import TYPE_CHECKING +from typing import List, Tuple from pip._internal.cli import cmdoptions from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter @@ -11,23 +11,18 @@ from pip._internal.exceptions import CommandError from pip._internal.utils.misc import get_pip_version, get_prog -if TYPE_CHECKING: - from typing import List, Tuple - - __all__ = ["create_main_parser", "parse_command"] def create_main_parser(): # type: () -> ConfigOptionParser - """Creates and returns the main parser for pip's CLI - """ + """Creates and returns the main parser for pip's CLI""" parser = ConfigOptionParser( - usage='\n%prog [options]', + usage="\n%prog [options]", add_help_option=False, formatter=UpdatingDefaultsHelpFormatter(), - name='global', + name="global", prog=get_prog(), ) parser.disable_interspersed_args() @@ -42,11 +37,11 @@ def create_main_parser(): parser.main = True # type: ignore # create command listing for description - description = [''] + [ - '{name:27} {command_info.summary}'.format(**locals()) + description = [""] + [ + f"{name:27} {command_info.summary}" for name, command_info in commands_dict.items() ] - parser.description = '\n'.join(description) + parser.description = "\n".join(description) return parser @@ -71,7 +66,7 @@ def parse_command(args): sys.exit() # pip || pip help -> print_help() - if not args_else or (args_else[0] == 'help' and len(args_else) == 1): + if not args_else or (args_else[0] == "help" and len(args_else) == 1): parser.print_help() sys.exit() @@ -85,7 +80,7 @@ def parse_command(args): if guess: msg.append(f'maybe you meant "{guess}"') - raise CommandError(' - '.join(msg)) + raise CommandError(" - ".join(msg)) # all the args without the subcommand cmd_args = args[:] diff --git a/src/pip/_internal/cli/parser.py b/src/pip/_internal/cli/parser.py index 1c8ce1451e6..16523c5a19c 100644 --- a/src/pip/_internal/cli/parser.py +++ b/src/pip/_internal/cli/parser.py @@ -1,23 +1,17 @@ """Base option parser setup""" -# The following comment should be removed at some point in the future. -# mypy: disallow-untyped-defs=False - import logging import optparse import shutil import sys import textwrap from contextlib import suppress -from typing import TYPE_CHECKING +from typing import Any, Dict, Iterator, List, Tuple from pip._internal.cli.status_codes import UNKNOWN_ERROR from pip._internal.configuration import Configuration, ConfigurationError from pip._internal.utils.misc import redact_auth_from_url, strtobool -if TYPE_CHECKING: - from typing import Any - logger = logging.getLogger(__name__) @@ -25,16 +19,19 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter): """A prettier/less verbose help formatter for optparse.""" def __init__(self, *args, **kwargs): + # type: (*Any, **Any) -> None # help position must be aligned with __init__.parseopts.description - kwargs['max_help_position'] = 30 - kwargs['indent_increment'] = 1 - kwargs['width'] = shutil.get_terminal_size()[0] - 2 + kwargs["max_help_position"] = 30 + kwargs["indent_increment"] = 1 + kwargs["width"] = shutil.get_terminal_size()[0] - 2 super().__init__(*args, **kwargs) def format_option_strings(self, option): + # type: (optparse.Option) -> str return self._format_option_strings(option) - def _format_option_strings(self, option, mvarfmt=' <{}>', optsep=', '): + def _format_option_strings(self, option, mvarfmt=" <{}>", optsep=", "): + # type: (optparse.Option, str, str) -> str """ Return a comma-separated list of option strings and metavars. @@ -52,52 +49,57 @@ def _format_option_strings(self, option, mvarfmt=' <{}>', optsep=', '): opts.insert(1, optsep) if option.takes_value(): + assert option.dest is not None metavar = option.metavar or option.dest.lower() opts.append(mvarfmt.format(metavar.lower())) - return ''.join(opts) + return "".join(opts) def format_heading(self, heading): - if heading == 'Options': - return '' - return heading + ':\n' + # type: (str) -> str + if heading == "Options": + return "" + return heading + ":\n" def format_usage(self, usage): + # type: (str) -> str """ Ensure there is only one newline between usage and the first heading if there is no description. """ - msg = '\nUsage: {}\n'.format( - self.indent_lines(textwrap.dedent(usage), " ")) + msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " ")) return msg def format_description(self, description): + # type: (str) -> str # leave full control over description to us if description: - if hasattr(self.parser, 'main'): - label = 'Commands' + if hasattr(self.parser, "main"): + label = "Commands" else: - label = 'Description' + label = "Description" # some doc strings have initial newlines, some don't - description = description.lstrip('\n') + description = description.lstrip("\n") # some doc strings have final newlines and spaces, some don't description = description.rstrip() # dedent, then reindent description = self.indent_lines(textwrap.dedent(description), " ") - description = f'{label}:\n{description}\n' + description = f"{label}:\n{description}\n" return description else: - return '' + return "" def format_epilog(self, epilog): + # type: (str) -> str # leave full control over epilog to us if epilog: return epilog else: - return '' + return "" def indent_lines(self, text, indent): - new_lines = [indent + line for line in text.split('\n')] + # type: (str, str) -> str + new_lines = [indent + line for line in text.split("\n")] return "\n".join(new_lines) @@ -111,13 +113,16 @@ class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter): """ def expand_default(self, option): + # type: (optparse.Option) -> str default_values = None if self.parser is not None: + assert isinstance(self.parser, ConfigOptionParser) self.parser._update_defaults(self.parser.defaults) + assert option.dest is not None default_values = self.parser.defaults.get(option.dest) help_text = super().expand_default(option) - if default_values and option.metavar == 'URL': + if default_values and option.metavar == "URL": if isinstance(default_values, str): default_values = [default_values] @@ -126,15 +131,14 @@ def expand_default(self, option): default_values = [] for val in default_values: - help_text = help_text.replace( - val, redact_auth_from_url(val)) + help_text = help_text.replace(val, redact_auth_from_url(val)) return help_text class CustomOptionParser(optparse.OptionParser): - def insert_option_group(self, idx, *args, **kwargs): + # type: (int, Any, Any) -> optparse.OptionGroup """Insert an OptionGroup at a given position.""" group = self.add_option_group(*args, **kwargs) @@ -145,6 +149,7 @@ def insert_option_group(self, idx, *args, **kwargs): @property def option_list_all(self): + # type: () -> List[optparse.Option] """Get a list of all options, including those in option groups.""" res = self.option_list[:] for i in self.option_groups: @@ -172,6 +177,7 @@ def __init__( super().__init__(*args, **kwargs) def check_default(self, option, key, val): + # type: (optparse.Option, str, Any) -> Any try: return option.check_value(key, val) except optparse.OptionValueError as exc: @@ -179,17 +185,20 @@ def check_default(self, option, key, val): sys.exit(3) def _get_ordered_configuration_items(self): + # type: () -> Iterator[Tuple[str, Any]] # Configuration gives keys in an unordered manner. Order them. override_order = ["global", self.name, ":env:"] # Pool the options into different groups - section_items = {name: [] for name in override_order} + section_items = { + name: [] for name in override_order + } # type: Dict[str, List[Tuple[str, Any]]] for section_key, val in self.config.items(): # ignore empty values if not val: logger.debug( "Ignoring configuration key '%s' as it's value is empty.", - section_key + section_key, ) continue @@ -203,6 +212,7 @@ def _get_ordered_configuration_items(self): yield key, val def _update_defaults(self, defaults): + # type: (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).""" @@ -213,7 +223,7 @@ def _update_defaults(self, defaults): # Then set the options with those values for key, val in self._get_ordered_configuration_items(): # '--' because configuration supports only long names - option = self.get_option('--' + key) + option = self.get_option("--" + key) # Ignore options not present in this parser. E.g. non-globals put # in [global] by users that want them to apply to all applicable @@ -221,31 +231,34 @@ def _update_defaults(self, defaults): if option is None: continue - if option.action in ('store_true', 'store_false'): + assert option.dest is not None + + if option.action in ("store_true", "store_false"): try: val = strtobool(val) except ValueError: self.error( - '{} is not a valid value for {} option, ' # noqa - 'please specify a boolean value like yes/no, ' - 'true/false or 1/0 instead.'.format(val, key) + "{} is not a valid value for {} option, " # noqa + "please specify a boolean value like yes/no, " + "true/false or 1/0 instead.".format(val, key) ) - elif option.action == 'count': + elif option.action == "count": with suppress(ValueError): val = strtobool(val) with suppress(ValueError): val = int(val) if not isinstance(val, int) or val < 0: self.error( - '{} is not a valid value for {} option, ' # noqa - 'please instead specify either a non-negative integer ' - 'or a boolean value like yes/no or false/true ' - 'which is equivalent to 1/0.'.format(val, key) + "{} is not a valid value for {} option, " # noqa + "please instead specify either a non-negative integer " + "or a boolean value like yes/no or false/true " + "which is equivalent to 1/0.".format(val, key) ) - elif option.action == 'append': + elif option.action == "append": val = val.split() val = [self.check_default(option, key, v) for v in val] - elif option.action == 'callback': + elif option.action == "callback": + assert option.callback is not None late_eval.add(option.dest) opt_str = option.get_opt_string() val = option.convert_value(opt_str, val) @@ -264,6 +277,7 @@ def _update_defaults(self, defaults): return defaults def get_default_values(self): + # type: () -> optparse.Values """Overriding to make updating the defaults after instantiation of the option parser possible, _update_defaults() does the dirty work.""" if not self.process_default_values: @@ -278,6 +292,7 @@ def get_default_values(self): defaults = self._update_defaults(self.defaults.copy()) # ours for option in self._get_all_options(): + assert option.dest is not None default = defaults.get(option.dest) if isinstance(default, str): opt_str = option.get_opt_string() @@ -285,5 +300,6 @@ def get_default_values(self): return optparse.Values(defaults) def error(self, msg): + # type: (str) -> None self.print_usage(sys.stderr) self.exit(UNKNOWN_ERROR, f"{msg}\n") diff --git a/src/pip/_internal/cli/progress_bars.py b/src/pip/_internal/cli/progress_bars.py index 2d3847082a8..3064c85697b 100644 --- a/src/pip/_internal/cli/progress_bars.py +++ b/src/pip/_internal/cli/progress_bars.py @@ -1,7 +1,7 @@ import itertools import sys from signal import SIGINT, default_int_handler, signal -from typing import TYPE_CHECKING +from typing import Any, Dict, List from pip._vendor.progress.bar import Bar, FillingCirclesBar, IncrementalBar from pip._vendor.progress.spinner import Spinner @@ -10,9 +10,6 @@ from pip._internal.utils.logging import get_indentation from pip._internal.utils.misc import format_size -if TYPE_CHECKING: - from typing import Any, Dict, List - try: from pip._vendor import colorama # Lots of different errors can come from this, including SystemError and @@ -111,7 +108,6 @@ def handle_sigint(self, signum, frame): # type: ignore class SilentBar(Bar): - def update(self): # type: () -> None pass @@ -126,14 +122,11 @@ class BlueEmojiBar(IncrementalBar): class DownloadProgressMixin: - def __init__(self, *args, **kwargs): # type: (List[Any], Dict[Any, Any]) -> None # https://github.com/python/mypy/issues/5887 super().__init__(*args, **kwargs) # type: ignore - self.message = (" " * ( - get_indentation() + 2 - )) + self.message # type: str + self.message = (" " * (get_indentation() + 2)) + self.message # type: str @property def downloaded(self): @@ -165,7 +158,6 @@ def iter(self, it): # type: ignore class WindowsMixin: - def __init__(self, *args, **kwargs): # type: (List[Any], Dict[Any, Any]) -> None # The Windows terminal does not support the hide/show cursor ANSI codes @@ -195,16 +187,14 @@ def __init__(self, *args, **kwargs): self.file.flush = lambda: self.file.wrapped.flush() -class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin, - DownloadProgressMixin): +class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin, DownloadProgressMixin): file = sys.stdout message = "%(percent)d%%" suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s" -class DefaultDownloadProgressBar(BaseDownloadProgressBar, - _BaseBar): +class DefaultDownloadProgressBar(BaseDownloadProgressBar, _BaseBar): pass @@ -212,23 +202,21 @@ class DownloadSilentBar(BaseDownloadProgressBar, SilentBar): pass -class DownloadBar(BaseDownloadProgressBar, - Bar): +class DownloadBar(BaseDownloadProgressBar, Bar): pass -class DownloadFillingCirclesBar(BaseDownloadProgressBar, - FillingCirclesBar): +class DownloadFillingCirclesBar(BaseDownloadProgressBar, FillingCirclesBar): pass -class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, - BlueEmojiBar): +class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, BlueEmojiBar): pass -class DownloadProgressSpinner(WindowsMixin, InterruptibleMixin, - DownloadProgressMixin, Spinner): +class DownloadProgressSpinner( + WindowsMixin, InterruptibleMixin, DownloadProgressMixin, Spinner +): file = sys.stdout suffix = "%(downloaded)s %(download_speed)s" @@ -244,13 +232,15 @@ def update(self): message = self.message % self phase = self.next_phase() suffix = self.suffix % self - line = ''.join([ - message, - " " if message else "", - phase, - " " if suffix else "", - suffix, - ]) + line = "".join( + [ + message, + " " if message else "", + phase, + " " if suffix else "", + suffix, + ] + ) self.writeln(line) @@ -260,7 +250,7 @@ def update(self): "on": (DefaultDownloadProgressBar, DownloadProgressSpinner), "ascii": (DownloadBar, DownloadProgressSpinner), "pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner), - "emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner) + "emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner), } diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index 436cd8ddab5..a55dd7516d8 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -8,8 +8,10 @@ import logging import os from functools import partial -from typing import TYPE_CHECKING +from optparse import Values +from typing import Any, List, Optional, Tuple +from pip._internal.cache import WheelCache from pip._internal.cli import cmdoptions from pip._internal.cli.base_command import Command from pip._internal.cli.command_context import CommandContextMixIn @@ -17,6 +19,7 @@ from pip._internal.index.collector import LinkCollector from pip._internal.index.package_finder import PackageFinder from pip._internal.models.selection_prefs import SelectionPreferences +from pip._internal.models.target_python import TargetPython from pip._internal.network.session import PipSession from pip._internal.operations.prepare import RequirementPreparer from pip._internal.req.constructors import ( @@ -26,20 +29,15 @@ install_req_from_req_string, ) from pip._internal.req.req_file import parse_requirements +from pip._internal.req.req_install import InstallRequirement +from pip._internal.req.req_tracker import RequirementTracker +from pip._internal.resolution.base import BaseResolver from pip._internal.self_outdated_check import pip_self_version_check -from pip._internal.utils.temp_dir import tempdir_kinds - -if TYPE_CHECKING: - from optparse import Values - from typing import Any, List, Optional, Tuple - - from pip._internal.cache import WheelCache - from pip._internal.models.target_python import TargetPython - from pip._internal.req.req_install import InstallRequirement - from pip._internal.req.req_tracker import RequirementTracker - from pip._internal.resolution.base import BaseResolver - from pip._internal.utils.temp_dir import TempDirectory, TempDirectoryTypeRegistry - +from pip._internal.utils.temp_dir import ( + TempDirectory, + TempDirectoryTypeRegistry, + tempdir_kinds, +) logger = logging.getLogger(__name__) @@ -49,6 +47,7 @@ class SessionCommandMixin(CommandContextMixIn): """ A class mixin for command classes needing _build_session(). """ + def __init__(self): # type: () -> None super().__init__() @@ -85,8 +84,7 @@ def _build_session(self, options, retries=None, timeout=None): assert not options.cache_dir or os.path.isabs(options.cache_dir) session = PipSession( cache=( - os.path.join(options.cache_dir, "http") - if options.cache_dir else None + os.path.join(options.cache_dir, "http") if options.cache_dir else None ), retries=retries if retries is not None else options.retries, trusted_hosts=options.trusted_hosts, @@ -103,9 +101,7 @@ def _build_session(self, options, retries=None, timeout=None): # Handle timeouts if options.timeout or timeout: - session.timeout = ( - timeout if timeout is not None else options.timeout - ) + session.timeout = timeout if timeout is not None else options.timeout # Handle configured proxies if options.proxy: @@ -136,16 +132,14 @@ def handle_pip_version_check(self, options): This overrides the default behavior of not doing the check. """ # Make sure the index_group options are present. - assert hasattr(options, 'no_index') + assert hasattr(options, "no_index") if options.disable_pip_version_check or options.no_index: return # Otherwise, check if we're using the latest version of pip available. session = self._build_session( - options, - retries=0, - timeout=min(5, options.timeout) + options, retries=0, timeout=min(5, options.timeout) ) with session: pip_self_version_check(session, options) @@ -163,6 +157,7 @@ def with_cleanup(func): """Decorator for common logic related to managing temporary directories. """ + def configure_tempdir_registry(registry): # type: (TempDirectoryTypeRegistry) -> None for t in KEEPABLE_TEMPDIR_TYPES: @@ -187,7 +182,6 @@ def wrapper(self, options, args): class RequirementCommand(IndexGroupCommand): - def __init__(self, *args, **kw): # type: (Any, Any) -> None super().__init__(*args, **kw) @@ -206,13 +200,13 @@ def determine_resolver_variant(options): @classmethod def make_requirement_preparer( cls, - temp_build_dir, # type: TempDirectory - options, # type: Values - req_tracker, # type: RequirementTracker - session, # type: PipSession - finder, # type: PackageFinder - use_user_site, # type: bool - download_dir=None, # type: str + temp_build_dir, # type: TempDirectory + options, # type: Values + req_tracker, # type: RequirementTracker + session, # type: PipSession + finder, # type: PackageFinder + use_user_site, # type: bool + download_dir=None, # type: str ): # type: (...) -> RequirementPreparer """ @@ -223,20 +217,20 @@ def make_requirement_preparer( resolver_variant = cls.determine_resolver_variant(options) if resolver_variant == "2020-resolver": - lazy_wheel = 'fast-deps' in options.features_enabled + lazy_wheel = "fast-deps" in options.features_enabled if lazy_wheel: logger.warning( - 'pip is using lazily downloaded wheels using HTTP ' - 'range requests to obtain dependency information. ' - 'This experimental feature is enabled through ' - '--use-feature=fast-deps and it is not ready for ' - 'production.' + "pip is using lazily downloaded wheels using HTTP " + "range requests to obtain dependency information. " + "This experimental feature is enabled through " + "--use-feature=fast-deps and it is not ready for " + "production." ) else: lazy_wheel = False - if 'fast-deps' in options.features_enabled: + if "fast-deps" in options.features_enabled: logger.warning( - 'fast-deps has no effect when used with the legacy resolver.' + "fast-deps has no effect when used with the legacy resolver." ) return RequirementPreparer( @@ -251,22 +245,23 @@ def make_requirement_preparer( require_hashes=options.require_hashes, use_user_site=use_user_site, lazy_wheel=lazy_wheel, + in_tree_build="in-tree-build" in options.features_enabled, ) @classmethod def make_resolver( cls, - preparer, # type: RequirementPreparer - finder, # type: PackageFinder - options, # type: Values - wheel_cache=None, # type: Optional[WheelCache] - use_user_site=False, # type: bool - ignore_installed=True, # type: bool - ignore_requires_python=False, # type: bool - force_reinstall=False, # type: bool + preparer, # type: RequirementPreparer + finder, # type: PackageFinder + options, # type: Values + wheel_cache=None, # type: Optional[WheelCache] + use_user_site=False, # type: bool + ignore_installed=True, # type: bool + ignore_requires_python=False, # type: bool + force_reinstall=False, # type: bool upgrade_strategy="to-satisfy-only", # type: str - use_pep517=None, # type: Optional[bool] - py_version_info=None, # type: Optional[Tuple[int, ...]] + use_pep517=None, # type: Optional[bool] + py_version_info=None, # type: Optional[Tuple[int, ...]] ): # type: (...) -> BaseResolver """ @@ -298,6 +293,7 @@ def make_resolver( py_version_info=py_version_info, ) import pip._internal.resolution.legacy.resolver + return pip._internal.resolution.legacy.resolver.Resolver( preparer=preparer, finder=finder, @@ -314,10 +310,10 @@ def make_resolver( def get_requirements( self, - args, # type: List[str] - options, # type: Values - finder, # type: PackageFinder - session, # type: PipSession + args, # type: List[str] + options, # type: Values + finder, # type: PackageFinder + session, # type: PipSession ): # type: (...) -> List[InstallRequirement] """ @@ -326,9 +322,12 @@ def get_requirements( requirements = [] # type: List[InstallRequirement] for filename in options.constraints: for parsed_req in parse_requirements( - filename, - constraint=True, finder=finder, options=options, - session=session): + filename, + constraint=True, + finder=finder, + options=options, + session=session, + ): req_to_add = install_req_from_parsed_requirement( parsed_req, isolated=options.isolated_mode, @@ -338,7 +337,9 @@ def get_requirements( for req in args: req_to_add = install_req_from_line( - req, None, isolated=options.isolated_mode, + req, + None, + isolated=options.isolated_mode, use_pep517=options.use_pep517, user_supplied=True, ) @@ -356,8 +357,8 @@ def get_requirements( # NOTE: options.require_hashes may be set if --require-hashes is True for filename in options.requirements: for parsed_req in parse_requirements( - filename, - finder=finder, options=options, session=session): + filename, finder=finder, options=options, session=session + ): req_to_add = install_req_from_parsed_requirement( parsed_req, isolated=options.isolated_mode, @@ -371,16 +372,19 @@ def get_requirements( options.require_hashes = True if not (args or options.editables or options.requirements): - opts = {'name': self.name} + opts = {"name": self.name} if options.find_links: raise CommandError( - 'You must give at least one requirement to {name} ' + "You must give at least one requirement to {name} " '(maybe you meant "pip {name} {links}"?)'.format( - **dict(opts, links=' '.join(options.find_links)))) + **dict(opts, links=" ".join(options.find_links)) + ) + ) else: raise CommandError( - 'You must give at least one requirement to {name} ' - '(see "pip help {name}")'.format(**opts)) + "You must give at least one requirement to {name} " + '(see "pip help {name}")'.format(**opts) + ) return requirements @@ -398,9 +402,9 @@ def trace_basic_info(finder): def _build_package_finder( self, - options, # type: Values - session, # type: PipSession - target_python=None, # type: Optional[TargetPython] + options, # type: Values + session, # type: PipSession + target_python=None, # type: Optional[TargetPython] ignore_requires_python=None, # type: Optional[bool] ): # type: (...) -> PackageFinder diff --git a/src/pip/_internal/cli/spinners.py b/src/pip/_internal/cli/spinners.py index d55434e6024..08e156617c4 100644 --- a/src/pip/_internal/cli/spinners.py +++ b/src/pip/_internal/cli/spinners.py @@ -3,16 +3,13 @@ import logging import sys import time -from typing import TYPE_CHECKING +from typing import IO, Iterator from pip._vendor.progress import HIDE_CURSOR, SHOW_CURSOR from pip._internal.utils.compat import WINDOWS from pip._internal.utils.logging import get_indentation -if TYPE_CHECKING: - from typing import IO, Iterator - logger = logging.getLogger(__name__) @@ -27,9 +24,14 @@ def finish(self, final_status): class InteractiveSpinner(SpinnerInterface): - def __init__(self, message, file=None, spin_chars="-\\|/", - # Empirically, 8 updates/second looks nice - min_update_interval_seconds=0.125): + def __init__( + self, + message, + file=None, + spin_chars="-\\|/", + # Empirically, 8 updates/second looks nice + min_update_interval_seconds=0.125, + ): # type: (str, IO[str], str, float) -> None self._message = message if file is None: @@ -104,8 +106,7 @@ def finish(self, final_status): # type: (str) -> None if self._finished: return - self._update( - "finished with status '{final_status}'".format(**locals())) + self._update(f"finished with status '{final_status}'") self._finished = True diff --git a/src/pip/_internal/commands/__init__.py b/src/pip/_internal/commands/__init__.py index 3037e9da861..31c985fdca5 100644 --- a/src/pip/_internal/commands/__init__.py +++ b/src/pip/_internal/commands/__init__.py @@ -4,13 +4,9 @@ import importlib from collections import OrderedDict, namedtuple -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Any, Optional - - from pip._internal.cli.base_command import Command +from typing import Any, Optional +from pip._internal.cli.base_command import Command CommandInfo = namedtuple('CommandInfo', 'module_path, class_name, summary') diff --git a/src/pip/_internal/commands/cache.py b/src/pip/_internal/commands/cache.py index 4f746dd980c..5155a5053e7 100644 --- a/src/pip/_internal/commands/cache.py +++ b/src/pip/_internal/commands/cache.py @@ -1,18 +1,14 @@ import logging import os import textwrap -from typing import TYPE_CHECKING +from optparse import Values +from typing import Any, List import pip._internal.utils.filesystem as filesystem from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import ERROR, SUCCESS from pip._internal.exceptions import CommandError, PipError -if TYPE_CHECKING: - from optparse import Values - from typing import Any, List - - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/commands/check.py b/src/pip/_internal/commands/check.py index 5bc07cb7eb2..70aa5af2249 100644 --- a/src/pip/_internal/commands/check.py +++ b/src/pip/_internal/commands/check.py @@ -1,5 +1,6 @@ import logging -from typing import TYPE_CHECKING +from optparse import Values +from typing import Any, List from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import ERROR, SUCCESS @@ -11,10 +12,6 @@ logger = logging.getLogger(__name__) -if TYPE_CHECKING: - from optparse import Values - from typing import Any, List - class CheckCommand(Command): """Verify installed packages have compatible dependencies.""" diff --git a/src/pip/_internal/commands/completion.py b/src/pip/_internal/commands/completion.py index ca336075210..92cb7882770 100644 --- a/src/pip/_internal/commands/completion.py +++ b/src/pip/_internal/commands/completion.py @@ -1,15 +1,12 @@ import sys import textwrap -from typing import TYPE_CHECKING +from optparse import Values +from typing import List from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import SUCCESS from pip._internal.utils.misc import get_prog -if TYPE_CHECKING: - from optparse import Values - from typing import List - BASE_COMPLETION = """ # pip {shell} completion start{script}# pip {shell} completion end """ diff --git a/src/pip/_internal/commands/configuration.py b/src/pip/_internal/commands/configuration.py index 8cf034aafb7..e13f7142ca3 100644 --- a/src/pip/_internal/commands/configuration.py +++ b/src/pip/_internal/commands/configuration.py @@ -1,21 +1,21 @@ import logging import os import subprocess -from typing import TYPE_CHECKING +from optparse import Values +from typing import Any, List, Optional from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import ERROR, SUCCESS -from pip._internal.configuration import Configuration, get_configuration_files, kinds +from pip._internal.configuration import ( + Configuration, + Kind, + get_configuration_files, + kinds, +) from pip._internal.exceptions import PipError from pip._internal.utils.logging import indent_log from pip._internal.utils.misc import get_prog, write_output -if TYPE_CHECKING: - from optparse import Values - from typing import Any, List, Optional - - from pip._internal.configuration import Kind - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/commands/debug.py b/src/pip/_internal/commands/debug.py index 15c66fd531e..480c0444dd2 100644 --- a/src/pip/_internal/commands/debug.py +++ b/src/pip/_internal/commands/debug.py @@ -2,7 +2,9 @@ import logging import os import sys -from typing import TYPE_CHECKING +from optparse import Values +from types import ModuleType +from typing import Dict, List, Optional import pip._vendor from pip._vendor.certifi import where @@ -13,17 +15,11 @@ from pip._internal.cli.base_command import Command from pip._internal.cli.cmdoptions import make_target_python from pip._internal.cli.status_codes import SUCCESS +from pip._internal.configuration import Configuration from pip._internal.metadata import get_environment from pip._internal.utils.logging import indent_log from pip._internal.utils.misc import get_pip_version -if TYPE_CHECKING: - from optparse import Values - from types import ModuleType - from typing import Dict, List, Optional - - from pip._internal.configuration import Configuration - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/commands/download.py b/src/pip/_internal/commands/download.py index 212b75c7a67..19f8d6c0275 100644 --- a/src/pip/_internal/commands/download.py +++ b/src/pip/_internal/commands/download.py @@ -1,6 +1,7 @@ import logging import os -from typing import TYPE_CHECKING +from optparse import Values +from typing import List from pip._internal.cli import cmdoptions from pip._internal.cli.cmdoptions import make_target_python @@ -10,10 +11,6 @@ from pip._internal.utils.misc import ensure_dir, normalize_path, write_output from pip._internal.utils.temp_dir import TempDirectory -if TYPE_CHECKING: - from optparse import Values - from typing import List - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/commands/freeze.py b/src/pip/_internal/commands/freeze.py index 6a3288d6be8..430d1018f04 100644 --- a/src/pip/_internal/commands/freeze.py +++ b/src/pip/_internal/commands/freeze.py @@ -1,5 +1,6 @@ import sys -from typing import TYPE_CHECKING +from optparse import Values +from typing import List from pip._internal.cli import cmdoptions from pip._internal.cli.base_command import Command @@ -10,10 +11,6 @@ DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'} -if TYPE_CHECKING: - from optparse import Values - from typing import List - class FreezeCommand(Command): """ diff --git a/src/pip/_internal/commands/hash.py b/src/pip/_internal/commands/hash.py index ff871b806ed..bca48dcc078 100644 --- a/src/pip/_internal/commands/hash.py +++ b/src/pip/_internal/commands/hash.py @@ -1,17 +1,14 @@ import hashlib import logging import sys -from typing import TYPE_CHECKING +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 from pip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES from pip._internal.utils.misc import read_chunks, write_output -if TYPE_CHECKING: - from optparse import Values - from typing import List - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/commands/help.py b/src/pip/_internal/commands/help.py index 0e1dc81fffd..79d0eb49b1a 100644 --- a/src/pip/_internal/commands/help.py +++ b/src/pip/_internal/commands/help.py @@ -1,13 +1,10 @@ -from typing import TYPE_CHECKING +from optparse import Values +from typing import List from pip._internal.cli.base_command import Command from pip._internal.cli.status_codes import SUCCESS from pip._internal.exceptions import CommandError -if TYPE_CHECKING: - from optparse import Values - from typing import List - class HelpCommand(Command): """Show help for commands""" diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index ca90a86f1dd..78cd0b5cf68 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -4,8 +4,8 @@ import os import shutil import site -from optparse import SUPPRESS_HELP -from typing import TYPE_CHECKING +from optparse import SUPPRESS_HELP, Values +from typing import Iterable, List, Optional from pip._vendor.packaging.utils import canonicalize_name @@ -17,8 +17,10 @@ from pip._internal.exceptions import CommandError, InstallationError from pip._internal.locations import distutils_scheme from pip._internal.metadata import get_environment -from pip._internal.operations.check import check_install_conflicts +from pip._internal.models.format_control import FormatControl +from pip._internal.operations.check import ConflictDetails, check_install_conflicts from pip._internal.req import install_given_reqs +from pip._internal.req.req_install import InstallRequirement from pip._internal.req.req_tracker import get_requirement_tracker from pip._internal.utils.distutils_args import parse_distutils_args from pip._internal.utils.filesystem import test_writable_dir @@ -30,17 +32,11 @@ ) from pip._internal.utils.temp_dir import TempDirectory from pip._internal.utils.virtualenv import virtualenv_no_global -from pip._internal.wheel_builder import build, should_build_for_install_command - -if TYPE_CHECKING: - from optparse import Values - from typing import Iterable, List, Optional - - from pip._internal.models.format_control import FormatControl - from pip._internal.operations.check import ConflictDetails - from pip._internal.req.req_install import InstallRequirement - from pip._internal.wheel_builder import BinaryAllowedPredicate - +from pip._internal.wheel_builder import ( + BinaryAllowedPredicate, + build, + should_build_for_install_command, +) logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py index 4ee0d54b48f..dcf9432638a 100644 --- a/src/pip/_internal/commands/list.py +++ b/src/pip/_internal/commands/list.py @@ -1,6 +1,9 @@ import json import logging -from typing import TYPE_CHECKING +from optparse import Values +from typing import Iterator, List, Set, Tuple + +from pip._vendor.pkg_resources import Distribution from pip._internal.cli import cmdoptions from pip._internal.cli.req_command import IndexGroupCommand @@ -9,6 +12,7 @@ from pip._internal.index.collector import LinkCollector from pip._internal.index.package_finder import PackageFinder from pip._internal.models.selection_prefs import SelectionPreferences +from pip._internal.network.session import PipSession from pip._internal.utils.compat import stdlib_pkgs from pip._internal.utils.misc import ( dist_is_editable, @@ -19,14 +23,6 @@ from pip._internal.utils.packaging import get_installer from pip._internal.utils.parallel import map_multithread -if TYPE_CHECKING: - from optparse import Values - from typing import Iterator, List, Set, Tuple - - from pip._vendor.pkg_resources import Distribution - - from pip._internal.network.session import PipSession - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/commands/search.py b/src/pip/_internal/commands/search.py index 1c7fa74ae90..e1f29e79bc3 100644 --- a/src/pip/_internal/commands/search.py +++ b/src/pip/_internal/commands/search.py @@ -2,15 +2,13 @@ import shutil import sys import textwrap +import xmlrpc.client from collections import OrderedDict -from typing import TYPE_CHECKING +from optparse import Values +from typing import TYPE_CHECKING, Dict, List, Optional from pip._vendor.packaging.version import parse as parse_version -# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is -# why we ignore the type on this import -from pip._vendor.six.moves import xmlrpc_client # type: ignore - from pip._internal.cli.base_command import Command from pip._internal.cli.req_command import SessionCommandMixin from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS @@ -22,10 +20,8 @@ from pip._internal.utils.misc import write_output if TYPE_CHECKING: - from optparse import Values - from typing import Dict, List, Optional + from typing import TypedDict - from typing_extensions import TypedDict TransformedHit = TypedDict( 'TransformedHit', {'name': str, 'summary': str, 'versions': List[str]}, @@ -76,15 +72,16 @@ def search(self, query, options): session = self.get_default_session(options) transport = PipXmlrpcTransport(index_url, session) - pypi = xmlrpc_client.ServerProxy(index_url, transport) + pypi = xmlrpc.client.ServerProxy(index_url, transport) try: hits = pypi.search({'name': query, 'summary': query}, 'or') - except xmlrpc_client.Fault as fault: + except xmlrpc.client.Fault as fault: message = "XMLRPC request failed [code: {code}]\n{string}".format( code=fault.faultCode, string=fault.faultString, ) raise CommandError(message) + assert isinstance(hits, list) return hits @@ -140,9 +137,8 @@ def print_results(hits, name_column_width=None, terminal_width=None): summary = ('\n' + ' ' * (name_column_width + 3)).join( summary_lines) - line = '{name_latest:{name_column_width}} - {summary}'.format( - name_latest='{name} ({latest})'.format(**locals()), - **locals()) + name_latest = f'{name} ({latest})' + line = f'{name_latest:{name_column_width}} - {summary}' try: write_output(line) dist = env.get_distribution(name) diff --git a/src/pip/_internal/commands/show.py b/src/pip/_internal/commands/show.py index e4d2502908f..24e855a80d8 100644 --- a/src/pip/_internal/commands/show.py +++ b/src/pip/_internal/commands/show.py @@ -1,7 +1,8 @@ import logging import os from email.parser import FeedParser -from typing import TYPE_CHECKING +from optparse import Values +from typing import Dict, Iterator, List from pip._vendor import pkg_resources from pip._vendor.packaging.utils import canonicalize_name @@ -10,10 +11,6 @@ from pip._internal.cli.status_codes import ERROR, SUCCESS from pip._internal.utils.misc import write_output -if TYPE_CHECKING: - from optparse import Values - from typing import Dict, Iterator, List - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/commands/uninstall.py b/src/pip/_internal/commands/uninstall.py index 5084f32d202..da56f4f5582 100644 --- a/src/pip/_internal/commands/uninstall.py +++ b/src/pip/_internal/commands/uninstall.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING +from optparse import Values +from typing import List from pip._vendor.packaging.utils import canonicalize_name @@ -13,10 +14,6 @@ ) from pip._internal.utils.misc import protect_pip_from_modification_on_windows -if TYPE_CHECKING: - from optparse import Values - from typing import List - class UninstallCommand(Command, SessionCommandMixin): """ @@ -76,8 +73,8 @@ def run(self, options, args): reqs_to_uninstall[canonicalize_name(req.name)] = req if not reqs_to_uninstall: raise InstallationError( - 'You must give at least one requirement to {self.name} (see ' - '"pip help {self.name}")'.format(**locals()) + f'You must give at least one requirement to {self.name} (see ' + f'"pip help {self.name}")' ) protect_pip_from_modification_on_windows( diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py index d97ac00c41c..842988ba570 100644 --- a/src/pip/_internal/commands/wheel.py +++ b/src/pip/_internal/commands/wheel.py @@ -1,24 +1,20 @@ import logging import os import shutil -from typing import TYPE_CHECKING +from optparse import Values +from typing import List from pip._internal.cache import WheelCache from pip._internal.cli import cmdoptions from pip._internal.cli.req_command import RequirementCommand, with_cleanup from pip._internal.cli.status_codes import SUCCESS from pip._internal.exceptions import CommandError +from pip._internal.req.req_install import InstallRequirement from pip._internal.req.req_tracker import get_requirement_tracker from pip._internal.utils.misc import ensure_dir, normalize_path from pip._internal.utils.temp_dir import TempDirectory from pip._internal.wheel_builder import build, should_build_for_wheel_command -if TYPE_CHECKING: - from optparse import Values - from typing import List - - from pip._internal.req.req_install import InstallRequirement - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/configuration.py b/src/pip/_internal/configuration.py index a52b83a110a..a4698ec1dda 100644 --- a/src/pip/_internal/configuration.py +++ b/src/pip/_internal/configuration.py @@ -16,7 +16,7 @@ import logging import os import sys -from typing import TYPE_CHECKING +from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple from pip._internal.exceptions import ( ConfigurationError, @@ -26,11 +26,8 @@ from pip._internal.utils.compat import WINDOWS from pip._internal.utils.misc import ensure_dir, enum -if TYPE_CHECKING: - from typing import Any, Dict, Iterable, List, NewType, Optional, Tuple - - RawConfigParser = configparser.RawConfigParser # Shorthand - Kind = NewType("Kind", str) +RawConfigParser = configparser.RawConfigParser # Shorthand +Kind = NewType("Kind", str) CONFIG_BASENAME = 'pip.ini' if WINDOWS else 'pip.conf' ENV_NAMES_IGNORED = "version", "help" diff --git a/src/pip/_internal/distributions/__init__.py b/src/pip/_internal/distributions/__init__.py index d68f358e244..a222f248f34 100644 --- a/src/pip/_internal/distributions/__init__.py +++ b/src/pip/_internal/distributions/__init__.py @@ -1,17 +1,12 @@ -from typing import TYPE_CHECKING - +from pip._internal.distributions.base import AbstractDistribution from pip._internal.distributions.sdist import SourceDistribution from pip._internal.distributions.wheel import WheelDistribution - -if TYPE_CHECKING: - from pip._internal.distributions.base import AbstractDistribution - from pip._internal.req.req_install import InstallRequirement +from pip._internal.req.req_install import InstallRequirement def make_distribution_for_install_requirement(install_req): # type: (InstallRequirement) -> AbstractDistribution - """Returns a Distribution for the given InstallRequirement - """ + """Returns a Distribution for the given InstallRequirement""" # Editable requirements will always be source distributions. They use the # legacy logic until we create a modern standard for them. if install_req.editable: diff --git a/src/pip/_internal/distributions/base.py b/src/pip/_internal/distributions/base.py index 50a21deff70..78ee91e76f1 100644 --- a/src/pip/_internal/distributions/base.py +++ b/src/pip/_internal/distributions/base.py @@ -1,13 +1,10 @@ import abc -from typing import TYPE_CHECKING +from typing import Optional -if TYPE_CHECKING: - from typing import Optional +from pip._vendor.pkg_resources import Distribution - from pip._vendor.pkg_resources import Distribution - - from pip._internal.index.package_finder import PackageFinder - from pip._internal.req import InstallRequirement +from pip._internal.index.package_finder import PackageFinder +from pip._internal.req import InstallRequirement class AbstractDistribution(metaclass=abc.ABCMeta): @@ -25,6 +22,7 @@ class AbstractDistribution(metaclass=abc.ABCMeta): - we must be able to create a Distribution object exposing the above metadata. """ + def __init__(self, req): # type: (InstallRequirement) -> None super().__init__() diff --git a/src/pip/_internal/distributions/installed.py b/src/pip/_internal/distributions/installed.py index 70f16499ba2..b19dfacb4db 100644 --- a/src/pip/_internal/distributions/installed.py +++ b/src/pip/_internal/distributions/installed.py @@ -1,13 +1,9 @@ -from typing import TYPE_CHECKING +from typing import Optional -from pip._internal.distributions.base import AbstractDistribution - -if TYPE_CHECKING: - from typing import Optional +from pip._vendor.pkg_resources import Distribution - from pip._vendor.pkg_resources import Distribution - - from pip._internal.index.package_finder import PackageFinder +from pip._internal.distributions.base import AbstractDistribution +from pip._internal.index.package_finder import PackageFinder class InstalledDistribution(AbstractDistribution): diff --git a/src/pip/_internal/distributions/sdist.py b/src/pip/_internal/distributions/sdist.py index 538bbfe8e74..c873a9f10e1 100644 --- a/src/pip/_internal/distributions/sdist.py +++ b/src/pip/_internal/distributions/sdist.py @@ -1,19 +1,14 @@ import logging -from typing import TYPE_CHECKING +from typing import Set, Tuple + +from pip._vendor.pkg_resources import Distribution from pip._internal.build_env import BuildEnvironment from pip._internal.distributions.base import AbstractDistribution from pip._internal.exceptions import InstallationError +from pip._internal.index.package_finder import PackageFinder from pip._internal.utils.subprocess import runner_with_spinner_message -if TYPE_CHECKING: - from typing import Set, Tuple - - from pip._vendor.pkg_resources import Distribution - - from pip._internal.index.package_finder import PackageFinder - - logger = logging.getLogger(__name__) @@ -51,10 +46,10 @@ def _raise_conflicts(conflicting_with, conflicting_reqs): error_message = format_string.format( requirement=self.req, conflicting_with=conflicting_with, - description=', '.join( - f'{installed} is incompatible with {wanted}' + description=", ".join( + f"{installed} is incompatible with {wanted}" for installed, wanted in sorted(conflicting) - ) + ), ) raise InstallationError(error_message) @@ -65,15 +60,13 @@ def _raise_conflicts(conflicting_with, conflicting_reqs): self.req.build_env = BuildEnvironment() self.req.build_env.install_requirements( - finder, pyproject_requires, 'overlay', - "Installing build dependencies" + finder, pyproject_requires, "overlay", "Installing build dependencies" ) conflicting, missing = self.req.build_env.check_requirements( self.req.requirements_to_check ) if conflicting: - _raise_conflicts("PEP 517/518 supported requirements", - conflicting) + _raise_conflicts("PEP 517/518 supported requirements", conflicting) if missing: logger.warning( "Missing build requirements in pyproject.toml for %s.", @@ -82,15 +75,13 @@ def _raise_conflicts(conflicting_with, conflicting_reqs): logger.warning( "The project does not specify a build backend, and " "pip cannot fall back to setuptools without %s.", - " and ".join(map(repr, sorted(missing))) + " and ".join(map(repr, sorted(missing))), ) # Install any extra build dependencies that the backend requests. # This must be done in a second pass, as the pyproject.toml # dependencies must be installed before we can call the backend. with self.req.build_env: - runner = runner_with_spinner_message( - "Getting requirements to build wheel" - ) + runner = runner_with_spinner_message("Getting requirements to build wheel") backend = self.req.pep517_backend assert backend is not None with backend.subprocess_runner(runner): @@ -100,6 +91,5 @@ def _raise_conflicts(conflicting_with, conflicting_reqs): if conflicting: _raise_conflicts("the backend dependencies", conflicting) self.req.build_env.install_requirements( - finder, missing, 'normal', - "Installing backend dependencies" + finder, missing, "normal", "Installing backend dependencies" ) diff --git a/src/pip/_internal/distributions/wheel.py b/src/pip/_internal/distributions/wheel.py index bc8ab99c0d9..d0384797b46 100644 --- a/src/pip/_internal/distributions/wheel.py +++ b/src/pip/_internal/distributions/wheel.py @@ -1,14 +1,11 @@ -from typing import TYPE_CHECKING from zipfile import ZipFile +from pip._vendor.pkg_resources import Distribution + from pip._internal.distributions.base import AbstractDistribution +from pip._internal.index.package_finder import PackageFinder from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel -if TYPE_CHECKING: - from pip._vendor.pkg_resources import Distribution - - from pip._internal.index.package_finder import PackageFinder - class WheelDistribution(AbstractDistribution): """Represents a wheel distribution. diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index 9af1961fff1..01ee4b76984 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -1,15 +1,14 @@ """Exceptions used throughout package""" +import configparser from itertools import chain, groupby, repeat -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, List, Optional + +from pip._vendor.pkg_resources import Distribution +from pip._vendor.requests.models import Request, Response if TYPE_CHECKING: - import configparser from hashlib import _Hash - from typing import Dict, List, Optional - - from pip._vendor.pkg_resources import Distribution - from pip._vendor.requests.models import Request, Response from pip._internal.req.req_install import InstallRequirement diff --git a/src/pip/_internal/index/collector.py b/src/pip/_internal/index/collector.py index 1472c763e9f..f6b0536d0d6 100644 --- a/src/pip/_internal/index/collector.py +++ b/src/pip/_internal/index/collector.py @@ -4,6 +4,7 @@ import cgi import functools +import html import itertools import logging import mimetypes @@ -11,46 +12,39 @@ import re import urllib.parse import urllib.request +import xml.etree.ElementTree from collections import OrderedDict -from typing import TYPE_CHECKING +from optparse import Values +from typing import ( + Callable, + Iterable, + List, + MutableMapping, + Optional, + Sequence, + Tuple, + Union, +) from pip._vendor import html5lib, requests -from pip._vendor.distlib.compat import unescape +from pip._vendor.requests import Response from pip._vendor.requests.exceptions import RetryError, SSLError from pip._internal.exceptions import NetworkConnectionError from pip._internal.models.link import Link from pip._internal.models.search_scope import SearchScope +from pip._internal.network.session import PipSession from pip._internal.network.utils import raise_for_status from pip._internal.utils.filetypes import is_archive_file from pip._internal.utils.misc import pairwise, redact_auth_from_url from pip._internal.utils.urls import path_to_url, url_to_path from pip._internal.vcs import is_url, vcs -if TYPE_CHECKING: - import xml.etree.ElementTree - from optparse import Values - from typing import ( - Callable, - Iterable, - List, - MutableMapping, - Optional, - Sequence, - Tuple, - Union, - ) - - from pip._vendor.requests import Response - - from pip._internal.network.session import PipSession - - HTMLElement = xml.etree.ElementTree.Element - ResponseHeaders = MutableMapping[str, str] - - logger = logging.getLogger(__name__) +HTMLElement = xml.etree.ElementTree.Element +ResponseHeaders = MutableMapping[str, str] + def _match_vcs_scheme(url): # type: (str) -> Optional[str] @@ -267,12 +261,11 @@ def _create_link_from_element( url = _clean_link(urllib.parse.urljoin(base_url, href)) pyrequire = anchor.get('data-requires-python') - pyrequire = unescape(pyrequire) if pyrequire else None + pyrequire = html.unescape(pyrequire) if pyrequire else None yanked_reason = anchor.get('data-yanked') if yanked_reason: - # This is a unicode string in Python 2 (and 3). - yanked_reason = unescape(yanked_reason) + yanked_reason = html.unescape(yanked_reason) link = Link( url, diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index 562ec4e9522..b826690fa5f 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -6,10 +6,12 @@ import functools import logging import re -from typing import TYPE_CHECKING +from typing import FrozenSet, Iterable, List, Optional, Set, Tuple, Union from pip._vendor.packaging import specifiers +from pip._vendor.packaging.tags import Tag from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.packaging.version import _BaseVersion from pip._vendor.packaging.version import parse as parse_version from pip._internal.exceptions import ( @@ -18,42 +20,33 @@ InvalidWheelFilename, UnsupportedWheel, ) -from pip._internal.index.collector import parse_links +from pip._internal.index.collector import LinkCollector, parse_links from pip._internal.models.candidate import InstallationCandidate from pip._internal.models.format_control import FormatControl from pip._internal.models.link import Link +from pip._internal.models.search_scope import SearchScope from pip._internal.models.selection_prefs import SelectionPreferences from pip._internal.models.target_python import TargetPython from pip._internal.models.wheel import Wheel +from pip._internal.req import InstallRequirement from pip._internal.utils.filetypes import WHEEL_EXTENSION +from pip._internal.utils.hashes import Hashes from pip._internal.utils.logging import indent_log from pip._internal.utils.misc import build_netloc from pip._internal.utils.packaging import check_requires_python from pip._internal.utils.unpacking import SUPPORTED_EXTENSIONS from pip._internal.utils.urls import url_to_path -if TYPE_CHECKING: - from typing import FrozenSet, Iterable, List, Optional, Set, Tuple, Union - - from pip._vendor.packaging.tags import Tag - from pip._vendor.packaging.version import _BaseVersion - - from pip._internal.index.collector import LinkCollector - from pip._internal.models.search_scope import SearchScope - from pip._internal.req import InstallRequirement - from pip._internal.utils.hashes import Hashes - - BuildTag = Union[Tuple[()], Tuple[int, str]] - CandidateSortingKey = ( - Tuple[int, int, int, _BaseVersion, BuildTag, Optional[int]] - ) - - __all__ = ['FormatControl', 'BestCandidateResult', 'PackageFinder'] logger = logging.getLogger(__name__) +BuildTag = Union[Tuple[()], Tuple[int, str]] +CandidateSortingKey = ( + Tuple[int, int, int, _BaseVersion, BuildTag, Optional[int]] +) + def _check_link_requires_python( link, # type: Link diff --git a/src/pip/_internal/locations.py b/src/pip/_internal/locations.py index 4e6b2810518..19c039eabf8 100644 --- a/src/pip/_internal/locations.py +++ b/src/pip/_internal/locations.py @@ -8,20 +8,16 @@ import site import sys import sysconfig +from distutils.cmd import Command as DistutilsCommand from distutils.command.install import SCHEME_KEYS from distutils.command.install import install as distutils_install_command -from typing import TYPE_CHECKING, cast +from typing import Dict, List, Optional, Union, cast from pip._internal.models.scheme import Scheme from pip._internal.utils import appdirs from pip._internal.utils.compat import WINDOWS from pip._internal.utils.virtualenv import running_under_virtualenv -if TYPE_CHECKING: - from distutils.cmd import Command as DistutilsCommand - from typing import Dict, List, Optional, Union - - # Application Directories USER_CACHE_DIR = appdirs.user_cache_dir("pip") diff --git a/src/pip/_internal/main.py b/src/pip/_internal/main.py index 647cde25f96..51eee1588d9 100644 --- a/src/pip/_internal/main.py +++ b/src/pip/_internal/main.py @@ -1,7 +1,4 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import List, Optional +from typing import List, Optional def main(args=None): diff --git a/src/pip/_internal/metadata/__init__.py b/src/pip/_internal/metadata/__init__.py index ecf2550e1dd..63335a19193 100644 --- a/src/pip/_internal/metadata/__init__.py +++ b/src/pip/_internal/metadata/__init__.py @@ -1,9 +1,6 @@ -from typing import TYPE_CHECKING +from typing import List, Optional -if TYPE_CHECKING: - from typing import List, Optional - - from .base import BaseDistribution, BaseEnvironment +from .base import BaseDistribution, BaseEnvironment def get_default_environment(): diff --git a/src/pip/_internal/metadata/base.py b/src/pip/_internal/metadata/base.py index 8ff57374c66..724b0c04494 100644 --- a/src/pip/_internal/metadata/base.py +++ b/src/pip/_internal/metadata/base.py @@ -1,11 +1,8 @@ -from typing import TYPE_CHECKING +from typing import Container, Iterator, List, Optional -from pip._internal.utils.misc import stdlib_pkgs # TODO: Move definition here. - -if TYPE_CHECKING: - from typing import Container, Iterator, List, Optional +from pip._vendor.packaging.version import _BaseVersion - from pip._vendor.packaging.version import _BaseVersion +from pip._internal.utils.misc import stdlib_pkgs # TODO: Move definition here. class BaseDistribution: diff --git a/src/pip/_internal/metadata/pkg_resources.py b/src/pip/_internal/metadata/pkg_resources.py index ccc3cc57b9d..d2fb29e2e9a 100644 --- a/src/pip/_internal/metadata/pkg_resources.py +++ b/src/pip/_internal/metadata/pkg_resources.py @@ -1,8 +1,9 @@ import zipfile -from typing import TYPE_CHECKING +from typing import Iterator, List, Optional from pip._vendor import pkg_resources from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.packaging.version import _BaseVersion from pip._internal.utils import misc # TODO: Move definition here. from pip._internal.utils.packaging import get_installer @@ -10,11 +11,6 @@ from .base import BaseDistribution, BaseEnvironment -if TYPE_CHECKING: - from typing import Iterator, List, Optional - - from pip._vendor.packaging.version import _BaseVersion - class Distribution(BaseDistribution): def __init__(self, dist): diff --git a/src/pip/_internal/models/candidate.py b/src/pip/_internal/models/candidate.py index d4eade13e60..10a144620ee 100644 --- a/src/pip/_internal/models/candidate.py +++ b/src/pip/_internal/models/candidate.py @@ -1,14 +1,9 @@ -from typing import TYPE_CHECKING - +from pip._vendor.packaging.version import _BaseVersion from pip._vendor.packaging.version import parse as parse_version +from pip._internal.models.link import Link from pip._internal.utils.models import KeyBasedCompareMixin -if TYPE_CHECKING: - from pip._vendor.packaging.version import _BaseVersion - - from pip._internal.models.link import Link - class InstallationCandidate(KeyBasedCompareMixin): """Represents a potential "candidate" for installation. diff --git a/src/pip/_internal/models/direct_url.py b/src/pip/_internal/models/direct_url.py index d10a8044d24..345dbaf109a 100644 --- a/src/pip/_internal/models/direct_url.py +++ b/src/pip/_internal/models/direct_url.py @@ -2,16 +2,7 @@ import json import re import urllib.parse -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Any, Dict, Iterable, Optional, Type, TypeVar, Union - - T = TypeVar("T") - - -DIRECT_URL_METADATA_NAME = "direct_url.json" -ENV_VAR_RE = re.compile(r"^\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?$") +from typing import Any, Dict, Iterable, Optional, Type, TypeVar, Union __all__ = [ "DirectUrl", @@ -21,6 +12,11 @@ "VcsInfo", ] +T = TypeVar("T") + +DIRECT_URL_METADATA_NAME = "direct_url.json" +ENV_VAR_RE = re.compile(r"^\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?$") + class DirectUrlValidationError(Exception): pass @@ -155,8 +151,7 @@ def _to_dict(self): return _filter_none(editable=self.editable or None) -if TYPE_CHECKING: - InfoType = Union[ArchiveInfo, DirInfo, VcsInfo] +InfoType = Union[ArchiveInfo, DirInfo, VcsInfo] class DirectUrl: diff --git a/src/pip/_internal/models/format_control.py b/src/pip/_internal/models/format_control.py index 73d045728a6..cf262af2918 100644 --- a/src/pip/_internal/models/format_control.py +++ b/src/pip/_internal/models/format_control.py @@ -1,12 +1,9 @@ -from typing import TYPE_CHECKING +from typing import FrozenSet, Optional, Set from pip._vendor.packaging.utils import canonicalize_name from pip._internal.exceptions import CommandError -if TYPE_CHECKING: - from typing import FrozenSet, Optional, Set - class FormatControl: """Helper for managing formats from which a package can be installed. diff --git a/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py index 757f43b9071..6f9a3244383 100644 --- a/src/pip/_internal/models/link.py +++ b/src/pip/_internal/models/link.py @@ -2,9 +2,10 @@ import posixpath import re import urllib.parse -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Tuple, Union from pip._internal.utils.filetypes import WHEEL_EXTENSION +from pip._internal.utils.hashes import Hashes from pip._internal.utils.misc import ( redact_auth_from_url, split_auth_from_netloc, @@ -14,10 +15,7 @@ from pip._internal.utils.urls import path_to_url, url_to_path if TYPE_CHECKING: - from typing import Optional, Tuple, Union - from pip._internal.index.collector import HTMLPage - from pip._internal.utils.hashes import Hashes class Link(KeyBasedCompareMixin): @@ -113,8 +111,7 @@ def filename(self): return netloc name = urllib.parse.unquote(name) - assert name, ( - 'URL {self._url!r} produced no filename'.format(**locals())) + assert name, f'URL {self._url!r} produced no filename' return name @property diff --git a/src/pip/_internal/models/search_scope.py b/src/pip/_internal/models/search_scope.py index 21907aab740..a3f0a5c0f87 100644 --- a/src/pip/_internal/models/search_scope.py +++ b/src/pip/_internal/models/search_scope.py @@ -3,7 +3,7 @@ import os import posixpath import urllib.parse -from typing import TYPE_CHECKING +from typing import List from pip._vendor.packaging.utils import canonicalize_name @@ -11,10 +11,6 @@ from pip._internal.utils.compat import has_tls from pip._internal.utils.misc import normalize_path, redact_auth_from_url -if TYPE_CHECKING: - from typing import List - - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/models/selection_prefs.py b/src/pip/_internal/models/selection_prefs.py index 65750ebb2bc..edc1cf79955 100644 --- a/src/pip/_internal/models/selection_prefs.py +++ b/src/pip/_internal/models/selection_prefs.py @@ -1,9 +1,6 @@ -from typing import TYPE_CHECKING +from typing import Optional -if TYPE_CHECKING: - from typing import Optional - - from pip._internal.models.format_control import FormatControl +from pip._internal.models.format_control import FormatControl class SelectionPreferences: diff --git a/src/pip/_internal/models/target_python.py b/src/pip/_internal/models/target_python.py index 742c089eb13..b91e349f566 100644 --- a/src/pip/_internal/models/target_python.py +++ b/src/pip/_internal/models/target_python.py @@ -1,14 +1,11 @@ import sys -from typing import TYPE_CHECKING +from typing import List, Optional, Tuple + +from pip._vendor.packaging.tags import Tag from pip._internal.utils.compatibility_tags import get_supported, version_info_to_nodot from pip._internal.utils.misc import normalize_version_info -if TYPE_CHECKING: - from typing import List, Optional, Tuple - - from pip._vendor.packaging.tags import Tag - class TargetPython: diff --git a/src/pip/_internal/models/wheel.py b/src/pip/_internal/models/wheel.py index 484596aaa32..708bff33067 100644 --- a/src/pip/_internal/models/wheel.py +++ b/src/pip/_internal/models/wheel.py @@ -2,15 +2,12 @@ name that have meaning. """ import re -from typing import TYPE_CHECKING +from typing import List from pip._vendor.packaging.tags import Tag from pip._internal.exceptions import InvalidWheelFilename -if TYPE_CHECKING: - from typing import List - class Wheel: """A wheel file""" diff --git a/src/pip/_internal/network/auth.py b/src/pip/_internal/network/auth.py index 315aca69994..bd54a5cba99 100644 --- a/src/pip/_internal/network/auth.py +++ b/src/pip/_internal/network/auth.py @@ -6,9 +6,10 @@ import logging import urllib.parse -from typing import TYPE_CHECKING +from typing import Any, Dict, List, Optional, Tuple from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth +from pip._vendor.requests.models import Request, Response from pip._vendor.requests.utils import get_netrc_auth from pip._internal.utils.misc import ( @@ -18,18 +19,12 @@ remove_auth_from_url, split_auth_netloc_from_url, ) - -if TYPE_CHECKING: - from typing import Any, Dict, List, Optional, Tuple - - from pip._vendor.requests.models import Request, Response - - from pip._internal.vcs.versioncontrol import AuthInfo - - Credentials = Tuple[str, str, str] +from pip._internal.vcs.versioncontrol import AuthInfo logger = logging.getLogger(__name__) +Credentials = Tuple[str, str, str] + try: import keyring except ImportError: @@ -42,7 +37,7 @@ def get_keyring_auth(url, username): - # type: (str, str) -> Optional[AuthInfo] + # type: (Optional[str], Optional[str]) -> Optional[AuthInfo] """Return the tuple auth for a given url from keyring.""" global keyring if not url or not keyring: diff --git a/src/pip/_internal/network/cache.py b/src/pip/_internal/network/cache.py index cdec4949c36..ce08932a57f 100644 --- a/src/pip/_internal/network/cache.py +++ b/src/pip/_internal/network/cache.py @@ -3,7 +3,7 @@ import os from contextlib import contextmanager -from typing import TYPE_CHECKING +from typing import Iterator, Optional from pip._vendor.cachecontrol.cache import BaseCache from pip._vendor.cachecontrol.caches import FileCache @@ -12,9 +12,6 @@ from pip._internal.utils.filesystem import adjacent_tmp_file, replace from pip._internal.utils.misc import ensure_dir -if TYPE_CHECKING: - from typing import Iterator, Optional - def is_from_cache(response): # type: (Response) -> bool diff --git a/src/pip/_internal/network/download.py b/src/pip/_internal/network/download.py index 7f02fa6f659..1897d99a13f 100644 --- a/src/pip/_internal/network/download.py +++ b/src/pip/_internal/network/download.py @@ -4,25 +4,19 @@ import logging import mimetypes import os -from typing import TYPE_CHECKING +from typing import Iterable, Optional, Tuple -from pip._vendor.requests.models import CONTENT_CHUNK_SIZE +from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response from pip._internal.cli.progress_bars import DownloadProgressProvider from pip._internal.exceptions import NetworkConnectionError from pip._internal.models.index import PyPI +from pip._internal.models.link import Link from pip._internal.network.cache import is_from_cache +from pip._internal.network.session import PipSession from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks from pip._internal.utils.misc import format_size, redact_auth_from_url, splitext -if TYPE_CHECKING: - from typing import Iterable, Optional, Tuple - - from pip._vendor.requests.models import Response - - from pip._internal.models.link import Link - from pip._internal.network.session import PipSession - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/network/lazy_wheel.py b/src/pip/_internal/network/lazy_wheel.py index fd7ab7f03a3..b877d3b7a67 100644 --- a/src/pip/_internal/network/lazy_wheel.py +++ b/src/pip/_internal/network/lazy_wheel.py @@ -5,22 +5,16 @@ from bisect import bisect_left, bisect_right from contextlib import contextmanager from tempfile import NamedTemporaryFile -from typing import TYPE_CHECKING +from typing import Any, Dict, Iterator, List, Optional, Tuple from zipfile import BadZipfile, ZipFile -from pip._vendor.requests.models import CONTENT_CHUNK_SIZE +from pip._vendor.pkg_resources import Distribution +from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response +from pip._internal.network.session import PipSession from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel -if TYPE_CHECKING: - from typing import Any, Dict, Iterator, List, Optional, Tuple - - from pip._vendor.pkg_resources import Distribution - from pip._vendor.requests.models import Response - - from pip._internal.network.session import PipSession - class HTTPRangeRequestUnsupported(Exception): pass diff --git a/src/pip/_internal/network/session.py b/src/pip/_internal/network/session.py index 03c3725abd1..b42d06bc3d3 100644 --- a/src/pip/_internal/network/session.py +++ b/src/pip/_internal/network/session.py @@ -2,8 +2,15 @@ network request configuration and behavior. """ -# The following comment should be removed at some point in the future. -# mypy: disallow-untyped-defs=False +# When mypy runs on Windows the call to distro.linux_distribution() is skipped +# resulting in the failure: +# +# error: unused 'type: ignore' comment +# +# If the upstream module adds typing, this comment should be removed. See +# https://github.com/nir0s/distro/pull/269 +# +# mypy: warn-unused-ignores=False import email.utils import ipaddress @@ -15,17 +22,19 @@ import sys import urllib.parse import warnings -from typing import TYPE_CHECKING +from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, Tuple, Union from pip._vendor import requests, urllib3 from pip._vendor.cachecontrol import CacheControlAdapter from pip._vendor.requests.adapters import BaseAdapter, HTTPAdapter -from pip._vendor.requests.models import Response +from pip._vendor.requests.models import PreparedRequest, Response from pip._vendor.requests.structures import CaseInsensitiveDict +from pip._vendor.urllib3.connectionpool import ConnectionPool from pip._vendor.urllib3.exceptions import InsecureRequestWarning from pip import __version__ from pip._internal.metadata import get_default_environment +from pip._internal.models.link import Link from pip._internal.network.auth import MultiDomainBasicAuth from pip._internal.network.cache import SafeFileCache @@ -35,16 +44,10 @@ from pip._internal.utils.misc import build_url_from_netloc, parse_netloc from pip._internal.utils.urls import url_to_path -if TYPE_CHECKING: - from typing import Any, Iterator, List, Optional, Sequence, Tuple, Union - - from pip._internal.models.link import Link - - SecureOrigin = Tuple[str, str, Optional[Union[int, str]]] - - logger = logging.getLogger(__name__) +SecureOrigin = Tuple[str, str, Optional[Union[int, str]]] + # Ignore warning raised when using --trusted-host. warnings.filterwarnings("ignore", category=InsecureRequestWarning) @@ -94,6 +97,7 @@ def looks_like_ci(): def user_agent(): + # type: () -> str """ Return a string representing the user agent. """ @@ -103,15 +107,14 @@ def user_agent(): "implementation": { "name": platform.python_implementation(), }, - } + } # type: Dict[str, Any] if data["implementation"]["name"] == 'CPython': data["implementation"]["version"] = platform.python_version() elif data["implementation"]["name"] == 'PyPy': - if sys.pypy_version_info.releaselevel == 'final': - pypy_version_info = sys.pypy_version_info[:3] - else: - pypy_version_info = sys.pypy_version_info + pypy_version_info = sys.pypy_version_info # type: ignore + if pypy_version_info.releaselevel == 'final': + pypy_version_info = pypy_version_info[:3] data["implementation"]["version"] = ".".join( [str(x) for x in pypy_version_info] ) @@ -124,9 +127,12 @@ def user_agent(): if sys.platform.startswith("linux"): from pip._vendor import distro + + # https://github.com/nir0s/distro/pull/269 + linux_distribution = distro.linux_distribution() # type: ignore distro_infos = dict(filter( lambda x: x[1], - zip(["name", "version", "id"], distro.linux_distribution()), + zip(["name", "version", "id"], linux_distribution), )) libc = dict(filter( lambda x: x[1], @@ -175,8 +181,16 @@ def user_agent(): class LocalFSAdapter(BaseAdapter): - def send(self, request, stream=None, timeout=None, verify=None, cert=None, - proxies=None): + def send( + self, + request, # type: PreparedRequest + stream=False, # type: bool + timeout=None, # type: Optional[Union[float, Tuple[float, float]]] + verify=True, # type: Union[bool, str] + cert=None, # type: Optional[Union[str, Tuple[str, str]]] + proxies=None, # type:Optional[Mapping[str, str]] + ): + # type: (...) -> Response pathname = url_to_path(request.url) resp = Response() @@ -203,18 +217,33 @@ def send(self, request, stream=None, timeout=None, verify=None, cert=None, return resp def close(self): + # type: () -> None pass class InsecureHTTPAdapter(HTTPAdapter): - def cert_verify(self, conn, url, verify, cert): + def cert_verify( + self, + conn, # type: ConnectionPool + url, # type: str + verify, # type: Union[bool, str] + cert, # type: Optional[Union[str, Tuple[str, str]]] + ): + # type: (...) -> None super().cert_verify(conn=conn, url=url, verify=False, cert=cert) class InsecureCacheControlAdapter(CacheControlAdapter): - def cert_verify(self, conn, url, verify, cert): + def cert_verify( + self, + conn, # type: ConnectionPool + url, # type: str + verify, # type: Union[bool, str] + cert, # type: Optional[Union[str, Tuple[str, str]]] + ): + # type: (...) -> None super().cert_verify(conn=conn, url=url, verify=False, cert=cert) @@ -412,6 +441,7 @@ def is_secure_origin(self, location): return False def request(self, method, url, *args, **kwargs): + # type: (str, str, *Any, **Any) -> Response # Allow setting a default timeout on a session kwargs.setdefault("timeout", self.timeout) diff --git a/src/pip/_internal/network/utils.py b/src/pip/_internal/network/utils.py index 47ece6d13dd..d29c7c0769d 100644 --- a/src/pip/_internal/network/utils.py +++ b/src/pip/_internal/network/utils.py @@ -1,12 +1,9 @@ -from typing import TYPE_CHECKING +from typing import Dict, Iterator from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response from pip._internal.exceptions import NetworkConnectionError -if TYPE_CHECKING: - from typing import Dict, Iterator - # The following comments and HTTP headers were originally added by # Donald Stufft in git commit 22c562429a61bb77172039e480873fb239dd8c03. # diff --git a/src/pip/_internal/network/xmlrpc.py b/src/pip/_internal/network/xmlrpc.py index d4aa71c0929..b92b8d9ae18 100644 --- a/src/pip/_internal/network/xmlrpc.py +++ b/src/pip/_internal/network/xmlrpc.py @@ -3,25 +3,20 @@ import logging import urllib.parse -from typing import TYPE_CHECKING - -# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is -# why we ignore the type on this import -from pip._vendor.six.moves import xmlrpc_client # type: ignore +import xmlrpc.client +from typing import TYPE_CHECKING, Tuple from pip._internal.exceptions import NetworkConnectionError +from pip._internal.network.session import PipSession from pip._internal.network.utils import raise_for_status if TYPE_CHECKING: - from typing import Dict - - from pip._internal.network.session import PipSession - + from xmlrpc.client import _HostType, _Marshallable logger = logging.getLogger(__name__) -class PipXmlrpcTransport(xmlrpc_client.Transport): +class PipXmlrpcTransport(xmlrpc.client.Transport): """Provide a `xmlrpclib.Transport` implementation via a `PipSession` object. """ @@ -34,7 +29,8 @@ def __init__(self, index_url, session, use_datetime=False): self._session = session def request(self, host, handler, request_body, verbose=False): - # type: (str, str, Dict[str, str], bool) -> None + # type: (_HostType, str, bytes, bool) -> Tuple[_Marshallable, ...] + assert isinstance(host, str) parts = (self._scheme, host, handler, None, None, None) url = urllib.parse.urlunparse(parts) try: diff --git a/src/pip/_internal/operations/build/metadata.py b/src/pip/_internal/operations/build/metadata.py index 21f86c8dc88..1c826835b49 100644 --- a/src/pip/_internal/operations/build/metadata.py +++ b/src/pip/_internal/operations/build/metadata.py @@ -2,16 +2,13 @@ """ import os -from typing import TYPE_CHECKING +from pip._vendor.pep517.wrappers import Pep517HookCaller + +from pip._internal.build_env import BuildEnvironment from pip._internal.utils.subprocess import runner_with_spinner_message from pip._internal.utils.temp_dir import TempDirectory -if TYPE_CHECKING: - from pip._vendor.pep517.wrappers import Pep517HookCaller - - from pip._internal.build_env import BuildEnvironment - def generate_metadata(build_env, backend): # type: (BuildEnvironment, Pep517HookCaller) -> str diff --git a/src/pip/_internal/operations/build/metadata_legacy.py b/src/pip/_internal/operations/build/metadata_legacy.py index a113a4a4e87..f46538a07f4 100644 --- a/src/pip/_internal/operations/build/metadata_legacy.py +++ b/src/pip/_internal/operations/build/metadata_legacy.py @@ -3,16 +3,13 @@ import logging import os -from typing import TYPE_CHECKING +from pip._internal.build_env import BuildEnvironment from pip._internal.exceptions import InstallationError from pip._internal.utils.setuptools_build import make_setuptools_egg_info_args from pip._internal.utils.subprocess import call_subprocess from pip._internal.utils.temp_dir import TempDirectory -if TYPE_CHECKING: - from pip._internal.build_env import BuildEnvironment - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/operations/build/wheel.py b/src/pip/_internal/operations/build/wheel.py index 9af53caa2f1..83fac3b3187 100644 --- a/src/pip/_internal/operations/build/wheel.py +++ b/src/pip/_internal/operations/build/wheel.py @@ -1,13 +1,10 @@ import logging import os -from typing import TYPE_CHECKING +from typing import List, Optional -from pip._internal.utils.subprocess import runner_with_spinner_message - -if TYPE_CHECKING: - from typing import List, Optional +from pip._vendor.pep517.wrappers import Pep517HookCaller - from pip._vendor.pep517.wrappers import Pep517HookCaller +from pip._internal.utils.subprocess import runner_with_spinner_message logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/operations/build/wheel_legacy.py b/src/pip/_internal/operations/build/wheel_legacy.py index 0a4a68d20bc..755c3bc83a2 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 TYPE_CHECKING +from typing import List, Optional from pip._internal.cli.spinners import open_spinner from pip._internal.utils.setuptools_build import make_setuptools_bdist_wheel_args @@ -10,9 +10,6 @@ format_command_args, ) -if TYPE_CHECKING: - from typing import List, Optional - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/operations/check.py b/src/pip/_internal/operations/check.py index a3189061c71..224633561aa 100644 --- a/src/pip/_internal/operations/check.py +++ b/src/pip/_internal/operations/check.py @@ -3,30 +3,26 @@ import logging from collections import namedtuple -from typing import TYPE_CHECKING +from typing import Any, Callable, Dict, List, Optional, Set, Tuple from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.pkg_resources import RequirementParseError from pip._internal.distributions import make_distribution_for_install_requirement +from pip._internal.req.req_install import InstallRequirement from pip._internal.utils.misc import get_installed_distributions logger = logging.getLogger(__name__) -if TYPE_CHECKING: - from typing import Any, Callable, Dict, List, Optional, Set, Tuple +# Shorthands +PackageSet = Dict[str, 'PackageDetails'] +Missing = Tuple[str, Any] +Conflicting = Tuple[str, str, Any] - from pip._internal.req.req_install import InstallRequirement - - # Shorthands - PackageSet = Dict[str, 'PackageDetails'] - Missing = Tuple[str, Any] - Conflicting = Tuple[str, str, Any] - - MissingDict = Dict[str, List[Missing]] - ConflictingDict = Dict[str, List[Conflicting]] - CheckResult = Tuple[MissingDict, ConflictingDict] - ConflictDetails = Tuple[PackageSet, CheckResult] +MissingDict = Dict[str, List[Missing]] +ConflictingDict = Dict[str, List[Conflicting]] +CheckResult = Tuple[MissingDict, ConflictingDict] +ConflictDetails = Tuple[PackageSet, CheckResult] PackageDetails = namedtuple('PackageDetails', ['version', 'requires']) diff --git a/src/pip/_internal/operations/freeze.py b/src/pip/_internal/operations/freeze.py index b082caa8ab2..f34a9d4be7e 100644 --- a/src/pip/_internal/operations/freeze.py +++ b/src/pip/_internal/operations/freeze.py @@ -1,10 +1,20 @@ import collections import logging import os -from typing import TYPE_CHECKING +from typing import ( + Container, + Dict, + Iterable, + Iterator, + List, + Optional, + Set, + Tuple, + Union, +) from pip._vendor.packaging.utils import canonicalize_name -from pip._vendor.pkg_resources import RequirementParseError +from pip._vendor.pkg_resources import Distribution, Requirement, RequirementParseError from pip._internal.exceptions import BadCommand, InstallationError from pip._internal.req.constructors import ( @@ -18,26 +28,10 @@ ) from pip._internal.utils.misc import dist_is_editable, get_installed_distributions -if TYPE_CHECKING: - from typing import ( - Container, - Dict, - Iterable, - Iterator, - List, - Optional, - Set, - Tuple, - Union, - ) - - from pip._vendor.pkg_resources import Distribution, Requirement - - RequirementInfo = Tuple[Optional[Union[str, Requirement]], bool, List[str]] - - logger = logging.getLogger(__name__) +RequirementInfo = Tuple[Optional[Union[str, Requirement]], bool, List[str]] + def freeze( requirement=None, # type: Optional[List[str]] diff --git a/src/pip/_internal/operations/install/editable_legacy.py b/src/pip/_internal/operations/install/editable_legacy.py index f2ec1f882be..6882c475cac 100644 --- a/src/pip/_internal/operations/install/editable_legacy.py +++ b/src/pip/_internal/operations/install/editable_legacy.py @@ -1,18 +1,13 @@ """Legacy editable installation process, i.e. `setup.py develop`. """ import logging -from typing import TYPE_CHECKING +from typing import List, Optional, Sequence +from pip._internal.build_env import BuildEnvironment from pip._internal.utils.logging import indent_log from pip._internal.utils.setuptools_build import make_setuptools_develop_args from pip._internal.utils.subprocess import call_subprocess -if TYPE_CHECKING: - from typing import List, Optional, Sequence - - from pip._internal.build_env import BuildEnvironment - - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/operations/install/legacy.py b/src/pip/_internal/operations/install/legacy.py index a70be0d22b9..41d0c1f9d0e 100644 --- a/src/pip/_internal/operations/install/legacy.py +++ b/src/pip/_internal/operations/install/legacy.py @@ -5,22 +5,17 @@ import os import sys from distutils.util import change_root -from typing import TYPE_CHECKING +from typing import List, Optional, Sequence +from pip._internal.build_env import BuildEnvironment from pip._internal.exceptions import InstallationError +from pip._internal.models.scheme import Scheme from pip._internal.utils.logging import indent_log from pip._internal.utils.misc import ensure_dir from pip._internal.utils.setuptools_build import make_setuptools_install_args from pip._internal.utils.subprocess import runner_with_spinner_message from pip._internal.utils.temp_dir import TempDirectory -if TYPE_CHECKING: - from typing import List, Optional, Sequence - - from pip._internal.build_env import BuildEnvironment - from pip._internal.models.scheme import Scheme - - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/operations/install/wheel.py b/src/pip/_internal/operations/install/wheel.py index db72f711192..10e5b15fd5c 100644 --- a/src/pip/_internal/operations/install/wheel.py +++ b/src/pip/_internal/operations/install/wheel.py @@ -13,19 +13,38 @@ import sys import warnings from base64 import urlsafe_b64encode +from email.message import Message from itertools import chain, filterfalse, starmap -from typing import TYPE_CHECKING, cast -from zipfile import ZipFile +from typing import ( + IO, + TYPE_CHECKING, + Any, + BinaryIO, + Callable, + Dict, + Iterable, + Iterator, + List, + NewType, + Optional, + Sequence, + Set, + Tuple, + Union, + cast, +) +from zipfile import ZipFile, ZipInfo from pip._vendor import pkg_resources from pip._vendor.distlib.scripts import ScriptMaker from pip._vendor.distlib.util import get_export_entry +from pip._vendor.pkg_resources import Distribution from pip._vendor.six import ensure_str, ensure_text, reraise from pip._internal.exceptions import InstallationError from pip._internal.locations import get_major_minor_version from pip._internal.models.direct_url import DIRECT_URL_METADATA_NAME, DirectUrl -from pip._internal.models.scheme import SCHEME_KEYS +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 captured_stdout, ensure_dir, hash_file, partition from pip._internal.utils.unpacking import ( @@ -37,32 +56,7 @@ from pip._internal.utils.wheel import parse_wheel, pkg_resources_distribution_for_wheel if TYPE_CHECKING: - from email.message import Message - from typing import ( - IO, - Any, - BinaryIO, - Callable, - Dict, - Iterable, - Iterator, - List, - NewType, - Optional, - Protocol, - Sequence, - Set, - Tuple, - Union, - ) - from zipfile import ZipInfo - - from pip._vendor.pkg_resources import Distribution - - from pip._internal.models.scheme import Scheme - - RecordPath = NewType('RecordPath', str) - InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]] + from typing import Protocol class File(Protocol): src_record_path = None # type: RecordPath @@ -76,6 +70,9 @@ def save(self): logger = logging.getLogger(__name__) +RecordPath = NewType('RecordPath', str) +InstalledCSVRow = Tuple[RecordPath, str, Union[int, str]] + def rehash(path, blocksize=1 << 20): # type: (str, int) -> Tuple[str, str] diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index e09fa7fb08f..3d074f9f629 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -8,9 +8,10 @@ import mimetypes import os import shutil -from typing import TYPE_CHECKING +from typing import Dict, Iterable, List, Optional, Tuple from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.pkg_resources import Distribution from pip._internal.distributions import make_distribution_for_install_requirement from pip._internal.distributions.installed import InstalledDistribution @@ -23,33 +24,26 @@ PreviousBuildDirError, VcsHashUnsupported, ) +from pip._internal.index.package_finder import PackageFinder +from pip._internal.models.link import Link from pip._internal.models.wheel import Wheel from pip._internal.network.download import BatchDownloader, Downloader from pip._internal.network.lazy_wheel import ( HTTPRangeRequestUnsupported, dist_from_wheel_url, ) +from pip._internal.network.session import PipSession +from pip._internal.req.req_install import InstallRequirement +from pip._internal.req.req_tracker import RequirementTracker +from pip._internal.utils.deprecation import deprecated from pip._internal.utils.filesystem import copy2_fixed -from pip._internal.utils.hashes import MissingHashes +from pip._internal.utils.hashes import Hashes, MissingHashes from pip._internal.utils.logging import indent_log -from pip._internal.utils.misc import display_path, hide_url, path_to_display, rmtree +from pip._internal.utils.misc import display_path, hide_url, rmtree from pip._internal.utils.temp_dir import TempDirectory from pip._internal.utils.unpacking import unpack_file from pip._internal.vcs import vcs -if TYPE_CHECKING: - from typing import Dict, Iterable, List, Optional, Tuple - - from pip._vendor.pkg_resources import Distribution - - from pip._internal.index.package_finder import PackageFinder - from pip._internal.models.link import Link - from pip._internal.network.session import PipSession - from pip._internal.req.req_install import InstallRequirement - from pip._internal.req.req_tracker import RequirementTracker - from pip._internal.utils.hashes import Hashes - - logger = logging.getLogger(__name__) @@ -128,8 +122,8 @@ def _copy2_ignoring_special_files(src, dest): logger.warning( "Ignoring special file error '%s' encountered copying %s to %s.", str(e), - path_to_display(src), - path_to_display(dest), + src, + dest, ) @@ -214,8 +208,23 @@ def unpack_url( unpack_vcs_link(link, location) return None - # If it's a url to a local directory + # Once out-of-tree-builds are no longer supported, could potentially + # replace the below condition with `assert not link.is_existing_dir` + # - unpack_url does not need to be called for in-tree-builds. + # + # As further cleanup, _copy_source_tree and accompanying tests can + # be removed. if link.is_existing_dir(): + deprecated( + "A future pip version will change local packages to be built " + "in-place without first copying to a temporary directory. " + "We recommend you use --use-feature=in-tree-build to test " + "your packages with this new behavior before it becomes the " + "default.\n", + replacement=None, + gone_in="21.3", + issue=7555 + ) if os.path.isdir(location): rmtree(location) _copy_source_tree(link.file_path, location) @@ -285,6 +294,7 @@ def __init__( require_hashes, # type: bool use_user_site, # type: bool lazy_wheel, # type: bool + in_tree_build, # type: bool ): # type: (...) -> None super().__init__() @@ -313,6 +323,9 @@ def __init__( # Should wheels be downloaded lazily? self.use_lazy_wheel = lazy_wheel + # Should in-tree builds be used for local paths? + self.in_tree_build = in_tree_build + # Memoized downloaded files, as mapping of url: (path, mime type) self._downloaded = {} # type: Dict[str, Tuple[str, str]] @@ -346,6 +359,11 @@ def _ensure_link_req_src_dir(self, req, parallel_builds): # directory. return assert req.source_dir is None + if req.link.is_existing_dir() and self.in_tree_build: + # build local directories in-tree + req.source_dir = req.link.file_path + return + # We always delete unpacked sdists after pip runs. req.ensure_has_source_dir( self.build_dir, @@ -524,11 +542,14 @@ def _prepare_linked_requirement(self, req, parallel_builds): self._ensure_link_req_src_dir(req, parallel_builds) hashes = self._get_linked_req_hashes(req) - if link.url not in self._downloaded: + + if link.is_existing_dir() and self.in_tree_build: + local_file = None + elif link.url not in self._downloaded: try: local_file = unpack_url( link, req.source_dir, self._download, - self.download_dir, hashes, + self.download_dir, hashes ) except NetworkConnectionError as exc: raise InstallationError( diff --git a/src/pip/_internal/pyproject.py b/src/pip/_internal/pyproject.py index fdd289b5631..9016d355f87 100644 --- a/src/pip/_internal/pyproject.py +++ b/src/pip/_internal/pyproject.py @@ -1,15 +1,12 @@ import os from collections import namedtuple -from typing import TYPE_CHECKING +from typing import Any, List, Optional from pip._vendor import toml from pip._vendor.packaging.requirements import InvalidRequirement, Requirement from pip._internal.exceptions import InstallationError -if TYPE_CHECKING: - from typing import Any, List, Optional - def _is_list_of_str(obj): # type: (Any) -> bool diff --git a/src/pip/_internal/req/__init__.py b/src/pip/_internal/req/__init__.py index ef6e162a21c..06f0a0823f1 100644 --- a/src/pip/_internal/req/__init__.py +++ b/src/pip/_internal/req/__init__.py @@ -1,6 +1,6 @@ import collections import logging -from typing import TYPE_CHECKING +from typing import Iterator, List, Optional, Sequence, Tuple from pip._internal.utils.logging import indent_log @@ -8,9 +8,6 @@ from .req_install import InstallRequirement from .req_set import RequirementSet -if TYPE_CHECKING: - from typing import Iterator, List, Optional, Sequence, Tuple - __all__ = [ "RequirementSet", "InstallRequirement", "parse_requirements", "install_given_reqs", diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py index 6a649f0d858..810fc085988 100644 --- a/src/pip/_internal/req/constructors.py +++ b/src/pip/_internal/req/constructors.py @@ -11,7 +11,7 @@ import logging import os import re -from typing import TYPE_CHECKING +from typing import Any, Dict, Optional, Set, Tuple, Union from pip._vendor.packaging.markers import Marker from pip._vendor.packaging.requirements import InvalidRequirement, Requirement @@ -23,18 +23,13 @@ from pip._internal.models.link import Link from pip._internal.models.wheel import Wheel from pip._internal.pyproject import make_pyproject_path +from pip._internal.req.req_file import ParsedRequirement from pip._internal.req.req_install import InstallRequirement from pip._internal.utils.filetypes import is_archive_file from pip._internal.utils.misc import is_installable_dir from pip._internal.utils.urls import path_to_url from pip._internal.vcs import is_url, vcs -if TYPE_CHECKING: - from typing import Any, Dict, Optional, Set, Tuple, Union - - from pip._internal.req.req_file import ParsedRequirement - - __all__ = [ "install_req_from_editable", "install_req_from_line", "parse_editable" @@ -145,7 +140,7 @@ def deduce_helpful_msg(req): msg = " The path does exist. " # Try to parse and check if it is a requirements file. try: - with open(req, 'r') as fp: + with open(req) as fp: # parse first line only next(parse_requirements(fp.read())) msg += ( @@ -261,8 +256,8 @@ def _get_url_from_path(path, name): if is_installable_dir(path): return path_to_url(path) raise InstallationError( - "Directory {name!r} is not installable. Neither 'setup.py' " - "nor 'pyproject.toml' found.".format(**locals()) + f"Directory {name!r} is not installable. Neither 'setup.py' " + "nor 'pyproject.toml' found." ) if not is_archive_file(path): return None @@ -319,7 +314,7 @@ def parse_req_from_line(name, line_source): # wheel file if link.is_wheel: wheel = Wheel(link.filename) # can raise InvalidWheelFilename - req_as_string = "{wheel.name}=={wheel.version}".format(**locals()) + req_as_string = f"{wheel.name}=={wheel.version}" else: # set the req to the egg fragment. when it's not there, this # will become an 'unnamed' requirement diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py index a2f87209b35..336cd137e4c 100644 --- a/src/pip/_internal/req/req_file.py +++ b/src/pip/_internal/req/req_file.py @@ -7,38 +7,36 @@ import re import shlex import urllib.parse -from typing import TYPE_CHECKING +from optparse import Values +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterator, + List, + NoReturn, + Optional, + Text, + Tuple, +) from pip._internal.cli import cmdoptions from pip._internal.exceptions import InstallationError, RequirementsFileParseError from pip._internal.models.search_scope import SearchScope +from pip._internal.network.session import PipSession from pip._internal.network.utils import raise_for_status from pip._internal.utils.encoding import auto_decode from pip._internal.utils.urls import get_url_scheme, url_to_path if TYPE_CHECKING: - from optparse import Values - from typing import ( - Any, - Callable, - Dict, - Iterator, - List, - NoReturn, - Optional, - Text, - Tuple, - ) - from pip._internal.index.package_finder import PackageFinder - from pip._internal.network.session import PipSession - - ReqFileLines = Iterator[Tuple[int, Text]] - LineParser = Callable[[Text], Tuple[str, Values]] +__all__ = ['parse_requirements'] +ReqFileLines = Iterator[Tuple[int, Text]] -__all__ = ['parse_requirements'] +LineParser = Callable[[Text], Tuple[str, Values]] SCHEME_RE = re.compile(r'^(http|https|file):', re.I) COMMENT_RE = re.compile(r'(^|\s+)#.*$') diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 29a5cd275ee..5eba2c2ae74 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -7,16 +7,19 @@ import sys import uuid import zipfile -from typing import TYPE_CHECKING +from typing import Any, Dict, Iterable, List, Optional, Sequence, Union from pip._vendor import pkg_resources, six +from pip._vendor.packaging.markers import Marker from pip._vendor.packaging.requirements import Requirement +from pip._vendor.packaging.specifiers import SpecifierSet from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.packaging.version import Version from pip._vendor.packaging.version import parse as parse_version from pip._vendor.pep517.wrappers import Pep517HookCaller +from pip._vendor.pkg_resources import Distribution -from pip._internal.build_env import NoOpBuildEnvironment +from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment from pip._internal.exceptions import InstallationError from pip._internal.locations import get_scheme from pip._internal.models.link import Link @@ -51,16 +54,6 @@ from pip._internal.utils.virtualenv import running_under_virtualenv from pip._internal.vcs import vcs -if TYPE_CHECKING: - from typing import Any, Dict, Iterable, List, Optional, Sequence, Union - - from pip._vendor.packaging.markers import Marker - from pip._vendor.packaging.specifiers import SpecifierSet - from pip._vendor.pkg_resources import Distribution - - from pip._internal.build_env import BuildEnvironment - - logger = logging.getLogger(__name__) @@ -658,8 +651,7 @@ def _get_archive_name(self, path, parentdir, rootdir): def _clean_zip_name(name, prefix): # type: (str, str) -> str assert name.startswith(prefix + os.path.sep), ( - "name {name!r} doesn't start with prefix {prefix!r}" - .format(**locals()) + f"name {name!r} doesn't start with prefix {prefix!r}" ) name = name[len(prefix) + 1:] name = name.replace(os.path.sep, '/') diff --git a/src/pip/_internal/req/req_set.py b/src/pip/_internal/req/req_set.py index c9552183286..7ff137b73ee 100644 --- a/src/pip/_internal/req/req_set.py +++ b/src/pip/_internal/req/req_set.py @@ -1,19 +1,14 @@ import logging from collections import OrderedDict -from typing import TYPE_CHECKING +from typing import Dict, Iterable, List, Optional, Tuple from pip._vendor.packaging.utils import canonicalize_name from pip._internal.exceptions import InstallationError from pip._internal.models.wheel import Wheel +from pip._internal.req.req_install import InstallRequirement from pip._internal.utils import compatibility_tags -if TYPE_CHECKING: - from typing import Dict, Iterable, List, Optional, Tuple - - from pip._internal.req.req_install import InstallRequirement - - logger = logging.getLogger(__name__) @@ -194,7 +189,7 @@ def get_requirement(self, name): if project_name in self.requirements: return self.requirements[project_name] - raise KeyError("No project with the name {name!r}".format(**locals())) + raise KeyError(f"No project with the name {name!r}") @property def all_requirements(self): diff --git a/src/pip/_internal/req/req_tracker.py b/src/pip/_internal/req/req_tracker.py index ab753ce6ddd..542e0d94e37 100644 --- a/src/pip/_internal/req/req_tracker.py +++ b/src/pip/_internal/req/req_tracker.py @@ -2,17 +2,13 @@ import hashlib import logging import os -from typing import TYPE_CHECKING +from types import TracebackType +from typing import Dict, Iterator, Optional, Set, Type, Union +from pip._internal.models.link import Link +from pip._internal.req.req_install import InstallRequirement from pip._internal.utils.temp_dir import TempDirectory -if TYPE_CHECKING: - from types import TracebackType - from typing import Dict, Iterator, Optional, Set, Type, Union - - from pip._internal.models.link import Link - from pip._internal.req.req_install import InstallRequirement - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/req/req_uninstall.py b/src/pip/_internal/req/req_uninstall.py index d7e28dc004e..64890e33912 100644 --- a/src/pip/_internal/req/req_uninstall.py +++ b/src/pip/_internal/req/req_uninstall.py @@ -5,9 +5,10 @@ import sys import sysconfig from importlib.util import cache_from_source -from typing import TYPE_CHECKING +from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Set, Tuple from pip._vendor import pkg_resources +from pip._vendor.pkg_resources import Distribution from pip._internal.exceptions import UninstallationError from pip._internal.locations import bin_py, bin_user @@ -25,21 +26,6 @@ ) from pip._internal.utils.temp_dir import AdjacentTempDirectory, TempDirectory -if TYPE_CHECKING: - from typing import ( - Any, - Callable, - Dict, - Iterable, - Iterator, - List, - Optional, - Set, - Tuple, - ) - - from pip._vendor.pkg_resources import Distribution - logger = logging.getLogger(__name__) @@ -543,7 +529,7 @@ def from_dist(cls, dist): elif develop_egg_link: # develop egg - with open(develop_egg_link, 'r') as fh: + with open(develop_egg_link) as fh: link_pointer = os.path.normcase(fh.readline().strip()) assert (link_pointer == dist.location), ( 'Egg-link {} does not match installed location of {} ' diff --git a/src/pip/_internal/resolution/base.py b/src/pip/_internal/resolution/base.py index caf4e0d8cf6..1be0cb279a0 100644 --- a/src/pip/_internal/resolution/base.py +++ b/src/pip/_internal/resolution/base.py @@ -1,14 +1,9 @@ -from typing import TYPE_CHECKING +from typing import Callable, List -if TYPE_CHECKING: - from typing import Callable, List +from pip._internal.req.req_install import InstallRequirement +from pip._internal.req.req_set import RequirementSet - from pip._internal.req.req_install import InstallRequirement - from pip._internal.req.req_set import RequirementSet - - InstallRequirementProvider = Callable[ - [str, InstallRequirement], InstallRequirement - ] +InstallRequirementProvider = Callable[[str, InstallRequirement], InstallRequirement] class BaseResolver: diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py index 8f5378a0787..17de7f09a37 100644 --- a/src/pip/_internal/resolution/legacy/resolver.py +++ b/src/pip/_internal/resolution/legacy/resolver.py @@ -12,16 +12,17 @@ # The following comment should be removed at some point in the future. # mypy: strict-optional=False -# mypy: disallow-untyped-defs=False import logging import sys from collections import defaultdict from itertools import chain -from typing import TYPE_CHECKING +from typing import DefaultDict, Iterable, List, Optional, Set, Tuple from pip._vendor.packaging import specifiers +from pip._vendor.pkg_resources import Distribution +from pip._internal.cache import WheelCache from pip._internal.exceptions import ( BestVersionAlreadyInstalled, DistributionNotFound, @@ -29,30 +30,24 @@ HashErrors, UnsupportedPythonVersion, ) -from pip._internal.req.req_install import check_invalid_constraint_type +from pip._internal.index.package_finder import PackageFinder +from pip._internal.models.link import Link +from pip._internal.operations.prepare import RequirementPreparer +from pip._internal.req.req_install import ( + InstallRequirement, + check_invalid_constraint_type, +) from pip._internal.req.req_set import RequirementSet -from pip._internal.resolution.base import BaseResolver +from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider from pip._internal.utils.compatibility_tags import get_supported from pip._internal.utils.logging import indent_log from pip._internal.utils.misc import dist_in_usersite, normalize_version_info from pip._internal.utils.packaging import check_requires_python, get_requires_python -if TYPE_CHECKING: - from typing import DefaultDict, List, Optional, Set, Tuple - - from pip._vendor.pkg_resources import Distribution - - from pip._internal.cache import WheelCache - from pip._internal.index.package_finder import PackageFinder - from pip._internal.models.link import Link - from pip._internal.operations.prepare import RequirementPreparer - from pip._internal.req.req_install import InstallRequirement - from pip._internal.resolution.base import InstallRequirementProvider - - DiscoveredDependencies = DefaultDict[str, List[InstallRequirement]] - logger = logging.getLogger(__name__) +DiscoveredDependencies = DefaultDict[str, List[InstallRequirement]] + def _check_dist_requires_python( dist, # type: Distribution @@ -75,31 +70,32 @@ def _check_dist_requires_python( requires_python = get_requires_python(dist) try: is_compatible = check_requires_python( - requires_python, version_info=version_info, + requires_python, version_info=version_info ) except specifiers.InvalidSpecifier as exc: logger.warning( - "Package %r has an invalid Requires-Python: %s", - dist.project_name, exc, + "Package %r has an invalid Requires-Python: %s", dist.project_name, exc ) return if is_compatible: return - version = '.'.join(map(str, version_info)) + version = ".".join(map(str, version_info)) if ignore_requires_python: logger.debug( - 'Ignoring failed Requires-Python check for package %r: ' - '%s not in %r', - dist.project_name, version, requires_python, + "Ignoring failed Requires-Python check for package %r: " "%s not in %r", + dist.project_name, + version, + requires_python, ) return raise UnsupportedPythonVersion( - 'Package {!r} requires a different Python: {} not in {!r}'.format( - dist.project_name, version, requires_python, - )) + "Package {!r} requires a different Python: {} not in {!r}".format( + dist.project_name, version, requires_python + ) + ) class Resolver(BaseResolver): @@ -146,8 +142,9 @@ def __init__( self.use_user_site = use_user_site self._make_install_req = make_install_req - self._discovered_dependencies = \ - defaultdict(list) # type: DiscoveredDependencies + self._discovered_dependencies = defaultdict( + list + ) # type: DiscoveredDependencies def resolve(self, root_reqs, check_supported_wheels): # type: (List[InstallRequirement], bool) -> RequirementSet @@ -161,9 +158,7 @@ def resolve(self, root_reqs, check_supported_wheels): possible to move the preparation to become a step separated from dependency resolution. """ - requirement_set = RequirementSet( - check_supported_wheels=check_supported_wheels - ) + requirement_set = RequirementSet(check_supported_wheels=check_supported_wheels) for req in root_reqs: if req.constraint: check_invalid_constraint_type(req) @@ -240,8 +235,8 @@ def _check_skip_installed(self, req_to_install): if not self._is_upgrade_allowed(req_to_install): if self.upgrade_strategy == "only-if-needed": - return 'already satisfied, skipping upgrade' - return 'already satisfied' + return "already satisfied, skipping upgrade" + return "already satisfied" # Check for the possibility of an upgrade. For link-based # requirements we have to pull the tree down and inspect to assess @@ -251,7 +246,7 @@ def _check_skip_installed(self, req_to_install): self.finder.find_requirement(req_to_install, upgrade=True) except BestVersionAlreadyInstalled: # Then the best version is installed. - return 'already up-to-date' + return "already up-to-date" except DistributionNotFound: # No distribution found, so we squash the error. It will # be raised later when we re-try later to do the install. @@ -271,14 +266,14 @@ def _find_requirement_link(self, req): # Log a warning per PEP 592 if necessary before returning. link = best_candidate.link if link.is_yanked: - reason = link.yanked_reason or '' + reason = link.yanked_reason or "" msg = ( # Mark this as a unicode string to prevent # "UnicodeEncodeError: 'ascii' codec can't encode character" # in Python 2 when the reason contains non-ascii characters. - 'The candidate selected for download or install is a ' - 'yanked version: {candidate}\n' - 'Reason for being yanked: {reason}' + "The candidate selected for download or install is a " + "yanked version: {candidate}\n" + "Reason for being yanked: {reason}" ).format(candidate=best_candidate, reason=reason) logger.warning(msg) @@ -309,7 +304,7 @@ def _populate_link(self, req): supported_tags=get_supported(), ) if cache_entry is not None: - logger.debug('Using cached wheel link: %s', cache_entry.link) + logger.debug("Using cached wheel link: %s", cache_entry.link) if req.link is req.original_link and cache_entry.persistent: req.original_link_is_in_wheel_cache = True req.link = cache_entry.link @@ -328,9 +323,7 @@ def _get_dist_for(self, req): skip_reason = self._check_skip_installed(req) if req.satisfied_by: - return self.preparer.prepare_installed_requirement( - req, skip_reason - ) + return self.preparer.prepare_installed_requirement(req, skip_reason) # We eagerly populate the link, since that's our "legacy" behavior. self._populate_link(req) @@ -349,17 +342,17 @@ def _get_dist_for(self, req): if req.satisfied_by: should_modify = ( - self.upgrade_strategy != "to-satisfy-only" or - self.force_reinstall or - self.ignore_installed or - req.link.scheme == 'file' + self.upgrade_strategy != "to-satisfy-only" + or self.force_reinstall + or self.ignore_installed + or req.link.scheme == "file" ) if should_modify: self._set_req_to_reinstall(req) else: logger.info( - 'Requirement already satisfied (use --upgrade to upgrade):' - ' %s', req, + "Requirement already satisfied (use --upgrade to upgrade):" " %s", + req, ) return dist @@ -386,13 +379,15 @@ def _resolve_one( # This will raise UnsupportedPythonVersion if the given Python # version isn't compatible with the distribution's Requires-Python. _check_dist_requires_python( - dist, version_info=self._py_version_info, + dist, + version_info=self._py_version_info, ignore_requires_python=self.ignore_requires_python, ) more_reqs = [] # type: List[InstallRequirement] def add_req(subreq, extras_requested): + # type: (Distribution, Iterable[str]) -> None sub_install_req = self._make_install_req( str(subreq), req_to_install, @@ -404,9 +399,7 @@ def add_req(subreq, extras_requested): extras_requested=extras_requested, ) if parent_req_name and add_to_parent: - self._discovered_dependencies[parent_req_name].append( - add_to_parent - ) + self._discovered_dependencies[parent_req_name].append(add_to_parent) more_reqs.extend(to_scan_again) with indent_log(): @@ -417,24 +410,19 @@ def add_req(subreq, extras_requested): # 'unnamed' requirements can only come from being directly # provided by the user. assert req_to_install.user_supplied - requirement_set.add_requirement( - req_to_install, parent_req_name=None, - ) + requirement_set.add_requirement(req_to_install, parent_req_name=None) if not self.ignore_dependencies: if req_to_install.extras: logger.debug( "Installing extra requirements: %r", - ','.join(req_to_install.extras), + ",".join(req_to_install.extras), ) missing_requested = sorted( set(req_to_install.extras) - set(dist.extras) ) for missing in missing_requested: - logger.warning( - "%s does not provide the extra '%s'", - dist, missing - ) + logger.warning("%s does not provide the extra '%s'", dist, missing) available_requested = sorted( set(dist.extras) & set(req_to_install.extras) @@ -459,6 +447,7 @@ def get_installation_order(self, req_set): ordered_reqs = set() # type: Set[InstallRequirement] def schedule(req): + # type: (InstallRequirement) -> None if req.satisfied_by or req in ordered_reqs: return if req.constraint: diff --git a/src/pip/_internal/resolution/resolvelib/base.py b/src/pip/_internal/resolution/resolvelib/base.py index 29d798c3065..81fee9b9e3e 100644 --- a/src/pip/_internal/resolution/resolvelib/base.py +++ b/src/pip/_internal/resolution/resolvelib/base.py @@ -1,22 +1,14 @@ -from typing import TYPE_CHECKING +from typing import FrozenSet, Iterable, Optional, Tuple from pip._vendor.packaging.specifiers import SpecifierSet from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.packaging.version import _BaseVersion +from pip._internal.models.link import Link from pip._internal.req.req_install import InstallRequirement from pip._internal.utils.hashes import Hashes -if TYPE_CHECKING: - from typing import FrozenSet, Iterable, Optional, Tuple - - from pip._vendor.packaging.version import _BaseVersion - - from pip._internal.models.link import Link - - CandidateLookup = Tuple[ - Optional["Candidate"], - Optional[InstallRequirement], - ] +CandidateLookup = Tuple[Optional["Candidate"], Optional[InstallRequirement]] def format_name(project, extras): diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 2240cac0893..035e118d022 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -1,12 +1,14 @@ import logging import sys -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, FrozenSet, Iterable, Optional, Tuple, Union from pip._vendor.packaging.specifiers import InvalidSpecifier, SpecifierSet from pip._vendor.packaging.utils import canonicalize_name -from pip._vendor.packaging.version import Version +from pip._vendor.packaging.version import Version, _BaseVersion +from pip._vendor.pkg_resources import Distribution from pip._internal.exceptions import HashError, MetadataInconsistent +from pip._internal.models.link import Link from pip._internal.models.wheel import Wheel from pip._internal.req.constructors import ( install_req_from_editable, @@ -16,28 +18,19 @@ from pip._internal.utils.misc import dist_is_editable, normalize_version_info from pip._internal.utils.packaging import get_requires_python -from .base import Candidate, format_name +from .base import Candidate, Requirement, format_name if TYPE_CHECKING: - from typing import Any, FrozenSet, Iterable, Optional, Tuple, Union - - from pip._vendor.packaging.version import _BaseVersion - from pip._vendor.pkg_resources import Distribution - - from pip._internal.models.link import Link - - from .base import Requirement from .factory import Factory - BaseCandidate = Union[ - "AlreadyInstalledCandidate", - "EditableCandidate", - "LinkCandidate", - ] - - logger = logging.getLogger(__name__) +BaseCandidate = Union[ + "AlreadyInstalledCandidate", + "EditableCandidate", + "LinkCandidate", +] + def make_install_req_from_link(link, template): # type: (Link, InstallRequirement) -> InstallRequirement @@ -56,7 +49,7 @@ def make_install_req_from_link(link, template): options=dict( install_options=template.install_options, global_options=template.global_options, - hashes=template.hash_options + hashes=template.hash_options, ), ) ireq.original_link = template.original_link @@ -77,7 +70,7 @@ def make_install_req_from_editable(link, template): options=dict( install_options=template.install_options, global_options=template.global_options, - hashes=template.hash_options + hashes=template.hash_options, ), ) @@ -101,7 +94,7 @@ def make_install_req_from_dist(dist, template): options=dict( install_options=template.install_options, global_options=template.global_options, - hashes=template.hash_options + hashes=template.hash_options, ), ) ireq.satisfied_by = dist @@ -123,15 +116,16 @@ class exposes appropriate information to the resolver. ``link`` would point to the wheel cache, while this points to the found remote link (e.g. from pypi.org). """ + is_installed = False def __init__( self, - link, # type: Link - source_link, # type: Link - ireq, # type: InstallRequirement - factory, # type: Factory - name=None, # type: Optional[str] + link, # type: Link + source_link, # type: Link + ireq, # type: InstallRequirement + factory, # type: Factory + name=None, # type: Optional[str] version=None, # type: Optional[_BaseVersion] ): # type: (...) -> None @@ -194,7 +188,7 @@ def format_for_error(self): return "{} {} (from {})".format( self.name, self.version, - self._link.file_path if self._link.is_file else self._link + self._link.file_path if self._link.is_file else self._link, ) def _prepare_distribution(self): @@ -263,10 +257,10 @@ class LinkCandidate(_InstallRequirementBackedCandidate): def __init__( self, - link, # type: Link - template, # type: InstallRequirement - factory, # type: Factory - name=None, # type: Optional[str] + link, # type: Link + template, # type: InstallRequirement + factory, # type: Factory + name=None, # type: Optional[str] version=None, # type: Optional[_BaseVersion] ): # type: (...) -> None @@ -280,21 +274,19 @@ def __init__( if ireq.link.is_wheel and not ireq.link.is_file: wheel = Wheel(ireq.link.filename) wheel_name = canonicalize_name(wheel.name) - assert name == wheel_name, ( - f"{name!r} != {wheel_name!r} for wheel" - ) + assert name == wheel_name, f"{name!r} != {wheel_name!r} for wheel" # Version may not be present for PEP 508 direct URLs if version is not None: wheel_version = Version(wheel.version) - assert version == wheel_version, ( - "{!r} != {!r} for wheel {}".format( - version, wheel_version, name - ) + assert version == wheel_version, "{!r} != {!r} for wheel {}".format( + version, wheel_version, name ) - if (cache_entry is not None and - cache_entry.persistent and - template.link is template.original_link): + if ( + cache_entry is not None + and cache_entry.persistent + and template.link is template.original_link + ): ireq.original_link_is_in_wheel_cache = True super().__init__( @@ -309,7 +301,7 @@ def __init__( def _prepare_distribution(self): # type: () -> Distribution return self._factory.preparer.prepare_linked_requirement( - self._ireq, parallel_builds=True, + self._ireq, parallel_builds=True ) @@ -318,10 +310,10 @@ class EditableCandidate(_InstallRequirementBackedCandidate): def __init__( self, - link, # type: Link - template, # type: InstallRequirement - factory, # type: Factory - name=None, # type: Optional[str] + link, # type: Link + template, # type: InstallRequirement + factory, # type: Factory + name=None, # type: Optional[str] version=None, # type: Optional[_BaseVersion] ): # type: (...) -> None @@ -442,6 +434,7 @@ class ExtrasCandidate(Candidate): version 2.0. Having those candidates depend on foo=1.0 and foo=2.0 respectively forces the resolver to recognise that this is a conflict. """ + def __init__( self, base, # type: BaseCandidate @@ -493,8 +486,7 @@ def version(self): def format_for_error(self): # type: () -> str return "{} [{}]".format( - self.base.format_for_error(), - ", ".join(sorted(self.extras)) + self.base.format_for_error(), ", ".join(sorted(self.extras)) ) @property @@ -531,12 +523,12 @@ def iter_dependencies(self, with_requires): "%s %s does not provide the extra '%s'", self.base.name, self.version, - extra + extra, ) for r in self.base.dist.requires(valid_extras): requirement = factory.make_requirement_from_spec( - str(r), self.base._ireq, valid_extras, + str(r), self.base._ireq, valid_extras ) if requirement: yield requirement diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 5bfe0cc066f..506a560d6fb 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -1,9 +1,25 @@ import functools import logging -from typing import TYPE_CHECKING +from typing import ( + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Sequence, + Set, + Tuple, + TypeVar, +) +from pip._vendor.packaging.specifiers import SpecifierSet from pip._vendor.packaging.utils import canonicalize_name +from pip._vendor.packaging.version import _BaseVersion +from pip._vendor.pkg_resources import Distribution +from pip._vendor.resolvelib import ResolutionImpossible +from pip._internal.cache import CacheEntry, WheelCache from pip._internal.exceptions import ( DistributionNotFound, InstallationError, @@ -12,8 +28,12 @@ UnsupportedPythonVersion, UnsupportedWheel, ) +from pip._internal.index.package_finder import PackageFinder +from pip._internal.models.link import Link from pip._internal.models.wheel import Wheel +from pip._internal.operations.prepare import RequirementPreparer from pip._internal.req.req_install import InstallRequirement +from pip._internal.resolution.base import InstallRequirementProvider from pip._internal.utils.compatibility_tags import get_supported from pip._internal.utils.hashes import Hashes from pip._internal.utils.misc import ( @@ -23,15 +43,16 @@ ) from pip._internal.utils.virtualenv import running_under_virtualenv -from .base import Constraint +from .base import Candidate, Constraint, Requirement from .candidates import ( AlreadyInstalledCandidate, + BaseCandidate, EditableCandidate, ExtrasCandidate, LinkCandidate, RequiresPythonCandidate, ) -from .found_candidates import FoundCandidates +from .found_candidates import FoundCandidates, IndexCandidateInfo from .requirements import ( ExplicitRequirement, RequiresPythonRequirement, @@ -39,40 +60,11 @@ UnsatisfiableRequirement, ) -if TYPE_CHECKING: - from typing import ( - Dict, - FrozenSet, - Iterable, - Iterator, - List, - Optional, - Sequence, - Set, - Tuple, - TypeVar, - ) - - from pip._vendor.packaging.specifiers import SpecifierSet - from pip._vendor.packaging.version import _BaseVersion - from pip._vendor.pkg_resources import Distribution - from pip._vendor.resolvelib import ResolutionImpossible - - from pip._internal.cache import CacheEntry, WheelCache - from pip._internal.index.package_finder import PackageFinder - from pip._internal.models.link import Link - from pip._internal.operations.prepare import RequirementPreparer - from pip._internal.resolution.base import InstallRequirementProvider - - from .base import Candidate, Requirement - from .candidates import BaseCandidate - from .found_candidates import IndexCandidateInfo - - C = TypeVar("C") - Cache = Dict[Link, C] - logger = logging.getLogger(__name__) +C = TypeVar("C") +Cache = Dict[Link, C] + class Factory: def __init__( @@ -100,8 +92,9 @@ def __init__( self._build_failures = {} # type: Cache[InstallationError] self._link_candidate_cache = {} # type: Cache[LinkCandidate] self._editable_candidate_cache = {} # type: Cache[EditableCandidate] - self._installed_candidate_cache = { - } # type: Dict[str, AlreadyInstalledCandidate] + self._installed_candidate_cache = ( + {} + ) # type: Dict[str, AlreadyInstalledCandidate] if not ignore_installed: self._installed_dists = { @@ -153,8 +146,11 @@ def _make_candidate_from_link( if link not in self._editable_candidate_cache: try: self._editable_candidate_cache[link] = EditableCandidate( - link, template, factory=self, - name=name, version=version, + link, + template, + factory=self, + name=name, + version=version, ) except (InstallationSubprocessError, MetadataInconsistent) as e: logger.warning("Discarding %s. %s", link, e) @@ -165,8 +161,11 @@ def _make_candidate_from_link( if link not in self._link_candidate_cache: try: self._link_candidate_cache[link] = LinkCandidate( - link, template, factory=self, - name=name, version=version, + link, + template, + factory=self, + name=name, + version=version, ) except (InstallationSubprocessError, MetadataInconsistent) as e: logger.warning("Discarding %s. %s", link, e) @@ -276,7 +275,8 @@ def find_candidates( ) return ( - c for c in explicit_candidates + c + for c in explicit_candidates if constraint.is_satisfied_by(c) and all(req.is_satisfied_by(c) for req in requirements) ) @@ -286,7 +286,8 @@ def make_requirement_from_install_req(self, ireq, requested_extras): if not ireq.match_markers(requested_extras): logger.info( "Ignoring %s: markers '%s' don't match your environment", - ireq.name, ireq.markers, + ireq.name, + ireq.markers, ) return None if not ireq.link: @@ -380,7 +381,8 @@ def get_dist_to_uninstall(self, candidate): raise InstallationError( "Will not install to the user site because it will " "lack sys.path precedence to {} in {}".format( - dist.project_name, dist.location, + dist.project_name, + dist.location, ) ) return None @@ -426,14 +428,12 @@ def get_installation_error(self, e): if parent is None: req_disp = str(req) else: - req_disp = f'{req} (from {parent.name})' + req_disp = f"{req} (from {parent.name})" logger.critical( "Could not find a version that satisfies the requirement %s", req_disp, ) - return DistributionNotFound( - f'No matching distribution found for {req}' - ) + return DistributionNotFound(f"No matching distribution found for {req}") # OK, we now have a list of requirements that can't all be # satisfied at once. @@ -469,26 +469,28 @@ def describe_trigger(parent): else: info = "the requested packages" - msg = "Cannot install {} because these package versions " \ + msg = ( + "Cannot install {} because these package versions " "have conflicting dependencies.".format(info) + ) logger.critical(msg) msg = "\nThe conflict is caused by:" for req, parent in e.causes: msg = msg + "\n " if parent: - msg = msg + "{} {} depends on ".format( - parent.name, - parent.version - ) + msg = msg + "{} {} depends on ".format(parent.name, parent.version) else: msg = msg + "The user requested " msg = msg + req.format_for_error() - msg = msg + "\n\n" + \ - "To fix this you could try to:\n" + \ - "1. loosen the range of package versions you've specified\n" + \ - "2. remove package versions to allow pip attempt to solve " + \ - "the dependency conflict\n" + msg = ( + msg + + "\n\n" + + "To fix this you could try to:\n" + + "1. loosen the range of package versions you've specified\n" + + "2. remove package versions to allow pip attempt to solve " + + "the dependency conflict\n" + ) logger.info(msg) diff --git a/src/pip/_internal/resolution/resolvelib/found_candidates.py b/src/pip/_internal/resolution/resolvelib/found_candidates.py index 2a8d58ce2ef..e8b72e66000 100644 --- a/src/pip/_internal/resolution/resolvelib/found_candidates.py +++ b/src/pip/_internal/resolution/resolvelib/found_candidates.py @@ -9,18 +9,14 @@ """ import functools -from typing import TYPE_CHECKING +from typing import Callable, Iterator, Optional, Set, Tuple +from pip._vendor.packaging.version import _BaseVersion from pip._vendor.six.moves import collections_abc # type: ignore -if TYPE_CHECKING: - from typing import Callable, Iterator, Optional, Set, Tuple +from .base import Candidate - from pip._vendor.packaging.version import _BaseVersion - - from .base import Candidate - - IndexCandidateInfo = Tuple[_BaseVersion, Callable[[], Optional[Candidate]]] +IndexCandidateInfo = Tuple[_BaseVersion, Callable[[], Optional[Candidate]]] def _iter_built(infos): @@ -101,6 +97,7 @@ class FoundCandidates(collections_abc.Sequence): page when remote packages are actually needed. This improve performances when suitable candidates are already installed on disk. """ + def __init__( self, get_infos, # type: Callable[[], Iterator[IndexCandidateInfo]] diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py index f8632410eb8..2085a0714a3 100644 --- a/src/pip/_internal/resolution/resolvelib/provider.py +++ b/src/pip/_internal/resolution/resolvelib/provider.py @@ -1,14 +1,9 @@ -from typing import TYPE_CHECKING +from typing import Any, Dict, Iterable, Optional, Sequence, Tuple, Union from pip._vendor.resolvelib.providers import AbstractProvider -from .base import Constraint - -if TYPE_CHECKING: - from typing import Any, Dict, Iterable, Optional, Sequence, Tuple, Union - - from .base import Candidate, Requirement - from .factory import Factory +from .base import Candidate, Constraint, Requirement +from .factory import Factory # Notes on the relationship between the provider, the factory, and the # candidate and requirement classes. @@ -63,7 +58,7 @@ def get_preference( self, resolution, # type: Optional[Candidate] candidates, # type: Sequence[Candidate] - information # type: Sequence[Tuple[Requirement, Candidate]] + information, # type: Sequence[Tuple[Requirement, Candidate]] ): # type: (...) -> Any """Produce a sort key for given requirement based on preference. @@ -104,9 +99,7 @@ def _get_restrictive_rating(requirements): return 0 spec_sets = (ireq.specifier for ireq in ireqs if ireq) operators = [ - specifier.operator - for spec_set in spec_sets - for specifier in spec_set + specifier.operator for spec_set in spec_sets for specifier in spec_set ] if any(op in ("==", "===") for op in operators): return 1 @@ -127,7 +120,7 @@ def _get_restrictive_rating(requirements): # delaying Setuptools helps reduce branches the resolver has to check. # This serves as a temporary fix for issues like "apache-airlfow[all]" # while we work on "proper" branch pruning techniques. - delay_this = (key == "setuptools") + delay_this = key == "setuptools" return (delay_this, restrictive, order, key) @@ -152,7 +145,7 @@ def _eligible_for_upgrade(name): if self._upgrade_strategy == "eager": return True elif self._upgrade_strategy == "only-if-needed": - return (name in self._user_requested) + return name in self._user_requested return False return self._factory.find_candidates( @@ -168,8 +161,4 @@ def is_satisfied_by(self, requirement, candidate): def get_dependencies(self, candidate): # type: (Candidate) -> Sequence[Requirement] with_requires = not self._ignore_dependencies - return [ - r - for r in candidate.iter_dependencies(with_requires) - if r is not None - ] + return [r for r in candidate.iter_dependencies(with_requires) if r is not None] diff --git a/src/pip/_internal/resolution/resolvelib/reporter.py b/src/pip/_internal/resolution/resolvelib/reporter.py index 6679d73f219..074583de0d9 100644 --- a/src/pip/_internal/resolution/resolvelib/reporter.py +++ b/src/pip/_internal/resolution/resolvelib/reporter.py @@ -1,20 +1,15 @@ from collections import defaultdict from logging import getLogger -from typing import TYPE_CHECKING +from typing import Any, DefaultDict from pip._vendor.resolvelib.reporters import BaseReporter -if TYPE_CHECKING: - from typing import Any, DefaultDict - - from .base import Candidate, Requirement - +from .base import Candidate, Requirement logger = getLogger(__name__) class PipReporter(BaseReporter): - def __init__(self): # type: () -> None self.backtracks_by_package = defaultdict(int) # type: DefaultDict[str, int] @@ -36,7 +31,7 @@ def __init__(self): "runtime. If you want to abort this run, you can press " "Ctrl + C to do so. To improve how pip performs, tell us what " "happened here: https://pip.pypa.io/surveys/backtracking" - ) + ), } def backtracking(self, candidate): diff --git a/src/pip/_internal/resolution/resolvelib/requirements.py b/src/pip/_internal/resolution/resolvelib/requirements.py index a2fad4bdb2e..70ad86af947 100644 --- a/src/pip/_internal/resolution/resolvelib/requirements.py +++ b/src/pip/_internal/resolution/resolvelib/requirements.py @@ -1,15 +1,9 @@ -from typing import TYPE_CHECKING - +from pip._vendor.packaging.specifiers import SpecifierSet from pip._vendor.packaging.utils import canonicalize_name -from .base import Requirement, format_name - -if TYPE_CHECKING: - from pip._vendor.packaging.specifiers import SpecifierSet - - from pip._internal.req.req_install import InstallRequirement +from pip._internal.req.req_install import InstallRequirement - from .base import Candidate, CandidateLookup +from .base import Candidate, CandidateLookup, Requirement, format_name class ExplicitRequirement(Requirement): @@ -102,9 +96,11 @@ def get_candidate_lookup(self): def is_satisfied_by(self, candidate): # type: (Candidate) -> bool - assert candidate.name == self.name, \ - "Internal issue: Candidate is not for this requirement " \ - " {} vs {}".format(candidate.name, self.name) + assert ( + candidate.name == self.name + ), "Internal issue: Candidate is not for this requirement " " {} vs {}".format( + candidate.name, self.name + ) # We can safely always allow prereleases here since PackageFinder # already implements the prerelease logic, and would have filtered out # prerelease candidates if the user does not expect them. @@ -113,8 +109,8 @@ def is_satisfied_by(self, candidate): class RequiresPythonRequirement(Requirement): - """A requirement representing Requires-Python metadata. - """ + """A requirement representing Requires-Python metadata.""" + def __init__(self, specifier, match): # type: (SpecifierSet, Candidate) -> None self.specifier = specifier @@ -161,8 +157,8 @@ def is_satisfied_by(self, candidate): class UnsatisfiableRequirement(Requirement): - """A requirement that cannot be satisfied. - """ + """A requirement that cannot be satisfied.""" + def __init__(self, name): # type: (str) -> None self._name = name diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py index 935723737e9..8828155a228 100644 --- a/src/pip/_internal/resolution/resolvelib/resolver.py +++ b/src/pip/_internal/resolution/resolvelib/resolver.py @@ -1,17 +1,24 @@ import functools import logging import os -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple from pip._vendor import six from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.resolvelib import ResolutionImpossible from pip._vendor.resolvelib import Resolver as RLResolver +from pip._vendor.resolvelib.resolvers import Result +from pip._internal.cache import WheelCache from pip._internal.exceptions import InstallationError -from pip._internal.req.req_install import check_invalid_constraint_type +from pip._internal.index.package_finder import PackageFinder +from pip._internal.operations.prepare import RequirementPreparer +from pip._internal.req.req_install import ( + InstallRequirement, + check_invalid_constraint_type, +) from pip._internal.req.req_set import RequirementSet -from pip._internal.resolution.base import BaseResolver +from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider from pip._internal.resolution.resolvelib.provider import PipProvider from pip._internal.resolution.resolvelib.reporter import ( PipDebuggingReporter, @@ -25,18 +32,8 @@ from .factory import Factory if TYPE_CHECKING: - from typing import Dict, List, Optional, Set, Tuple - - from pip._vendor.resolvelib.resolvers import Result from pip._vendor.resolvelib.structs import Graph - from pip._internal.cache import WheelCache - from pip._internal.index.package_finder import PackageFinder - from pip._internal.operations.prepare import RequirementPreparer - from pip._internal.req.req_install import InstallRequirement - from pip._internal.resolution.base import InstallRequirementProvider - - logger = logging.getLogger(__name__) @@ -100,7 +97,7 @@ def resolve(self, root_reqs, check_supported_wheels): if canonical_name not in user_requested: user_requested[canonical_name] = i r = self.factory.make_requirement_from_install_req( - req, requested_extras=(), + req, requested_extras=() ) if r is not None: requirements.append(r) @@ -121,7 +118,7 @@ def resolve(self, root_reqs, check_supported_wheels): try: try_to_avoid_resolution_too_deep = 2000000 self._result = resolver.resolve( - requirements, max_rounds=try_to_avoid_resolution_too_deep, + requirements, max_rounds=try_to_avoid_resolution_too_deep ) except ResolutionImpossible as e: @@ -191,14 +188,14 @@ def resolve(self, root_reqs, check_supported_wheels): # The reason can contain non-ASCII characters, Unicode # is required for Python 2. msg = ( - 'The candidate selected for download or install is a ' - 'yanked version: {name!r} candidate (version {version} ' - 'at {link})\nReason for being yanked: {reason}' + "The candidate selected for download or install is a " + "yanked version: {name!r} candidate (version {version} " + "at {link})\nReason for being yanked: {reason}" ).format( name=candidate.name, version=candidate.version, link=link, - reason=link.yanked_reason or '', + reason=link.yanked_reason or "", ) logger.warning(msg) @@ -284,7 +281,7 @@ def visit(node): def _req_set_item_sorter( - item, # type: Tuple[str, InstallRequirement] + item, # type: Tuple[str, InstallRequirement] weights, # type: Dict[Optional[str], int] ): # type: (...) -> Tuple[int, str] diff --git a/src/pip/_internal/self_outdated_check.py b/src/pip/_internal/self_outdated_check.py index f705dbc8308..6b24965b802 100644 --- a/src/pip/_internal/self_outdated_check.py +++ b/src/pip/_internal/self_outdated_check.py @@ -2,9 +2,10 @@ import hashlib import json import logging +import optparse import os.path import sys -from typing import TYPE_CHECKING +from typing import Any, Dict from pip._vendor.packaging.version import parse as parse_version @@ -12,16 +13,10 @@ from pip._internal.index.package_finder import PackageFinder from pip._internal.metadata import get_default_environment from pip._internal.models.selection_prefs import SelectionPreferences +from pip._internal.network.session import PipSession from pip._internal.utils.filesystem import adjacent_tmp_file, check_path_owner, replace from pip._internal.utils.misc import ensure_dir -if TYPE_CHECKING: - import optparse - from typing import Any, Dict - - from pip._internal.network.session import PipSession - - SELFCHECK_DATE_FMT = "%Y-%m-%dT%H:%M:%SZ" diff --git a/src/pip/_internal/utils/appdirs.py b/src/pip/_internal/utils/appdirs.py index 55e83e0d689..db974dad635 100644 --- a/src/pip/_internal/utils/appdirs.py +++ b/src/pip/_internal/utils/appdirs.py @@ -7,13 +7,10 @@ """ import os -from typing import TYPE_CHECKING +from typing import List from pip._vendor import appdirs as _appdirs -if TYPE_CHECKING: - from typing import List - def user_cache_dir(appname): # type: (str) -> str @@ -24,7 +21,7 @@ def user_config_dir(appname, roaming=True): # type: (str, bool) -> str path = _appdirs.user_config_dir(appname, appauthor=False, roaming=roaming) if _appdirs.system == "darwin" and not os.path.isdir(path): - path = os.path.expanduser('~/.config/') + path = os.path.expanduser("~/.config/") if appname: path = os.path.join(path, appname) return path @@ -37,5 +34,5 @@ def site_config_dirs(appname): dirval = _appdirs.site_config_dir(appname, appauthor=False, multipath=True) if _appdirs.system not in ["win32", "darwin"]: # always look in /etc directly as well - return dirval.split(os.pathsep) + ['/etc'] + return dirval.split(os.pathsep) + ["/etc"] return [dirval] diff --git a/src/pip/_internal/utils/compat.py b/src/pip/_internal/utils/compat.py index 0b059952348..0cb1c469704 100644 --- a/src/pip/_internal/utils/compat.py +++ b/src/pip/_internal/utils/compat.py @@ -1,21 +1,11 @@ """Stuff that differs in different Python versions and platform distributions.""" -# The following comment should be removed at some point in the future. -# mypy: disallow-untyped-defs=False - -import codecs -import locale import logging import os import sys -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Optional, Union - -__all__ = ["console_to_str", "get_path_uid", "stdlib_pkgs", "WINDOWS"] +__all__ = ["get_path_uid", "stdlib_pkgs", "WINDOWS"] logger = logging.getLogger(__name__) @@ -25,86 +15,14 @@ def has_tls(): # type: () -> bool try: import _ssl # noqa: F401 # ignore unused + return True except ImportError: pass from pip._vendor.urllib3.util import IS_PYOPENSSL - return IS_PYOPENSSL - - -def str_to_display(data, desc=None): - # type: (Union[bytes, str], Optional[str]) -> str - """ - For display or logging purposes, convert a bytes object (or text) to - text (e.g. unicode in Python 2) safe for output. - :param desc: An optional phrase describing the input data, for use in - the log message if a warning is logged. Defaults to "Bytes object". - - This function should never error out and so can take a best effort - approach. It is okay to be lossy if needed since the return value is - just for display. - - We assume the data is in the locale preferred encoding. If it won't - decode properly, we warn the user but decode as best we can. - - We also ensure that the output can be safely written to standard output - without encoding errors. - """ - if isinstance(data, str): - return data - - # Otherwise, data is a bytes object (str in Python 2). - # First, get the encoding we assume. This is the preferred - # encoding for the locale, unless that is not found, or - # it is ASCII, in which case assume UTF-8 - encoding = locale.getpreferredencoding() - if (not encoding) or codecs.lookup(encoding).name == "ascii": - encoding = "utf-8" - - # Now try to decode the data - if we fail, warn the user and - # decode with replacement. - try: - decoded_data = data.decode(encoding) - except UnicodeDecodeError: - logger.warning( - '%s does not appear to be encoded as %s', - desc or 'Bytes object', - encoding, - ) - decoded_data = data.decode(encoding, errors="backslashreplace") - - # Make sure we can print the output, by encoding it to the output - # encoding with replacement of unencodable characters, and then - # decoding again. - # We use stderr's encoding because it's less likely to be - # redirected and if we don't find an encoding we skip this - # step (on the assumption that output is wrapped by something - # that won't fail). - # The double getattr is to deal with the possibility that we're - # being called in a situation where sys.__stderr__ doesn't exist, - # or doesn't have an encoding attribute. Neither of these cases - # should occur in normal pip use, but there's no harm in checking - # in case people use pip in (unsupported) unusual situations. - output_encoding = getattr(getattr(sys, "__stderr__", None), - "encoding", None) - - if output_encoding: - output_encoded = decoded_data.encode( - output_encoding, - errors="backslashreplace" - ) - decoded_data = output_encoded.decode(output_encoding) - - return decoded_data - - -def console_to_str(data): - # type: (bytes) -> str - """Return a string, safe for output, of subprocess output. - """ - return str_to_display(data, desc='Subprocess output') + return IS_PYOPENSSL def get_path_uid(path): @@ -120,7 +38,7 @@ def get_path_uid(path): :raises OSError: When path is a symlink or can't be read. """ - if hasattr(os, 'O_NOFOLLOW'): + if hasattr(os, "O_NOFOLLOW"): fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW) file_uid = os.fstat(fd).st_uid os.close(fd) @@ -132,8 +50,7 @@ def get_path_uid(path): else: # raise OSError for parity with os.O_NOFOLLOW above raise OSError( - "{} is a symlink; Will not return uid for symlinks".format( - path) + "{} is a symlink; Will not return uid for symlinks".format(path) ) return file_uid @@ -147,5 +64,4 @@ def get_path_uid(path): # windows detection, covers cpython and ironpython -WINDOWS = (sys.platform.startswith("win") or - (sys.platform == 'cli' and os.name == 'nt')) +WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") diff --git a/src/pip/_internal/utils/compatibility_tags.py b/src/pip/_internal/utils/compatibility_tags.py index cfba97a5328..14fe51c1a51 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 TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional, Tuple from pip._vendor.packaging.tags import ( Tag, @@ -15,17 +15,16 @@ ) if TYPE_CHECKING: - from typing import List, Optional, Tuple - from pip._vendor.packaging.tags import PythonVersion -_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)') + +_osx_arch_pat = re.compile(r"(.+)_(\d+)_(\d+)_(.+)") def version_info_to_nodot(version_info): # type: (Tuple[int, ...]) -> str # Only use up to the first two numbers. - return ''.join(map(str, version_info[:2])) + return "".join(map(str, version_info[:2])) def _mac_platforms(arch): @@ -40,7 +39,7 @@ def _mac_platforms(arch): # actual prefix provided by the user in case they provided # something like "macosxcustom_". It may be good to remove # this as undocumented or deprecate it in the future. - '{}_{}'.format(name, arch[len('macosx_'):]) + "{}_{}".format(name, arch[len("macosx_") :]) for arch in mac_platforms(mac_version, actual_arch) ] else: @@ -52,31 +51,31 @@ def _mac_platforms(arch): def _custom_manylinux_platforms(arch): # type: (str) -> List[str] arches = [arch] - arch_prefix, arch_sep, arch_suffix = arch.partition('_') - if arch_prefix == 'manylinux2014': + arch_prefix, arch_sep, arch_suffix = arch.partition("_") + if arch_prefix == "manylinux2014": # manylinux1/manylinux2010 wheels run on most manylinux2014 systems # with the exception of wheels depending on ncurses. PEP 599 states # manylinux1/manylinux2010 wheels should be considered # manylinux2014 wheels: # https://www.python.org/dev/peps/pep-0599/#backwards-compatibility-with-manylinux2010-wheels - if arch_suffix in {'i686', 'x86_64'}: - arches.append('manylinux2010' + arch_sep + arch_suffix) - arches.append('manylinux1' + arch_sep + arch_suffix) - elif arch_prefix == 'manylinux2010': + if arch_suffix in {"i686", "x86_64"}: + arches.append("manylinux2010" + arch_sep + arch_suffix) + arches.append("manylinux1" + arch_sep + arch_suffix) + elif arch_prefix == "manylinux2010": # manylinux1 wheels run on most manylinux2010 systems with the # exception of wheels depending on ncurses. PEP 571 states # manylinux1 wheels should be considered manylinux2010 wheels: # https://www.python.org/dev/peps/pep-0571/#backwards-compatibility-with-manylinux1-wheels - arches.append('manylinux1' + arch_sep + arch_suffix) + arches.append("manylinux1" + arch_sep + arch_suffix) return arches def _get_custom_platforms(arch): # type: (str) -> List[str] - arch_prefix, arch_sep, arch_suffix = arch.partition('_') - if arch.startswith('macosx'): + arch_prefix, arch_sep, arch_suffix = arch.partition("_") + if arch.startswith("macosx"): arches = _mac_platforms(arch) - elif arch_prefix in ['manylinux2014', 'manylinux2010']: + elif arch_prefix in ["manylinux2014", "manylinux2010"]: arches = _custom_manylinux_platforms(arch) else: arches = [arch] @@ -122,7 +121,7 @@ def get_supported( version=None, # type: Optional[str] platforms=None, # type: Optional[List[str]] impl=None, # type: Optional[str] - abis=None # type: Optional[List[str]] + abis=None, # type: Optional[List[str]] ): # type: (...) -> List[Tag] """Return a list of supported tags for each version specified in diff --git a/src/pip/_internal/utils/deprecation.py b/src/pip/_internal/utils/deprecation.py index d4b60ea1a19..b62b3fb6509 100644 --- a/src/pip/_internal/utils/deprecation.py +++ b/src/pip/_internal/utils/deprecation.py @@ -2,21 +2,14 @@ A module that implements tooling to enable easy warnings about deprecations. """ -# The following comment should be removed at some point in the future. -# mypy: disallow-untyped-defs=False - import logging import warnings -from typing import TYPE_CHECKING +from typing import Any, Optional, TextIO, Type, Union from pip._vendor.packaging.version import parse from pip import __version__ as current_version -if TYPE_CHECKING: - from typing import Any, Optional - - DEPRECATION_MSG_PREFIX = "DEPRECATION: " @@ -28,21 +21,25 @@ class PipDeprecationWarning(Warning): # Warnings <-> Logging Integration -def _showwarning(message, category, filename, lineno, file=None, line=None): +def _showwarning( + message, # type: Union[Warning, str] + category, # type: Type[Warning] + filename, # type: str + lineno, # type: int + file=None, # type: Optional[TextIO] + line=None, # type: Optional[str] +): + # type: (...) -> None if file is not None: if _original_showwarning is not None: - _original_showwarning( - message, category, filename, lineno, file, line, - ) + _original_showwarning(message, category, filename, lineno, file, line) elif issubclass(category, PipDeprecationWarning): # We use a specially named logger which will handle all of the # deprecation messages for pip. logger = logging.getLogger("pip._internal.deprecations") logger.warning(message) else: - _original_showwarning( - message, category, filename, lineno, file, line, - ) + _original_showwarning(message, category, filename, lineno, file, line) def install_warning_logger(): @@ -86,10 +83,13 @@ def deprecated(reason, replacement, gone_in, issue=None): (reason, DEPRECATION_MSG_PREFIX + "{}"), (gone_in, "pip {} will remove support for this functionality."), (replacement, "A possible replacement is {}."), - (issue, ( - "You can find discussion regarding this at " - "https://github.com/pypa/pip/issues/{}." - )), + ( + issue, + ( + "You can find discussion regarding this at " + "https://github.com/pypa/pip/issues/{}." + ), + ), ] message = " ".join( template.format(val) for val, template in sentences if val is not None diff --git a/src/pip/_internal/utils/direct_url_helpers.py b/src/pip/_internal/utils/direct_url_helpers.py index caf2fa1481d..eb50ac42be8 100644 --- a/src/pip/_internal/utils/direct_url_helpers.py +++ b/src/pip/_internal/utils/direct_url_helpers.py @@ -1,6 +1,8 @@ import json import logging -from typing import TYPE_CHECKING +from typing import Optional + +from pip._vendor.pkg_resources import Distribution from pip._internal.models.direct_url import ( DIRECT_URL_METADATA_NAME, @@ -10,15 +12,9 @@ DirInfo, VcsInfo, ) +from pip._internal.models.link import Link from pip._internal.vcs import vcs -if TYPE_CHECKING: - from typing import Optional - - from pip._vendor.pkg_resources import Distribution - - from pip._internal.models.link import Link - logger = logging.getLogger(__name__) @@ -51,8 +47,8 @@ def direct_url_from_link(link, source_dir=None, link_is_in_wheel_cache=False): if link.is_vcs: vcs_backend = vcs.get_backend_for_scheme(link.scheme) assert vcs_backend - url, requested_revision, _ = ( - vcs_backend.get_url_rev_and_auth(link.url_without_fragment) + url, requested_revision, _ = vcs_backend.get_url_rev_and_auth( + link.url_without_fragment ) # For VCS links, we need to find out and add commit_id. if link_is_in_wheel_cache: @@ -110,7 +106,7 @@ def dist_get_direct_url(dist): except ( DirectUrlValidationError, json.JSONDecodeError, - UnicodeDecodeError + UnicodeDecodeError, ) as e: logger.warning( "Error parsing %s for %s: %s", diff --git a/src/pip/_internal/utils/distutils_args.py b/src/pip/_internal/utils/distutils_args.py index 7d3dae78577..e886c8884d0 100644 --- a/src/pip/_internal/utils/distutils_args.py +++ b/src/pip/_internal/utils/distutils_args.py @@ -1,10 +1,6 @@ from distutils.errors import DistutilsArgError from distutils.fancy_getopt import FancyGetopt -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Dict, List - +from typing import Dict, List _options = [ ("exec-prefix=", None, ""), diff --git a/src/pip/_internal/utils/encoding.py b/src/pip/_internal/utils/encoding.py index 122c4ab29e4..7c8893d559e 100644 --- a/src/pip/_internal/utils/encoding.py +++ b/src/pip/_internal/utils/encoding.py @@ -2,22 +2,19 @@ import locale import re import sys -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import List, Tuple +from typing import List, Tuple BOMS = [ - (codecs.BOM_UTF8, 'utf-8'), - (codecs.BOM_UTF16, 'utf-16'), - (codecs.BOM_UTF16_BE, 'utf-16-be'), - (codecs.BOM_UTF16_LE, 'utf-16-le'), - (codecs.BOM_UTF32, 'utf-32'), - (codecs.BOM_UTF32_BE, 'utf-32-be'), - (codecs.BOM_UTF32_LE, 'utf-32-le'), + (codecs.BOM_UTF8, "utf-8"), + (codecs.BOM_UTF16, "utf-16"), + (codecs.BOM_UTF16_BE, "utf-16-be"), + (codecs.BOM_UTF16_LE, "utf-16-le"), + (codecs.BOM_UTF32, "utf-32"), + (codecs.BOM_UTF32_BE, "utf-32-be"), + (codecs.BOM_UTF32_LE, "utf-32-le"), ] # type: List[Tuple[bytes, str]] -ENCODING_RE = re.compile(br'coding[:=]\s*([-\w.]+)') +ENCODING_RE = re.compile(br"coding[:=]\s*([-\w.]+)") def auto_decode(data): @@ -27,13 +24,13 @@ def auto_decode(data): Fallback to locale.getpreferredencoding(False) like open() on Python3""" for bom, encoding in BOMS: if data.startswith(bom): - return data[len(bom):].decode(encoding) + return data[len(bom) :].decode(encoding) # Lets check the first two lines as in PEP263 - for line in data.split(b'\n')[:2]: - if line[0:1] == b'#' and ENCODING_RE.search(line): + for line in data.split(b"\n")[:2]: + if line[0:1] == b"#" and ENCODING_RE.search(line): result = ENCODING_RE.search(line) assert result is not None - encoding = result.groups()[0].decode('ascii') + encoding = result.groups()[0].decode("ascii") return data.decode(encoding) return data.decode( locale.getpreferredencoding(False) or sys.getdefaultencoding(), diff --git a/src/pip/_internal/utils/entrypoints.py b/src/pip/_internal/utils/entrypoints.py index 9c0454a627d..879bf21ac5f 100644 --- a/src/pip/_internal/utils/entrypoints.py +++ b/src/pip/_internal/utils/entrypoints.py @@ -1,11 +1,8 @@ import sys -from typing import TYPE_CHECKING +from typing import List, Optional from pip._internal.cli.main import main -if TYPE_CHECKING: - from typing import List, Optional - def _wrapper(args=None): # type: (Optional[List[str]]) -> int diff --git a/src/pip/_internal/utils/filesystem.py b/src/pip/_internal/utils/filesystem.py index 1a9d952f4f9..8c580adadc3 100644 --- a/src/pip/_internal/utils/filesystem.py +++ b/src/pip/_internal/utils/filesystem.py @@ -7,7 +7,7 @@ import sys from contextlib import contextmanager from tempfile import NamedTemporaryFile -from typing import TYPE_CHECKING, cast +from typing import Any, BinaryIO, Iterator, List, Union, cast # NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is # why we ignore the type on this import. @@ -16,9 +16,6 @@ from pip._internal.utils.compat import get_path_uid from pip._internal.utils.misc import format_size -if TYPE_CHECKING: - from typing import Any, BinaryIO, Iterator, List, Union - def check_path_owner(path): # type: (str) -> bool @@ -67,8 +64,7 @@ def copy2_fixed(src, dest): pass else: if is_socket_file: - raise shutil.SpecialFileError( - "`{f}` is a socket".format(**locals())) + raise shutil.SpecialFileError(f"`{f}` is a socket") raise @@ -93,10 +89,10 @@ def adjacent_tmp_file(path, **kwargs): delete=False, dir=os.path.dirname(path), prefix=os.path.basename(path), - suffix='.tmp', - **kwargs + suffix=".tmp", + **kwargs, ) as f: - result = cast('BinaryIO', f) + result = cast(BinaryIO, f) try: yield result finally: @@ -124,7 +120,7 @@ def test_writable_dir(path): break # Should never get here, but infinite loops are bad path = parent - if os.name == 'posix': + if os.name == "posix": return os.access(path, os.W_OK) return _test_writable_dir_win(path) @@ -134,10 +130,10 @@ def _test_writable_dir_win(path): # type: (str) -> bool # os.access doesn't work on Windows: http://bugs.python.org/issue2528 # and we can't use tempfile: http://bugs.python.org/issue22107 - basename = 'accesstest_deleteme_fishfingers_custard_' - alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789' + basename = "accesstest_deleteme_fishfingers_custard_" + alphabet = "abcdefghijklmnopqrstuvwxyz0123456789" for _ in range(10): - name = basename + ''.join(random.choice(alphabet) for _ in range(6)) + name = basename + "".join(random.choice(alphabet) for _ in range(6)) file = os.path.join(path, name) try: fd = os.open(file, os.O_RDWR | os.O_CREAT | os.O_EXCL) @@ -156,9 +152,7 @@ def _test_writable_dir_win(path): return True # This should never be reached - raise OSError( - 'Unexpected condition testing for writable directory' - ) + raise OSError("Unexpected condition testing for writable directory") def find_files(path, pattern): diff --git a/src/pip/_internal/utils/filetypes.py b/src/pip/_internal/utils/filetypes.py index 440151d5f32..da935846f61 100644 --- a/src/pip/_internal/utils/filetypes.py +++ b/src/pip/_internal/utils/filetypes.py @@ -1,21 +1,22 @@ """Filetype information. """ -from typing import TYPE_CHECKING -from pip._internal.utils.misc import splitext +from typing import Tuple -if TYPE_CHECKING: - from typing import Tuple +from pip._internal.utils.misc import splitext -WHEEL_EXTENSION = '.whl' -BZ2_EXTENSIONS = ('.tar.bz2', '.tbz') # type: Tuple[str, ...] -XZ_EXTENSIONS = ('.tar.xz', '.txz', '.tlz', - '.tar.lz', '.tar.lzma') # type: Tuple[str, ...] -ZIP_EXTENSIONS = ('.zip', WHEEL_EXTENSION) # type: Tuple[str, ...] -TAR_EXTENSIONS = ('.tar.gz', '.tgz', '.tar') # type: Tuple[str, ...] -ARCHIVE_EXTENSIONS = ( - ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS -) +WHEEL_EXTENSION = ".whl" +BZ2_EXTENSIONS = (".tar.bz2", ".tbz") # type: Tuple[str, ...] +XZ_EXTENSIONS = ( + ".tar.xz", + ".txz", + ".tlz", + ".tar.lz", + ".tar.lzma", +) # type: Tuple[str, ...] +ZIP_EXTENSIONS = (".zip", WHEEL_EXTENSION) # type: Tuple[str, ...] +TAR_EXTENSIONS = (".tar.gz", ".tgz", ".tar") # type: Tuple[str, ...] +ARCHIVE_EXTENSIONS = ZIP_EXTENSIONS + BZ2_EXTENSIONS + TAR_EXTENSIONS + XZ_EXTENSIONS def is_archive_file(name): diff --git a/src/pip/_internal/utils/glibc.py b/src/pip/_internal/utils/glibc.py index 37caad45ef6..1c9ff35446d 100644 --- a/src/pip/_internal/utils/glibc.py +++ b/src/pip/_internal/utils/glibc.py @@ -3,10 +3,7 @@ import os import sys -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Optional, Tuple +from typing import Optional, Tuple def glibc_version_string(): diff --git a/src/pip/_internal/utils/hashes.py b/src/pip/_internal/utils/hashes.py index 612c5e740d7..e0ecf6ee902 100644 --- a/src/pip/_internal/utils/hashes.py +++ b/src/pip/_internal/utils/hashes.py @@ -1,22 +1,21 @@ import hashlib -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, BinaryIO, Dict, Iterator, List, NoReturn from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError from pip._internal.utils.misc import read_chunks if TYPE_CHECKING: from hashlib import _Hash - from typing import BinaryIO, Dict, Iterator, List, NoReturn # The recommended hash algo of the moment. Change this whenever the state of # the art changes; it won't hurt backward compatibility. -FAVORITE_HASH = 'sha256' +FAVORITE_HASH = "sha256" # Names of hashlib algorithms allowed by the --hash option and ``pip hash`` # Currently, those are the ones at least as collision-resistant as sha256. -STRONG_HASHES = ['sha256', 'sha384', 'sha512'] +STRONG_HASHES = ["sha256", "sha384", "sha512"] class Hashes: @@ -24,6 +23,7 @@ class Hashes: known-good values """ + def __init__(self, hashes=None): # type: (Dict[str, List[str]]) -> None """ @@ -64,7 +64,7 @@ def digest_count(self): def is_hash_allowed( self, - hash_name, # type: str + hash_name, # type: str hex_digest, # type: str ): # type: (...) -> bool @@ -84,9 +84,7 @@ def check_against_chunks(self, chunks): try: gots[hash_name] = hashlib.new(hash_name) except (ValueError, TypeError): - raise InstallationError( - f'Unknown hash name: {hash_name}' - ) + raise InstallationError(f"Unknown hash name: {hash_name}") for chunk in chunks: for hash in gots.values(): @@ -112,7 +110,7 @@ def check_against_file(self, file): def check_against_path(self, path): # type: (str) -> None - with open(path, 'rb') as file: + with open(path, "rb") as file: return self.check_against_file(file) def __nonzero__(self): @@ -133,11 +131,13 @@ def __eq__(self, other): def __hash__(self): # type: () -> int return hash( - ",".join(sorted( - ":".join((alg, digest)) - for alg, digest_list in self._allowed.items() - for digest in digest_list - )) + ",".join( + sorted( + ":".join((alg, digest)) + for alg, digest_list in self._allowed.items() + for digest in digest_list + ) + ) ) @@ -148,6 +148,7 @@ class MissingHashes(Hashes): exception showing it to the user. """ + def __init__(self): # type: () -> None """Don't offer the ``hashes`` kwarg.""" diff --git a/src/pip/_internal/utils/inject_securetransport.py b/src/pip/_internal/utils/inject_securetransport.py index 5b93b1d6730..b6863d93405 100644 --- a/src/pip/_internal/utils/inject_securetransport.py +++ b/src/pip/_internal/utils/inject_securetransport.py @@ -22,7 +22,7 @@ def inject_securetransport(): return # Checks for OpenSSL 1.0.1 - if ssl.OPENSSL_VERSION_NUMBER >= 0x1000100f: + if ssl.OPENSSL_VERSION_NUMBER >= 0x1000100F: return try: diff --git a/src/pip/_internal/utils/logging.py b/src/pip/_internal/utils/logging.py index 82b99762807..70b86cc0e21 100644 --- a/src/pip/_internal/utils/logging.py +++ b/src/pip/_internal/utils/logging.py @@ -1,6 +1,3 @@ -# The following comment should be removed at some point in the future. -# mypy: disallow-untyped-defs=False - import contextlib import errno import logging @@ -8,15 +5,12 @@ import os import sys from logging import Filter, getLogger -from typing import TYPE_CHECKING +from typing import IO, Any, Callable, Iterator, Optional, TextIO, Type, cast from pip._internal.utils.compat import WINDOWS from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX from pip._internal.utils.misc import ensure_dir -if TYPE_CHECKING: - from typing import Any - try: import threading except ImportError: @@ -32,13 +26,14 @@ _log_state = threading.local() -subprocess_logger = getLogger('pip.subprocessor') +subprocess_logger = getLogger("pip.subprocessor") class BrokenStdoutLoggingError(Exception): """ Raised if BrokenPipeError occurs for the stdout stream while logging. """ + pass @@ -48,13 +43,17 @@ class BrokenStdoutLoggingError(Exception): # https://bugs.python.org/issue19612 # https://bugs.python.org/issue30418 def _is_broken_pipe_error(exc_class, exc): + # type: (Type[BaseException], BaseException) -> bool """See the docstring for non-Windows below.""" - return ((exc_class is BrokenPipeError) or - (exc_class is OSError and - exc.errno in (errno.EINVAL, errno.EPIPE))) + return (exc_class is BrokenPipeError) or ( + isinstance(exc, OSError) and exc.errno in (errno.EINVAL, errno.EPIPE) + ) + + else: # Then we are in the non-Windows case. def _is_broken_pipe_error(exc_class, exc): + # type: (Type[BaseException], BaseException) -> bool """ Return whether an exception is a broken pipe error. @@ -62,11 +61,12 @@ def _is_broken_pipe_error(exc_class, exc): exc_class: an exception class. exc: an exception instance. """ - return (exc_class is BrokenPipeError) + return exc_class is BrokenPipeError @contextlib.contextmanager def indent_log(num=2): + # type: (int) -> Iterator[None] """ A context manager which will cause the log output to be indented for any log messages emitted inside it. @@ -81,7 +81,8 @@ def indent_log(num=2): def get_indentation(): - return getattr(_log_state, 'indentation', 0) + # type: () -> int + return getattr(_log_state, "indentation", 0) class IndentingFormatter(logging.Formatter): @@ -104,22 +105,24 @@ def __init__( super().__init__(*args, **kwargs) def get_message_start(self, formatted, levelno): + # type: (str, int) -> str """ Return the start of the formatted log message (not counting the prefix to add to each line). """ if levelno < logging.WARNING: - return '' + return "" if formatted.startswith(DEPRECATION_MSG_PREFIX): # Then the message already has a prefix. We don't want it to # look like "WARNING: DEPRECATION: ...." - return '' + return "" if levelno < logging.ERROR: - return 'WARNING: ' + return "WARNING: " - return 'ERROR: ' + return "ERROR: " def format(self, record): + # type: (logging.LogRecord) -> str """ Calls the standard formatter, but will indent all of the log message lines by our current indentation level. @@ -128,20 +131,20 @@ def format(self, record): message_start = self.get_message_start(formatted, record.levelno) formatted = message_start + formatted - prefix = '' + prefix = "" if self.add_timestamp: prefix = f"{self.formatTime(record)} " prefix += " " * get_indentation() - formatted = "".join([ - prefix + line - for line in formatted.splitlines(True) - ]) + formatted = "".join([prefix + line for line in formatted.splitlines(True)]) return formatted def _color_wrap(*colors): + # type: (*str) -> Callable[[str], str] def wrapped(inp): + # type: (str) -> str return "".join(list(colors) + [inp, colorama.Style.RESET_ALL]) + return wrapped @@ -158,6 +161,7 @@ class ColorizedStreamHandler(logging.StreamHandler): COLORS = [] def __init__(self, stream=None, no_color=None): + # type: (Optional[TextIO], bool) -> None super().__init__(stream) self._no_color = no_color @@ -165,22 +169,26 @@ def __init__(self, stream=None, no_color=None): self.stream = colorama.AnsiToWin32(self.stream) def _using_stdout(self): + # type: () -> bool """ Return whether the handler is using sys.stdout. """ if WINDOWS and colorama: # Then self.stream is an AnsiToWin32 object. - return self.stream.wrapped is sys.stdout + stream = cast(colorama.AnsiToWin32, self.stream) + return stream.wrapped is sys.stdout return self.stream is sys.stdout def should_color(self): + # type: () -> bool # Don't colorize things if we do not have colorama or if told not to if not colorama or self._no_color: return False real_stream = ( - self.stream if not isinstance(self.stream, colorama.AnsiToWin32) + self.stream + if not isinstance(self.stream, colorama.AnsiToWin32) else self.stream.wrapped ) @@ -196,6 +204,7 @@ def should_color(self): return False def format(self, record): + # type: (logging.LogRecord) -> str msg = logging.StreamHandler.format(self, record) if self.should_color(): @@ -208,31 +217,37 @@ def format(self, record): # The logging module says handleError() can be customized. def handleError(self, record): + # type: (logging.LogRecord) -> None exc_class, exc = sys.exc_info()[:2] # If a broken pipe occurred while calling write() or flush() on the # stdout stream in logging's Handler.emit(), then raise our special # exception so we can handle it in main() instead of logging the # broken pipe error and continuing. - if (exc_class and self._using_stdout() and - _is_broken_pipe_error(exc_class, exc)): + if ( + exc_class + and exc + and self._using_stdout() + and _is_broken_pipe_error(exc_class, exc) + ): raise BrokenStdoutLoggingError() return super().handleError(record) class BetterRotatingFileHandler(logging.handlers.RotatingFileHandler): - def _open(self): + # type: () -> IO[Any] ensure_dir(os.path.dirname(self.baseFilename)) return logging.handlers.RotatingFileHandler._open(self) class MaxLevelFilter(Filter): - def __init__(self, level): + # type: (int) -> None self.level = level def filter(self, record): + # type: (logging.LogRecord) -> bool return record.levelno < self.level @@ -243,12 +258,14 @@ class ExcludeLoggerFilter(Filter): """ def filter(self, record): + # type: (logging.LogRecord) -> bool # The base Filter class allows only records from a logger (or its # children). return not super().filter(record) def setup_logging(verbosity, no_color, user_log_file): + # type: (int, bool, Optional[str]) -> int """Configures and sets up all of the logging Returns the requested logging level, as its integer value. @@ -295,78 +312,76 @@ def setup_logging(verbosity, no_color, user_log_file): ["user_log"] if include_user_log else [] ) - logging.config.dictConfig({ - "version": 1, - "disable_existing_loggers": False, - "filters": { - "exclude_warnings": { - "()": "pip._internal.utils.logging.MaxLevelFilter", - "level": logging.WARNING, - }, - "restrict_to_subprocess": { - "()": "logging.Filter", - "name": subprocess_logger.name, - }, - "exclude_subprocess": { - "()": "pip._internal.utils.logging.ExcludeLoggerFilter", - "name": subprocess_logger.name, - }, - }, - "formatters": { - "indent": { - "()": IndentingFormatter, - "format": "%(message)s", + logging.config.dictConfig( + { + "version": 1, + "disable_existing_loggers": False, + "filters": { + "exclude_warnings": { + "()": "pip._internal.utils.logging.MaxLevelFilter", + "level": logging.WARNING, + }, + "restrict_to_subprocess": { + "()": "logging.Filter", + "name": subprocess_logger.name, + }, + "exclude_subprocess": { + "()": "pip._internal.utils.logging.ExcludeLoggerFilter", + "name": subprocess_logger.name, + }, }, - "indent_with_timestamp": { - "()": IndentingFormatter, - "format": "%(message)s", - "add_timestamp": True, + "formatters": { + "indent": { + "()": IndentingFormatter, + "format": "%(message)s", + }, + "indent_with_timestamp": { + "()": IndentingFormatter, + "format": "%(message)s", + "add_timestamp": True, + }, }, - }, - "handlers": { - "console": { - "level": level, - "class": handler_classes["stream"], - "no_color": no_color, - "stream": log_streams["stdout"], - "filters": ["exclude_subprocess", "exclude_warnings"], - "formatter": "indent", + "handlers": { + "console": { + "level": level, + "class": handler_classes["stream"], + "no_color": no_color, + "stream": log_streams["stdout"], + "filters": ["exclude_subprocess", "exclude_warnings"], + "formatter": "indent", + }, + "console_errors": { + "level": "WARNING", + "class": handler_classes["stream"], + "no_color": no_color, + "stream": log_streams["stderr"], + "filters": ["exclude_subprocess"], + "formatter": "indent", + }, + # A handler responsible for logging to the console messages + # from the "subprocessor" logger. + "console_subprocess": { + "level": level, + "class": handler_classes["stream"], + "no_color": no_color, + "stream": log_streams["stderr"], + "filters": ["restrict_to_subprocess"], + "formatter": "indent", + }, + "user_log": { + "level": "DEBUG", + "class": handler_classes["file"], + "filename": additional_log_file, + "delay": True, + "formatter": "indent_with_timestamp", + }, }, - "console_errors": { - "level": "WARNING", - "class": handler_classes["stream"], - "no_color": no_color, - "stream": log_streams["stderr"], - "filters": ["exclude_subprocess"], - "formatter": "indent", + "root": { + "level": root_level, + "handlers": handlers, }, - # A handler responsible for logging to the console messages - # from the "subprocessor" logger. - "console_subprocess": { - "level": level, - "class": handler_classes["stream"], - "no_color": no_color, - "stream": log_streams["stderr"], - "filters": ["restrict_to_subprocess"], - "formatter": "indent", - }, - "user_log": { - "level": "DEBUG", - "class": handler_classes["file"], - "filename": additional_log_file, - "delay": True, - "formatter": "indent_with_timestamp", - }, - }, - "root": { - "level": root_level, - "handlers": handlers, - }, - "loggers": { - "pip._vendor": { - "level": vendored_log_level - } - }, - }) + "loggers": {"pip._vendor": {"level": vendored_log_level}}, + } + ) return level_number diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index 200fe841814..e41a138a385 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -1,6 +1,5 @@ # The following comment should be removed at some point in the future. # mypy: strict-optional=False -# mypy: disallow-untyped-defs=False import contextlib import errno @@ -16,7 +15,26 @@ import urllib.parse from io import StringIO from itertools import filterfalse, tee, zip_longest -from typing import TYPE_CHECKING, cast +from types import TracebackType +from typing import ( + Any, + AnyStr, + BinaryIO, + Callable, + Container, + ContextManager, + Iterable, + Iterator, + List, + Optional, + TextIO, + Tuple, + Type, + TypeVar, + cast, +) + +from pip._vendor.pkg_resources import Distribution # NOTE: retrying is not annotated in typeshed as on 2017-07-17, which is # why we ignore the type on this import. @@ -31,47 +49,40 @@ virtualenv_no_global, ) -if TYPE_CHECKING: - from typing import ( - Any, - AnyStr, - Callable, - Container, - Iterable, - Iterator, - List, - Optional, - Tuple, - TypeVar, - ) - - from pip._vendor.pkg_resources import Distribution - - VersionInfo = Tuple[int, int, int] - T = TypeVar("T") - - -__all__ = ['rmtree', 'display_path', 'backup_dir', - 'ask', 'splitext', - 'format_size', 'is_installable_dir', - 'normalize_path', - 'renames', 'get_prog', - 'captured_stdout', 'ensure_dir', - 'remove_auth_from_url'] +__all__ = [ + "rmtree", + "display_path", + "backup_dir", + "ask", + "splitext", + "format_size", + "is_installable_dir", + "normalize_path", + "renames", + "get_prog", + "captured_stdout", + "ensure_dir", + "remove_auth_from_url", +] 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]]] + def get_pip_version(): # type: () -> str pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..") pip_pkg_dir = os.path.abspath(pip_pkg_dir) - return ( - 'pip {} from {} (python {})'.format( - __version__, pip_pkg_dir, get_major_minor_version(), - ) + return "pip {} from {} (python {})".format( + __version__, + pip_pkg_dir, + get_major_minor_version(), ) @@ -92,7 +103,7 @@ def normalize_version_info(py_version_info): elif len(py_version_info) > 3: py_version_info = py_version_info[:3] - return cast('VersionInfo', py_version_info) + return cast("VersionInfo", py_version_info) def ensure_dir(path): @@ -110,24 +121,24 @@ def get_prog(): # type: () -> str try: prog = os.path.basename(sys.argv[0]) - if prog in ('__main__.py', '-c'): + if prog in ("__main__.py", "-c"): return f"{sys.executable} -m pip" else: return prog except (AttributeError, TypeError, IndexError): pass - return 'pip' + return "pip" # Retry every half second for up to 3 seconds @retry(stop_max_delay=3000, wait_fixed=500) def rmtree(dir, ignore_errors=False): # type: (AnyStr, bool) -> None - shutil.rmtree(dir, ignore_errors=ignore_errors, - onerror=rmtree_errorhandler) + shutil.rmtree(dir, ignore_errors=ignore_errors, onerror=rmtree_errorhandler) def rmtree_errorhandler(func, path, exc_info): + # type: (Callable[..., Any], str, ExcInfo) -> None """On Windows, the files in .svn are read-only, so when rmtree() tries to remove them, an exception is thrown. We catch that here, remove the read-only attribute, and hopefully continue without problems.""" @@ -147,41 +158,17 @@ def rmtree_errorhandler(func, path, exc_info): raise -def path_to_display(path): - # type: (Optional[str]) -> Optional[str] - """ - Convert a bytes (or text) path to text (unicode in Python 2) for display - and logging purposes. - - This function should never error out. Also, this function is mainly needed - for Python 2 since in Python 3 str paths are already text. - """ - if path is None: - return None - if isinstance(path, str): - return path - # Otherwise, path is a bytes object (str in Python 2). - try: - display_path = path.decode(sys.getfilesystemencoding(), 'strict') - except UnicodeDecodeError: - # Include the full bytes to make troubleshooting easier, even though - # it may not be very human readable. - display_path = ascii(path) - - return display_path - - def display_path(path): # type: (str) -> str """Gives the display value for a given path, making it relative to cwd if possible.""" path = os.path.normcase(os.path.abspath(path)) if path.startswith(os.getcwd() + os.path.sep): - path = '.' + path[len(os.getcwd()):] + path = "." + path[len(os.getcwd()) :] return path -def backup_dir(dir, ext='.bak'): +def backup_dir(dir, ext=".bak"): # type: (str, str) -> str """Figure out the name of a directory to back up the given dir to (adding .bak, .bak2, etc)""" @@ -195,7 +182,7 @@ def backup_dir(dir, ext='.bak'): def ask_path_exists(message, options): # type: (str, Iterable[str]) -> str - for action in os.environ.get('PIP_EXISTS_ACTION', '').split(): + for action in os.environ.get("PIP_EXISTS_ACTION", "").split(): if action in options: return action return ask(message, options) @@ -204,10 +191,9 @@ def ask_path_exists(message, options): def _check_no_input(message): # type: (str) -> None """Raise an error if no input is allowed.""" - if os.environ.get('PIP_NO_INPUT'): + if os.environ.get("PIP_NO_INPUT"): raise Exception( - 'No input was expected ($PIP_NO_INPUT set); question: {}'.format( - message) + "No input was expected ($PIP_NO_INPUT set); question: {}".format(message) ) @@ -220,8 +206,8 @@ def ask(message, options): response = response.strip().lower() if response not in options: print( - 'Your response ({!r}) was not one of the expected responses: ' - '{}'.format(response, ', '.join(options)) + "Your response ({!r}) was not one of the expected responses: " + "{}".format(response, ", ".join(options)) ) else: return response @@ -250,9 +236,9 @@ def strtobool(val): 'val' is anything else. """ val = val.lower() - if val in ('y', 'yes', 't', 'true', 'on', '1'): + if val in ("y", "yes", "t", "true", "on", "1"): return 1 - elif val in ('n', 'no', 'f', 'false', 'off', '0'): + elif val in ("n", "no", "f", "false", "off", "0"): return 0 else: raise ValueError("invalid truth value %r" % (val,)) @@ -261,13 +247,13 @@ def strtobool(val): def format_size(bytes): # type: (float) -> str if bytes > 1000 * 1000: - return '{:.1f} MB'.format(bytes / 1000.0 / 1000) + return "{:.1f} MB".format(bytes / 1000.0 / 1000) elif bytes > 10 * 1000: - return '{} kB'.format(int(bytes / 1000)) + return "{} kB".format(int(bytes / 1000)) elif bytes > 1000: - return '{:.1f} kB'.format(bytes / 1000.0) + return "{:.1f} kB".format(bytes / 1000.0) else: - return '{} bytes'.format(int(bytes)) + return "{} bytes".format(int(bytes)) def tabulate(rows): @@ -280,27 +266,27 @@ def tabulate(rows): (['foobar 2000', '3735928559'], [10, 4]) """ rows = [tuple(map(str, row)) for row in rows] - sizes = [max(map(len, col)) for col in zip_longest(*rows, fillvalue='')] + sizes = [max(map(len, col)) for col in zip_longest(*rows, fillvalue="")] table = [" ".join(map(str.ljust, row, sizes)).rstrip() for row in rows] return table, sizes def is_installable_dir(path): # type: (str) -> bool - """Is path is a directory containing setup.py or pyproject.toml? - """ + """Is path is a directory containing setup.py or pyproject.toml?""" if not os.path.isdir(path): return False - setup_py = os.path.join(path, 'setup.py') + setup_py = os.path.join(path, "setup.py") if os.path.isfile(setup_py): return True - pyproject_toml = os.path.join(path, 'pyproject.toml') + pyproject_toml = os.path.join(path, "pyproject.toml") if os.path.isfile(pyproject_toml): return True return False def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE): + # type: (BinaryIO, int) -> Iterator[bytes] """Yield pieces of data from a file-like object until EOF.""" while True: chunk = file.read(size) @@ -327,7 +313,7 @@ def splitext(path): # type: (str) -> Tuple[str, str] """Like os.path.splitext, but take off .tar too""" base, ext = posixpath.splitext(path) - if base.lower().endswith('.tar'): + if base.lower().endswith(".tar"): ext = base[-4:] + ext base = base[:-4] return base, ext @@ -401,19 +387,19 @@ def dist_is_editable(dist): Return True if given Distribution is an editable install. """ for path_item in sys.path: - egg_link = os.path.join(path_item, dist.project_name + '.egg-link') + egg_link = os.path.join(path_item, dist.project_name + ".egg-link") if os.path.isfile(egg_link): return True return False def get_installed_distributions( - local_only=True, # type: bool - skip=stdlib_pkgs, # type: Container[str] - include_editables=True, # type: bool - editables_only=False, # type: bool - user_only=False, # type: bool - paths=None # type: Optional[List[str]] + local_only=True, # type: bool + skip=stdlib_pkgs, # type: Container[str] + include_editables=True, # type: bool + editables_only=False, # type: bool + user_only=False, # type: bool + paths=None, # type: Optional[List[str]] ): # type: (...) -> List[Distribution] """Return a list of installed Distribution objects. @@ -448,6 +434,7 @@ def get_distribution(req_name): """ from pip._internal.metadata import get_default_environment from pip._internal.metadata.pkg_resources import Distribution as _Dist + dist = get_default_environment().get_distribution(req_name) if dist is None: return None @@ -484,7 +471,7 @@ def egg_link_path(dist): sites.append(site_packages) for site in sites: - egglink = os.path.join(site, dist.project_name) + '.egg-link' + egglink = os.path.join(site, dist.project_name) + ".egg-link" if os.path.isfile(egglink): return egglink return None @@ -512,20 +499,24 @@ def write_output(msg, *args): class StreamWrapper(StringIO): + orig_stream = None # type: TextIO @classmethod def from_stream(cls, orig_stream): + # type: (TextIO) -> StreamWrapper cls.orig_stream = orig_stream return cls() # compileall.compile_dir() needs stdout.encoding to print to stdout + # https://github.com/python/mypy/issues/4125 @property - def encoding(self): + def encoding(self): # type: ignore return self.orig_stream.encoding @contextlib.contextmanager def captured_output(stream_name): + # type: (str) -> Iterator[StreamWrapper] """Return a context manager used by captured_stdout/stdin/stderr that temporarily replaces the sys stream *stream_name* with a StringIO. @@ -540,6 +531,7 @@ def captured_output(stream_name): def captured_stdout(): + # type: () -> ContextManager[StreamWrapper] """Capture the output of sys.stdout: with captured_stdout() as stdout: @@ -548,22 +540,24 @@ def captured_stdout(): Taken from Lib/support/__init__.py in the CPython repo. """ - return captured_output('stdout') + return captured_output("stdout") def captured_stderr(): + # type: () -> ContextManager[StreamWrapper] """ See captured_stdout(). """ - return captured_output('stderr') + return captured_output("stderr") # Simulates an enum def enum(*sequential, **named): + # type: (*Any, **Any) -> Type[Any] enums = dict(zip(sequential, range(len(sequential))), **named) reverse = {value: key for key, value in enums.items()} - enums['reverse_mapping'] = reverse - return type('Enum', (), enums) + enums["reverse_mapping"] = reverse + return type("Enum", (), enums) def build_netloc(host, port): @@ -573,21 +567,21 @@ def build_netloc(host, port): """ if port is None: return host - if ':' in host: + if ":" in host: # Only wrap host with square brackets when it is IPv6 - host = f'[{host}]' - return f'{host}:{port}' + host = f"[{host}]" + return f"{host}:{port}" -def build_url_from_netloc(netloc, scheme='https'): +def build_url_from_netloc(netloc, scheme="https"): # type: (str, str) -> str """ Build a full URL from a netloc. """ - if netloc.count(':') >= 2 and '@' not in netloc and '[' not in netloc: + if netloc.count(":") >= 2 and "@" not in netloc and "[" not in netloc: # It must be a bare IPv6 address, so wrap it with brackets. - netloc = f'[{netloc}]' - return f'{scheme}://{netloc}' + netloc = f"[{netloc}]" + return f"{scheme}://{netloc}" def parse_netloc(netloc): @@ -601,31 +595,33 @@ def parse_netloc(netloc): def split_auth_from_netloc(netloc): + # type: (str) -> NetlocTuple """ Parse out and remove the auth information from a netloc. Returns: (netloc, (username, password)). """ - if '@' not in netloc: + if "@" not in netloc: return netloc, (None, None) # Split from the right because that's how urllib.parse.urlsplit() # behaves if more than one @ is present (which can be checked using # the password attribute of urlsplit()'s return value). - auth, netloc = netloc.rsplit('@', 1) - if ':' in auth: + auth, netloc = netloc.rsplit("@", 1) + pw = None # type: Optional[str] + if ":" in auth: # Split from the left because that's how urllib.parse.urlsplit() # behaves if more than one : is present (which again can be checked # using the password attribute of the return value) - user_pass = auth.split(':', 1) + user, pw = auth.split(":", 1) else: - user_pass = auth, None + user, pw = auth, None - user_pass = tuple( - None if x is None else urllib.parse.unquote(x) for x in user_pass - ) + user = urllib.parse.unquote(user) + if pw is not None: + pw = urllib.parse.unquote(pw) - return netloc, user_pass + return netloc, (user, pw) def redact_netloc(netloc): @@ -641,17 +637,18 @@ def redact_netloc(netloc): if user is None: return netloc if password is None: - user = '****' - password = '' + user = "****" + password = "" else: user = urllib.parse.quote(user) - password = ':****' - return '{user}{password}@{netloc}'.format(user=user, - password=password, - netloc=netloc) + password = ":****" + return "{user}{password}@{netloc}".format( + user=user, password=password, netloc=netloc + ) def _transform_url(url, transform_netloc): + # type: (str, 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 @@ -664,18 +661,18 @@ def _transform_url(url, transform_netloc): purl = urllib.parse.urlsplit(url) netloc_tuple = transform_netloc(purl.netloc) # stripped url - url_pieces = ( - purl.scheme, netloc_tuple[0], purl.path, purl.query, purl.fragment - ) + url_pieces = (purl.scheme, netloc_tuple[0], purl.path, purl.query, purl.fragment) surl = urllib.parse.urlunsplit(url_pieces) - return surl, netloc_tuple + return surl, cast("NetlocTuple", netloc_tuple) def _get_netloc(netloc): + # type: (str) -> NetlocTuple return split_auth_from_netloc(netloc) def _redact_netloc(netloc): + # type: (str) -> Tuple[str,] return (redact_netloc(netloc),) @@ -707,7 +704,7 @@ def redact_auth_from_url(url): class HiddenText: def __init__( self, - secret, # type: str + secret, # type: str redacted, # type: str ): # type: (...) -> None @@ -716,7 +713,7 @@ def __init__( def __repr__(self): # type: (...) -> str - return ''.format(str(self)) + return "".format(str(self)) def __str__(self): # type: (...) -> str @@ -730,12 +727,12 @@ def __eq__(self, other): # The string being used for redaction doesn't also have to match, # just the raw, original string. - return (self.secret == other.secret) + return self.secret == other.secret def hide_value(value): # type: (str) -> HiddenText - return HiddenText(value, redacted='****') + return HiddenText(value, redacted="****") def hide_url(url): @@ -754,41 +751,36 @@ def protect_pip_from_modification_on_windows(modifying_pip): pip_names = [ "pip.exe", "pip{}.exe".format(sys.version_info[0]), - "pip{}.{}.exe".format(*sys.version_info[:2]) + "pip{}.{}.exe".format(*sys.version_info[:2]), ] # See https://github.com/pypa/pip/issues/1299 for more discussion should_show_use_python_msg = ( - modifying_pip and - WINDOWS and - os.path.basename(sys.argv[0]) in pip_names + modifying_pip and WINDOWS and os.path.basename(sys.argv[0]) in pip_names ) if should_show_use_python_msg: - new_command = [ - sys.executable, "-m", "pip" - ] + sys.argv[1:] + new_command = [sys.executable, "-m", "pip"] + sys.argv[1:] raise CommandError( - 'To modify pip, please run the following command:\n{}' - .format(" ".join(new_command)) + "To modify pip, please run the following command:\n{}".format( + " ".join(new_command) + ) ) def is_console_interactive(): # type: () -> bool - """Is this console interactive? - """ + """Is this console interactive?""" return sys.stdin is not None and sys.stdin.isatty() def hash_file(path, blocksize=1 << 20): # type: (str, int) -> Tuple[Any, int] - """Return (hash, length) for path using hashlib.sha256() - """ + """Return (hash, length) for path using hashlib.sha256()""" h = hashlib.sha256() length = 0 - with open(path, 'rb') as f: + with open(path, "rb") as f: for block in read_chunks(f, size=blocksize): length += len(block) h.update(block) @@ -796,6 +788,7 @@ def hash_file(path, blocksize=1 << 20): def is_wheel_installed(): + # type: () -> bool """ Return whether the wheel package is installed. """ diff --git a/src/pip/_internal/utils/models.py b/src/pip/_internal/utils/models.py index c14e9ff926e..0e02bc7a5b1 100644 --- a/src/pip/_internal/utils/models.py +++ b/src/pip/_internal/utils/models.py @@ -1,40 +1,46 @@ """Utilities for defining models """ -# The following comment should be removed at some point in the future. -# mypy: disallow-untyped-defs=False import operator +from typing import Any, Callable, Type class KeyBasedCompareMixin: - """Provides comparison capabilities that is based on a key - """ + """Provides comparison capabilities that is based on a key""" - __slots__ = ['_compare_key', '_defining_class'] + __slots__ = ["_compare_key", "_defining_class"] def __init__(self, key, defining_class): + # type: (Any, Type[KeyBasedCompareMixin]) -> None self._compare_key = key self._defining_class = defining_class def __hash__(self): + # type: () -> int return hash(self._compare_key) def __lt__(self, other): + # type: (Any) -> bool return self._compare(other, operator.__lt__) def __le__(self, other): + # type: (Any) -> bool return self._compare(other, operator.__le__) def __gt__(self, other): + # type: (Any) -> bool return self._compare(other, operator.__gt__) def __ge__(self, other): + # type: (Any) -> bool return self._compare(other, operator.__ge__) def __eq__(self, other): + # type: (Any) -> bool return self._compare(other, operator.__eq__) def _compare(self, other, method): + # type: (Any, Callable[[Any, Any], bool]) -> bool if not isinstance(other, self._defining_class): return NotImplemented diff --git a/src/pip/_internal/utils/packaging.py b/src/pip/_internal/utils/packaging.py index f8de544d30c..3f9dbd3b7a0 100644 --- a/src/pip/_internal/utils/packaging.py +++ b/src/pip/_internal/utils/packaging.py @@ -1,20 +1,15 @@ import logging +from email.message import Message from email.parser import FeedParser -from typing import TYPE_CHECKING +from typing import Optional, Tuple from pip._vendor import pkg_resources from pip._vendor.packaging import specifiers, version +from pip._vendor.pkg_resources import Distribution from pip._internal.exceptions import NoneMetadataError from pip._internal.utils.misc import display_path -if TYPE_CHECKING: - from email.message import Message - from typing import Optional, Tuple - - from pip._vendor.pkg_resources import Distribution - - logger = logging.getLogger(__name__) @@ -36,7 +31,7 @@ def check_requires_python(requires_python, version_info): return True requires_python_specifier = specifiers.SpecifierSet(requires_python) - python_version = version.parse('.'.join(map(str, version_info))) + python_version = version.parse(".".join(map(str, version_info))) return python_version in requires_python_specifier @@ -46,16 +41,17 @@ def get_metadata(dist): :raises NoneMetadataError: if the distribution reports `has_metadata()` True but `get_metadata()` returns None. """ - metadata_name = 'METADATA' - if (isinstance(dist, pkg_resources.DistInfoDistribution) and - dist.has_metadata(metadata_name)): + metadata_name = "METADATA" + if isinstance(dist, pkg_resources.DistInfoDistribution) and dist.has_metadata( + metadata_name + ): metadata = dist.get_metadata(metadata_name) - elif dist.has_metadata('PKG-INFO'): - metadata_name = 'PKG-INFO' + elif dist.has_metadata("PKG-INFO"): + metadata_name = "PKG-INFO" metadata = dist.get_metadata(metadata_name) else: logger.warning("No metadata found in %s", display_path(dist.location)) - metadata = '' + metadata = "" if metadata is None: raise NoneMetadataError(dist, metadata_name) @@ -74,7 +70,7 @@ def get_requires_python(dist): if not present. """ pkg_info_dict = get_metadata(dist) - requires_python = pkg_info_dict.get('Requires-Python') + requires_python = pkg_info_dict.get("Requires-Python") if requires_python is not None: # Convert to a str to satisfy the type checker, since requires_python @@ -86,8 +82,8 @@ def get_requires_python(dist): def get_installer(dist): # type: (Distribution) -> str - if dist.has_metadata('INSTALLER'): - for line in dist.get_metadata_lines('INSTALLER'): + if dist.has_metadata("INSTALLER"): + for line in dist.get_metadata_lines("INSTALLER"): if line.strip(): return line.strip() - return '' + return "" diff --git a/src/pip/_internal/utils/parallel.py b/src/pip/_internal/utils/parallel.py index af5d4a9df98..de91dc8abc8 100644 --- a/src/pip/_internal/utils/parallel.py +++ b/src/pip/_internal/utils/parallel.py @@ -16,22 +16,19 @@ than using the default value of 1. """ -__all__ = ['map_multiprocess', 'map_multithread'] +__all__ = ["map_multiprocess", "map_multithread"] from contextlib import contextmanager from multiprocessing import Pool as ProcessPool +from multiprocessing import pool from multiprocessing.dummy import Pool as ThreadPool -from typing import TYPE_CHECKING +from typing import Callable, Iterable, Iterator, TypeVar, Union from pip._vendor.requests.adapters import DEFAULT_POOLSIZE -if TYPE_CHECKING: - from multiprocessing import pool - from typing import Callable, Iterable, Iterator, TypeVar, Union - - Pool = Union[pool.Pool, pool.ThreadPool] - S = TypeVar('S') - T = TypeVar('T') +Pool = Union[pool.Pool, pool.ThreadPool] +S = TypeVar("S") +T = TypeVar("T") # On platforms without sem_open, multiprocessing[.dummy] Pool # cannot be created. diff --git a/src/pip/_internal/utils/pkg_resources.py b/src/pip/_internal/utils/pkg_resources.py index 913bebd9834..ee1eca30081 100644 --- a/src/pip/_internal/utils/pkg_resources.py +++ b/src/pip/_internal/utils/pkg_resources.py @@ -1,14 +1,11 @@ -from typing import TYPE_CHECKING +from typing import Dict, Iterable, List from pip._vendor.pkg_resources import yield_lines -if TYPE_CHECKING: - from typing import Dict, Iterable, List - class DictMetadata: - """IMetadataProvider that reads metadata files from a dictionary. - """ + """IMetadataProvider that reads metadata files from a dictionary.""" + def __init__(self, metadata): # type: (Dict[str, bytes]) -> None self._metadata = metadata diff --git a/src/pip/_internal/utils/setuptools_build.py b/src/pip/_internal/utils/setuptools_build.py index 49b0f22f5a2..1f1980dd30a 100644 --- a/src/pip/_internal/utils/setuptools_build.py +++ b/src/pip/_internal/utils/setuptools_build.py @@ -1,8 +1,5 @@ import sys -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import List, Optional, Sequence +from typing import List, Optional, Sequence # Shim to wrap setup.py invocation with setuptools # @@ -23,7 +20,7 @@ def make_setuptools_shim_args( setup_py_path, # type: str global_options=None, # type: Sequence[str] no_user_config=False, # type: bool - unbuffered_output=False # type: bool + unbuffered_output=False, # type: bool ): # type: (...) -> List[str] """ @@ -58,9 +55,7 @@ def make_setuptools_bdist_wheel_args( # relies on site.py to find parts of the standard library outside the # virtualenv. args = make_setuptools_shim_args( - setup_py_path, - global_options=global_options, - unbuffered_output=True + setup_py_path, global_options=global_options, unbuffered_output=True ) args += ["bdist_wheel", "-d", destination_dir] args += build_options @@ -73,9 +68,7 @@ def make_setuptools_clean_args( ): # type: (...) -> List[str] args = make_setuptools_shim_args( - setup_py_path, - global_options=global_options, - unbuffered_output=True + setup_py_path, global_options=global_options, unbuffered_output=True ) args += ["clean", "--all"] return args @@ -120,9 +113,7 @@ def make_setuptools_egg_info_args( no_user_config, # type: bool ): # type: (...) -> List[str] - args = make_setuptools_shim_args( - setup_py_path, no_user_config=no_user_config - ) + args = make_setuptools_shim_args(setup_py_path, no_user_config=no_user_config) args += ["egg_info"] @@ -143,7 +134,7 @@ def make_setuptools_install_args( home, # type: Optional[str] use_user_site, # type: bool no_user_config, # type: bool - pycompile # type: bool + pycompile, # type: bool ): # type: (...) -> List[str] assert not (use_user_site and prefix) @@ -153,7 +144,7 @@ def make_setuptools_install_args( setup_py_path, global_options=global_options, no_user_config=no_user_config, - unbuffered_output=True + unbuffered_output=True, ) args += ["install", "--record", record_filename] args += ["--single-version-externally-managed"] diff --git a/src/pip/_internal/utils/subprocess.py b/src/pip/_internal/utils/subprocess.py index 82bc3987ccf..cfde1870081 100644 --- a/src/pip/_internal/utils/subprocess.py +++ b/src/pip/_internal/utils/subprocess.py @@ -2,21 +2,17 @@ import os import shlex import subprocess -from typing import TYPE_CHECKING +from typing import Any, Callable, Iterable, List, Mapping, Optional, Union from pip._internal.cli.spinners import SpinnerInterface, open_spinner from pip._internal.exceptions import InstallationSubprocessError -from pip._internal.utils.compat import console_to_str, str_to_display from pip._internal.utils.logging import subprocess_logger -from pip._internal.utils.misc import HiddenText, path_to_display +from pip._internal.utils.misc import HiddenText -if TYPE_CHECKING: - from typing import Any, Callable, Iterable, List, Mapping, Optional, Union +CommandArgs = List[Union[str, HiddenText]] - CommandArgs = List[Union[str, HiddenText]] - -LOG_DIVIDER = '----------------------------------------' +LOG_DIVIDER = "----------------------------------------" def make_command(*args): @@ -47,9 +43,9 @@ def format_command_args(args): # this can trigger a UnicodeDecodeError in Python 2 if the argument # has type unicode and includes a non-ascii character. (The type # checker doesn't ensure the annotations are correct in all cases.) - return ' '.join( - shlex.quote(str(arg)) if isinstance(arg, HiddenText) - else shlex.quote(arg) for arg in args + return " ".join( + shlex.quote(str(arg)) if isinstance(arg, HiddenText) else shlex.quote(arg) + for arg in args ) @@ -58,15 +54,13 @@ def reveal_command_args(args): """ Return the arguments in their raw, unredacted form. """ - return [ - arg.secret if isinstance(arg, HiddenText) else arg for arg in args - ] + return [arg.secret if isinstance(arg, HiddenText) else arg for arg in args] def make_subprocess_output_error( - cmd_args, # type: Union[List[str], CommandArgs] - cwd, # type: Optional[str] - lines, # type: List[str] + cmd_args, # type: Union[List[str], CommandArgs] + cwd, # type: Optional[str] + lines, # type: List[str] exit_status, # type: int ): # type: (...) -> str @@ -77,27 +71,21 @@ def make_subprocess_output_error( :param lines: A list of lines, each ending with a newline. """ command = format_command_args(cmd_args) - # Convert `command` and `cwd` to text (unicode in Python 2) so we can use - # them as arguments in the unicode format string below. This avoids - # "UnicodeDecodeError: 'ascii' codec can't decode byte ..." in Python 2 - # if either contains a non-ascii character. - command_display = str_to_display(command, desc='command bytes') - cwd_display = path_to_display(cwd) # We know the joined output value ends in a newline. - output = ''.join(lines) + output = "".join(lines) msg = ( # Use a unicode string to avoid "UnicodeEncodeError: 'ascii' # codec can't encode character ..." in Python 2 when a format # argument (e.g. `output`) has a non-ascii character. - 'Command errored out with exit status {exit_status}:\n' - ' command: {command_display}\n' - ' cwd: {cwd_display}\n' - 'Complete output ({line_count} lines):\n{output}{divider}' + "Command errored out with exit status {exit_status}:\n" + " command: {command_display}\n" + " cwd: {cwd_display}\n" + "Complete output ({line_count} lines):\n{output}{divider}" ).format( exit_status=exit_status, - command_display=command_display, - cwd_display=cwd_display, + command_display=command, + cwd_display=cwd, line_count=len(lines), output=output, divider=LOG_DIVIDER, @@ -109,7 +97,7 @@ def call_subprocess( cmd, # type: Union[List[str], CommandArgs] show_stdout=False, # type: bool cwd=None, # type: Optional[str] - on_returncode='raise', # type: str + on_returncode="raise", # type: str extra_ok_returncodes=None, # type: Optional[Iterable[int]] command_desc=None, # type: Optional[str] extra_environ=None, # type: Optional[Mapping[str, Any]] @@ -186,11 +174,14 @@ def call_subprocess( stderr=subprocess.STDOUT if not stdout_only else subprocess.PIPE, cwd=cwd, env=env, + errors="backslashreplace", ) except Exception as exc: if log_failed_cmd: subprocess_logger.critical( - "Error %s while executing command %s", exc, command_desc, + "Error %s while executing command %s", + exc, + command_desc, ) raise all_output = [] @@ -200,12 +191,11 @@ def call_subprocess( proc.stdin.close() # In this mode, stdout and stderr are in the same pipe. while True: - # The "line" value is a unicode string in Python 2. - line = console_to_str(proc.stdout.readline()) + line = proc.stdout.readline() # type: str if not line: break line = line.rstrip() - all_output.append(line + '\n') + all_output.append(line + "\n") # Show the line immediately. log_subprocess(line) @@ -218,25 +208,21 @@ def call_subprocess( finally: if proc.stdout: proc.stdout.close() - output = ''.join(all_output) + output = "".join(all_output) else: # In this mode, stdout and stderr are in different pipes. # We must use communicate() which is the only safe way to read both. - out_bytes, err_bytes = proc.communicate() + out, err = proc.communicate() # log line by line to preserve pip log indenting - out = console_to_str(out_bytes) for out_line in out.splitlines(): log_subprocess(out_line) all_output.append(out) - err = console_to_str(err_bytes) for err_line in err.splitlines(): log_subprocess(err_line) all_output.append(err) output = out - proc_had_error = ( - proc.returncode and proc.returncode not in extra_ok_returncodes - ) + proc_had_error = proc.returncode and proc.returncode not in extra_ok_returncodes if use_spinner: assert spinner if proc_had_error: @@ -244,7 +230,7 @@ def call_subprocess( else: spinner.finish("done") if proc_had_error: - if on_returncode == 'raise': + if on_returncode == "raise": if not showing_subprocess and log_failed_cmd: # Then the subprocess streams haven't been logged to the # console yet. @@ -256,18 +242,17 @@ def call_subprocess( ) subprocess_logger.error(msg) raise InstallationSubprocessError(proc.returncode, command_desc) - elif on_returncode == 'warn': + elif on_returncode == "warn": subprocess_logger.warning( 'Command "%s" had error code %s in %s', command_desc, proc.returncode, cwd, ) - elif on_returncode == 'ignore': + elif on_returncode == "ignore": pass else: - raise ValueError('Invalid value: on_returncode={!r}'.format( - on_returncode)) + raise ValueError("Invalid value: on_returncode={!r}".format(on_returncode)) return output @@ -282,7 +267,7 @@ def runner_with_spinner_message(message): def runner( cmd, # type: List[str] cwd=None, # type: Optional[str] - extra_environ=None # type: Optional[Mapping[str, Any]] + extra_environ=None, # type: Optional[Mapping[str, Any]] ): # type: (...) -> None with open_spinner(message) as spinner: diff --git a/src/pip/_internal/utils/temp_dir.py b/src/pip/_internal/utils/temp_dir.py index 872b5b55fd5..477cbe6b1aa 100644 --- a/src/pip/_internal/utils/temp_dir.py +++ b/src/pip/_internal/utils/temp_dir.py @@ -4,18 +4,14 @@ import os.path import tempfile from contextlib import ExitStack, contextmanager -from typing import TYPE_CHECKING +from typing import Any, Dict, Iterator, Optional, TypeVar, Union from pip._internal.utils.misc import enum, rmtree -if TYPE_CHECKING: - from typing import Any, Dict, Iterator, Optional, TypeVar, Union - - _T = TypeVar('_T', bound='TempDirectory') - - logger = logging.getLogger(__name__) +_T = TypeVar("_T", bound="TempDirectory") + # Kinds of temporary directories. Only needed for ones that are # globally-managed. @@ -42,8 +38,7 @@ def global_tempdir_manager(): class TempDirectoryTypeRegistry: - """Manages temp directory behavior - """ + """Manages temp directory behavior""" def __init__(self): # type: () -> None @@ -112,7 +107,7 @@ class TempDirectory: def __init__( self, - path=None, # type: Optional[str] + path=None, # type: Optional[str] delete=_default, # type: Union[bool, None, _Default] kind="temp", # type: str globally_managed=False, # type: bool @@ -146,9 +141,7 @@ def __init__( @property def path(self): # type: () -> str - assert not self._deleted, ( - f"Attempted to access deleted path: {self._path}" - ) + assert not self._deleted, f"Attempted to access deleted path: {self._path}" return self._path def __repr__(self): @@ -173,22 +166,18 @@ def __exit__(self, exc, value, tb): def _create(self, kind): # type: (str) -> str - """Create a temporary directory and store its path in self.path - """ + """Create a temporary directory and store its path in self.path""" # We realpath here because some systems have their default tmpdir # symlinked to another directory. This tends to confuse build # scripts, so we canonicalize the path by traversing potential # symlinks here. - path = os.path.realpath( - tempfile.mkdtemp(prefix=f"pip-{kind}-") - ) + path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-")) logger.debug("Created temporary directory: %s", path) return path def cleanup(self): # type: () -> None - """Remove the temporary directory created and reset state - """ + """Remove the temporary directory created and reset state""" self._deleted = True if not os.path.exists(self._path): return @@ -209,6 +198,7 @@ class AdjacentTempDirectory(TempDirectory): (when used as a contextmanager) """ + # The characters that may be used to name the temp directory # We always prepend a ~ and then rotate through these until # a usable name is found. @@ -218,7 +208,7 @@ class AdjacentTempDirectory(TempDirectory): def __init__(self, original, delete=None): # type: (str, Optional[bool]) -> None - self.original = original.rstrip('/\\') + self.original = original.rstrip("/\\") super().__init__(delete=delete) @classmethod @@ -233,16 +223,18 @@ def _generate_names(cls, name): """ for i in range(1, len(name)): for candidate in itertools.combinations_with_replacement( - cls.LEADING_CHARS, i - 1): - new_name = '~' + ''.join(candidate) + name[i:] + cls.LEADING_CHARS, i - 1 + ): + new_name = "~" + "".join(candidate) + name[i:] if new_name != name: yield new_name # If we make it this far, we will have to make a longer name for i in range(len(cls.LEADING_CHARS)): for candidate in itertools.combinations_with_replacement( - cls.LEADING_CHARS, i): - new_name = '~' + ''.join(candidate) + name + cls.LEADING_CHARS, i + ): + new_name = "~" + "".join(candidate) + name if new_name != name: yield new_name @@ -262,9 +254,7 @@ def _create(self, kind): break else: # Final fallback on the default behavior. - path = os.path.realpath( - tempfile.mkdtemp(prefix=f"pip-{kind}-") - ) + path = os.path.realpath(tempfile.mkdtemp(prefix=f"pip-{kind}-")) logger.debug("Created temporary directory: %s", path) return path diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py index 86c474458e2..44ac475357d 100644 --- a/src/pip/_internal/utils/unpacking.py +++ b/src/pip/_internal/utils/unpacking.py @@ -7,7 +7,8 @@ import stat import tarfile import zipfile -from typing import TYPE_CHECKING +from typing import Iterable, List, Optional +from zipfile import ZipInfo from pip._internal.exceptions import InstallationError from pip._internal.utils.filetypes import ( @@ -18,11 +19,6 @@ ) from pip._internal.utils.misc import ensure_dir -if TYPE_CHECKING: - from typing import Iterable, List, Optional - from zipfile import ZipInfo - - logger = logging.getLogger(__name__) @@ -30,16 +26,18 @@ try: import bz2 # noqa + SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS except ImportError: - logger.debug('bz2 module is not available') + logger.debug("bz2 module is not available") try: # Only for Python 3.3+ import lzma # noqa + SUPPORTED_EXTENSIONS += XZ_EXTENSIONS except ImportError: - logger.debug('lzma module is not available') + logger.debug("lzma module is not available") def current_umask(): @@ -52,18 +50,15 @@ def current_umask(): def split_leading_dir(path): # type: (str) -> List[str] - path = path.lstrip('/').lstrip('\\') - if ( - '/' in path and ( - ('\\' in path and path.find('/') < path.find('\\')) or - '\\' not in path - ) + path = path.lstrip("/").lstrip("\\") + if "/" in path and ( + ("\\" in path and path.find("/") < path.find("\\")) or "\\" not in path ): - return path.split('/', 1) - elif '\\' in path: - return path.split('\\', 1) + return path.split("/", 1) + elif "\\" in path: + return path.split("\\", 1) else: - return [path, ''] + return [path, ""] def has_leading_dir(paths): @@ -122,7 +117,7 @@ def unzip_file(filename, location, flatten=True): no-ops per the python docs. """ ensure_dir(location) - zipfp = open(filename, 'rb') + zipfp = open(filename, "rb") try: zip = zipfile.ZipFile(zipfp, allowZip64=True) leading = has_leading_dir(zip.namelist()) and flatten @@ -135,11 +130,11 @@ def unzip_file(filename, location, flatten=True): dir = os.path.dirname(fn) if not is_within_directory(location, fn): message = ( - 'The zip file ({}) has a file ({}) trying to install ' - 'outside target directory ({})' + "The zip file ({}) has a file ({}) trying to install " + "outside target directory ({})" ) raise InstallationError(message.format(filename, fn, location)) - if fn.endswith('/') or fn.endswith('\\'): + if fn.endswith("/") or fn.endswith("\\"): # A directory ensure_dir(fn) else: @@ -148,7 +143,7 @@ def unzip_file(filename, location, flatten=True): # chunk of memory for the file's content fp = zip.open(name) try: - with open(fn, 'wb') as destfp: + with open(fn, "wb") as destfp: shutil.copyfileobj(fp, destfp) finally: fp.close() @@ -169,24 +164,23 @@ def untar_file(filename, location): no-ops per the python docs. """ ensure_dir(location) - if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'): - mode = 'r:gz' + if filename.lower().endswith(".gz") or filename.lower().endswith(".tgz"): + mode = "r:gz" elif filename.lower().endswith(BZ2_EXTENSIONS): - mode = 'r:bz2' + mode = "r:bz2" elif filename.lower().endswith(XZ_EXTENSIONS): - mode = 'r:xz' - elif filename.lower().endswith('.tar'): - mode = 'r' + mode = "r:xz" + elif filename.lower().endswith(".tar"): + mode = "r" else: logger.warning( - 'Cannot determine compression type for file %s', filename, + "Cannot determine compression type for file %s", + filename, ) - mode = 'r:*' + mode = "r:*" tar = tarfile.open(filename, mode) try: - leading = has_leading_dir([ - member.name for member in tar.getmembers() - ]) + leading = has_leading_dir([member.name for member in tar.getmembers()]) for member in tar.getmembers(): fn = member.name if leading: @@ -194,12 +188,10 @@ def untar_file(filename, location): path = os.path.join(location, fn) if not is_within_directory(location, path): message = ( - 'The tar file ({}) has a file ({}) trying to install ' - 'outside target directory ({})' - ) - raise InstallationError( - message.format(filename, path, location) + "The tar file ({}) has a file ({}) trying to install " + "outside target directory ({})" ) + raise InstallationError(message.format(filename, path, location)) if member.isdir(): ensure_dir(path) elif member.issym(): @@ -210,8 +202,10 @@ def untar_file(filename, location): # Some corrupt tar files seem to produce this # (specifically bad symlinks) logger.warning( - 'In the tar file %s the member %s is invalid: %s', - filename, member.name, exc, + "In the tar file %s the member %s is invalid: %s", + filename, + member.name, + exc, ) continue else: @@ -221,13 +215,15 @@ def untar_file(filename, location): # Some corrupt tar files seem to produce this # (specifically bad symlinks) logger.warning( - 'In the tar file %s the member %s is invalid: %s', - filename, member.name, exc, + "In the tar file %s the member %s is invalid: %s", + filename, + member.name, + exc, ) continue ensure_dir(os.path.dirname(path)) assert fp is not None - with open(path, 'wb') as destfp: + with open(path, "wb") as destfp: shutil.copyfileobj(fp, destfp) fp.close() # Update the timestamp (useful for cython compiled files) @@ -240,38 +236,32 @@ def untar_file(filename, location): def unpack_file( - filename, # type: str - location, # type: str - content_type=None, # type: Optional[str] + filename, # type: str + location, # type: str + content_type=None, # type: Optional[str] ): # type: (...) -> None filename = os.path.realpath(filename) if ( - content_type == 'application/zip' or - filename.lower().endswith(ZIP_EXTENSIONS) or - zipfile.is_zipfile(filename) + content_type == "application/zip" + or filename.lower().endswith(ZIP_EXTENSIONS) + or zipfile.is_zipfile(filename) ): - unzip_file( - filename, - location, - flatten=not filename.endswith('.whl') - ) + unzip_file(filename, location, flatten=not filename.endswith(".whl")) elif ( - content_type == 'application/x-gzip' or - tarfile.is_tarfile(filename) or - filename.lower().endswith( - TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS - ) + content_type == "application/x-gzip" + or tarfile.is_tarfile(filename) + or filename.lower().endswith(TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS) ): untar_file(filename, location) else: # FIXME: handle? # FIXME: magic signatures? logger.critical( - 'Cannot unpack file %s (downloaded from %s, content-type: %s); ' - 'cannot detect archive format', - filename, location, content_type, - ) - raise InstallationError( - f'Cannot determine archive format of {location}' + "Cannot unpack file %s (downloaded from %s, content-type: %s); " + "cannot detect archive format", + filename, + location, + content_type, ) + raise InstallationError(f"Cannot determine archive format of {location}") diff --git a/src/pip/_internal/utils/urls.py b/src/pip/_internal/utils/urls.py index da8e91a4bdc..50a04d861d4 100644 --- a/src/pip/_internal/utils/urls.py +++ b/src/pip/_internal/utils/urls.py @@ -2,17 +2,14 @@ import sys import urllib.parse import urllib.request -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Optional +from typing import Optional def get_url_scheme(url): # type: (str) -> Optional[str] - if ':' not in url: + if ":" not in url: return None - return url.split(':', 1)[0].lower() + return url.split(":", 1)[0].lower() def path_to_url(path): @@ -22,7 +19,7 @@ def path_to_url(path): quoted path parts. """ path = os.path.normpath(os.path.abspath(path)) - url = urllib.parse.urljoin('file:', urllib.request.pathname2url(path)) + url = urllib.parse.urljoin("file:", urllib.request.pathname2url(path)) return url @@ -31,22 +28,21 @@ def url_to_path(url): """ Convert a file: URL to a path. """ - assert url.startswith('file:'), ( - "You can only turn file: urls into filenames (not {url!r})" - .format(**locals())) + assert url.startswith( + "file:" + ), f"You can only turn file: urls into filenames (not {url!r})" _, netloc, path, _, _ = urllib.parse.urlsplit(url) - if not netloc or netloc == 'localhost': + if not netloc or netloc == "localhost": # According to RFC 8089, same as empty authority. - netloc = '' - elif sys.platform == 'win32': + netloc = "" + elif sys.platform == "win32": # If we have a UNC path, prepend UNC share notation. - netloc = '\\\\' + netloc + netloc = "\\\\" + netloc else: raise ValueError( - 'non-local file URIs are not supported on this platform: {url!r}' - .format(**locals()) + f"non-local file URIs are not supported on this platform: {url!r}" ) path = urllib.request.url2pathname(netloc + path) diff --git a/src/pip/_internal/utils/virtualenv.py b/src/pip/_internal/utils/virtualenv.py index eb91c907185..51cacb55ca1 100644 --- a/src/pip/_internal/utils/virtualenv.py +++ b/src/pip/_internal/utils/virtualenv.py @@ -3,10 +3,7 @@ import re import site import sys -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import List, Optional +from typing import List, Optional logger = logging.getLogger(__name__) _INCLUDE_SYSTEM_SITE_PACKAGES_REGEX = re.compile( @@ -30,13 +27,12 @@ def _running_under_regular_virtualenv(): This handles virtual environments created with pypa's virtualenv. """ # pypa/virtualenv case - return hasattr(sys, 'real_prefix') + return hasattr(sys, "real_prefix") def running_under_virtualenv(): # type: () -> bool - """Return True if we're running inside a virtualenv, False otherwise. - """ + """Return True if we're running inside a virtualenv, False otherwise.""" return _running_under_venv() or _running_under_regular_virtualenv() @@ -46,11 +42,11 @@ def _get_pyvenv_cfg_lines(): Returns None, if it could not read/access the file. """ - pyvenv_cfg_file = os.path.join(sys.prefix, 'pyvenv.cfg') + pyvenv_cfg_file = os.path.join(sys.prefix, "pyvenv.cfg") try: # Although PEP 405 does not specify, the built-in venv module always # writes with UTF-8. (pypa/pip#8717) - with open(pyvenv_cfg_file, encoding='utf-8') as f: + with open(pyvenv_cfg_file, encoding="utf-8") as f: return f.read().splitlines() # avoids trailing newlines except OSError: return None @@ -81,7 +77,7 @@ def _no_global_under_venv(): for line in cfg_lines: match = _INCLUDE_SYSTEM_SITE_PACKAGES_REGEX.match(line) - if match is not None and match.group('value') == 'false': + if match is not None and match.group("value") == "false": return True return False @@ -95,15 +91,15 @@ def _no_global_under_regular_virtualenv(): """ site_mod_dir = os.path.dirname(os.path.abspath(site.__file__)) no_global_site_packages_file = os.path.join( - site_mod_dir, 'no-global-site-packages.txt', + site_mod_dir, + "no-global-site-packages.txt", ) return os.path.exists(no_global_site_packages_file) def virtualenv_no_global(): # type: () -> bool - """Returns a boolean, whether running in venv with no system site-packages. - """ + """Returns a boolean, whether running in venv with no system site-packages.""" # PEP 405 compliance needs to be checked first since virtualenv >=20 would # return True for both checks, but is only able to use the PEP 405 config. if _running_under_venv(): diff --git a/src/pip/_internal/utils/wheel.py b/src/pip/_internal/utils/wheel.py index 65ebf6424df..982508eb4f6 100644 --- a/src/pip/_internal/utils/wheel.py +++ b/src/pip/_internal/utils/wheel.py @@ -2,23 +2,17 @@ """ import logging +from email.message import Message from email.parser import Parser -from typing import TYPE_CHECKING +from typing import Dict, Tuple from zipfile import BadZipFile, ZipFile from pip._vendor.packaging.utils import canonicalize_name -from pip._vendor.pkg_resources import DistInfoDistribution +from pip._vendor.pkg_resources import DistInfoDistribution, Distribution from pip._internal.exceptions import UnsupportedWheel from pip._internal.utils.pkg_resources import DictMetadata -if TYPE_CHECKING: - from email.message import Message - from typing import Dict, Tuple - - from pip._vendor.pkg_resources import Distribution - - VERSION_COMPATIBLE = (1, 0) @@ -29,6 +23,7 @@ class WheelMetadata(DictMetadata): """Metadata provider that maps metadata decoding exceptions to our internal exception type. """ + def __init__(self, metadata, wheel_name): # type: (Dict[str, bytes], str) -> None super().__init__(metadata) @@ -41,9 +36,7 @@ def get_metadata(self, name): except UnicodeDecodeError as e: # Augment the default error with the origin of the file. raise UnsupportedWheel( - "Error decoding metadata for {}: {}".format( - self._wheel_name, e - ) + "Error decoding metadata for {}: {}".format(self._wheel_name, e) ) @@ -55,9 +48,7 @@ def pkg_resources_distribution_for_wheel(wheel_zip, name, location): """ info_dir, _ = parse_wheel(wheel_zip, name) - metadata_files = [ - p for p in wheel_zip.namelist() if p.startswith(f"{info_dir}/") - ] + metadata_files = [p for p in wheel_zip.namelist() if p.startswith(f"{info_dir}/")] metadata_text = {} # type: Dict[str, bytes] for path in metadata_files: @@ -66,15 +57,11 @@ def pkg_resources_distribution_for_wheel(wheel_zip, name, location): try: metadata_text[metadata_name] = read_wheel_metadata_file(wheel_zip, path) except UnsupportedWheel as e: - raise UnsupportedWheel( - "{} has an invalid wheel, {}".format(name, str(e)) - ) + raise UnsupportedWheel("{} has an invalid wheel, {}".format(name, str(e))) metadata = WheelMetadata(metadata_text, location) - return DistInfoDistribution( - location=location, metadata=metadata, project_name=name - ) + return DistInfoDistribution(location=location, metadata=metadata, project_name=name) def parse_wheel(wheel_zip, name): @@ -89,9 +76,7 @@ def parse_wheel(wheel_zip, name): metadata = wheel_metadata(wheel_zip, info_dir) version = wheel_version(metadata) except UnsupportedWheel as e: - raise UnsupportedWheel( - "{} has an invalid wheel, {}".format(name, str(e)) - ) + raise UnsupportedWheel("{} has an invalid wheel, {}".format(name, str(e))) check_compatibility(version, name) @@ -108,16 +93,14 @@ def wheel_dist_info_dir(source, name): # Zip file path separators must be / subdirs = {p.split("/", 1)[0] for p in source.namelist()} - info_dirs = [s for s in subdirs if s.endswith('.dist-info')] + info_dirs = [s for s in subdirs if s.endswith(".dist-info")] if not info_dirs: raise UnsupportedWheel(".dist-info directory not found") if len(info_dirs) > 1: raise UnsupportedWheel( - "multiple .dist-info directories found: {}".format( - ", ".join(info_dirs) - ) + "multiple .dist-info directories found: {}".format(", ".join(info_dirs)) ) info_dir = info_dirs[0] @@ -141,9 +124,7 @@ def read_wheel_metadata_file(source, path): # BadZipFile for general corruption, KeyError for missing entry, # and RuntimeError for password-protected files except (BadZipFile, KeyError, RuntimeError) as e: - raise UnsupportedWheel( - f"could not read {path!r} file: {e!r}" - ) + raise UnsupportedWheel(f"could not read {path!r} file: {e!r}") def wheel_metadata(source, dist_info_dir): @@ -178,7 +159,7 @@ def wheel_version(wheel_data): version = version_text.strip() try: - return tuple(map(int, version.split('.'))) + return tuple(map(int, version.split("."))) except ValueError: raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}") @@ -199,10 +180,10 @@ def check_compatibility(version, name): if version[0] > VERSION_COMPATIBLE[0]: raise UnsupportedWheel( "{}'s Wheel-Version ({}) is not compatible with this version " - "of pip".format(name, '.'.join(map(str, version))) + "of pip".format(name, ".".join(map(str, version))) ) elif version > VERSION_COMPATIBLE: logger.warning( - 'Installing from a newer Wheel-Version (%s)', - '.'.join(map(str, version)), + "Installing from a newer Wheel-Version (%s)", + ".".join(map(str, version)), ) diff --git a/src/pip/_internal/vcs/__init__.py b/src/pip/_internal/vcs/__init__.py index 2ed7c177f7c..30025d632a9 100644 --- a/src/pip/_internal/vcs/__init__.py +++ b/src/pip/_internal/vcs/__init__.py @@ -1,7 +1,6 @@ # Expose a limited set of classes and functions so callers outside of # the vcs package don't need to import deeper than `pip._internal.vcs`. -# (The test directory and imports protected by TYPE_CHECKING may -# still need to import from a vcs sub-package.) +# (The test directory may still need to import from a vcs sub-package.) # Import all vcs modules to register each VCS in the VcsSupport object. import pip._internal.vcs.bazaar import pip._internal.vcs.git diff --git a/src/pip/_internal/vcs/bazaar.py b/src/pip/_internal/vcs/bazaar.py index 6ccf8df5b96..3d603727c57 100644 --- a/src/pip/_internal/vcs/bazaar.py +++ b/src/pip/_internal/vcs/bazaar.py @@ -1,18 +1,17 @@ import logging import os -from typing import TYPE_CHECKING +from typing import List, Optional, Tuple -from pip._internal.utils.misc import display_path, rmtree +from pip._internal.utils.misc import HiddenText, display_path, rmtree from pip._internal.utils.subprocess import make_command from pip._internal.utils.urls import path_to_url -from pip._internal.vcs.versioncontrol import RemoteNotFoundError, VersionControl, vcs - -if TYPE_CHECKING: - from typing import List, Optional, Tuple - - from pip._internal.utils.misc import HiddenText - from pip._internal.vcs.versioncontrol import AuthInfo, RevOptions - +from pip._internal.vcs.versioncontrol import ( + AuthInfo, + RemoteNotFoundError, + RevOptions, + VersionControl, + vcs, +) logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/vcs/git.py b/src/pip/_internal/vcs/git.py index 7c7104c9f35..e0704091dc6 100644 --- a/src/pip/_internal/vcs/git.py +++ b/src/pip/_internal/vcs/git.py @@ -3,30 +3,24 @@ import re import urllib.parse import urllib.request -from typing import TYPE_CHECKING +from typing import List, Optional, Tuple +from pip._vendor.packaging.version import _BaseVersion from pip._vendor.packaging.version import parse as parse_version from pip._internal.exceptions import BadCommand, InstallationError -from pip._internal.utils.misc import display_path, hide_url +from pip._internal.utils.misc import HiddenText, display_path, hide_url from pip._internal.utils.subprocess import make_command from pip._internal.utils.temp_dir import TempDirectory from pip._internal.vcs.versioncontrol import ( + AuthInfo, RemoteNotFoundError, + RevOptions, VersionControl, find_path_to_setup_from_repo_root, vcs, ) -if TYPE_CHECKING: - from typing import List, Optional, Tuple - - from pip._vendor.packaging.version import _BaseVersion - - from pip._internal.utils.misc import HiddenText - from pip._internal.vcs.versioncontrol import AuthInfo, RevOptions - - urlsplit = urllib.parse.urlsplit urlunsplit = urllib.parse.urlunsplit diff --git a/src/pip/_internal/vcs/mercurial.py b/src/pip/_internal/vcs/mercurial.py index 079672191f9..fdd71f43894 100644 --- a/src/pip/_internal/vcs/mercurial.py +++ b/src/pip/_internal/vcs/mercurial.py @@ -1,26 +1,20 @@ import configparser import logging import os -from typing import TYPE_CHECKING +from typing import List, Optional from pip._internal.exceptions import BadCommand, InstallationError -from pip._internal.utils.misc import display_path +from pip._internal.utils.misc import HiddenText, display_path from pip._internal.utils.subprocess import make_command from pip._internal.utils.temp_dir import TempDirectory from pip._internal.utils.urls import path_to_url from pip._internal.vcs.versioncontrol import ( + RevOptions, VersionControl, find_path_to_setup_from_repo_root, vcs, ) -if TYPE_CHECKING: - from typing import List, Optional - - from pip._internal.utils.misc import HiddenText - from pip._internal.vcs.versioncontrol import RevOptions - - logger = logging.getLogger(__name__) diff --git a/src/pip/_internal/vcs/subversion.py b/src/pip/_internal/vcs/subversion.py index 2ddb9c0a150..f58264446eb 100644 --- a/src/pip/_internal/vcs/subversion.py +++ b/src/pip/_internal/vcs/subversion.py @@ -1,17 +1,26 @@ import logging import os import re -from typing import TYPE_CHECKING +from typing import List, Optional, Tuple from pip._internal.utils.logging import indent_log from pip._internal.utils.misc import ( + HiddenText, display_path, is_console_interactive, rmtree, split_auth_from_netloc, ) -from pip._internal.utils.subprocess import make_command -from pip._internal.vcs.versioncontrol import RemoteNotFoundError, VersionControl, vcs +from pip._internal.utils.subprocess import CommandArgs, make_command +from pip._internal.vcs.versioncontrol import ( + AuthInfo, + RemoteNotFoundError, + RevOptions, + VersionControl, + vcs, +) + +logger = logging.getLogger(__name__) _svn_xml_url_re = re.compile('url="([^"]+)"') _svn_rev_re = re.compile(r'committed-rev="(\d+)"') @@ -19,17 +28,6 @@ _svn_info_xml_url_re = re.compile(r'(.*)') -if TYPE_CHECKING: - from typing import List, Optional, Tuple - - from pip._internal.utils.misc import HiddenText - from pip._internal.utils.subprocess import CommandArgs - from pip._internal.vcs.versioncontrol import AuthInfo, RevOptions - - -logger = logging.getLogger(__name__) - - class Subversion(VersionControl): name = 'svn' dirname = '.svn' @@ -161,8 +159,7 @@ def _get_svn_url_rev(cls, location): elif data.startswith(' bool @@ -68,7 +60,7 @@ def make_vcs_requirement_url(repo_url, rev, project_name, subdir=None): repo_url: the remote VCS url, with any needed VCS prefix (e.g. "git+"). project_name: the (unescaped) project name. """ - egg_project_name = pkg_resources.to_filename(project_name) + egg_project_name = project_name.replace("-", "_") req = f'{repo_url}@{rev}#egg={egg_project_name}' if subdir: req += f'&subdirectory={subdir}' @@ -690,9 +682,8 @@ def run_command( # errno.ENOENT = no such file or directory # In other words, the VCS executable isn't available raise BadCommand( - 'Cannot find command {cls.name!r} - do you have ' - '{cls.name!r} installed and in your ' - 'PATH?'.format(**locals())) + f'Cannot find command {cls.name!r} - do you have ' + f'{cls.name!r} installed and in your PATH?') @classmethod def is_repository_directory(cls, path): diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py index 4b72e512466..d6314714acd 100644 --- a/src/pip/_internal/wheel_builder.py +++ b/src/pip/_internal/wheel_builder.py @@ -5,17 +5,19 @@ import os.path import re import shutil -from typing import TYPE_CHECKING +from typing import Any, Callable, Iterable, List, Optional, Tuple from pip._vendor.packaging.utils import canonicalize_name, canonicalize_version from pip._vendor.packaging.version import InvalidVersion, Version +from pip._internal.cache import WheelCache from pip._internal.exceptions import InvalidWheelFilename, UnsupportedWheel from pip._internal.metadata import get_wheel_distribution from pip._internal.models.link import Link from pip._internal.models.wheel import Wheel from pip._internal.operations.build.wheel import build_wheel_pep517 from pip._internal.operations.build.wheel_legacy import build_wheel_legacy +from pip._internal.req.req_install import InstallRequirement from pip._internal.utils.logging import indent_log from pip._internal.utils.misc import ensure_dir, hash_file, is_wheel_installed from pip._internal.utils.setuptools_build import make_setuptools_clean_args @@ -24,19 +26,13 @@ from pip._internal.utils.urls import path_to_url from pip._internal.vcs import vcs -if TYPE_CHECKING: - from typing import Any, Callable, Iterable, List, Optional, Tuple - - from pip._internal.cache import WheelCache - from pip._internal.req.req_install import InstallRequirement - - BinaryAllowedPredicate = Callable[[InstallRequirement], bool] - BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]] - logger = logging.getLogger(__name__) _egg_info_re = re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.IGNORECASE) +BinaryAllowedPredicate = Callable[[InstallRequirement], bool] +BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]] + def _contains_egg_info(s): # type: (str) -> bool diff --git a/tests/conftest.py b/tests/conftest.py index e54b9d75ce1..9d1ee4443b9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ import sys import time from contextlib import ExitStack, contextmanager -from typing import TYPE_CHECKING +from typing import Dict, Iterable from unittest.mock import patch import pytest @@ -19,17 +19,12 @@ from tests.lib import DATA_DIR, SRC_DIR, PipTestEnvironment, TestData from tests.lib.certs import make_tls_cert, serialize_cert, serialize_key from tests.lib.path import Path -from tests.lib.server import make_mock_server, server_running +from tests.lib.server import MockServer as _MockServer +from tests.lib.server import Responder, make_mock_server, server_running from tests.lib.venv import VirtualEnvironment from .lib.compat import nullcontext -if TYPE_CHECKING: - from typing import Dict, Iterable - - from tests.lib.server import MockServer as _MockServer - from tests.lib.server import Responder - def pytest_addoption(parser): parser.addoption( @@ -282,12 +277,12 @@ def not_code_files_and_folders(path, names): def _common_wheel_editable_install(tmpdir_factory, common_wheels, package): wheel_candidates = list( - common_wheels.glob('{package}-*.whl'.format(**locals()))) + common_wheels.glob(f'{package}-*.whl')) assert len(wheel_candidates) == 1, wheel_candidates install_dir = Path(str(tmpdir_factory.mktemp(package))) / 'install' Wheel(wheel_candidates[0]).install_as_egg(install_dir) (install_dir / 'EGG-INFO').rename( - install_dir / '{package}.egg-info'.format(**locals())) + install_dir / f'{package}.egg-info') assert compileall.compile_dir(str(install_dir), quiet=1) return install_dir diff --git a/tests/data/src/chattymodule/setup.py b/tests/data/src/chattymodule/setup.py index 01d7720765f..68099f2f8c9 100644 --- a/tests/data/src/chattymodule/setup.py +++ b/tests/data/src/chattymodule/setup.py @@ -5,7 +5,7 @@ from setuptools import setup -print("HELLO FROM CHATTYMODULE {sys.argv[1]}".format(**locals())) +print(f"HELLO FROM CHATTYMODULE {sys.argv[1]}") print(os.environ) print(sys.argv) if "--fail" in sys.argv: diff --git a/tests/functional/test_build_env.py b/tests/functional/test_build_env.py index 7a392f42646..b3f51a8808e 100644 --- a/tests/functional/test_build_env.py +++ b/tests/functional/test_build_env.py @@ -79,15 +79,15 @@ def test_build_env_allow_only_one_install(script): for prefix in ('normal', 'overlay'): build_env.install_requirements( finder, ['foo'], prefix, - 'installing foo in {prefix}'.format(**locals())) + f'installing foo in {prefix}') with pytest.raises(AssertionError): build_env.install_requirements( finder, ['bar'], prefix, - 'installing bar in {prefix}'.format(**locals())) + f'installing bar in {prefix}') with pytest.raises(AssertionError): build_env.install_requirements( finder, [], prefix, - 'installing in {prefix}'.format(**locals())) + f'installing in {prefix}') def test_build_env_requirements_check(script): @@ -201,7 +201,7 @@ def test_build_env_isolation(script): pass else: print( - 'imported `pkg` from `{pkg.__file__}`'.format(**locals()), + f'imported `pkg` from `{pkg.__file__}`', file=sys.stderr) print('system sites:\n ' + '\n '.join(sorted({ get_python_lib(plat_specific=0), diff --git a/tests/functional/test_completion.py b/tests/functional/test_completion.py index a3986811b6f..8a7464982ee 100644 --- a/tests/functional/test_completion.py +++ b/tests/functional/test_completion.py @@ -230,7 +230,7 @@ def test_completion_not_files_after_nonexpecting_option( (e.g. ``pip install``) """ res, env = autocomplete( - words=('pip install {cl_opts} r'.format(**locals())), + words=(f'pip install {cl_opts} r'), cword='2', cwd=data.completion_paths, ) diff --git a/tests/functional/test_freeze.py b/tests/functional/test_freeze.py index 5d3d4968619..4fd91b5d8e5 100644 --- a/tests/functional/test_freeze.py +++ b/tests/functional/test_freeze.py @@ -42,7 +42,7 @@ def _check_output(result, expected): actual = distribute_re.sub('', actual) def banner(msg): - return '\n========== {msg} ==========\n'.format(**locals()) + return f'\n========== {msg} ==========\n' assert checker.check_output(expected, actual, ELLIPSIS), ( banner('EXPECTED') + expected + banner('ACTUAL') + actual + @@ -272,7 +272,7 @@ def test_freeze_git_clone(script, tmpdir): _check_output(result.stdout, expected) result = script.pip( - 'freeze', '-f', '{repo_dir}#egg=pip_test_package'.format(**locals()), + 'freeze', '-f', f'{repo_dir}#egg=pip_test_package', expect_stderr=True, ) expected = textwrap.dedent( @@ -337,7 +337,7 @@ def test_freeze_git_clone_srcdir(script, tmpdir): _check_output(result.stdout, expected) result = script.pip( - 'freeze', '-f', '{repo_dir}#egg=pip_test_package'.format(**locals()), + 'freeze', '-f', f'{repo_dir}#egg=pip_test_package', expect_stderr=True, ) expected = textwrap.dedent( @@ -378,7 +378,7 @@ def test_freeze_mercurial_clone_srcdir(script, tmpdir): _check_output(result.stdout, expected) result = script.pip( - 'freeze', '-f', '{repo_dir}#egg=pip_test_package'.format(**locals()), + 'freeze', '-f', f'{repo_dir}#egg=pip_test_package', expect_stderr=True, ) expected = textwrap.dedent( @@ -473,7 +473,7 @@ def test_freeze_mercurial_clone(script, tmpdir): _check_output(result.stdout, expected) result = script.pip( - 'freeze', '-f', '{repo_dir}#egg=pip_test_package'.format(**locals()), + 'freeze', '-f', f'{repo_dir}#egg=pip_test_package', expect_stderr=True, ) expected = textwrap.dedent( @@ -513,7 +513,7 @@ def test_freeze_bazaar_clone(script, tmpdir): result = script.pip( 'freeze', '-f', - '{checkout_path}/#egg=django-wikiapp'.format(**locals()), + f'{checkout_path}/#egg=django-wikiapp', expect_stderr=True, ) expected = textwrap.dedent("""\ diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index 8bb8b01fa7c..3ab2c401597 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -23,9 +23,7 @@ need_svn, path_to_url, pyversion, - pyversion_tuple, requirements_file, - windows_workaround_7667, ) from tests.lib.filesystem import make_socket_file from tests.lib.local_repos import local_checkout @@ -193,16 +191,10 @@ def test_pip_second_command_line_interface_works( """ # Re-install pip so we get the launchers. script.pip_install_local('-f', common_wheels, pip_src) - # On old versions of Python, urllib3/requests will raise a warning about - # the lack of an SSLContext. - kwargs = {'expect_stderr': deprecated_python} - if pyversion_tuple < (2, 7, 9): - kwargs['expect_stderr'] = True - - args = ['pip{pyversion}'.format(**globals())] + args = [f'pip{pyversion}'] args.extend(['install', 'INITools==0.2']) args.extend(['-f', data.packages]) - result = script.run(*args, **kwargs) + result = script.run(*args) dist_info_folder = ( script.site_packages / 'INITools-0.2.dist-info' @@ -578,7 +570,29 @@ def test_install_from_local_directory_with_symlinks_to_directories( result.did_create(dist_info_folder) -@pytest.mark.skipif("sys.platform == 'win32' or sys.version_info < (3,)") +def test_install_from_local_directory_with_in_tree_build( + script, data, with_wheel +): + """ + Test installing from a local directory with --use-feature=in-tree-build. + """ + to_install = data.packages.joinpath("FSPkg") + args = ["install", "--use-feature=in-tree-build", to_install] + + in_tree_build_dir = to_install / "build" + assert not in_tree_build_dir.exists() + result = script.pip(*args) + fspkg_folder = script.site_packages / 'fspkg' + dist_info_folder = ( + script.site_packages / + 'FSPkg-0.1.dev0.dist-info' + ) + result.did_create(fspkg_folder) + result.did_create(dist_info_folder) + assert in_tree_build_dir.exists() + + +@pytest.mark.skipif("sys.platform == 'win32'") def test_install_from_local_directory_with_socket_file( script, data, tmpdir, with_wheel ): @@ -724,11 +738,10 @@ def test_install_using_install_option_and_editable(script, tmpdir): """ folder = 'script_folder' script.scratch_path.joinpath(folder).mkdir() - url = 'git+git://github.com/pypa/pip-test-package' + url = local_checkout('git+git://github.com/pypa/pip-test-package', tmpdir) result = script.pip( - 'install', '-e', '{url}#egg=pip-test-package' - .format(url=local_checkout(url, tmpdir)), - '--install-option=--script-dir={folder}'.format(**locals()), + 'install', '-e', f'{url}#egg=pip-test-package', + f'--install-option=--script-dir={folder}', expect_stderr=True) script_file = ( script.venv / 'src' / 'pip-test-package' / @@ -740,7 +753,6 @@ def test_install_using_install_option_and_editable(script, tmpdir): @pytest.mark.xfail @pytest.mark.network @need_mercurial -@windows_workaround_7667 def test_install_global_option_using_editable(script, tmpdir): """ Test using global distutils options, but in an editable installation @@ -796,10 +808,7 @@ def test_install_folder_using_slash_in_the_end(script, with_wheel): pkg_path = script.scratch_path / 'mock' pkg_path.joinpath("setup.py").write_text(mock100_setup_py) result = script.pip('install', 'mock' + os.path.sep) - dist_info_folder = ( - script.site_packages / - 'mock-100.1.dist-info' - ) + dist_info_folder = script.site_packages / 'mock-100.1.dist-info' result.did_create(dist_info_folder) @@ -812,10 +821,7 @@ def test_install_folder_using_relative_path(script, with_wheel): pkg_path = script.scratch_path / 'initools' / 'mock' pkg_path.joinpath("setup.py").write_text(mock100_setup_py) result = script.pip('install', Path('initools') / 'mock') - dist_info_folder = ( - script.site_packages / - 'mock-100.1.dist-info'.format(**globals()) - ) + dist_info_folder = script.site_packages / 'mock-100.1.dist-info' result.did_create(dist_info_folder) @@ -1291,11 +1297,11 @@ def test_install_subprocess_output_handling(script, data): def test_install_log(script, data, tmpdir): # test that verbose logs go to "--log" file f = tmpdir.joinpath("log.txt") - args = ['--log={f}'.format(**locals()), + args = [f'--log={f}', 'install', data.src.joinpath('chattymodule')] result = script.pip(*args) assert 0 == result.stdout.count("HELLO FROM CHATTYMODULE") - with open(f, 'r') as fp: + with open(f) as fp: # one from egg_info, one from install assert 2 == fp.read().count("HELLO FROM CHATTYMODULE") @@ -1318,7 +1324,8 @@ def test_cleanup_after_failed_wheel(script, with_wheel): # One of the effects of not cleaning up is broken scripts: script_py = script.bin_path / "script.py" assert script_py.exists(), script_py - shebang = open(script_py, 'r').readline().strip() + with open(script_py) as f: + shebang = f.readline().strip() assert shebang != '#!python', shebang # OK, assert that we *said* we were cleaning up: # /!\ if in need to change this, also change test_pep517_no_legacy_cleanup @@ -1389,7 +1396,6 @@ def test_install_no_binary_disables_building_wheels(script, data, with_wheel): @pytest.mark.network -@windows_workaround_7667 def test_install_no_binary_builds_pep_517_wheel(script, data, with_wheel): to_install = data.packages.joinpath('pep517_setup_and_pyproject') res = script.pip( @@ -1404,7 +1410,6 @@ def test_install_no_binary_builds_pep_517_wheel(script, data, with_wheel): @pytest.mark.network -@windows_workaround_7667 def test_install_no_binary_uses_local_backend( script, data, with_wheel, tmpdir): to_install = data.packages.joinpath('pep517_wrapper_buildsys') @@ -1445,7 +1450,7 @@ def test_install_editable_with_wrong_egg_name(script, resolver_variant): """)) result = script.pip( 'install', '--editable', - 'file://{pkga_path}#egg=pkgb'.format(**locals()), + f'file://{pkga_path}#egg=pkgb', expect_error=(resolver_variant == "2020-resolver"), ) assert ("Generating metadata for package pkgb produced metadata " @@ -1531,7 +1536,7 @@ def test_install_incompatible_python_requires_editable(script): """)) result = script.pip( 'install', - '--editable={pkga_path}'.format(**locals()), + f'--editable={pkga_path}', expect_error=True) assert _get_expected_error_text() in result.stderr, str(result) @@ -1648,7 +1653,7 @@ def test_installed_files_recorded_in_deterministic_order(script, data): to_install = data.packages.joinpath("FSPkg") result = script.pip('install', to_install) fspkg_folder = script.site_packages / 'fspkg' - egg_info = 'FSPkg-0.1.dev0-py{pyversion}.egg-info'.format(**globals()) + egg_info = f'FSPkg-0.1.dev0-py{pyversion}.egg-info' installed_files_path = ( script.site_packages / egg_info / 'installed-files.txt' ) @@ -1711,10 +1716,10 @@ def test_target_install_ignores_distutils_config_install_prefix(script): 'pydistutils.cfg' if sys.platform == 'win32' else '.pydistutils.cfg') distutils_config.write_text(textwrap.dedent( - ''' + f''' [install] prefix={prefix} - '''.format(**locals()))) + ''')) target = script.scratch_path / 'target' result = script.pip_install_local('simplewheel', '-t', target) diff --git a/tests/functional/test_install_compat.py b/tests/functional/test_install_compat.py index a5a0df65218..44b9b290e5d 100644 --- a/tests/functional/test_install_compat.py +++ b/tests/functional/test_install_compat.py @@ -26,7 +26,7 @@ def test_debian_egg_name_workaround(script): egg_info = os.path.join( script.site_packages, - "INITools-0.2-py{pyversion}.egg-info".format(**globals())) + f"INITools-0.2-py{pyversion}.egg-info") # Debian only removes pyversion for global installs, not inside a venv # so even if this test runs on a Debian/Ubuntu system with broken @@ -34,14 +34,14 @@ def test_debian_egg_name_workaround(script): # .egg-info result.did_create( egg_info, - message="Couldn't find {egg_info}".format(**locals()) + message=f"Couldn't find {egg_info}" ) # The Debian no-pyversion version of the .egg-info mangled = os.path.join(script.site_packages, "INITools-0.2.egg-info") result.did_not_create( mangled, - message="Found unexpected {mangled}".format(**locals()) + message=f"Found unexpected {mangled}" ) # Simulate a Debian install by copying the .egg-info to their name for it diff --git a/tests/functional/test_install_config.py b/tests/functional/test_install_config.py index 27e4f0b0cc4..ed33b0c9f83 100644 --- a/tests/functional/test_install_config.py +++ b/tests/functional/test_install_config.py @@ -112,7 +112,7 @@ def test_command_line_appends_correctly(script, data): """ script.environ['PIP_FIND_LINKS'] = ( - 'https://test.pypi.org {data.find_links}'.format(**locals()) + f'https://test.pypi.org {data.find_links}' ) result = script.pip( 'install', '-vvv', 'INITools', '--trusted-host', diff --git a/tests/functional/test_install_extras.py b/tests/functional/test_install_extras.py index e960100729f..de1ee3795ea 100644 --- a/tests/functional/test_install_extras.py +++ b/tests/functional/test_install_extras.py @@ -136,7 +136,7 @@ def test_install_special_extra(script): """)) result = script.pip( - 'install', '--no-index', '{pkga_path}[Hop_hOp-hoP]'.format(**locals()), + 'install', '--no-index', f'{pkga_path}[Hop_hOp-hoP]', expect_error=True) assert ( "Could not find a version that satisfies the requirement missing_pkg" @@ -165,7 +165,7 @@ def test_install_extra_merging(script, data, extra_to_install, simple_version): """)) result = script.pip_install_local( - '{pkga_path}{extra_to_install}'.format(**locals()), + f'{pkga_path}{extra_to_install}', ) assert f'Successfully installed pkga-0.1 simple-{simple_version}' in result.stdout diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py index 9c35aee8320..9e6e5580ae2 100644 --- a/tests/functional/test_install_reqs.py +++ b/tests/functional/test_install_reqs.py @@ -68,11 +68,11 @@ def test_requirements_file(script, with_wheel): """ other_lib_name, other_lib_version = 'anyjson', '0.3' - script.scratch_path.joinpath("initools-req.txt").write_text(textwrap.dedent("""\ + script.scratch_path.joinpath("initools-req.txt").write_text(textwrap.dedent(f"""\ INITools==0.2 # and something else to test out: {other_lib_name}<={other_lib_version} - """.format(**locals()))) + """)) result = script.pip( 'install', '-r', script.scratch_path / 'initools-req.txt' ) @@ -178,15 +178,14 @@ def test_multiple_requirements_files(script, tmpdir, with_wheel): other_lib_name ), ) - script.scratch_path.joinpath( - "{other_lib_name}-req.txt".format(**locals())).write_text( - "{other_lib_name}<={other_lib_version}".format(**locals()) + script.scratch_path.joinpath(f"{other_lib_name}-req.txt").write_text( + f"{other_lib_name}<={other_lib_version}" ) result = script.pip( 'install', '-r', script.scratch_path / 'initools-req.txt' ) assert result.files_created[script.site_packages / other_lib_name].dir - fn = '{other_lib_name}-{other_lib_version}.dist-info'.format(**locals()) + fn = f'{other_lib_name}-{other_lib_version}.dist-info' assert result.files_created[script.site_packages / fn].dir result.did_create(script.venv / 'src' / 'initools') @@ -295,9 +294,9 @@ def test_wheel_user_with_prefix_in_pydistutils_cfg( user_cfg = os.path.join(os.path.expanduser('~'), user_filename) script.scratch_path.joinpath("bin").mkdir() with open(user_cfg, "w") as cfg: - cfg.write(textwrap.dedent(""" + cfg.write(textwrap.dedent(f""" [install] - prefix={script.scratch_path}""".format(**locals()))) + prefix={script.scratch_path}""")) result = script.pip( 'install', '--user', '--no-index', @@ -559,8 +558,7 @@ def test_install_distribution_duplicate_extras(script, data): package_name = to_install + "[bar]" with pytest.raises(AssertionError): result = script.pip_install_local(package_name, package_name) - expected = ( - 'Double requirement given: {package_name}'.format(**locals())) + expected = (f'Double requirement given: {package_name}') assert expected in result.stderr @@ -571,7 +569,7 @@ def test_install_distribution_union_with_constraints( ): to_install = data.packages.joinpath("LocalExtras") script.scratch_path.joinpath("constraints.txt").write_text( - "{to_install}[bar]".format(**locals())) + f"{to_install}[bar]") result = script.pip_install_local( '-c', script.scratch_path / 'constraints.txt', to_install + '[baz]', allow_stderr_warning=True, @@ -647,9 +645,7 @@ def test_install_unsupported_wheel_file(script, data): # Trying to install a local wheel with an incompatible version/type # should fail. path = data.packages.joinpath("simple.dist-0.1-py1-none-invalid.whl") - script.scratch_path.joinpath("wheel-file.txt").write_text(textwrap.dedent("""\ - {path} - """.format(**locals()))) + script.scratch_path.joinpath("wheel-file.txt").write_text(path + '\n') result = script.pip( 'install', '-r', script.scratch_path / 'wheel-file.txt', expect_error=True, diff --git a/tests/functional/test_install_upgrade.py b/tests/functional/test_install_upgrade.py index 46aac8f9d26..d7586cd5835 100644 --- a/tests/functional/test_install_upgrade.py +++ b/tests/functional/test_install_upgrade.py @@ -421,8 +421,7 @@ class TestUpgradeDistributeToSetuptools: def prep_ve(self, script, version, pip_src, distribute=False): self.script = script - self.script.pip_install_local( - 'virtualenv=={version}'.format(**locals())) + self.script.pip_install_local(f'virtualenv=={version}') args = ['virtualenv', self.script.scratch_path / 'VE'] if distribute: args.insert(1, '--distribute') diff --git a/tests/functional/test_install_user.py b/tests/functional/test_install_user.py index c5d7acced80..538556ed907 100644 --- a/tests/functional/test_install_user.py +++ b/tests/functional/test_install_user.py @@ -118,8 +118,7 @@ def test_install_user_conflict_in_usersite(self, script): # usersite has 0.1 # we still test for egg-info because no-binary implies setup.py install egg_info_folder = ( - script.user_site / - 'INITools-0.1-py{pyversion}.egg-info'.format(**globals()) + script.user_site / f'INITools-0.1-py{pyversion}.egg-info' ) initools_v3_file = ( # file only in 0.3 @@ -146,8 +145,7 @@ def test_install_user_conflict_in_globalsite(self, virtualenv, script): # usersite has 0.1 # we still test for egg-info because no-binary implies setup.py install egg_info_folder = ( - script.user_site / - 'INITools-0.1-py{pyversion}.egg-info'.format(**globals()) + script.user_site / f'INITools-0.1-py{pyversion}.egg-info' ) initools_folder = script.user_site / 'initools' result2.did_create(egg_info_folder) @@ -156,7 +154,7 @@ def test_install_user_conflict_in_globalsite(self, virtualenv, script): # site still has 0.2 (can't look in result1; have to check) egg_info_folder = ( script.base_path / script.site_packages / - 'INITools-0.2-py{pyversion}.egg-info'.format(**globals()) + f'INITools-0.2-py{pyversion}.egg-info' ) initools_folder = script.base_path / script.site_packages / 'initools' assert isdir(egg_info_folder) @@ -178,8 +176,7 @@ def test_upgrade_user_conflict_in_globalsite(self, virtualenv, script): # usersite has 0.3.1 # we still test for egg-info because no-binary implies setup.py install egg_info_folder = ( - script.user_site / - 'INITools-0.3.1-py{pyversion}.egg-info'.format(**globals()) + script.user_site / f'INITools-0.3.1-py{pyversion}.egg-info' ) initools_folder = script.user_site / 'initools' result2.did_create(egg_info_folder) @@ -188,7 +185,7 @@ def test_upgrade_user_conflict_in_globalsite(self, virtualenv, script): # site still has 0.2 (can't look in result1; have to check) egg_info_folder = ( script.base_path / script.site_packages / - 'INITools-0.2-py{pyversion}.egg-info'.format(**globals()) + f'INITools-0.2-py{pyversion}.egg-info' ) initools_folder = script.base_path / script.site_packages / 'initools' assert isdir(egg_info_folder), result2.stdout @@ -213,8 +210,7 @@ def test_install_user_conflict_in_globalsite_and_usersite( # usersite has 0.1 # we still test for egg-info because no-binary implies setup.py install egg_info_folder = ( - script.user_site / - 'INITools-0.1-py{pyversion}.egg-info'.format(**globals()) + script.user_site / f'INITools-0.1-py{pyversion}.egg-info' ) initools_v3_file = ( # file only in 0.3 @@ -227,7 +223,7 @@ def test_install_user_conflict_in_globalsite_and_usersite( # site still has 0.2 (can't just look in result1; have to check) egg_info_folder = ( script.base_path / script.site_packages / - 'INITools-0.2-py{pyversion}.egg-info'.format(**globals()) + f'INITools-0.2-py{pyversion}.egg-info' ) initools_folder = script.base_path / script.site_packages / 'initools' assert isdir(egg_info_folder) diff --git a/tests/functional/test_no_color.py b/tests/functional/test_no_color.py index 48ed3ff7848..3fd943f9327 100644 --- a/tests/functional/test_no_color.py +++ b/tests/functional/test_no_color.py @@ -33,7 +33,7 @@ def get_run_output(option): pytest.skip("Unable to capture output using script: " + cmd) try: - with open("/tmp/pip-test-no-color.txt", "r") as output_file: + with open("/tmp/pip-test-no-color.txt") as output_file: retval = output_file.read() return retval finally: diff --git a/tests/functional/test_pep517.py b/tests/functional/test_pep517.py index bcad4793672..a747b8a0756 100644 --- a/tests/functional/test_pep517.py +++ b/tests/functional/test_pep517.py @@ -3,7 +3,7 @@ from pip._internal.build_env import BuildEnvironment from pip._internal.req import InstallRequirement -from tests.lib import make_test_finder, path_to_url, windows_workaround_7667 +from tests.lib import make_test_finder, path_to_url def make_project(tmpdir, requires=None, backend=None, backend_path=None): @@ -255,7 +255,6 @@ def test_explicit_setuptools_backend(script, tmpdir, data, common_wheels): @pytest.mark.network -@windows_workaround_7667 def test_pep517_and_build_options(script, tmpdir, data, common_wheels): """Backend generated requirements are installed in the build env""" project_dir, name = make_pyproject_with_setup(tmpdir) diff --git a/tests/functional/test_uninstall_user.py b/tests/functional/test_uninstall_user.py index 2dbf032ac38..7a0006d474b 100644 --- a/tests/functional/test_uninstall_user.py +++ b/tests/functional/test_uninstall_user.py @@ -46,7 +46,7 @@ def test_uninstall_from_usersite_with_dist_in_global_site( # keep checking for egg-info because no-binary implies setup.py install egg_info_folder = ( script.base_path / script.site_packages / - 'pip_test_package-0.1-py{pyversion}.egg-info'.format(**globals()) + f'pip_test_package-0.1-py{pyversion}.egg-info' ) assert isdir(egg_info_folder) diff --git a/tests/functional/test_vcs_bazaar.py b/tests/functional/test_vcs_bazaar.py index ad24d73d5ba..57fee51e780 100644 --- a/tests/functional/test_vcs_bazaar.py +++ b/tests/functional/test_vcs_bazaar.py @@ -64,7 +64,7 @@ def test_export_rev(script, tmpdir): url = hide_url('bzr+' + _test_path_to_file_url(source_dir) + '@1') Bazaar().export(str(export_dir), url=url) - with open(export_dir / 'test_file', 'r') as f: + with open(export_dir / 'test_file') as f: assert f.read() == 'something initial' diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py index c5e16803983..da040c30765 100644 --- a/tests/functional/test_wheel.py +++ b/tests/functional/test_wheel.py @@ -49,8 +49,7 @@ def test_pip_wheel_success(script, data): 'wheel', '--no-index', '-f', data.find_links, 'simple==3.0', ) - wheel_file_name = 'simple-3.0-py{pyversion[0]}-none-any.whl' \ - .format(**globals()) + wheel_file_name = f'simple-3.0-py{pyversion[0]}-none-any.whl' wheel_file_path = script.scratch / wheel_file_name assert re.search( r"Created wheel for simple: " @@ -70,8 +69,7 @@ def test_pip_wheel_build_cache(script, data): 'wheel', '--no-index', '-f', data.find_links, 'simple==3.0', ) - wheel_file_name = 'simple-3.0-py{pyversion[0]}-none-any.whl' \ - .format(**globals()) + wheel_file_name = f'simple-3.0-py{pyversion[0]}-none-any.whl' wheel_file_path = script.scratch / wheel_file_name result.did_create(wheel_file_path) assert "Successfully built simple" in result.stdout, result.stdout @@ -148,8 +146,7 @@ def test_pip_wheel_builds_editable_deps(script, data): 'wheel', '--no-index', '-f', data.find_links, '-e', editable_path ) - wheel_file_name = 'simple-1.0-py{pyversion[0]}-none-any.whl' \ - .format(**globals()) + wheel_file_name = f'simple-1.0-py{pyversion[0]}-none-any.whl' wheel_file_path = script.scratch / wheel_file_name result.did_create(wheel_file_path) @@ -163,8 +160,7 @@ def test_pip_wheel_builds_editable(script, data): 'wheel', '--no-index', '-f', data.find_links, '-e', editable_path ) - wheel_file_name = 'simplewheel-1.0-py{pyversion[0]}-none-any.whl' \ - .format(**globals()) + wheel_file_name = f'simplewheel-1.0-py{pyversion[0]}-none-any.whl' wheel_file_path = script.scratch / wheel_file_name result.did_create(wheel_file_path) @@ -213,8 +209,7 @@ def test_pip_wheel_fail(script, data): 'wheelbroken==0.1', expect_error=True, ) - wheel_file_name = 'wheelbroken-0.1-py{pyversion[0]}-none-any.whl' \ - .format(**globals()) + wheel_file_name = f'wheelbroken-0.1-py{pyversion[0]}-none-any.whl' wheel_file_path = script.scratch / wheel_file_name result.did_not_create(wheel_file_path) assert "FakeError" in result.stderr, result.stderr @@ -236,7 +231,7 @@ def test_no_clean_option_blocks_cleaning_after_wheel( build = script.venv_path / 'build' result = script.pip( 'wheel', '--no-clean', '--no-index', '--build', build, - '--find-links={data.find_links}'.format(**locals()), + f'--find-links={data.find_links}', 'simple', expect_temp=True, # TODO: allow_stderr_warning is used for the --build deprecation, @@ -260,8 +255,7 @@ def test_pip_wheel_source_deps(script, data): 'wheel', '--no-index', '-f', data.find_links, 'requires_source', ) - wheel_file_name = 'source-1.0-py{pyversion[0]}-none-any.whl' \ - .format(**globals()) + wheel_file_name = f'source-1.0-py{pyversion[0]}-none-any.whl' wheel_file_path = script.scratch / wheel_file_name result.did_create(wheel_file_path) assert "Successfully built source" in result.stdout, result.stdout @@ -278,8 +272,7 @@ def test_wheel_package_with_latin1_setup(script, data): def test_pip_wheel_with_pep518_build_reqs(script, data, common_wheels): result = script.pip('wheel', '--no-index', '-f', data.find_links, '-f', common_wheels, 'pep518==3.0',) - wheel_file_name = 'pep518-3.0-py{pyversion[0]}-none-any.whl' \ - .format(**globals()) + wheel_file_name = f'pep518-3.0-py{pyversion[0]}-none-any.whl' wheel_file_path = script.scratch / wheel_file_name result.did_create(wheel_file_path) assert "Successfully built pep518" in result.stdout, result.stdout @@ -292,8 +285,7 @@ def test_pip_wheel_with_pep518_build_reqs_no_isolation(script, data): 'wheel', '--no-index', '-f', data.find_links, '--no-build-isolation', 'pep518==3.0', ) - wheel_file_name = 'pep518-3.0-py{pyversion[0]}-none-any.whl' \ - .format(**globals()) + wheel_file_name = f'pep518-3.0-py{pyversion[0]}-none-any.whl' wheel_file_path = script.scratch / wheel_file_name result.did_create(wheel_file_path) assert "Successfully built pep518" in result.stdout, result.stdout @@ -339,8 +331,7 @@ def test_pep517_wheels_are_not_confused_with_other_files(script, tmpdir, data): result = script.pip('wheel', pkg_to_wheel, '-w', script.scratch_path) assert "Installing build dependencies" in result.stdout, result.stdout - wheel_file_name = 'withpyproject-0.0.1-py{pyversion[0]}-none-any.whl' \ - .format(**globals()) + wheel_file_name = f'withpyproject-0.0.1-py{pyversion[0]}-none-any.whl' wheel_file_path = script.scratch / wheel_file_name result.did_create(wheel_file_path) @@ -354,7 +345,6 @@ def test_legacy_wheels_are_not_confused_with_other_files(script, tmpdir, data): result = script.pip('wheel', pkg_to_wheel, '-w', script.scratch_path) assert "Installing build dependencies" not in result.stdout, result.stdout - wheel_file_name = 'simplewheel-1.0-py{pyversion[0]}-none-any.whl' \ - .format(**globals()) + wheel_file_name = f'simplewheel-1.0-py{pyversion[0]}-none-any.whl' wheel_file_path = script.scratch / wheel_file_name result.did_create(wheel_file_path) diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index 02395b75468..98474dda46f 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -10,7 +10,7 @@ from hashlib import sha256 from io import BytesIO from textwrap import dedent -from typing import TYPE_CHECKING +from typing import List, Optional from zipfile import ZipFile import pytest @@ -21,22 +21,16 @@ from pip._internal.locations import get_major_minor_version from pip._internal.models.search_scope import SearchScope from pip._internal.models.selection_prefs import SelectionPreferences +from pip._internal.models.target_python import TargetPython from pip._internal.network.session import PipSession from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX from tests.lib.path import Path, curdir from tests.lib.wheel import make_wheel -if TYPE_CHECKING: - from typing import List, Optional - - from pip._internal.models.target_python import TargetPython - - DATA_DIR = Path(__file__).parent.parent.joinpath("data").resolve() SRC_DIR = Path(__file__).resolve().parent.parent.parent pyversion = get_major_minor_version() -pyversion_tuple = sys.version_info CURRENT_PY_VERSION_INFO = sys.version_info[:3] @@ -287,15 +281,13 @@ def assert_installed(self, pkg_name, editable=True, with_files=None, if egg_link_path in self.files_created: raise TestFailure( 'unexpected egg link file created: ' - '{egg_link_path!r}\n{self}' - .format(**locals()) + f'{egg_link_path!r}\n{self}' ) else: if egg_link_path not in self.files_created: raise TestFailure( 'expected egg link file missing: ' - '{egg_link_path!r}\n{self}' - .format(**locals()) + f'{egg_link_path!r}\n{self}' ) egg_link_file = self.files_created[egg_link_path] @@ -304,15 +296,14 @@ def assert_installed(self, pkg_name, editable=True, with_files=None, # FIXME: I don't understand why there's a trailing . here if not (egg_link_contents.endswith('\n.') and egg_link_contents[:-2].endswith(pkg_dir)): + expected_ending = pkg_dir + '\n.' raise TestFailure(textwrap.dedent( - '''\ + f'''\ Incorrect egg_link file {egg_link_file!r} Expected ending: {expected_ending!r} ------- Actual contents ------- {egg_link_contents!r} - -------------------------------'''.format( - expected_ending=pkg_dir + '\n.', - **locals()) + -------------------------------''' )) if use_user_site: @@ -321,36 +312,33 @@ def assert_installed(self, pkg_name, editable=True, with_files=None, pth_file = e.site_packages / 'easy-install.pth' if (pth_file in self.files_updated) == without_egg_link: + maybe = '' if without_egg_link else 'not ' raise TestFailure( - '{pth_file} unexpectedly {maybe}updated by install'.format( - maybe=not without_egg_link and 'not ' or '', - **locals())) + f'{pth_file} unexpectedly {maybe}updated by install' + ) if (pkg_dir in self.files_created) == (curdir in without_files): - raise TestFailure(textwrap.dedent('''\ + maybe = 'not ' if curdir in without_files else '' + files = sorted(self.files_created) + raise TestFailure(textwrap.dedent(f'''\ expected package directory {pkg_dir!r} {maybe}to be created actually created: {files} - ''').format( - pkg_dir=pkg_dir, - maybe=curdir in without_files and 'not ' or '', - files=sorted(self.files_created.keys()), - )) + ''')) for f in with_files: normalized_path = os.path.normpath(pkg_dir / f) if normalized_path not in self.files_created: raise TestFailure( - 'Package directory {pkg_dir!r} missing ' - 'expected content {f!r}'.format(**locals()) + f'Package directory {pkg_dir!r} missing ' + f'expected content {f!r}' ) for f in without_files: normalized_path = os.path.normpath(pkg_dir / f) if normalized_path in self.files_created: raise TestFailure( - 'Package directory {pkg_dir!r} has unexpected content {f}' - .format(**locals()) + f'Package directory {pkg_dir!r} has unexpected content {f}' ) def did_create(self, path, message=None): @@ -509,7 +497,7 @@ def __init__(self, base_path, *args, virtualenv, pip_expect_warning=None, **kwar # Expand our absolute path directories into relative for name in ["base", "venv", "bin", "lib", "site_packages", "user_base", "user_site", "user_bin", "scratch"]: - real_name = "{name}_path".format(**locals()) + real_name = f"{name}_path" relative_path = Path(os.path.relpath( getattr(self, real_name), self.base_path )) @@ -572,7 +560,7 @@ def run( compatibility. """ if self.verbose: - print('>> running {args} {kw}'.format(**locals())) + print(f'>> running {args} {kw}') assert not cwd or not run_from, "Don't use run_from; it's going away" cwd = cwd or run_from or self.cwd @@ -822,7 +810,7 @@ def _vcs_add(script, version_pkg_path, vcs='git'): '-m', 'initial version', cwd=version_pkg_path, ) else: - raise ValueError('Unknown vcs: {vcs}'.format(**locals())) + raise ValueError(f'Unknown vcs: {vcs}') return version_pkg_path @@ -931,7 +919,7 @@ def assert_raises_regexp(exception, reg, run, *args, **kwargs): try: run(*args, **kwargs) - assert False, "{exception} should have been thrown".format(**locals()) + assert False, f"{exception} should have been thrown" except exception: e = sys.exc_info()[1] p = re.compile(reg) @@ -957,11 +945,11 @@ def create_test_package_with_setup(script, **setup_kwargs): assert 'name' in setup_kwargs, setup_kwargs pkg_path = script.scratch_path / setup_kwargs['name'] pkg_path.mkdir() - pkg_path.joinpath("setup.py").write_text(textwrap.dedent(""" + pkg_path.joinpath("setup.py").write_text(textwrap.dedent(f""" from setuptools import setup kwargs = {setup_kwargs!r} setup(**kwargs) - """).format(**locals())) + """)) return pkg_path @@ -1156,10 +1144,3 @@ def need_mercurial(fn): return pytest.mark.mercurial(need_executable( 'Mercurial', ('hg', 'version') )(fn)) - - -# Workaround for test failures after new wheel release. -windows_workaround_7667 = pytest.mark.skipif( - "sys.platform == 'win32' and sys.version_info < (3,)", - reason="Workaround for #7667", -) diff --git a/tests/lib/certs.py b/tests/lib/certs.py index 779afd018e3..b3a9b8e1046 100644 --- a/tests/lib/certs.py +++ b/tests/lib/certs.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import TYPE_CHECKING +from typing import Tuple from cryptography import x509 from cryptography.hazmat.backends import default_backend @@ -7,9 +7,6 @@ from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.x509.oid import NameOID -if TYPE_CHECKING: - from typing import Tuple - def make_tls_cert(hostname): # type: (str) -> Tuple[x509.Certificate, rsa.RSAPrivateKey] diff --git a/tests/lib/local_repos.py b/tests/lib/local_repos.py index 09e38640e00..0aa75787e0c 100644 --- a/tests/lib/local_repos.py +++ b/tests/lib/local_repos.py @@ -1,14 +1,11 @@ import os import subprocess import urllib.request -from typing import TYPE_CHECKING from pip._internal.utils.misc import hide_url from pip._internal.vcs import vcs from tests.lib import path_to_url - -if TYPE_CHECKING: - from tests.lib.path import Path +from tests.lib.path import Path def _create_svn_initools_repo(initools_dir): diff --git a/tests/lib/server.py b/tests/lib/server.py index 356495fa0ff..caaa3ffece6 100644 --- a/tests/lib/server.py +++ b/tests/lib/server.py @@ -5,31 +5,28 @@ from base64 import b64encode from contextlib import contextmanager from textwrap import dedent -from typing import TYPE_CHECKING +from types import TracebackType +from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type from unittest.mock import Mock -from werkzeug.serving import WSGIRequestHandler +from werkzeug.serving import BaseWSGIServer, WSGIRequestHandler from werkzeug.serving import make_server as _make_server from .compat import nullcontext -if TYPE_CHECKING: - from types import TracebackType - from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type +Environ = Dict[str, str] +Status = str +Headers = Iterable[Tuple[str, str]] +ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType] +Write = Callable[[bytes], None] +StartResponse = Callable[[Status, Headers, Optional[ExcInfo]], Write] +Body = List[bytes] +Responder = Callable[[Environ, StartResponse], Body] - from werkzeug.serving import BaseWSGIServer - Environ = Dict[str, str] - Status = str - Headers = Iterable[Tuple[str, str]] - ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType] - Write = Callable[[bytes], None] - StartResponse = Callable[[Status, Headers, Optional[ExcInfo]], Write] - Body = List[bytes] - Responder = Callable[[Environ, StartResponse], Body] +class MockServer(BaseWSGIServer): + mock = Mock() # type: Mock - class MockServer(BaseWSGIServer): - mock = Mock() # type: Mock # Applies on Python 2 and Windows. if not hasattr(signal, "pthread_sigmask"): diff --git a/tests/lib/test_lib.py b/tests/lib/test_lib.py index 47b97724f23..88c10501b70 100644 --- a/tests/lib/test_lib.py +++ b/tests/lib/test_lib.py @@ -63,8 +63,8 @@ def test_correct_pip_version(script): if x.endswith('.py') ] assert not mismatch_py, ( - 'mismatched source files in {pip_folder!r} ' - 'and {pip_folder_outputed!r}: {mismatch_py!r}'.format(**locals()) + f'mismatched source files in {pip_folder!r} ' + f'and {pip_folder_outputed!r}: {mismatch_py!r}' ) diff --git a/tests/lib/test_wheel.py b/tests/lib/test_wheel.py index 294fd7c037a..835ad31ec39 100644 --- a/tests/lib/test_wheel.py +++ b/tests/lib/test_wheel.py @@ -2,8 +2,8 @@ """ import csv from email import message_from_string +from email.message import Message from functools import partial -from typing import TYPE_CHECKING from zipfile import ZipFile from tests.lib.wheel import ( @@ -14,9 +14,6 @@ message_from_dict, ) -if TYPE_CHECKING: - from email import Message - def test_message_from_dict_one_value(): message = message_from_dict({"a": "1"}) diff --git a/tests/lib/venv.py b/tests/lib/venv.py index 6dbdb4dc75b..bd6426a81b8 100644 --- a/tests/lib/venv.py +++ b/tests/lib/venv.py @@ -32,7 +32,7 @@ def _update_paths(self): self.site = Path(lib) / 'site-packages' # Workaround for https://github.com/pypa/virtualenv/issues/306 if hasattr(sys, "pypy_version_info"): - version_dir = '{0}'.format(*sys.version_info) + version_dir = str(sys.version_info.major) self.lib = Path(home, 'lib-python', version_dir) else: self.lib = Path(lib) diff --git a/tests/lib/wheel.py b/tests/lib/wheel.py index 6028f117d7a..e88ce8c6101 100644 --- a/tests/lib/wheel.py +++ b/tests/lib/wheel.py @@ -10,34 +10,31 @@ from functools import partial from hashlib import sha256 from io import BytesIO, StringIO -from typing import TYPE_CHECKING +from typing import ( + AnyStr, + Callable, + Dict, + Iterable, + List, + Optional, + Sequence, + Tuple, + TypeVar, + Union, +) from zipfile import ZipFile from pip._vendor.requests.structures import CaseInsensitiveDict from tests.lib.path import Path -if TYPE_CHECKING: - from typing import ( - AnyStr, - Callable, - Dict, - Iterable, - List, - Optional, - Sequence, - Tuple, - TypeVar, - Union, - ) - - # path, digest, size - RecordLike = Tuple[str, str, str] - RecordCallback = Callable[ - [List["Record"]], Union[str, bytes, List[RecordLike]] - ] - # As would be used in metadata - HeaderValue = Union[str, List[str]] +# path, digest, size +RecordLike = Tuple[str, str, str] +RecordCallback = Callable[ + [List["Record"]], Union[str, bytes, List[RecordLike]] +] +# As would be used in metadata +HeaderValue = Union[str, List[str]] File = namedtuple("File", ["name", "contents"]) @@ -50,14 +47,10 @@ class Default(Enum): _default = Default.token +T = TypeVar("T") -if TYPE_CHECKING: - T = TypeVar("T") - - class Defaulted(Union[Default, T]): - """A type which may be defaulted. - """ - pass +# A type which may be defaulted. +Defaulted = Union[Default, T] def ensure_binary(value): diff --git a/tests/unit/test_collector.py b/tests/unit/test_collector.py index 87c947e905a..059fbc71985 100644 --- a/tests/unit/test_collector.py +++ b/tests/unit/test_collector.py @@ -608,7 +608,7 @@ def test_group_locations__file_expand_dir(data): files, urls = group_locations([data.find_links], expand_dir=True) assert files and not urls, ( "files and not urls should have been found " - "at find-links url: {data.find_links}".format(**locals()) + f"at find-links url: {data.find_links}" ) diff --git a/tests/unit/test_compat.py b/tests/unit/test_compat.py index 655e45ab75e..2d7cbf5c3af 100644 --- a/tests/unit/test_compat.py +++ b/tests/unit/test_compat.py @@ -1,11 +1,8 @@ -import locale import os -import sys import pytest -import pip._internal.utils.compat as pip_compat -from pip._internal.utils.compat import console_to_str, get_path_uid, str_to_display +from pip._internal.utils.compat import get_path_uid def test_get_path_uid(): @@ -44,81 +41,3 @@ def test_get_path_uid_symlink_without_NOFOLLOW(tmpdir, monkeypatch): os.symlink(f, fs) with pytest.raises(OSError): get_path_uid(fs) - - -@pytest.mark.parametrize('data, expected', [ - ('abc', 'abc'), - # Test text input with non-ascii characters. - ('déf', 'déf'), -]) -def test_str_to_display(data, expected): - actual = str_to_display(data) - assert actual == expected, ( - # Show the encoding for easier troubleshooting. - f'encoding: {locale.getpreferredencoding()!r}' - ) - - -@pytest.mark.parametrize('data, encoding, expected', [ - # Test str input with non-ascii characters. - ('déf', 'utf-8', 'déf'), - # Test bytes input with non-ascii characters: - ('déf'.encode('utf-8'), 'utf-8', 'déf'), - # Test a Windows encoding. - ('déf'.encode('cp1252'), 'cp1252', 'déf'), - # Test a Windows encoding with incompatibly encoded text. - ('déf'.encode('utf-8'), 'cp1252', 'déf'), -]) -def test_str_to_display__encoding(monkeypatch, data, encoding, expected): - monkeypatch.setattr(locale, 'getpreferredencoding', lambda: encoding) - actual = str_to_display(data) - assert actual == expected, ( - # Show the encoding for easier troubleshooting. - f'encoding: {locale.getpreferredencoding()!r}' - ) - - -def test_str_to_display__decode_error(monkeypatch, caplog): - monkeypatch.setattr(locale, 'getpreferredencoding', lambda: 'utf-8') - # Encode with an incompatible encoding. - data = 'ab'.encode('utf-16') - actual = str_to_display(data) - # Keep the expected value endian safe - if sys.byteorder == "little": - expected = "\\xff\\xfea\x00b\x00" - elif sys.byteorder == "big": - expected = "\\xfe\\xff\x00a\x00b" - - assert actual == expected, ( - # Show the encoding for easier troubleshooting. - f'encoding: {locale.getpreferredencoding()!r}' - ) - assert len(caplog.records) == 1 - record = caplog.records[0] - assert record.levelname == 'WARNING' - assert record.message == ( - 'Bytes object does not appear to be encoded as utf-8' - ) - - -def test_console_to_str(monkeypatch): - some_bytes = b"a\xE9\xC3\xE9b" - encodings = ('ascii', 'utf-8', 'iso-8859-1', 'iso-8859-5', - 'koi8_r', 'cp850') - for e in encodings: - monkeypatch.setattr(locale, 'getpreferredencoding', lambda: e) - result = console_to_str(some_bytes) - assert result.startswith("a") - assert result.endswith("b") - - -def test_console_to_str_warning(monkeypatch): - some_bytes = b"a\xE9b" - - def check_warning(msg, *args, **kwargs): - assert 'does not appear to be encoded as' in msg - assert args[0] == 'Subprocess output' - - monkeypatch.setattr(locale, 'getpreferredencoding', lambda: 'utf-8') - monkeypatch.setattr(pip_compat.logger, 'warning', check_warning) - console_to_str(some_bytes) diff --git a/tests/unit/test_operations_prepare.py b/tests/unit/test_operations_prepare.py index f6122cebeb4..4d912fb6eac 100644 --- a/tests/unit/test_operations_prepare.py +++ b/tests/unit/test_operations_prepare.py @@ -102,7 +102,7 @@ def test_copy_source_tree(clean_project, tmpdir): assert expected_files == copied_files -@pytest.mark.skipif("sys.platform == 'win32' or sys.version_info < (3,)") +@pytest.mark.skipif("sys.platform == 'win32'") def test_copy_source_tree_with_socket(clean_project, tmpdir, caplog): target = tmpdir.joinpath("target") expected_files = get_filelist(clean_project) @@ -121,7 +121,7 @@ def test_copy_source_tree_with_socket(clean_project, tmpdir, caplog): assert socket_path in record.message -@pytest.mark.skipif("sys.platform == 'win32' or sys.version_info < (3,)") +@pytest.mark.skipif("sys.platform == 'win32'") def test_copy_source_tree_with_socket_fails_with_no_socket_error( clean_project, tmpdir ): diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index db638659bce..5f01a9ecc23 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -89,6 +89,7 @@ def _basic_resolver(self, finder, require_hashes=False): require_hashes=require_hashes, use_user_site=False, lazy_wheel=False, + in_tree_build=False, ) yield Resolver( preparer=preparer, @@ -194,7 +195,7 @@ def test_unsupported_hashes(self, data): )) dir_path = data.packages.joinpath('FSPkg') reqset.add_requirement(get_processed_req_from_line( - 'file://{dir_path}'.format(**locals()), + f'file://{dir_path}', lineno=2, )) finder = make_test_finder(find_links=[data.find_links]) @@ -254,7 +255,7 @@ def test_hash_mismatch(self, data): (data.packages / 'simple-1.0.tar.gz').resolve()) reqset = RequirementSet() reqset.add_requirement(get_processed_req_from_line( - '{file_url} --hash=sha256:badbad'.format(**locals()), lineno=1, + f'{file_url} --hash=sha256:badbad', lineno=1, )) finder = make_test_finder(find_links=[data.find_links]) with self._basic_resolver(finder, require_hashes=True) as resolver: @@ -466,7 +467,7 @@ def test_markers_match_from_line(self): # match for markers in ( 'python_version >= "1.0"', - 'sys_platform == {sys.platform!r}'.format(**globals()), + f'sys_platform == {sys.platform!r}', ): line = 'name; ' + markers req = install_req_from_line(line) @@ -476,7 +477,7 @@ def test_markers_match_from_line(self): # don't match for markers in ( 'python_version >= "5.0"', - 'sys_platform != {sys.platform!r}'.format(**globals()), + f'sys_platform != {sys.platform!r}', ): line = 'name; ' + markers req = install_req_from_line(line) @@ -487,7 +488,7 @@ def test_markers_match(self): # match for markers in ( 'python_version >= "1.0"', - 'sys_platform == {sys.platform!r}'.format(**globals()), + f'sys_platform == {sys.platform!r}', ): line = 'name; ' + markers req = install_req_from_line(line, comes_from='') @@ -497,7 +498,7 @@ def test_markers_match(self): # don't match for markers in ( 'python_version >= "5.0"', - 'sys_platform != {sys.platform!r}'.format(**globals()), + f'sys_platform != {sys.platform!r}', ): line = 'name; ' + markers req = install_req_from_line(line, comes_from='') @@ -507,7 +508,7 @@ def test_markers_match(self): def test_extras_for_line_path_requirement(self): line = 'SomeProject[ex1,ex2]' filename = 'filename' - comes_from = '-r {} (line {})'.format(filename, 1) + comes_from = f'-r {filename} (line 1)' req = install_req_from_line(line, comes_from=comes_from) assert len(req.extras) == 2 assert req.extras == {'ex1', 'ex2'} @@ -515,7 +516,7 @@ def test_extras_for_line_path_requirement(self): def test_extras_for_line_url_requirement(self): line = 'git+https://url#egg=SomeProject[ex1,ex2]' filename = 'filename' - comes_from = '-r {} (line {})'.format(filename, 1) + comes_from = f'-r {filename} (line 1)' req = install_req_from_line(line, comes_from=comes_from) assert len(req.extras) == 2 assert req.extras == {'ex1', 'ex2'} @@ -523,7 +524,7 @@ def test_extras_for_line_url_requirement(self): def test_extras_for_editable_path_requirement(self): url = '.[ex1,ex2]' filename = 'filename' - comes_from = '-r {} (line {})'.format(filename, 1) + comes_from = f'-r {filename} (line 1)' req = install_req_from_editable(url, comes_from=comes_from) assert len(req.extras) == 2 assert req.extras == {'ex1', 'ex2'} @@ -531,7 +532,7 @@ def test_extras_for_editable_path_requirement(self): def test_extras_for_editable_url_requirement(self): url = 'git+https://url#egg=SomeProject[ex1,ex2]' filename = 'filename' - comes_from = '-r {} (line {})'.format(filename, 1) + comes_from = f'-r {filename} (line 1)' req = install_req_from_editable(url, comes_from=comes_from) assert len(req.extras) == 2 assert req.extras == {'ex1', 'ex2'} diff --git a/tests/unit/test_req_file.py b/tests/unit/test_req_file.py index 8d61e2b6cf3..3c534c9ee01 100644 --- a/tests/unit/test_req_file.py +++ b/tests/unit/test_req_file.py @@ -229,14 +229,14 @@ def test_error_message(self, line_processor): def test_yield_line_requirement(self, line_processor): line = 'SomeProject' filename = 'filename' - comes_from = '-r {} (line {})'.format(filename, 1) + comes_from = f'-r {filename} (line 1)' req = install_req_from_line(line, comes_from=comes_from) assert repr(line_processor(line, filename, 1)[0]) == repr(req) def test_yield_pep440_line_requirement(self, line_processor): line = 'SomeProject @ https://url/SomeProject-py2-py3-none-any.whl' filename = 'filename' - comes_from = '-r {} (line {})'.format(filename, 1) + comes_from = f'-r {filename} (line 1)' req = install_req_from_line(line, comes_from=comes_from) assert repr(line_processor(line, filename, 1)[0]) == repr(req) @@ -255,16 +255,16 @@ def test_yield_line_requirement_with_spaces_in_specifier( ): line = 'SomeProject >= 2' filename = 'filename' - comes_from = '-r {} (line {})'.format(filename, 1) + comes_from = f'-r {filename} (line 1)' req = install_req_from_line(line, comes_from=comes_from) assert repr(line_processor(line, filename, 1)[0]) == repr(req) assert str(req.req.specifier) == '>=2' def test_yield_editable_requirement(self, line_processor): url = 'git+https://url#egg=SomeProject' - line = '-e {url}'.format(**locals()) + line = f'-e {url}' filename = 'filename' - comes_from = '-r {} (line {})'.format(filename, 1) + comes_from = f'-r {filename} (line 1)' req = install_req_from_editable(url, comes_from=comes_from) assert repr(line_processor(line, filename, 1)[0]) == repr(req) @@ -588,7 +588,7 @@ def test_expand_existing_env_variables(self, tmpdir, finder): ) def make_var(name): - return '${{{name}}}'.format(**locals()) + return f'${{{name}}}' env_vars = collections.OrderedDict([ ('GITHUB_TOKEN', 'notarealtoken'), diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 8b4f9e79777..ebccd666011 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -38,7 +38,6 @@ normalize_path, normalize_version_info, parse_netloc, - path_to_display, redact_auth_from_url, redact_netloc, remove_auth_from_url, @@ -431,22 +430,6 @@ def test_rmtree_retries_for_3sec(tmpdir, monkeypatch): ) -@pytest.mark.parametrize('path, fs_encoding, expected', [ - (None, None, None), - # Test passing a text (unicode) string. - ('/path/déf', None, '/path/déf'), - # Test a bytes object with a non-ascii character. - ('/path/déf'.encode('utf-8'), 'utf-8', '/path/déf'), - # Test a bytes object with a character that can't be decoded. - ('/path/déf'.encode('utf-8'), 'ascii', "b'/path/d\\xc3\\xa9f'"), - ('/path/déf'.encode('utf-16'), 'utf-8', expected_byte_string), -]) -def test_path_to_display(monkeypatch, path, fs_encoding, expected): - monkeypatch.setattr(sys, 'getfilesystemencoding', lambda: fs_encoding) - actual = path_to_display(path) - assert actual == expected, f'actual: {actual!r}' - - class Test_normalize_path: # Technically, symlinks are possible on Windows, but you need a special # permission bit to create them, and Python 2 doesn't support it anyway, so diff --git a/tests/unit/test_utils_subprocess.py b/tests/unit/test_utils_subprocess.py index ecae2295c88..7a31eeb7425 100644 --- a/tests/unit/test_utils_subprocess.py +++ b/tests/unit/test_utils_subprocess.py @@ -57,11 +57,6 @@ def test_make_subprocess_output_error__non_ascii_command_arg(monkeypatch): Test a command argument with a non-ascii character. """ cmd_args = ['foo', 'déf'] - if sys.version_info[0] == 2: - # Check in Python 2 that the str (bytes object) with the non-ascii - # character has the encoding we expect. (This comes from the source - # code encoding at the top of the file.) - assert cmd_args[1].decode('utf-8') == 'déf' # We need to monkeypatch so the encoding will be correct on Windows. monkeypatch.setattr(locale, 'getpreferredencoding', lambda: 'utf-8') @@ -80,7 +75,6 @@ def test_make_subprocess_output_error__non_ascii_command_arg(monkeypatch): assert actual == expected, f'actual: {actual}' -@pytest.mark.skipif("sys.version_info < (3,)") def test_make_subprocess_output_error__non_ascii_cwd_python_3(monkeypatch): """ Test a str (text) cwd with a non-ascii character in Python 3. @@ -102,36 +96,6 @@ def test_make_subprocess_output_error__non_ascii_cwd_python_3(monkeypatch): assert actual == expected, f'actual: {actual}' -@pytest.mark.parametrize('encoding', [ - 'utf-8', - # Test a Windows encoding. - 'cp1252', -]) -@pytest.mark.skipif("sys.version_info >= (3,)") -def test_make_subprocess_output_error__non_ascii_cwd_python_2( - monkeypatch, encoding, -): - """ - Test a str (bytes object) cwd with a non-ascii character in Python 2. - """ - cmd_args = ['test'] - cwd = '/path/to/cwd/déf'.encode(encoding) - monkeypatch.setattr(sys, 'getfilesystemencoding', lambda: encoding) - actual = make_subprocess_output_error( - cmd_args=cmd_args, - cwd=cwd, - lines=[], - exit_status=1, - ) - expected = dedent("""\ - Command errored out with exit status 1: - command: test - cwd: /path/to/cwd/déf - Complete output (0 lines): - ----------------------------------------""") - assert actual == expected, f'actual: {actual}' - - # This test is mainly important for checking unicode in Python 2. def test_make_subprocess_output_error__non_ascii_line(): """ @@ -430,3 +394,21 @@ def test_closes_stdin(self): [sys.executable, '-c', 'input()'], show_stdout=True, ) + + +def test_unicode_decode_error(caplog): + if locale.getpreferredencoding() != "UTF-8": + pytest.skip("locale.getpreferredencoding() is not UTF-8") + caplog.set_level(INFO) + call_subprocess( + [ + sys.executable, + "-c", + "import sys; sys.stdout.buffer.write(b'\\xff')", + ], + show_stdout=True + ) + + assert len(caplog.records) == 2 + # First log record is "Running command ..." + assert caplog.record_tuples[1] == ("pip.subprocessor", INFO, "\\xff") diff --git a/tests/unit/test_utils_wheel.py b/tests/unit/test_utils_wheel.py index 461a29b5e01..878d8d777e5 100644 --- a/tests/unit/test_utils_wheel.py +++ b/tests/unit/test_utils_wheel.py @@ -2,16 +2,13 @@ from contextlib import ExitStack from email import message_from_string from io import BytesIO -from typing import TYPE_CHECKING from zipfile import ZipFile import pytest from pip._internal.exceptions import UnsupportedWheel from pip._internal.utils import wheel - -if TYPE_CHECKING: - from tests.lib.path import Path +from tests.lib.path import Path @pytest.fixture diff --git a/tools/automation/release/__init__.py b/tools/automation/release/__init__.py index 20775d5e21d..a10ccd1f55c 100644 --- a/tools/automation/release/__init__.py +++ b/tools/automation/release/__init__.py @@ -26,7 +26,8 @@ def get_version_from_arguments(session: Session) -> Optional[str]: # We delegate to a script here, so that it can depend on packaging. session.install("packaging") cmd = [ - os.path.join(session.bin, "python"), + # https://github.com/theacodes/nox/pull/378 + os.path.join(session.bin, "python"), # type: ignore "tools/automation/release/check_version.py", version ] @@ -90,7 +91,7 @@ def generate_news(session: Session, version: str) -> None: def update_version_file(version: str, filepath: str) -> None: - with open(filepath, "r", encoding="utf-8") as f: + with open(filepath, encoding="utf-8") as f: content = list(f) file_modified = False @@ -153,11 +154,12 @@ def workdir( """Temporarily chdir when entering CM and chdir back on exit.""" orig_dir = pathlib.Path.cwd() - nox_session.chdir(dir_path) + # https://github.com/theacodes/nox/pull/376 + nox_session.chdir(dir_path) # type: ignore try: yield dir_path finally: - nox_session.chdir(orig_dir) + nox_session.chdir(orig_dir) # type: ignore @contextlib.contextmanager diff --git a/tools/tox_pip.py b/tools/tox_pip.py index 5996dade6d2..fe7621342a6 100644 --- a/tools/tox_pip.py +++ b/tools/tox_pip.py @@ -1,17 +1,16 @@ -# The following comment should be removed at some point in the future. -# mypy: disallow-untyped-defs=False - import os import shutil import subprocess import sys from glob import glob +from typing import List VIRTUAL_ENV = os.environ['VIRTUAL_ENV'] TOX_PIP_DIR = os.path.join(VIRTUAL_ENV, 'pip') def pip(args): + # type: (List[str]) -> None # First things first, get a recent (stable) version of pip. if not os.path.exists(TOX_PIP_DIR): subprocess.check_call([sys.executable, '-m', 'pip', @@ -20,8 +19,8 @@ def pip(args): 'pip']) shutil.rmtree(glob(os.path.join(TOX_PIP_DIR, 'pip-*.dist-info'))[0]) # And use that version. - pypath = os.environ.get('PYTHONPATH') - pypath = pypath.split(os.pathsep) if pypath is not None else [] + pypath_env = os.environ.get('PYTHONPATH') + pypath = pypath_env.split(os.pathsep) if pypath_env is not None else [] pypath.insert(0, TOX_PIP_DIR) os.environ['PYTHONPATH'] = os.pathsep.join(pypath) subprocess.check_call([sys.executable, '-m', 'pip'] + args)