From 576dbd813c3a0e989c14a36f6d214ad66309ff97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sat, 21 Oct 2023 12:57:41 +0200 Subject: [PATCH 01/12] Bump for development --- src/pip/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pip/__init__.py b/src/pip/__init__.py index f1263cdc1b6..46e56014998 100644 --- a/src/pip/__init__.py +++ b/src/pip/__init__.py @@ -1,6 +1,6 @@ from typing import List, Optional -__version__ = "23.3.1" +__version__ = "24.0.dev0" def main(args: Optional[List[str]] = None) -> int: From 6dbd9c68f085c5bf304247bf7c7933842092efb2 Mon Sep 17 00:00:00 2001 From: efflamlemaillet <6533295+efflamlemaillet@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:08:17 +0200 Subject: [PATCH 02/12] Fix hg: "parse error at 0: not a prefix:" (#12373) Use two hypen argument `--rev=` instead of `-r=` Co-authored-by: Efflam Lemaillet Co-authored-by: Pradyun Gedam --- news/370392cf-52cd-402c-b402-06d2ff398f89.bugfix.rst | 1 + src/pip/_internal/vcs/mercurial.py | 2 +- tests/unit/test_vcs.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/370392cf-52cd-402c-b402-06d2ff398f89.bugfix.rst diff --git a/news/370392cf-52cd-402c-b402-06d2ff398f89.bugfix.rst b/news/370392cf-52cd-402c-b402-06d2ff398f89.bugfix.rst new file mode 100644 index 00000000000..76a8e6b96db --- /dev/null +++ b/news/370392cf-52cd-402c-b402-06d2ff398f89.bugfix.rst @@ -0,0 +1 @@ +Fix mercurial revision "parse error": use ``--rev={ref}`` instead of ``-r={ref}`` diff --git a/src/pip/_internal/vcs/mercurial.py b/src/pip/_internal/vcs/mercurial.py index e440c122169..c183d41d09c 100644 --- a/src/pip/_internal/vcs/mercurial.py +++ b/src/pip/_internal/vcs/mercurial.py @@ -31,7 +31,7 @@ class Mercurial(VersionControl): @staticmethod def get_base_rev_args(rev: str) -> List[str]: - return [f"-r={rev}"] + return [f"--rev={rev}"] def fetch_new( self, dest: str, url: HiddenText, rev_options: RevOptions, verbosity: int diff --git a/tests/unit/test_vcs.py b/tests/unit/test_vcs.py index 4a3750f2d36..5291f129cf7 100644 --- a/tests/unit/test_vcs.py +++ b/tests/unit/test_vcs.py @@ -66,7 +66,7 @@ def test_rev_options_repr() -> None: # First check VCS-specific RevOptions behavior. (Bazaar, [], ["-r", "123"], {}), (Git, ["HEAD"], ["123"], {}), - (Mercurial, [], ["-r=123"], {}), + (Mercurial, [], ["--rev=123"], {}), (Subversion, [], ["-r", "123"], {}), # Test extra_args. For this, test using a single VersionControl class. ( From fd77ebfc742de4d76ff976de22e86d116e0faad3 Mon Sep 17 00:00:00 2001 From: Dale <70705126+dalebrydon@users.noreply.github.com> Date: Fri, 27 Oct 2023 08:59:56 -0400 Subject: [PATCH 03/12] Rework the functionality of PIP_CONFIG_FILE (#11850) --- docs/html/topics/configuration.md | 19 ++++++++++++------- news/11815.doc.rst | 1 + src/pip/_internal/configuration.py | 26 ++++++++++++++------------ 3 files changed, 27 insertions(+), 19 deletions(-) create mode 100644 news/11815.doc.rst diff --git a/docs/html/topics/configuration.md b/docs/html/topics/configuration.md index e4aafcd2b98..8b54db56ce6 100644 --- a/docs/html/topics/configuration.md +++ b/docs/html/topics/configuration.md @@ -19,8 +19,8 @@ and how they are related to pip's various command line options. ## Configuration Files -Configuration files can change the default values for command line option. -They are written using a standard INI style configuration files. +Configuration files can change the default values for command line options. +The files are written using standard INI format. pip has 3 "levels" of configuration files: @@ -28,11 +28,15 @@ pip has 3 "levels" of configuration files: - `user`: per-user configuration file. - `site`: per-environment configuration file; i.e. per-virtualenv. +Additionally, environment variables can be specified which will override any of the above. + ### Location pip's configuration files are located in fairly standard locations. This location is different on different operating systems, and has some additional -complexity for backwards compatibility reasons. +complexity for backwards compatibility reasons. Note that if user config files +exist in both the legacy and current locations, values in the current file +will override values in the legacy file. ```{tab} Unix @@ -88,9 +92,10 @@ Site ### `PIP_CONFIG_FILE` Additionally, the environment variable `PIP_CONFIG_FILE` can be used to specify -a configuration file that's loaded first, and whose values are overridden by -the values set in the aforementioned files. Setting this to {any}`os.devnull` -disables the loading of _all_ configuration files. +a configuration file that's loaded last, and whose values override the values +set in the aforementioned files. Setting this to {any}`os.devnull` +disables the loading of _all_ configuration files. Note that if a file exists +at the location that this is set to, the user config file will not be loaded. (config-precedence)= @@ -99,10 +104,10 @@ disables the loading of _all_ configuration files. When multiple configuration files are found, pip combines them in the following order: -- `PIP_CONFIG_FILE`, if given. - Global - User - Site +- `PIP_CONFIG_FILE`, if given. Each file read overrides any values read from previous files, so if the global timeout is specified in both the global file and the per-user file diff --git a/news/11815.doc.rst b/news/11815.doc.rst new file mode 100644 index 00000000000..8e7e8d21bef --- /dev/null +++ b/news/11815.doc.rst @@ -0,0 +1 @@ +Fix explanation of how PIP_CONFIG_FILE works diff --git a/src/pip/_internal/configuration.py b/src/pip/_internal/configuration.py index 96f824955bf..124a7ca5db7 100644 --- a/src/pip/_internal/configuration.py +++ b/src/pip/_internal/configuration.py @@ -327,33 +327,35 @@ def get_environ_vars(self) -> Iterable[Tuple[str, str]]: def iter_config_files(self) -> Iterable[Tuple[Kind, List[str]]]: """Yields variant and configuration files associated with it. - This should be treated like items of a dictionary. + This should be treated like items of a dictionary. The order + here doesn't affect what gets overridden. That is controlled + by OVERRIDE_ORDER. However this does control the order they are + displayed to the user. It's probably most ergononmic to display + things in the same order as OVERRIDE_ORDER """ # SMELL: Move the conditions out of this function - # environment variables have the lowest priority - config_file = os.environ.get("PIP_CONFIG_FILE", None) - if config_file is not None: - yield kinds.ENV, [config_file] - else: - yield kinds.ENV, [] - + env_config_file = os.environ.get("PIP_CONFIG_FILE", None) config_files = get_configuration_files() - # at the base we have any global configuration yield kinds.GLOBAL, config_files[kinds.GLOBAL] - # per-user configuration next + # per-user config is not loaded when env_config_file exists should_load_user_config = not self.isolated and not ( - config_file and os.path.exists(config_file) + env_config_file and os.path.exists(env_config_file) ) if should_load_user_config: # The legacy config file is overridden by the new config file yield kinds.USER, config_files[kinds.USER] - # finally virtualenv configuration first trumping others + # virtualenv config yield kinds.SITE, config_files[kinds.SITE] + if env_config_file is not None: + yield kinds.ENV, [env_config_file] + else: + yield kinds.ENV, [] + def get_values_in_config(self, variant: Kind) -> Dict[str, Any]: """Get values present in a config file""" return self._config[variant] From 9685f64fe8c78e1e39cd9b32e5615f42e0a01f1c Mon Sep 17 00:00:00 2001 From: Damian Shaw Date: Mon, 6 Nov 2023 04:30:05 -0500 Subject: [PATCH 04/12] Update ruff and config (#12390) --- .pre-commit-config.yaml | 3 ++- news/12390.trivial.rst | 1 + pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 news/12390.trivial.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c576d90a5b..999bd8b1d68 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,9 +22,10 @@ repos: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.292 + rev: v0.1.4 hooks: - id: ruff + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.961 diff --git a/news/12390.trivial.rst b/news/12390.trivial.rst new file mode 100644 index 00000000000..52b21413ca0 --- /dev/null +++ b/news/12390.trivial.rst @@ -0,0 +1 @@ +Update ruff versions and config for dev diff --git a/pyproject.toml b/pyproject.toml index b720c460297..d22e5c66896 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,8 +84,8 @@ ignore = [ "B020", "B904", # Ruff enables opinionated warnings by default "B905", # Ruff enables opinionated warnings by default - "G202", ] +target-version = "py37" line-length = 88 select = [ "ASYNC", From 9ec7e36dd6e1ef80c80c9fefde69bf97631f51c9 Mon Sep 17 00:00:00 2001 From: Sander Van Balen Date: Mon, 6 Nov 2023 17:13:58 +0100 Subject: [PATCH 05/12] Fixed bug in extras handling for link requirements --- news/12372.bugfix.rst | 1 + .../resolution/resolvelib/factory.py | 53 +++++++++++++------ tests/functional/test_install_reqs.py | 6 +-- tests/functional/test_new_resolver.py | 21 ++++++++ 4 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 news/12372.bugfix.rst diff --git a/news/12372.bugfix.rst b/news/12372.bugfix.rst new file mode 100644 index 00000000000..6c0b48dec3c --- /dev/null +++ b/news/12372.bugfix.rst @@ -0,0 +1 @@ +Fix a bug in extras handling for link requirements diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 38c199448a1..241b74b59bb 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -36,7 +36,10 @@ 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.constructors import install_req_from_link_and_ireq +from pip._internal.req.constructors import ( + install_req_drop_extras, + install_req_from_link_and_ireq, +) from pip._internal.req.req_install import ( InstallRequirement, check_invalid_constraint_type, @@ -176,6 +179,20 @@ def _make_candidate_from_link( name: Optional[NormalizedName], version: Optional[CandidateVersion], ) -> Optional[Candidate]: + base: Optional[BaseCandidate] = self._make_base_candidate_from_link( + link, template, name, version + ) + if not extras or base is None: + return base + return self._make_extras_candidate(base, extras, comes_from=template) + + def _make_base_candidate_from_link( + self, + link: Link, + template: InstallRequirement, + name: Optional[NormalizedName], + version: Optional[CandidateVersion], + ) -> Optional[BaseCandidate]: # TODO: Check already installed candidate, and use it if the link and # editable flag match. @@ -204,7 +221,7 @@ def _make_candidate_from_link( self._build_failures[link] = e return None - base: BaseCandidate = self._editable_candidate_cache[link] + return self._editable_candidate_cache[link] else: if link not in self._link_candidate_cache: try: @@ -224,11 +241,7 @@ def _make_candidate_from_link( ) self._build_failures[link] = e return None - base = self._link_candidate_cache[link] - - if not extras: - return base - return self._make_extras_candidate(base, extras, comes_from=template) + return self._link_candidate_cache[link] def _iter_found_candidates( self, @@ -362,9 +375,8 @@ def _iter_candidates_from_constraints( """ for link in constraint.links: self._fail_if_link_is_unsupported_wheel(link) - candidate = self._make_candidate_from_link( + candidate = self._make_base_candidate_from_link( link, - extras=frozenset(), template=install_req_from_link_and_ireq(link, template), name=canonicalize_name(identifier), version=None, @@ -454,10 +466,10 @@ def _make_requirements_from_install_req( Returns requirement objects associated with the given InstallRequirement. In most cases this will be a single object but the following special cases exist: - the InstallRequirement has markers that do not apply -> result is empty - - the InstallRequirement has both a constraint and extras -> result is split - in two requirement objects: one with the constraint and one with the - extra. This allows centralized constraint handling for the base, - resulting in fewer candidate rejections. + - the InstallRequirement has both a constraint (or link) and extras + -> result is split in two requirement objects: one with the constraint + (or link) and one with the extra. This allows centralized constraint + handling for the base, resulting in fewer candidate rejections. """ if not ireq.match_markers(requested_extras): logger.info( @@ -471,10 +483,13 @@ def _make_requirements_from_install_req( yield SpecifierRequirement(ireq) else: self._fail_if_link_is_unsupported_wheel(ireq.link) - cand = self._make_candidate_from_link( + # Always make the link candidate for the base requirement to make it + # available to `find_candidates` for explicit candidate lookup for any + # set of extras. + # The extras are required separately via a second requirement. + cand = self._make_base_candidate_from_link( ireq.link, - extras=frozenset(ireq.extras), - template=ireq, + template=install_req_drop_extras(ireq) if ireq.extras else ireq, name=canonicalize_name(ireq.name) if ireq.name else None, version=None, ) @@ -489,7 +504,13 @@ def _make_requirements_from_install_req( raise self._build_failures[ireq.link] yield UnsatisfiableRequirement(canonicalize_name(ireq.name)) else: + # require the base from the link yield self.make_requirement_from_candidate(cand) + if ireq.extras: + # require the extras on top of the base candidate + yield self.make_requirement_from_candidate( + self._make_extras_candidate(cand, frozenset(ireq.extras)) + ) def collect_root_requirements( self, root_ireqs: List[InstallRequirement] diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py index c21b9ba83de..f649be00090 100644 --- a/tests/functional/test_install_reqs.py +++ b/tests/functional/test_install_reqs.py @@ -671,9 +671,9 @@ def test_install_distribution_union_with_versions( expect_error=(resolver_variant == "resolvelib"), ) if resolver_variant == "resolvelib": - assert "Cannot install localextras[bar]" in result.stderr - assert ("localextras[bar] 0.0.1 depends on localextras 0.0.1") in result.stdout - assert ("localextras[baz] 0.0.2 depends on localextras 0.0.2") in result.stdout + assert "Cannot install localextras" in result.stderr + assert ("The user requested localextras 0.0.1") in result.stdout + assert ("The user requested localextras 0.0.2") in result.stdout else: assert ( "Successfully installed LocalExtras-0.0.1 simple-3.0 singlemodule-0.0.1" diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py index b5945edf89b..1e7672a46ae 100644 --- a/tests/functional/test_new_resolver.py +++ b/tests/functional/test_new_resolver.py @@ -2406,6 +2406,27 @@ def test_new_resolver_respect_user_requested_if_extra_is_installed( script.assert_installed(pkg3="1.0", pkg2="2.0", pkg1="1.0") +def test_new_resolver_constraint_on_link_with_extra( + script: PipTestEnvironment, +) -> None: + """ + Verify that installing works from a link with both an extra and a constraint. + """ + wheel: pathlib.Path = create_basic_wheel_for_package( + script, "pkg", "1.0", extras={"ext": []} + ) + + script.pip( + "install", + "--no-cache-dir", + # no index, no --find-links: only the explicit path + "--no-index", + f"{wheel}[ext]", + "pkg==1", + ) + script.assert_installed(pkg="1.0") + + def test_new_resolver_do_not_backtrack_on_build_failure( script: PipTestEnvironment, ) -> None: From 68529081c27e2372971f114d5464c0850837405b Mon Sep 17 00:00:00 2001 From: Damian Shaw Date: Tue, 7 Nov 2023 04:14:56 -0500 Subject: [PATCH 06/12] Enforce f-strings via Ruff (#12393) --- docs/pip_sphinxext.py | 11 +--- news/12393.trivial.rst | 1 + pyproject.toml | 1 + setup.py | 2 +- src/pip/_internal/cli/cmdoptions.py | 9 +-- src/pip/_internal/cli/parser.py | 8 +-- src/pip/_internal/commands/cache.py | 2 +- src/pip/_internal/commands/configuration.py | 10 ++- src/pip/_internal/commands/debug.py | 8 +-- src/pip/_internal/commands/index.py | 4 +- src/pip/_internal/commands/install.py | 6 +- src/pip/_internal/configuration.py | 4 +- src/pip/_internal/exceptions.py | 13 ++-- src/pip/_internal/index/package_finder.py | 8 +-- src/pip/_internal/models/candidate.py | 6 +- src/pip/_internal/models/direct_url.py | 4 +- src/pip/_internal/models/format_control.py | 4 +- src/pip/_internal/models/link.py | 4 +- src/pip/_internal/network/download.py | 2 +- src/pip/_internal/operations/install/wheel.py | 20 +++--- src/pip/_internal/operations/prepare.py | 10 +-- src/pip/_internal/req/constructors.py | 2 +- src/pip/_internal/req/req_install.py | 8 +-- src/pip/_internal/req/req_uninstall.py | 8 +-- .../_internal/resolution/legacy/resolver.py | 14 ++--- .../resolution/resolvelib/candidates.py | 16 +---- .../resolution/resolvelib/factory.py | 4 +- .../resolution/resolvelib/requirements.py | 20 ++---- src/pip/_internal/utils/misc.py | 20 +++--- src/pip/_internal/utils/wheel.py | 6 +- src/pip/_internal/vcs/versioncontrol.py | 10 +-- src/pip/_internal/wheel_builder.py | 11 ++-- tests/conftest.py | 2 +- tests/functional/test_cli.py | 8 +-- tests/functional/test_completion.py | 2 +- tests/functional/test_debug.py | 2 +- tests/functional/test_freeze.py | 28 ++++----- tests/functional/test_install.py | 12 ++-- tests/functional/test_install_config.py | 18 +++--- tests/functional/test_install_index.py | 8 +-- tests/functional/test_install_reqs.py | 8 +-- tests/functional/test_install_wheel.py | 4 +- tests/functional/test_new_resolver.py | 2 +- tests/functional/test_new_resolver_errors.py | 6 +- tests/functional/test_new_resolver_hashes.py | 63 ++++++------------- tests/functional/test_new_resolver_target.py | 7 +-- tests/functional/test_pep517.py | 12 ++-- tests/functional/test_uninstall.py | 4 +- tests/functional/test_wheel.py | 4 +- tests/lib/__init__.py | 30 ++++----- tests/lib/local_repos.py | 2 +- tests/lib/server.py | 2 +- tests/lib/test_lib.py | 4 +- tests/lib/wheel.py | 2 +- tests/unit/test_collector.py | 12 ++-- tests/unit/test_link.py | 5 +- tests/unit/test_network_utils.py | 4 +- tests/unit/test_req.py | 4 +- tests/unit/test_req_file.py | 10 ++- tests/unit/test_resolution_legacy_resolver.py | 4 +- tests/unit/test_wheel.py | 8 +-- tools/release/check_version.py | 2 +- 62 files changed, 201 insertions(+), 334 deletions(-) create mode 100644 news/12393.trivial.rst diff --git a/docs/pip_sphinxext.py b/docs/pip_sphinxext.py index 2e559702294..fe3f41e8b79 100644 --- a/docs/pip_sphinxext.py +++ b/docs/pip_sphinxext.py @@ -194,22 +194,17 @@ def process_options(self) -> None: opt = option() opt_name = opt._long_opts[0] if opt._short_opts: - short_opt_name = "{}, ".format(opt._short_opts[0]) + short_opt_name = f"{opt._short_opts[0]}, " else: short_opt_name = "" if option in cmdoptions.general_group["options"]: prefix = "" else: - prefix = "{}_".format(self.determine_opt_prefix(opt_name)) + prefix = f"{self.determine_opt_prefix(opt_name)}_" self.view_list.append( - "* :ref:`{short}{long}<{prefix}{opt_name}>`".format( - short=short_opt_name, - long=opt_name, - prefix=prefix, - opt_name=opt_name, - ), + f"* :ref:`{short_opt_name}{opt_name}<{prefix}{opt_name}>`", "\n", ) diff --git a/news/12393.trivial.rst b/news/12393.trivial.rst new file mode 100644 index 00000000000..15452737aef --- /dev/null +++ b/news/12393.trivial.rst @@ -0,0 +1 @@ +Enforce and update code to use f-strings via Ruff rule UP032 diff --git a/pyproject.toml b/pyproject.toml index d22e5c66896..0ac70174ad1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,6 +102,7 @@ select = [ "PLR0", "W", "RUF100", + "UP032", ] [tool.ruff.isort] diff --git a/setup.py b/setup.py index d73c77b7346..569389b1ed0 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def get_version(rel_path: str) -> str: entry_points={ "console_scripts": [ "pip=pip._internal.cli.main:main", - "pip{}=pip._internal.cli.main:main".format(sys.version_info[0]), + f"pip{sys.version_info[0]}=pip._internal.cli.main:main", "pip{}.{}=pip._internal.cli.main:main".format(*sys.version_info[:2]), ], }, diff --git a/src/pip/_internal/cli/cmdoptions.py b/src/pip/_internal/cli/cmdoptions.py index 8fb16dc4a6a..d05e502f908 100644 --- a/src/pip/_internal/cli/cmdoptions.py +++ b/src/pip/_internal/cli/cmdoptions.py @@ -582,10 +582,7 @@ def _handle_python_version( """ 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 = f"invalid --python-version value: {value!r}: {error_msg}" raise_option_error(parser, option=option, msg=msg) parser.values.python_version = version_info @@ -921,9 +918,9 @@ def _handle_merge_hash( algo, digest = value.split(":", 1) except ValueError: parser.error( - "Arguments to {} must be a hash name " + f"Arguments to {opt_str} must be a hash name " "followed by a value, like --hash=sha256:" - "abcde...".format(opt_str) + "abcde..." ) if algo not in STRONG_HASHES: parser.error( diff --git a/src/pip/_internal/cli/parser.py b/src/pip/_internal/cli/parser.py index 64cf9719730..ae554b24cae 100644 --- a/src/pip/_internal/cli/parser.py +++ b/src/pip/_internal/cli/parser.py @@ -229,9 +229,9 @@ def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]: val = strtobool(val) except ValueError: self.error( - "{} is not a valid value for {} option, " + f"{val} is not a valid value for {key} option, " "please specify a boolean value like yes/no, " - "true/false or 1/0 instead.".format(val, key) + "true/false or 1/0 instead." ) elif option.action == "count": with suppress(ValueError): @@ -240,10 +240,10 @@ def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]: val = int(val) if not isinstance(val, int) or val < 0: self.error( - "{} is not a valid value for {} option, " + f"{val} is not a valid value for {key} option, " "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) + "which is equivalent to 1/0." ) elif option.action == "append": val = val.split() diff --git a/src/pip/_internal/commands/cache.py b/src/pip/_internal/commands/cache.py index 1f3b5fe142b..328336152cc 100644 --- a/src/pip/_internal/commands/cache.py +++ b/src/pip/_internal/commands/cache.py @@ -175,7 +175,7 @@ def remove_cache_items(self, options: Values, args: List[Any]) -> None: files += self._find_http_files(options) else: # Add the pattern to the log message - no_matching_msg += ' for pattern "{}"'.format(args[0]) + no_matching_msg += f' for pattern "{args[0]}"' if not files: logger.warning(no_matching_msg) diff --git a/src/pip/_internal/commands/configuration.py b/src/pip/_internal/commands/configuration.py index 84b134e490b..1a1dc6b6cd8 100644 --- a/src/pip/_internal/commands/configuration.py +++ b/src/pip/_internal/commands/configuration.py @@ -242,17 +242,15 @@ def open_in_editor(self, options: Values, args: List[str]) -> None: e.filename = editor raise except subprocess.CalledProcessError as e: - raise PipError( - "Editor Subprocess exited with exit code {}".format(e.returncode) - ) + raise PipError(f"Editor Subprocess exited with exit code {e.returncode}") def _get_n_args(self, args: List[str], example: str, n: int) -> Any: """Helper to make sure the command got the right number of arguments""" if len(args) != n: msg = ( - "Got unexpected number of arguments, expected {}. " - '(example: "{} config {}")' - ).format(n, get_prog(), example) + f"Got unexpected number of arguments, expected {n}. " + f'(example: "{get_prog()} config {example}")' + ) raise PipError(msg) if n == 1: diff --git a/src/pip/_internal/commands/debug.py b/src/pip/_internal/commands/debug.py index 5dc91bf4950..7e5271c9886 100644 --- a/src/pip/_internal/commands/debug.py +++ b/src/pip/_internal/commands/debug.py @@ -95,7 +95,7 @@ def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None: elif parse_version(actual_version) != parse_version(expected_version): extra_message = ( " (CONFLICT: vendor.txt suggests version should" - " be {})".format(expected_version) + f" be {expected_version})" ) logger.info("%s==%s%s", module_name, actual_version, extra_message) @@ -120,7 +120,7 @@ def show_tags(options: Values) -> None: if formatted_target: suffix = f" (target: {formatted_target})" - msg = "Compatible tags: {}{}".format(len(tags), suffix) + msg = f"Compatible tags: {len(tags)}{suffix}" logger.info(msg) if options.verbose < 1 and len(tags) > tag_limit: @@ -134,9 +134,7 @@ def show_tags(options: Values) -> None: logger.info(str(tag)) if tags_limited: - msg = ( - "...\n[First {tag_limit} tags shown. Pass --verbose to show all.]" - ).format(tag_limit=tag_limit) + msg = f"...\n[First {tag_limit} tags shown. Pass --verbose to show all.]" logger.info(msg) diff --git a/src/pip/_internal/commands/index.py b/src/pip/_internal/commands/index.py index 7267effed24..f55e9e49974 100644 --- a/src/pip/_internal/commands/index.py +++ b/src/pip/_internal/commands/index.py @@ -128,12 +128,12 @@ def get_available_package_versions(self, options: Values, args: List[Any]) -> No if not versions: raise DistributionNotFound( - "No matching distribution found for {}".format(query) + f"No matching distribution found for {query}" ) formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)] latest = formatted_versions[0] - write_output("{} ({})".format(query, latest)) + write_output(f"{query} ({latest})") write_output("Available versions: {}".format(", ".join(formatted_versions))) print_dist_installation_info(query, latest) diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 365764fc7cb..e944bb95a50 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -607,12 +607,8 @@ def _warn_about_conflicts( version = package_set[project_name][0] for dependency in missing[project_name]: message = ( - "{name} {version} requires {requirement}, " + f"{project_name} {version} requires {dependency[1]}, " "which is not installed." - ).format( - name=project_name, - version=version, - requirement=dependency[1], ) parts.append(message) diff --git a/src/pip/_internal/configuration.py b/src/pip/_internal/configuration.py index 124a7ca5db7..c25273d5f0b 100644 --- a/src/pip/_internal/configuration.py +++ b/src/pip/_internal/configuration.py @@ -59,8 +59,8 @@ def _disassemble_key(name: str) -> List[str]: if "." not in name: error_message = ( "Key does not contain dot separated section and key. " - "Perhaps you wanted to use 'global.{}' instead?" - ).format(name) + f"Perhaps you wanted to use 'global.{name}' instead?" + ) raise ConfigurationError(error_message) return name.split(".", 1) diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index d95fe44b34a..5007a622d82 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -247,10 +247,7 @@ def __init__( def __str__(self) -> str: # Use `dist` in the error message because its stringification # includes more information, like the version and location. - return "None {} metadata found for distribution: {}".format( - self.metadata_name, - self.dist, - ) + return f"None {self.metadata_name} metadata found for distribution: {self.dist}" class UserInstallationInvalid(InstallationError): @@ -594,7 +591,7 @@ def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> N self.gots = gots def body(self) -> str: - return " {}:\n{}".format(self._requirement_name(), self._hash_comparison()) + return f" {self._requirement_name()}:\n{self._hash_comparison()}" def _hash_comparison(self) -> str: """ @@ -616,11 +613,9 @@ def hash_then_or(hash_name: str) -> "chain[str]": lines: List[str] = [] for hash_name, expecteds in self.allowed.items(): prefix = hash_then_or(hash_name) - lines.extend( - (" Expected {} {}".format(next(prefix), e)) for e in expecteds - ) + lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds) lines.append( - " Got {}\n".format(self.gots[hash_name].hexdigest()) + f" Got {self.gots[hash_name].hexdigest()}\n" ) return "\n".join(lines) diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index 2121ca327e6..ec9ebc36718 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -533,8 +533,8 @@ def _sort_key(self, candidate: InstallationCandidate) -> CandidateSortingKey: ) except ValueError: raise UnsupportedWheel( - "{} is not a supported wheel for this platform. It " - "can't be sorted.".format(wheel.filename) + f"{wheel.filename} is not a supported wheel for this platform. It " + "can't be sorted." ) if self._prefer_binary: binary_preference = 1 @@ -939,9 +939,7 @@ def _format_versions(cand_iter: Iterable[InstallationCandidate]) -> str: _format_versions(best_candidate_result.iter_all()), ) - raise DistributionNotFound( - "No matching distribution found for {}".format(req) - ) + raise DistributionNotFound(f"No matching distribution found for {req}") def _should_install_candidate( candidate: Optional[InstallationCandidate], diff --git a/src/pip/_internal/models/candidate.py b/src/pip/_internal/models/candidate.py index a4963aec638..9184a902aef 100644 --- a/src/pip/_internal/models/candidate.py +++ b/src/pip/_internal/models/candidate.py @@ -27,8 +27,4 @@ def __repr__(self) -> str: ) def __str__(self) -> str: - return "{!r} candidate (version {} at {})".format( - self.name, - self.version, - self.link, - ) + return f"{self.name!r} candidate (version {self.version} at {self.link})" diff --git a/src/pip/_internal/models/direct_url.py b/src/pip/_internal/models/direct_url.py index e219d73849b..0af884bd8e3 100644 --- a/src/pip/_internal/models/direct_url.py +++ b/src/pip/_internal/models/direct_url.py @@ -31,9 +31,7 @@ def _get( value = d[key] if not isinstance(value, expected_type): raise DirectUrlValidationError( - "{!r} has unexpected type for {} (expected {})".format( - value, key, expected_type - ) + f"{value!r} has unexpected type for {key} (expected {expected_type})" ) return value diff --git a/src/pip/_internal/models/format_control.py b/src/pip/_internal/models/format_control.py index db3995eac9f..ccd11272c03 100644 --- a/src/pip/_internal/models/format_control.py +++ b/src/pip/_internal/models/format_control.py @@ -33,9 +33,7 @@ def __eq__(self, other: object) -> bool: return all(getattr(self, k) == getattr(other, k) for k in self.__slots__) def __repr__(self) -> str: - return "{}({}, {})".format( - self.__class__.__name__, self.no_binary, self.only_binary - ) + return f"{self.__class__.__name__}({self.no_binary}, {self.only_binary})" @staticmethod def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None: diff --git a/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py index 4453519ad02..73041b864c3 100644 --- a/src/pip/_internal/models/link.py +++ b/src/pip/_internal/models/link.py @@ -368,9 +368,7 @@ def __str__(self) -> str: else: rp = "" if self.comes_from: - return "{} (from {}){}".format( - redact_auth_from_url(self._url), self.comes_from, rp - ) + return f"{redact_auth_from_url(self._url)} (from {self.comes_from}){rp}" else: return redact_auth_from_url(str(self._url)) diff --git a/src/pip/_internal/network/download.py b/src/pip/_internal/network/download.py index 79b82a570e5..d1d43541e6b 100644 --- a/src/pip/_internal/network/download.py +++ b/src/pip/_internal/network/download.py @@ -42,7 +42,7 @@ def _prepare_download( logged_url = redact_auth_from_url(url) if total_length: - logged_url = "{} ({})".format(logged_url, format_size(total_length)) + logged_url = f"{logged_url} ({format_size(total_length)})" if is_from_cache(resp): logger.info("Using cached %s", logged_url) diff --git a/src/pip/_internal/operations/install/wheel.py b/src/pip/_internal/operations/install/wheel.py index 58a7730597b..f67180c9e65 100644 --- a/src/pip/_internal/operations/install/wheel.py +++ b/src/pip/_internal/operations/install/wheel.py @@ -164,16 +164,14 @@ def message_about_scripts_not_on_PATH(scripts: Sequence[str]) -> Optional[str]: for parent_dir, dir_scripts in warn_for.items(): sorted_scripts: List[str] = sorted(dir_scripts) if len(sorted_scripts) == 1: - start_text = "script {} is".format(sorted_scripts[0]) + start_text = f"script {sorted_scripts[0]} is" else: start_text = "scripts {} are".format( ", ".join(sorted_scripts[:-1]) + " and " + sorted_scripts[-1] ) msg_lines.append( - "The {} installed in '{}' which is not on PATH.".format( - start_text, parent_dir - ) + f"The {start_text} installed in '{parent_dir}' which is not on PATH." ) last_line_fmt = ( @@ -321,9 +319,7 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]: scripts_to_generate.append("pip = " + pip_script) if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall": - scripts_to_generate.append( - "pip{} = {}".format(sys.version_info[0], pip_script) - ) + scripts_to_generate.append(f"pip{sys.version_info[0]} = {pip_script}") scripts_to_generate.append(f"pip{get_major_minor_version()} = {pip_script}") # Delete any other versioned pip entry points @@ -336,9 +332,7 @@ def get_console_script_specs(console: Dict[str, str]) -> List[str]: scripts_to_generate.append("easy_install = " + easy_install_script) scripts_to_generate.append( - "easy_install-{} = {}".format( - get_major_minor_version(), easy_install_script - ) + f"easy_install-{get_major_minor_version()} = {easy_install_script}" ) # Delete any other versioned easy_install entry points easy_install_ep = [ @@ -408,10 +402,10 @@ def save(self) -> None: class MissingCallableSuffix(InstallationError): def __init__(self, entry_point: str) -> None: super().__init__( - "Invalid script entry point: {} - A callable " + f"Invalid script entry point: {entry_point} - A callable " "suffix is required. Cf https://packaging.python.org/" "specifications/entry-points/#use-for-scripts for more " - "information.".format(entry_point) + "information." ) @@ -712,7 +706,7 @@ def req_error_context(req_description: str) -> Generator[None, None, None]: try: yield except InstallationError as e: - message = "For req: {}. {}".format(req_description, e.args[0]) + message = f"For req: {req_description}. {e.args[0]}" raise InstallationError(message) from e diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index 488e76358be..956717d1e52 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -603,8 +603,8 @@ def _prepare_linked_requirement( ) except NetworkConnectionError as exc: raise InstallationError( - "Could not install requirement {} because of HTTP " - "error {} for URL {}".format(req, exc, link) + f"Could not install requirement {req} because of HTTP " + f"error {exc} for URL {link}" ) else: file_path = self._downloaded[link.url] @@ -684,9 +684,9 @@ def prepare_editable_requirement( with indent_log(): if self.require_hashes: raise InstallationError( - "The editable requirement {} cannot be installed when " + f"The editable requirement {req} cannot be installed when " "requiring hashes, because there is no single file to " - "hash.".format(req) + "hash." ) req.ensure_has_source_dir(self.src_dir) req.update_editable() @@ -714,7 +714,7 @@ def prepare_installed_requirement( assert req.satisfied_by, "req should have been satisfied but isn't" assert skip_reason is not None, ( "did not get skip reason skipped but req.satisfied_by " - "is set to {}".format(req.satisfied_by) + f"is set to {req.satisfied_by}" ) logger.info( "Requirement %s: %s (%s)", skip_reason, req, req.satisfied_by.version diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py index b52c9a456bb..7e2d0e5b879 100644 --- a/src/pip/_internal/req/constructors.py +++ b/src/pip/_internal/req/constructors.py @@ -462,7 +462,7 @@ def install_req_from_req_string( raise InstallationError( "Packages installed from PyPI cannot depend on packages " "which are not also hosted on PyPI.\n" - "{} depends on {} ".format(comes_from.name, req) + f"{comes_from.name} depends on {req} " ) return InstallRequirement( diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index e556be2b40b..b61a219df68 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -191,7 +191,7 @@ def __str__(self) -> str: if self.req: s = redact_auth_from_requirement(self.req) if self.link: - s += " from {}".format(redact_auth_from_url(self.link.url)) + s += f" from {redact_auth_from_url(self.link.url)}" elif self.link: s = redact_auth_from_url(self.link.url) else: @@ -221,7 +221,7 @@ def format_debug(self) -> str: attributes = vars(self) names = sorted(attributes) - state = ("{}={!r}".format(attr, attributes[attr]) for attr in sorted(names)) + state = (f"{attr}={attributes[attr]!r}" for attr in sorted(names)) return "<{name} object: {{{state}}}>".format( name=self.__class__.__name__, state=", ".join(state), @@ -754,8 +754,8 @@ def archive(self, build_dir: Optional[str]) -> None: if os.path.exists(archive_path): response = ask_path_exists( - "The file {} exists. (i)gnore, (w)ipe, " - "(b)ackup, (a)bort ".format(display_path(archive_path)), + f"The file {display_path(archive_path)} exists. (i)gnore, (w)ipe, " + "(b)ackup, (a)bort ", ("i", "w", "b", "a"), ) if response == "i": diff --git a/src/pip/_internal/req/req_uninstall.py b/src/pip/_internal/req/req_uninstall.py index 861aa4f2286..3ca10098cf9 100644 --- a/src/pip/_internal/req/req_uninstall.py +++ b/src/pip/_internal/req/req_uninstall.py @@ -71,16 +71,16 @@ def uninstallation_paths(dist: BaseDistribution) -> Generator[str, None, None]: entries = dist.iter_declared_entries() if entries is None: - msg = "Cannot uninstall {dist}, RECORD file not found.".format(dist=dist) + msg = f"Cannot uninstall {dist}, RECORD file not found." installer = dist.installer if not installer or installer == "pip": - dep = "{}=={}".format(dist.raw_name, dist.version) + dep = f"{dist.raw_name}=={dist.version}" msg += ( " You might be able to recover from this via: " - "'pip install --force-reinstall --no-deps {}'.".format(dep) + f"'pip install --force-reinstall --no-deps {dep}'." ) else: - msg += " Hint: The package was installed by {}.".format(installer) + msg += f" Hint: The package was installed by {installer}." raise UninstallationError(msg) for entry in entries: diff --git a/src/pip/_internal/resolution/legacy/resolver.py b/src/pip/_internal/resolution/legacy/resolver.py index b17b7e4530b..5ddb848a9bc 100644 --- a/src/pip/_internal/resolution/legacy/resolver.py +++ b/src/pip/_internal/resolution/legacy/resolver.py @@ -231,9 +231,7 @@ def _add_requirement_to_set( tags = compatibility_tags.get_supported() if requirement_set.check_supported_wheels and not wheel.supported(tags): raise InstallationError( - "{} is not a supported wheel on this platform.".format( - wheel.filename - ) + f"{wheel.filename} is not a supported wheel on this platform." ) # This next bit is really a sanity check. @@ -287,9 +285,9 @@ def _add_requirement_to_set( ) if does_not_satisfy_constraint: raise InstallationError( - "Could not satisfy constraints for '{}': " + f"Could not satisfy constraints for '{install_req.name}': " "installation from path or url cannot be " - "constrained to a version".format(install_req.name) + "constrained to a version" ) # If we're now installing a constraint, mark the existing # object for real installation. @@ -398,9 +396,9 @@ def _find_requirement_link(self, req: InstallRequirement) -> Optional[Link]: # "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}" - ).format(candidate=best_candidate, reason=reason) + f"yanked version: {best_candidate}\n" + f"Reason for being yanked: {reason}" + ) logger.warning(msg) return link diff --git a/src/pip/_internal/resolution/resolvelib/candidates.py b/src/pip/_internal/resolution/resolvelib/candidates.py index 97541655fa0..4125cda2b7c 100644 --- a/src/pip/_internal/resolution/resolvelib/candidates.py +++ b/src/pip/_internal/resolution/resolvelib/candidates.py @@ -159,10 +159,7 @@ def __str__(self) -> str: return f"{self.name} {self.version}" def __repr__(self) -> str: - return "{class_name}({link!r})".format( - class_name=self.__class__.__name__, - link=str(self._link), - ) + return f"{self.__class__.__name__}({str(self._link)!r})" def __hash__(self) -> int: return hash((self.__class__, self._link)) @@ -354,10 +351,7 @@ def __str__(self) -> str: return str(self.dist) def __repr__(self) -> str: - return "{class_name}({distribution!r})".format( - class_name=self.__class__.__name__, - distribution=self.dist, - ) + return f"{self.__class__.__name__}({self.dist!r})" def __hash__(self) -> int: return hash((self.__class__, self.name, self.version)) @@ -455,11 +449,7 @@ def __str__(self) -> str: return "{}[{}] {}".format(name, ",".join(self.extras), rest) def __repr__(self) -> str: - return "{class_name}(base={base!r}, extras={extras!r})".format( - class_name=self.__class__.__name__, - base=self.base, - extras=self.extras, - ) + return f"{self.__class__.__name__}(base={self.base!r}, extras={self.extras!r})" def __hash__(self) -> int: return hash((self.base, self.extras)) diff --git a/src/pip/_internal/resolution/resolvelib/factory.py b/src/pip/_internal/resolution/resolvelib/factory.py index 38c199448a1..97137c997c8 100644 --- a/src/pip/_internal/resolution/resolvelib/factory.py +++ b/src/pip/_internal/resolution/resolvelib/factory.py @@ -753,8 +753,8 @@ def describe_trigger(parent: Candidate) -> str: info = "the requested packages" msg = ( - "Cannot install {} because these package versions " - "have conflicting dependencies.".format(info) + f"Cannot install {info} because these package versions " + "have conflicting dependencies." ) logger.critical(msg) msg = "\nThe conflict is caused by:" diff --git a/src/pip/_internal/resolution/resolvelib/requirements.py b/src/pip/_internal/resolution/resolvelib/requirements.py index 7d1e7bfddd1..4af4a9f25a6 100644 --- a/src/pip/_internal/resolution/resolvelib/requirements.py +++ b/src/pip/_internal/resolution/resolvelib/requirements.py @@ -15,10 +15,7 @@ def __str__(self) -> str: return str(self.candidate) def __repr__(self) -> str: - return "{class_name}({candidate!r})".format( - class_name=self.__class__.__name__, - candidate=self.candidate, - ) + return f"{self.__class__.__name__}({self.candidate!r})" @property def project_name(self) -> NormalizedName: @@ -50,10 +47,7 @@ def __str__(self) -> str: return str(self._ireq.req) def __repr__(self) -> str: - return "{class_name}({requirement!r})".format( - class_name=self.__class__.__name__, - requirement=str(self._ireq.req), - ) + return f"{self.__class__.__name__}({str(self._ireq.req)!r})" @property def project_name(self) -> NormalizedName: @@ -116,10 +110,7 @@ def __str__(self) -> str: return f"Python {self.specifier}" def __repr__(self) -> str: - return "{class_name}({specifier!r})".format( - class_name=self.__class__.__name__, - specifier=str(self.specifier), - ) + return f"{self.__class__.__name__}({str(self.specifier)!r})" @property def project_name(self) -> NormalizedName: @@ -155,10 +146,7 @@ def __str__(self) -> str: return f"{self._name} (unavailable)" def __repr__(self) -> str: - return "{class_name}({name!r})".format( - class_name=self.__class__.__name__, - name=str(self._name), - ) + return f"{self.__class__.__name__}({str(self._name)!r})" @property def project_name(self) -> NormalizedName: diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index 78060e86417..42a8536cdd3 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -77,11 +77,7 @@ def get_pip_version() -> 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 f"pip {__version__} from {pip_pkg_dir} (python {get_major_minor_version()})" def normalize_version_info(py_version_info: Tuple[int, ...]) -> Tuple[int, int, int]: @@ -279,13 +275,13 @@ def strtobool(val: str) -> int: def format_size(bytes: float) -> str: if bytes > 1000 * 1000: - return "{:.1f} MB".format(bytes / 1000.0 / 1000) + return f"{bytes / 1000.0 / 1000:.1f} MB" elif bytes > 10 * 1000: - return "{} kB".format(int(bytes / 1000)) + return f"{int(bytes / 1000)} kB" elif bytes > 1000: - return "{:.1f} kB".format(bytes / 1000.0) + return f"{bytes / 1000.0:.1f} kB" else: - return "{} bytes".format(int(bytes)) + return f"{int(bytes)} bytes" def tabulate(rows: Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]: @@ -522,9 +518,7 @@ def redact_netloc(netloc: str) -> str: else: user = urllib.parse.quote(user) password = ":****" - return "{user}{password}@{netloc}".format( - user=user, password=password, netloc=netloc - ) + return f"{user}{password}@{netloc}" def _transform_url( @@ -592,7 +586,7 @@ def __init__(self, secret: str, redacted: str) -> None: self.redacted = redacted def __repr__(self) -> str: - return "".format(str(self)) + return f"" def __str__(self) -> str: return self.redacted diff --git a/src/pip/_internal/utils/wheel.py b/src/pip/_internal/utils/wheel.py index e5e3f34ed81..3551f8f19bc 100644 --- a/src/pip/_internal/utils/wheel.py +++ b/src/pip/_internal/utils/wheel.py @@ -28,7 +28,7 @@ def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]: 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(f"{name} has an invalid wheel, {str(e)}") check_compatibility(version, name) @@ -60,9 +60,7 @@ def wheel_dist_info_dir(source: ZipFile, name: str) -> str: canonical_name = canonicalize_name(name) if not info_dir_name.startswith(canonical_name): raise UnsupportedWheel( - ".dist-info directory {!r} does not start with {!r}".format( - info_dir, canonical_name - ) + f".dist-info directory {info_dir!r} does not start with {canonical_name!r}" ) return info_dir diff --git a/src/pip/_internal/vcs/versioncontrol.py b/src/pip/_internal/vcs/versioncontrol.py index 02bbf68e7ad..46ca2799b76 100644 --- a/src/pip/_internal/vcs/versioncontrol.py +++ b/src/pip/_internal/vcs/versioncontrol.py @@ -405,9 +405,9 @@ def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: scheme, netloc, path, query, frag = urllib.parse.urlsplit(url) if "+" not in scheme: raise ValueError( - "Sorry, {!r} is a malformed VCS url. " + f"Sorry, {url!r} is a malformed VCS url. " "The format is +://, " - "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp".format(url) + "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp" ) # Remove the vcs prefix. scheme = scheme.split("+", 1)[1] @@ -417,9 +417,9 @@ def get_url_rev_and_auth(cls, url: str) -> Tuple[str, Optional[str], AuthInfo]: path, rev = path.rsplit("@", 1) if not rev: raise InstallationError( - "The URL {!r} has an empty revision (after @) " + f"The URL {url!r} has an empty revision (after @) " "which is not supported. Include a revision after @ " - "or remove @ from the URL.".format(url) + "or remove @ from the URL." ) url = urllib.parse.urlunsplit((scheme, netloc, path, query, "")) return url, rev, user_pass @@ -566,7 +566,7 @@ def obtain(self, dest: str, url: HiddenText, verbosity: int) -> None: self.name, url, ) - response = ask_path_exists("What to do? {}".format(prompt[0]), prompt[1]) + response = ask_path_exists(f"What to do? {prompt[0]}", prompt[1]) if response == "a": sys.exit(-1) diff --git a/src/pip/_internal/wheel_builder.py b/src/pip/_internal/wheel_builder.py index 60d75dd18ef..b1debe3496c 100644 --- a/src/pip/_internal/wheel_builder.py +++ b/src/pip/_internal/wheel_builder.py @@ -140,15 +140,15 @@ def _verify_one(req: InstallRequirement, wheel_path: str) -> None: w = Wheel(os.path.basename(wheel_path)) if canonicalize_name(w.name) != canonical_name: raise InvalidWheelFilename( - "Wheel has unexpected file name: expected {!r}, " - "got {!r}".format(canonical_name, w.name), + f"Wheel has unexpected file name: expected {canonical_name!r}, " + f"got {w.name!r}", ) dist = get_wheel_distribution(FilesystemWheel(wheel_path), canonical_name) dist_verstr = str(dist.version) if canonicalize_version(dist_verstr) != canonicalize_version(w.version): raise InvalidWheelFilename( - "Wheel has unexpected file name: expected {!r}, " - "got {!r}".format(dist_verstr, w.version), + f"Wheel has unexpected file name: expected {dist_verstr!r}, " + f"got {w.version!r}", ) metadata_version_value = dist.metadata_version if metadata_version_value is None: @@ -160,8 +160,7 @@ def _verify_one(req: InstallRequirement, wheel_path: str) -> None: raise UnsupportedWheel(msg) if metadata_version >= Version("1.2") and not isinstance(dist.version, Version): raise UnsupportedWheel( - "Metadata 1.2 mandates PEP 440 version, " - "but {!r} is not".format(dist_verstr) + f"Metadata 1.2 mandates PEP 440 version, but {dist_verstr!r} is not" ) diff --git a/tests/conftest.py b/tests/conftest.py index 6ae2e6d62d8..8e498abd08e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -141,7 +141,7 @@ def pytest_collection_modifyitems(config: Config, items: List[pytest.Function]) if "script" in item.fixturenames: raise RuntimeError( "Cannot use the ``script`` funcarg in a unit test: " - "(filename = {}, item = {})".format(module_path, item) + f"(filename = {module_path}, item = {item})" ) else: raise RuntimeError(f"Unknown test type (filename = {module_path})") diff --git a/tests/functional/test_cli.py b/tests/functional/test_cli.py index a1b69b72106..3c3f45d51d1 100644 --- a/tests/functional/test_cli.py +++ b/tests/functional/test_cli.py @@ -23,7 +23,7 @@ def test_entrypoints_work(entrypoint: str, script: PipTestEnvironment) -> None: fake_pkg.mkdir() fake_pkg.joinpath("setup.py").write_text( dedent( - """ + f""" from setuptools import setup setup( @@ -31,13 +31,11 @@ def test_entrypoints_work(entrypoint: str, script: PipTestEnvironment) -> None: version="0.1.0", entry_points={{ "console_scripts": [ - {!r} + {entrypoint!r} ] }} ) - """.format( - entrypoint - ) + """ ) ) diff --git a/tests/functional/test_completion.py b/tests/functional/test_completion.py index 2aa861aacb7..4be033583ca 100644 --- a/tests/functional/test_completion.py +++ b/tests/functional/test_completion.py @@ -400,7 +400,7 @@ def test_completion_path_after_option( def test_completion_uses_same_executable_name( autocomplete_script: PipTestEnvironment, flag: str, deprecated_python: bool ) -> None: - executable_name = "pip{}".format(sys.version_info[0]) + executable_name = f"pip{sys.version_info[0]}" # Deprecated python versions produce an extra deprecation warning result = autocomplete_script.run( executable_name, diff --git a/tests/functional/test_debug.py b/tests/functional/test_debug.py index 77cd732f9f1..77d4bea335f 100644 --- a/tests/functional/test_debug.py +++ b/tests/functional/test_debug.py @@ -68,7 +68,7 @@ def test_debug__tags(script: PipTestEnvironment, args: List[str]) -> None: stdout = result.stdout tags = compatibility_tags.get_supported() - expected_tag_header = "Compatible tags: {}".format(len(tags)) + expected_tag_header = f"Compatible tags: {len(tags)}" assert expected_tag_header in stdout show_verbose_note = "--verbose" not in args diff --git a/tests/functional/test_freeze.py b/tests/functional/test_freeze.py index 9a5937df3de..b2fd1d62982 100644 --- a/tests/functional/test_freeze.py +++ b/tests/functional/test_freeze.py @@ -166,13 +166,11 @@ def fake_install(pkgname: str, dest: str) -> None: with open(egg_info_path, "w") as egg_info_file: egg_info_file.write( textwrap.dedent( - """\ + f"""\ Metadata-Version: 1.0 - Name: {} + Name: {pkgname} Version: 1.0 - """.format( - pkgname - ) + """ ) ) @@ -221,12 +219,10 @@ def test_freeze_editable_not_vcs(script: PipTestEnvironment) -> None: # We need to apply os.path.normcase() to the path since that is what # the freeze code does. expected = textwrap.dedent( - """\ + f"""\ ...# Editable install with no version control (version-pkg==0.1) - -e {} - ...""".format( - os.path.normcase(pkg_path) - ) + -e {os.path.normcase(pkg_path)} + ...""" ) _check_output(result.stdout, expected) @@ -248,12 +244,10 @@ def test_freeze_editable_git_with_no_remote( # We need to apply os.path.normcase() to the path since that is what # the freeze code does. expected = textwrap.dedent( - """\ + f"""\ ...# Editable Git install with no remote (version-pkg==0.1) - -e {} - ...""".format( - os.path.normcase(pkg_path) - ) + -e {os.path.normcase(pkg_path)} + ...""" ) _check_output(result.stdout, expected) @@ -653,9 +647,9 @@ def test_freeze_with_requirement_option_file_url_egg_not_installed( expect_stderr=True, ) expected_err = ( - "WARNING: Requirement file [requirements.txt] contains {}, " + f"WARNING: Requirement file [requirements.txt] contains {url}, " "but package 'Does.Not-Exist' is not installed\n" - ).format(url) + ) if deprecated_python: assert expected_err in result.stderr else: diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index 140061a17a3..b18fabc84c9 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -106,10 +106,10 @@ def test_pep518_refuses_conflicting_requires( assert ( result.returncode != 0 and ( - "Some build dependencies for {url} conflict " + f"Some build dependencies for {project_dir.as_uri()} conflict " "with PEP 517/518 supported " "requirements: setuptools==1.0 is incompatible with " - "setuptools>=40.8.0.".format(url=project_dir.as_uri()) + "setuptools>=40.8.0." ) in result.stderr ), str(result) @@ -595,8 +595,8 @@ def test_hashed_install_success( with requirements_file( "simple2==1.0 --hash=sha256:9336af72ca661e6336eb87bc7de3e8844d853e" "3848c2b9bbd2e8bf01db88c2c7\n" - "{simple} --hash=sha256:393043e672415891885c9a2a0929b1af95fb866d6c" - "a016b42d2e6ce53619b653".format(simple=file_url), + f"{file_url} --hash=sha256:393043e672415891885c9a2a0929b1af95fb866d6c" + "a016b42d2e6ce53619b653", tmpdir, ) as reqs_file: script.pip_install_local("-r", reqs_file.resolve()) @@ -1735,7 +1735,7 @@ def test_install_builds_wheels(script: PipTestEnvironment, data: TestData) -> No # into the cache assert wheels != [], str(res) assert wheels == [ - "Upper-2.0-py{}-none-any.whl".format(sys.version_info[0]), + f"Upper-2.0-py{sys.version_info[0]}-none-any.whl", ] @@ -2387,7 +2387,7 @@ def test_install_verify_package_name_normalization( assert "Successfully installed simple-package" in result.stdout result = script.pip("install", package_name) - assert "Requirement already satisfied: {}".format(package_name) in result.stdout + assert f"Requirement already satisfied: {package_name}" in result.stdout def test_install_logs_pip_version_in_debug( diff --git a/tests/functional/test_install_config.py b/tests/functional/test_install_config.py index ecaf2f705a2..7f418067f97 100644 --- a/tests/functional/test_install_config.py +++ b/tests/functional/test_install_config.py @@ -184,12 +184,10 @@ def test_config_file_override_stack( config_file.write_text( textwrap.dedent( - """\ + f"""\ [global] - index-url = {}/simple1 - """.format( - base_address - ) + index-url = {base_address}/simple1 + """ ) ) script.pip("install", "-vvv", "INITools", expect_error=True) @@ -197,14 +195,12 @@ def test_config_file_override_stack( config_file.write_text( textwrap.dedent( - """\ + f"""\ [global] - index-url = {address}/simple1 + index-url = {base_address}/simple1 [install] - index-url = {address}/simple2 - """.format( - address=base_address - ) + index-url = {base_address}/simple2 + """ ) ) script.pip("install", "-vvv", "INITools", expect_error=True) diff --git a/tests/functional/test_install_index.py b/tests/functional/test_install_index.py index b73e28f4794..72b0b9db7bd 100644 --- a/tests/functional/test_install_index.py +++ b/tests/functional/test_install_index.py @@ -41,13 +41,11 @@ def test_find_links_requirements_file_relative_path( """Test find-links as a relative path to a reqs file.""" script.scratch_path.joinpath("test-req.txt").write_text( textwrap.dedent( - """ + f""" --no-index - --find-links={} + --find-links={data.packages.as_posix()} parent==0.1 - """.format( - data.packages.as_posix() - ) + """ ) ) result = script.pip( diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py index c21b9ba83de..c1325817838 100644 --- a/tests/functional/test_install_reqs.py +++ b/tests/functional/test_install_reqs.py @@ -95,7 +95,7 @@ def test_requirements_file(script: PipTestEnvironment) -> None: result.did_create(script.site_packages / "INITools-0.2.dist-info") result.did_create(script.site_packages / "initools") assert result.files_created[script.site_packages / other_lib_name].dir - fn = "{}-{}.dist-info".format(other_lib_name, other_lib_version) + fn = f"{other_lib_name}-{other_lib_version}.dist-info" assert result.files_created[script.site_packages / fn].dir @@ -260,13 +260,13 @@ def test_respect_order_in_requirements_file( assert ( "parent" in downloaded[0] - ), 'First download should be "parent" but was "{}"'.format(downloaded[0]) + ), f'First download should be "parent" but was "{downloaded[0]}"' assert ( "child" in downloaded[1] - ), 'Second download should be "child" but was "{}"'.format(downloaded[1]) + ), f'Second download should be "child" but was "{downloaded[1]}"' assert ( "simple" in downloaded[2] - ), 'Third download should be "simple" but was "{}"'.format(downloaded[2]) + ), f'Third download should be "simple" but was "{downloaded[2]}"' def test_install_local_editable_with_extras( diff --git a/tests/functional/test_install_wheel.py b/tests/functional/test_install_wheel.py index 4221ae76ae2..7e7aeaf7a81 100644 --- a/tests/functional/test_install_wheel.py +++ b/tests/functional/test_install_wheel.py @@ -169,9 +169,9 @@ def get_header_scheme_path_for_script( ) -> Path: command = ( "from pip._internal.locations import get_scheme;" - "scheme = get_scheme({!r});" + f"scheme = get_scheme({dist_name!r});" "print(scheme.headers);" - ).format(dist_name) + ) result = script.run("python", "-c", command).stdout return Path(result.strip()) diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py index b5945edf89b..df82713d0e8 100644 --- a/tests/functional/test_new_resolver.py +++ b/tests/functional/test_new_resolver.py @@ -1185,7 +1185,7 @@ def test_new_resolver_presents_messages_when_backtracking_a_lot( for index in range(1, N + 1): A_version = f"{index}.0.0" B_version = f"{index}.0.0" - C_version = "{index_minus_one}.0.0".format(index_minus_one=index - 1) + C_version = f"{index - 1}.0.0" depends = ["B == " + B_version] if index != 1: diff --git a/tests/functional/test_new_resolver_errors.py b/tests/functional/test_new_resolver_errors.py index 62304131283..5976de52e39 100644 --- a/tests/functional/test_new_resolver_errors.py +++ b/tests/functional/test_new_resolver_errors.py @@ -71,8 +71,8 @@ def test_new_resolver_conflict_constraints_file( def test_new_resolver_requires_python_error(script: PipTestEnvironment) -> None: - compatible_python = ">={0.major}.{0.minor}".format(sys.version_info) - incompatible_python = "<{0.major}.{0.minor}".format(sys.version_info) + compatible_python = f">={sys.version_info.major}.{sys.version_info.minor}" + incompatible_python = f"<{sys.version_info.major}.{sys.version_info.minor}" pkga = create_test_package_with_setup( script, @@ -99,7 +99,7 @@ def test_new_resolver_requires_python_error(script: PipTestEnvironment) -> None: def test_new_resolver_checks_requires_python_before_dependencies( script: PipTestEnvironment, ) -> None: - incompatible_python = "<{0.major}.{0.minor}".format(sys.version_info) + incompatible_python = f"<{sys.version_info.major}.{sys.version_info.minor}" pkg_dep = create_basic_wheel_for_package( script, diff --git a/tests/functional/test_new_resolver_hashes.py b/tests/functional/test_new_resolver_hashes.py index 6db2efd0e4c..d26def14ae9 100644 --- a/tests/functional/test_new_resolver_hashes.py +++ b/tests/functional/test_new_resolver_hashes.py @@ -24,18 +24,11 @@ def _create_find_links(script: PipTestEnvironment) -> _FindLinks: index_html = script.scratch_path / "index.html" index_html.write_text( - """ + f""" - {sdist_path.stem} - {wheel_path.stem} - """.format( - sdist_url=sdist_path.as_uri(), - sdist_hash=sdist_hash, - sdist_path=sdist_path, - wheel_url=wheel_path.as_uri(), - wheel_hash=wheel_hash, - wheel_path=wheel_path, - ).strip() + {sdist_path.stem} + {wheel_path.stem} + """.strip() ) return _FindLinks(index_html, sdist_hash, wheel_hash) @@ -99,9 +92,7 @@ def test_new_resolver_hash_intersect_from_constraint( constraints_txt = script.scratch_path / "constraints.txt" constraints_txt.write_text( - "base==0.1.0 --hash=sha256:{sdist_hash}".format( - sdist_hash=find_links.sdist_hash, - ), + f"base==0.1.0 --hash=sha256:{find_links.sdist_hash}", ) requirements_txt = script.scratch_path / "requirements.txt" requirements_txt.write_text( @@ -200,13 +191,10 @@ def test_new_resolver_hash_intersect_empty_from_constraint( constraints_txt = script.scratch_path / "constraints.txt" constraints_txt.write_text( - """ - base==0.1.0 --hash=sha256:{sdist_hash} - base==0.1.0 --hash=sha256:{wheel_hash} - """.format( - sdist_hash=find_links.sdist_hash, - wheel_hash=find_links.wheel_hash, - ), + f""" + base==0.1.0 --hash=sha256:{find_links.sdist_hash} + base==0.1.0 --hash=sha256:{find_links.wheel_hash} + """, ) result = script.pip( @@ -240,19 +228,15 @@ def test_new_resolver_hash_requirement_and_url_constraint_can_succeed( requirements_txt = script.scratch_path / "requirements.txt" requirements_txt.write_text( - """ + f""" base==0.1.0 --hash=sha256:{wheel_hash} - """.format( - wheel_hash=wheel_hash, - ), + """, ) constraints_txt = script.scratch_path / "constraints.txt" - constraint_text = "base @ {wheel_url}\n".format(wheel_url=wheel_path.as_uri()) + constraint_text = f"base @ {wheel_path.as_uri()}\n" if constrain_by_hash: - constraint_text += "base==0.1.0 --hash=sha256:{wheel_hash}\n".format( - wheel_hash=wheel_hash, - ) + constraint_text += f"base==0.1.0 --hash=sha256:{wheel_hash}\n" constraints_txt.write_text(constraint_text) script.pip( @@ -280,19 +264,15 @@ def test_new_resolver_hash_requirement_and_url_constraint_can_fail( requirements_txt = script.scratch_path / "requirements.txt" requirements_txt.write_text( - """ + f""" base==0.1.0 --hash=sha256:{other_hash} - """.format( - other_hash=other_hash, - ), + """, ) constraints_txt = script.scratch_path / "constraints.txt" - constraint_text = "base @ {wheel_url}\n".format(wheel_url=wheel_path.as_uri()) + constraint_text = f"base @ {wheel_path.as_uri()}\n" if constrain_by_hash: - constraint_text += "base==0.1.0 --hash=sha256:{other_hash}\n".format( - other_hash=other_hash, - ) + constraint_text += f"base==0.1.0 --hash=sha256:{other_hash}\n" constraints_txt.write_text(constraint_text) result = script.pip( @@ -343,17 +323,12 @@ def test_new_resolver_hash_with_extras(script: PipTestEnvironment) -> None: requirements_txt = script.scratch_path / "requirements.txt" requirements_txt.write_text( - """ + f""" child[extra]==0.1.0 --hash=sha256:{child_hash} parent_with_extra==0.1.0 --hash=sha256:{parent_with_extra_hash} parent_without_extra==0.1.0 --hash=sha256:{parent_without_extra_hash} extra==0.1.0 --hash=sha256:{extra_hash} - """.format( - child_hash=child_hash, - parent_with_extra_hash=parent_with_extra_hash, - parent_without_extra_hash=parent_without_extra_hash, - extra_hash=extra_hash, - ), + """, ) script.pip( diff --git a/tests/functional/test_new_resolver_target.py b/tests/functional/test_new_resolver_target.py index 811ae935aec..a81cfe5e83d 100644 --- a/tests/functional/test_new_resolver_target.py +++ b/tests/functional/test_new_resolver_target.py @@ -58,12 +58,7 @@ def test_new_resolver_target_checks_compatibility_failure( if platform: args += ["--platform", platform] - args_tag = "{}{}-{}-{}".format( - implementation, - python_version, - abi, - platform, - ) + args_tag = f"{implementation}{python_version}-{abi}-{platform}" wheel_tag_matches = args_tag == fake_wheel_tag result = script.pip(*args, expect_error=(not wheel_tag_matches)) diff --git a/tests/functional/test_pep517.py b/tests/functional/test_pep517.py index a642a3f8bfb..78a6c2bbc6c 100644 --- a/tests/functional/test_pep517.py +++ b/tests/functional/test_pep517.py @@ -159,9 +159,9 @@ def test_conflicting_pep517_backend_requirements( expect_error=True, ) msg = ( - "Some build dependencies for {url} conflict with the backend " + f"Some build dependencies for {project_dir.as_uri()} conflict with the backend " "dependencies: simplewheel==1.0 is incompatible with " - "simplewheel==2.0.".format(url=project_dir.as_uri()) + "simplewheel==2.0." ) assert result.returncode != 0 and msg in result.stderr, str(result) @@ -205,8 +205,8 @@ def test_validate_missing_pep517_backend_requirements( expect_error=True, ) msg = ( - "Some build dependencies for {url} are missing: " - "'simplewheel==1.0', 'test_backend'.".format(url=project_dir.as_uri()) + f"Some build dependencies for {project_dir.as_uri()} are missing: " + "'simplewheel==1.0', 'test_backend'." ) assert result.returncode != 0 and msg in result.stderr, str(result) @@ -231,9 +231,9 @@ def test_validate_conflicting_pep517_backend_requirements( expect_error=True, ) msg = ( - "Some build dependencies for {url} conflict with the backend " + f"Some build dependencies for {project_dir.as_uri()} conflict with the backend " "dependencies: simplewheel==2.0 is incompatible with " - "simplewheel==1.0.".format(url=project_dir.as_uri()) + "simplewheel==1.0." ) assert result.returncode != 0 and msg in result.stderr, str(result) diff --git a/tests/functional/test_uninstall.py b/tests/functional/test_uninstall.py index be7fe4c3341..69e340a5675 100644 --- a/tests/functional/test_uninstall.py +++ b/tests/functional/test_uninstall.py @@ -604,9 +604,7 @@ def test_uninstall_without_record_fails( "simple.dist==0.1'." ) elif installer: - expected_error_message += " Hint: The package was installed by {}.".format( - installer - ) + expected_error_message += f" Hint: The package was installed by {installer}." assert result2.stderr.rstrip() == expected_error_message assert_all_changes(result.files_after, result2, ignore_changes) diff --git a/tests/functional/test_wheel.py b/tests/functional/test_wheel.py index 042f5824613..b1183fc830b 100644 --- a/tests/functional/test_wheel.py +++ b/tests/functional/test_wheel.py @@ -59,9 +59,7 @@ def test_pip_wheel_success(script: PipTestEnvironment, data: TestData) -> None: wheel_file_path = script.scratch / wheel_file_name assert re.search( r"Created wheel for simple: " - r"filename={filename} size=\d+ sha256=[A-Fa-f0-9]{{64}}".format( - filename=re.escape(wheel_file_name) - ), + rf"filename={re.escape(wheel_file_name)} size=\d+ sha256=[A-Fa-f0-9]{{64}}", result.stdout, ) assert re.search(r"^\s+Stored in directory: ", result.stdout, re.M) diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index d27c02e25f0..f14837e24ea 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -747,7 +747,7 @@ def assert_installed(self, **kwargs: str) -> None: for val in json.loads(ret.stdout) } expected = {(canonicalize_name(k), v) for k, v in kwargs.items()} - assert expected <= installed, "{!r} not all in {!r}".format(expected, installed) + assert expected <= installed, f"{expected!r} not all in {installed!r}" def assert_not_installed(self, *args: str) -> None: ret = self.pip("list", "--format=json") @@ -755,9 +755,7 @@ def assert_not_installed(self, *args: str) -> None: # None of the given names should be listed as installed, i.e. their # intersection should be empty. expected = {canonicalize_name(k) for k in args} - assert not (expected & installed), "{!r} contained in {!r}".format( - expected, installed - ) + assert not (expected & installed), f"{expected!r} contained in {installed!r}" # FIXME ScriptTest does something similar, but only within a single @@ -1028,7 +1026,7 @@ def _create_test_package_with_srcdir( pkg_path.joinpath("__init__.py").write_text("") subdir_path.joinpath("setup.py").write_text( textwrap.dedent( - """ + f""" from setuptools import setup, find_packages setup( name="{name}", @@ -1036,9 +1034,7 @@ def _create_test_package_with_srcdir( packages=find_packages(), package_dir={{"": "src"}}, ) - """.format( - name=name - ) + """ ) ) return _vcs_add(dir_path, version_pkg_path, vcs) @@ -1052,7 +1048,7 @@ def _create_test_package( _create_main_file(version_pkg_path, name=name, output="0.1") version_pkg_path.joinpath("setup.py").write_text( textwrap.dedent( - """ + f""" from setuptools import setup, find_packages setup( name="{name}", @@ -1061,9 +1057,7 @@ def _create_test_package( py_modules=["{name}"], entry_points=dict(console_scripts=["{name}={name}:main"]), ) - """.format( - name=name - ) + """ ) ) return _vcs_add(dir_path, version_pkg_path, vcs) @@ -1137,7 +1131,7 @@ def urlsafe_b64encode_nopad(data: bytes) -> str: def create_really_basic_wheel(name: str, version: str) -> bytes: def digest(contents: bytes) -> str: - return "sha256={}".format(urlsafe_b64encode_nopad(sha256(contents).digest())) + return f"sha256={urlsafe_b64encode_nopad(sha256(contents).digest())}" def add_file(path: str, text: str) -> None: contents = text.encode("utf-8") @@ -1153,13 +1147,11 @@ def add_file(path: str, text: str) -> None: add_file( f"{dist_info}/METADATA", dedent( - """\ + f"""\ Metadata-Version: 2.1 - Name: {} - Version: {} - """.format( - name, version - ) + Name: {name} + Version: {version} + """ ), ) z.writestr(record_path, "\n".join(",".join(r) for r in records)) diff --git a/tests/lib/local_repos.py b/tests/lib/local_repos.py index a04d1d0fe58..a8cf4aa6c74 100644 --- a/tests/lib/local_repos.py +++ b/tests/lib/local_repos.py @@ -56,7 +56,7 @@ def local_checkout( assert vcs_backend is not None vcs_backend.obtain(repo_url_path, url=hide_url(remote_repo), verbosity=0) - return "{}+{}".format(vcs_name, Path(repo_url_path).as_uri()) + return f"{vcs_name}+{Path(repo_url_path).as_uri()}" def local_repo(remote_repo: str, temp_path: Path) -> str: diff --git a/tests/lib/server.py b/tests/lib/server.py index 1048a173d40..96ac5930dc9 100644 --- a/tests/lib/server.py +++ b/tests/lib/server.py @@ -152,7 +152,7 @@ def html5_page(text: str) -> str: def package_page(spec: Dict[str, str]) -> "WSGIApplication": def link(name: str, value: str) -> str: - return '{}'.format(value, name) + return f'{name}' links = "".join(link(*kv) for kv in spec.items()) return text_html_response(html5_page(links)) diff --git a/tests/lib/test_lib.py b/tests/lib/test_lib.py index a541a0a204d..c01c1beb883 100644 --- a/tests/lib/test_lib.py +++ b/tests/lib/test_lib.py @@ -107,8 +107,8 @@ def run_with_log_command( """ command = ( "import logging; logging.basicConfig(level='INFO'); " - "logging.getLogger().info('sub: {}', 'foo')" - ).format(sub_string) + f"logging.getLogger().info('sub: {sub_string}', 'foo')" + ) args = [sys.executable, "-c", command] script.run(*args, **kwargs) diff --git a/tests/lib/wheel.py b/tests/lib/wheel.py index f2ddfd3b7e1..a4efe53b404 100644 --- a/tests/lib/wheel.py +++ b/tests/lib/wheel.py @@ -190,7 +190,7 @@ def urlsafe_b64encode_nopad(data: bytes) -> str: def digest(contents: bytes) -> str: - return "sha256={}".format(urlsafe_b64encode_nopad(sha256(contents).digest())) + return f"sha256={urlsafe_b64encode_nopad(sha256(contents).digest())}" def record_file_maker_wrapper( diff --git a/tests/unit/test_collector.py b/tests/unit/test_collector.py index 3c8b81de44d..dae083efee1 100644 --- a/tests/unit/test_collector.py +++ b/tests/unit/test_collector.py @@ -119,8 +119,8 @@ def test_get_index_content_invalid_content_type_archive( assert ( "pip._internal.index.collector", logging.WARNING, - "Skipping page {} because it looks like an archive, and cannot " - "be checked by a HTTP HEAD request.".format(url), + f"Skipping page {url} because it looks like an archive, and cannot " + "be checked by a HTTP HEAD request.", ) in caplog.record_tuples @@ -417,8 +417,8 @@ def _test_parse_links_data_attribute( html = ( "" '' - "{}" - ).format(anchor_html) + f"{anchor_html}" + ) html_bytes = html.encode("utf-8") page = IndexContent( html_bytes, @@ -764,8 +764,8 @@ def test_get_index_content_invalid_scheme( ( "pip._internal.index.collector", logging.WARNING, - "Cannot look at {} URL {} because it does not support " - "lookup as web pages.".format(vcs_scheme, url), + f"Cannot look at {vcs_scheme} URL {url} because it does not support " + "lookup as web pages.", ), ] diff --git a/tests/unit/test_link.py b/tests/unit/test_link.py index 311be588858..a379d877b2c 100644 --- a/tests/unit/test_link.py +++ b/tests/unit/test_link.py @@ -143,10 +143,7 @@ def test_is_yanked(self, yanked_reason: Optional[str], expected: bool) -> None: def test_is_hash_allowed( self, hash_name: str, hex_digest: str, expected: bool ) -> None: - url = "https://example.com/wheel.whl#{hash_name}={hex_digest}".format( - hash_name=hash_name, - hex_digest=hex_digest, - ) + url = f"https://example.com/wheel.whl#{hash_name}={hex_digest}" link = Link(url) hashes_data = { "sha512": [128 * "a", 128 * "b"], diff --git a/tests/unit/test_network_utils.py b/tests/unit/test_network_utils.py index cdc10b2ba6e..380d5741ff6 100644 --- a/tests/unit/test_network_utils.py +++ b/tests/unit/test_network_utils.py @@ -21,8 +21,8 @@ def test_raise_for_status_raises_exception(status_code: int, error_type: str) -> with pytest.raises(NetworkConnectionError) as excinfo: raise_for_status(resp) assert str(excinfo.value) == ( - "{} {}: Network Error for url:" - " http://www.example.com/whatever.tgz".format(status_code, error_type) + f"{status_code} {error_type}: Network Error for url:" + " http://www.example.com/whatever.tgz" ) diff --git a/tests/unit/test_req.py b/tests/unit/test_req.py index 863f070af3c..5e3c640a55e 100644 --- a/tests/unit/test_req.py +++ b/tests/unit/test_req.py @@ -235,8 +235,8 @@ def test_unsupported_hashes(self, data: TestData) -> None: r"file \(line 1\)\)\n" r"Can't verify hashes for these file:// requirements because " r"they point to directories:\n" - r" file://.*{sep}data{sep}packages{sep}FSPkg " - r"\(from -r file \(line 2\)\)".format(sep=sep) + rf" file://.*{sep}data{sep}packages{sep}FSPkg " + r"\(from -r file \(line 2\)\)" ), ): resolver.resolve(reqset.all_requirements, True) diff --git a/tests/unit/test_req_file.py b/tests/unit/test_req_file.py index 7a196eb8dd6..94ccfb98d86 100644 --- a/tests/unit/test_req_file.py +++ b/tests/unit/test_req_file.py @@ -297,7 +297,7 @@ def test_yield_pep440_line_requirement(self, line_processor: LineProcessor) -> N def test_yield_line_constraint(self, line_processor: LineProcessor) -> None: line = "SomeProject" filename = "filename" - comes_from = "-c {} (line {})".format(filename, 1) + comes_from = f"-c {filename} (line {1})" req = install_req_from_line(line, comes_from=comes_from, constraint=True) found_req = line_processor(line, filename, 1, constraint=True)[0] assert repr(found_req) == repr(req) @@ -326,7 +326,7 @@ def test_yield_editable_constraint(self, line_processor: LineProcessor) -> None: url = "git+https://url#egg=SomeProject" line = f"-e {url}" filename = "filename" - comes_from = "-c {} (line {})".format(filename, 1) + comes_from = f"-c {filename} (line {1})" req = install_req_from_editable(url, comes_from=comes_from, constraint=True) found_req = line_processor(line, filename, 1, constraint=True)[0] assert repr(found_req) == repr(req) @@ -873,12 +873,10 @@ def test_install_requirements_with_options( ) -> None: global_option = "--dry-run" - content = """ + content = f""" --only-binary :all: INITools==2.0 --global-option="{global_option}" - """.format( - global_option=global_option - ) + """ with requirements_file(content, tmpdir) as reqs_file: req = next( diff --git a/tests/unit/test_resolution_legacy_resolver.py b/tests/unit/test_resolution_legacy_resolver.py index 8b9d1a58a33..133e5922298 100644 --- a/tests/unit/test_resolution_legacy_resolver.py +++ b/tests/unit/test_resolution_legacy_resolver.py @@ -261,8 +261,8 @@ def metadata(self) -> email.message.Message: ignore_requires_python=False, ) assert str(exc.value) == ( - "None {} metadata found for distribution: " - "".format(metadata_name) + f"None {metadata_name} metadata found for distribution: " + "" ) diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py index 6d6d1a3dc87..33329fbce05 100644 --- a/tests/unit/test_wheel.py +++ b/tests/unit/test_wheel.py @@ -102,15 +102,13 @@ def test_get_legacy_build_wheel_path__multiple_names( ], ) def test_get_entrypoints(tmp_path: pathlib.Path, console_scripts: str) -> None: - entry_points_text = """ + entry_points_text = f""" [console_scripts] - {} + {console_scripts} [section] common:one = module:func common:two = module:other_func - """.format( - console_scripts - ) + """ distribution = make_wheel( "simple", diff --git a/tools/release/check_version.py b/tools/release/check_version.py index e89d1b5bad9..de3658faacd 100644 --- a/tools/release/check_version.py +++ b/tools/release/check_version.py @@ -27,7 +27,7 @@ def is_this_a_good_version_number(string: str) -> Optional[str]: expected_major = datetime.now().year % 100 if len(release) not in [2, 3]: - return "Not of the form: {0}.N or {0}.N.P".format(expected_major) + return f"Not of the form: {expected_major}.N or {expected_major}.N.P" return None From 2a0acb595c4b6394f1f3a4e7ef034ac2e3e8c17e Mon Sep 17 00:00:00 2001 From: Damian Shaw Date: Tue, 7 Nov 2023 04:39:01 -0500 Subject: [PATCH 07/12] Update and provide fixes for mypy pre-commit (#12389) * Update mypy to 1.6.1 * Fix mypy "Source file found twice under different module names" error * Ignore type of intialized abstract class in tests * Use more specific type ignore method-assign * Type ignore for message.get_all * Remove unused type ignore * Add SizedBuffer type for xmlrpc.client.Transport subclass * Add Self type for RequestHandlerClass in test * Add type ignore for shutil.rmtree onexc handler * Quote SizedBuffer * Add news entry * Remove no longer correct comment * Update self import * Also ignore type onerror=handler * Update news entry * Update news entry --- .pre-commit-config.yaml | 16 ++++++++-------- news/12389.bugfix.rst | 1 + src/pip/_internal/locations/_distutils.py | 3 +-- src/pip/_internal/metadata/_json.py | 4 ++-- src/pip/_internal/network/xmlrpc.py | 4 +++- src/pip/_internal/utils/misc.py | 4 ++-- tests/conftest.py | 6 +++++- tests/lib/configuration_helpers.py | 2 +- tests/lib/test_wheel.py | 10 +++++----- tests/unit/metadata/test_metadata.py | 6 +++--- tests/unit/test_base_command.py | 6 +++--- tests/unit/test_configuration.py | 6 +++--- tests/unit/test_resolution_legacy_resolver.py | 2 +- tools/__init__.py | 0 14 files changed, 38 insertions(+), 32 deletions(-) create mode 100644 news/12389.bugfix.rst create mode 100644 tools/__init__.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 999bd8b1d68..18d911256d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,20 +28,20 @@ repos: args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.961 + rev: v1.6.1 hooks: - id: mypy exclude: tests/data args: ["--pretty", "--show-error-codes"] additional_dependencies: [ - 'keyring==23.0.1', - 'nox==2021.6.12', + 'keyring==24.2.0', + 'nox==2023.4.22', 'pytest', - 'types-docutils==0.18.3', - 'types-setuptools==57.4.14', - 'types-freezegun==1.1.9', - 'types-six==1.16.15', - 'types-pyyaml==6.0.12.2', + 'types-docutils==0.20.0.3', + 'types-setuptools==68.2.0.0', + 'types-freezegun==1.1.10', + 'types-six==1.16.21.9', + 'types-pyyaml==6.0.12.12', ] - repo: https://github.com/pre-commit/pygrep-hooks diff --git a/news/12389.bugfix.rst b/news/12389.bugfix.rst new file mode 100644 index 00000000000..84871873328 --- /dev/null +++ b/news/12389.bugfix.rst @@ -0,0 +1 @@ +Update mypy to 1.6.1 and fix/ignore types diff --git a/src/pip/_internal/locations/_distutils.py b/src/pip/_internal/locations/_distutils.py index 48689f5fbe4..0e18c6e1e14 100644 --- a/src/pip/_internal/locations/_distutils.py +++ b/src/pip/_internal/locations/_distutils.py @@ -56,8 +56,7 @@ def distutils_scheme( try: d.parse_config_files() except UnicodeDecodeError: - # Typeshed does not include find_config_files() for some reason. - paths = d.find_config_files() # type: ignore + paths = d.find_config_files() logger.warning( "Ignore distutils configs in %s due to encoding errors.", ", ".join(os.path.basename(p) for p in paths), diff --git a/src/pip/_internal/metadata/_json.py b/src/pip/_internal/metadata/_json.py index 336b52f1efd..27362fc726c 100644 --- a/src/pip/_internal/metadata/_json.py +++ b/src/pip/_internal/metadata/_json.py @@ -64,10 +64,10 @@ def sanitise_header(h: Union[Header, str]) -> str: key = json_name(field) if multi: value: Union[str, List[str]] = [ - sanitise_header(v) for v in msg.get_all(field) + sanitise_header(v) for v in msg.get_all(field) # type: ignore ] else: - value = sanitise_header(msg.get(field)) + value = sanitise_header(msg.get(field)) # type: ignore if key == "keywords": # Accept both comma-separated and space-separated # forms, for better compatibility with old data. diff --git a/src/pip/_internal/network/xmlrpc.py b/src/pip/_internal/network/xmlrpc.py index 4a7d55d0e50..22ec8d2f4a6 100644 --- a/src/pip/_internal/network/xmlrpc.py +++ b/src/pip/_internal/network/xmlrpc.py @@ -13,6 +13,8 @@ if TYPE_CHECKING: from xmlrpc.client import _HostType, _Marshallable + from _typeshed import SizedBuffer + logger = logging.getLogger(__name__) @@ -33,7 +35,7 @@ def request( self, host: "_HostType", handler: str, - request_body: bytes, + request_body: "SizedBuffer", verbose: bool = False, ) -> Tuple["_Marshallable", ...]: assert isinstance(host, str) diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py index 42a8536cdd3..1ad3f6162a2 100644 --- a/src/pip/_internal/utils/misc.py +++ b/src/pip/_internal/utils/misc.py @@ -141,9 +141,9 @@ def rmtree( ) if sys.version_info >= (3, 12): # See https://docs.python.org/3.12/whatsnew/3.12.html#shutil. - shutil.rmtree(dir, onexc=handler) + shutil.rmtree(dir, onexc=handler) # type: ignore else: - shutil.rmtree(dir, onerror=handler) + shutil.rmtree(dir, onerror=handler) # type: ignore def _onerror_ignore(*_args: Any) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index 8e498abd08e..c5bf4bb9567 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,7 @@ from pathlib import Path from textwrap import dedent from typing import ( + TYPE_CHECKING, Any, AnyStr, Callable, @@ -58,6 +59,9 @@ from tests.lib.server import MockServer, make_mock_server from tests.lib.venv import VirtualEnvironment, VirtualEnvironmentType +if TYPE_CHECKING: + from pip._vendor.typing_extensions import Self + def pytest_addoption(parser: Parser) -> None: parser.addoption( @@ -941,7 +945,7 @@ def html_index_with_onetime_server( """ class InDirectoryServer(http.server.ThreadingHTTPServer): - def finish_request(self, request: Any, client_address: Any) -> None: + def finish_request(self: "Self", request: Any, client_address: Any) -> None: self.RequestHandlerClass( request, client_address, diff --git a/tests/lib/configuration_helpers.py b/tests/lib/configuration_helpers.py index ec824ffd3b8..b6e398c5bf1 100644 --- a/tests/lib/configuration_helpers.py +++ b/tests/lib/configuration_helpers.py @@ -38,7 +38,7 @@ def overridden() -> None: old() # https://github.com/python/mypy/issues/2427 - self.configuration._load_config_files = overridden # type: ignore[assignment] + self.configuration._load_config_files = overridden # type: ignore[method-assign] @contextlib.contextmanager def tmpfile(self, contents: str) -> Iterator[str]: diff --git a/tests/lib/test_wheel.py b/tests/lib/test_wheel.py index 86994c28e57..ffe96cc4335 100644 --- a/tests/lib/test_wheel.py +++ b/tests/lib/test_wheel.py @@ -19,12 +19,12 @@ def test_message_from_dict_one_value() -> None: message = message_from_dict({"a": "1"}) - assert set(message.get_all("a")) == {"1"} + assert set(message.get_all("a")) == {"1"} # type: ignore def test_message_from_dict_multiple_values() -> None: message = message_from_dict({"a": ["1", "2"]}) - assert set(message.get_all("a")) == {"1", "2"} + assert set(message.get_all("a")) == {"1", "2"} # type: ignore def message_from_bytes(contents: bytes) -> Message: @@ -67,7 +67,7 @@ def test_make_metadata_file_custom_value_list() -> None: f = default_make_metadata(updates={"a": ["1", "2"]}) assert f is not None message = default_metadata_checks(f) - assert set(message.get_all("a")) == {"1", "2"} + assert set(message.get_all("a")) == {"1", "2"} # type: ignore def test_make_metadata_file_custom_value_overrides() -> None: @@ -101,7 +101,7 @@ def default_wheel_metadata_checks(f: File) -> Message: assert message.get_all("Wheel-Version") == ["1.0"] assert message.get_all("Generator") == ["pip-test-suite"] assert message.get_all("Root-Is-Purelib") == ["true"] - assert set(message.get_all("Tag")) == {"py2-none-any", "py3-none-any"} + assert set(message.get_all("Tag")) == {"py2-none-any", "py3-none-any"} # type: ignore return message @@ -122,7 +122,7 @@ def test_make_wheel_metadata_file_custom_value_list() -> None: f = default_make_wheel_metadata(updates={"a": ["1", "2"]}) assert f is not None message = default_wheel_metadata_checks(f) - assert set(message.get_all("a")) == {"1", "2"} + assert set(message.get_all("a")) == {"1", "2"} # type: ignore def test_make_wheel_metadata_file_custom_value_override() -> None: diff --git a/tests/unit/metadata/test_metadata.py b/tests/unit/metadata/test_metadata.py index 47093fb54d1..ccc8ceb2e75 100644 --- a/tests/unit/metadata/test_metadata.py +++ b/tests/unit/metadata/test_metadata.py @@ -23,7 +23,7 @@ def test_dist_get_direct_url_no_metadata(mock_read_text: mock.Mock) -> None: class FakeDistribution(BaseDistribution): pass - dist = FakeDistribution() + dist = FakeDistribution() # type: ignore assert dist.direct_url is None mock_read_text.assert_called_once_with(DIRECT_URL_METADATA_NAME) @@ -35,7 +35,7 @@ def test_dist_get_direct_url_invalid_json( class FakeDistribution(BaseDistribution): canonical_name = cast(NormalizedName, "whatever") # Needed for error logging. - dist = FakeDistribution() + dist = FakeDistribution() # type: ignore with caplog.at_level(logging.WARNING): assert dist.direct_url is None @@ -84,7 +84,7 @@ def test_dist_get_direct_url_valid_metadata(mock_read_text: mock.Mock) -> None: class FakeDistribution(BaseDistribution): pass - dist = FakeDistribution() + dist = FakeDistribution() # type: ignore direct_url = dist.direct_url assert direct_url is not None mock_read_text.assert_called_once_with(DIRECT_URL_METADATA_NAME) diff --git a/tests/unit/test_base_command.py b/tests/unit/test_base_command.py index daec5fc6c65..44dae384a75 100644 --- a/tests/unit/test_base_command.py +++ b/tests/unit/test_base_command.py @@ -151,7 +151,7 @@ def assert_helpers_set(options: Values, args: List[str]) -> int: c = Command("fake", "fake") # https://github.com/python/mypy/issues/2427 - c.run = Mock(side_effect=assert_helpers_set) # type: ignore[assignment] + c.run = Mock(side_effect=assert_helpers_set) # type: ignore[method-assign] assert c.main(["fake"]) == SUCCESS c.run.assert_called_once() @@ -176,7 +176,7 @@ def create_temp_dirs(options: Values, args: List[str]) -> int: c = Command("fake", "fake") # https://github.com/python/mypy/issues/2427 - c.run = Mock(side_effect=create_temp_dirs) # type: ignore[assignment] + c.run = Mock(side_effect=create_temp_dirs) # type: ignore[method-assign] assert c.main(["fake"]) == SUCCESS c.run.assert_called_once() assert os.path.exists(Holder.value) == exists @@ -200,6 +200,6 @@ def create_temp_dirs(options: Values, args: List[str]) -> int: c = Command("fake", "fake") # https://github.com/python/mypy/issues/2427 - c.run = Mock(side_effect=create_temp_dirs) # type: ignore[assignment] + c.run = Mock(side_effect=create_temp_dirs) # type: ignore[method-assign] assert c.main(["fake"]) == SUCCESS c.run.assert_called_once() diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py index c6b44d45aad..1a0acb7b411 100644 --- a/tests/unit/test_configuration.py +++ b/tests/unit/test_configuration.py @@ -215,7 +215,7 @@ def test_site_modification(self) -> None: # Mock out the method mymock = MagicMock(spec=self.configuration._mark_as_modified) # https://github.com/python/mypy/issues/2427 - self.configuration._mark_as_modified = mymock # type: ignore[assignment] + self.configuration._mark_as_modified = mymock # type: ignore[method-assign] self.configuration.set_value("test.hello", "10") @@ -231,7 +231,7 @@ def test_user_modification(self) -> None: # Mock out the method mymock = MagicMock(spec=self.configuration._mark_as_modified) # https://github.com/python/mypy/issues/2427 - self.configuration._mark_as_modified = mymock # type: ignore[assignment] + self.configuration._mark_as_modified = mymock # type: ignore[method-assign] self.configuration.set_value("test.hello", "10") @@ -250,7 +250,7 @@ def test_global_modification(self) -> None: # Mock out the method mymock = MagicMock(spec=self.configuration._mark_as_modified) # https://github.com/python/mypy/issues/2427 - self.configuration._mark_as_modified = mymock # type: ignore[assignment] + self.configuration._mark_as_modified = mymock # type: ignore[method-assign] self.configuration.set_value("test.hello", "10") diff --git a/tests/unit/test_resolution_legacy_resolver.py b/tests/unit/test_resolution_legacy_resolver.py index 133e5922298..b2f93b3d4f5 100644 --- a/tests/unit/test_resolution_legacy_resolver.py +++ b/tests/unit/test_resolution_legacy_resolver.py @@ -252,7 +252,7 @@ class NotWorkingFakeDist(FakeDist): def metadata(self) -> email.message.Message: raise FileNotFoundError(metadata_name) - dist = make_fake_dist(klass=NotWorkingFakeDist) + dist = make_fake_dist(klass=NotWorkingFakeDist) # type: ignore with pytest.raises(NoneMetadataError) as exc: _check_dist_requires_python( diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From 88ac529219f8c1072a3c8e694d59dfa52a4a9f22 Mon Sep 17 00:00:00 2001 From: Qiming Xu <33349132+xqm32@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:15:31 +0800 Subject: [PATCH 08/12] Fix outdated pip install argument description --- docs/html/cli/pip_install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/html/cli/pip_install.rst b/docs/html/cli/pip_install.rst index 951dc2705a3..5569a49c92a 100644 --- a/docs/html/cli/pip_install.rst +++ b/docs/html/cli/pip_install.rst @@ -45,7 +45,7 @@ When looking at the items to be installed, pip checks what type of item each is, in the following order: 1. Project or archive URL. -2. Local directory (which must contain a ``setup.py``, or pip will report +2. Local directory (which must contain a ``pyproject.toml`` or ``setup.py``, otherwise pip will report an error). 3. Local file (a sdist or wheel format archive, following the naming conventions for those formats). From 28250baffbf1d9dae29644717616a224f511fc87 Mon Sep 17 00:00:00 2001 From: Qiming Xu <33349132+xqm32@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:17:51 +0800 Subject: [PATCH 09/12] Fix line wrap length and add news entry --- docs/html/cli/pip_install.rst | 4 ++-- news/12417.doc.rst | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/12417.doc.rst diff --git a/docs/html/cli/pip_install.rst b/docs/html/cli/pip_install.rst index 5569a49c92a..35b82c05e09 100644 --- a/docs/html/cli/pip_install.rst +++ b/docs/html/cli/pip_install.rst @@ -45,8 +45,8 @@ When looking at the items to be installed, pip checks what type of item each is, in the following order: 1. Project or archive URL. -2. Local directory (which must contain a ``pyproject.toml`` or ``setup.py``, otherwise pip will report - an error). +2. Local directory (which must contain a ``pyproject.toml`` or ``setup.py``, + otherwise pip will report an error). 3. Local file (a sdist or wheel format archive, following the naming conventions for those formats). 4. A requirement, as specified in :pep:`440`. diff --git a/news/12417.doc.rst b/news/12417.doc.rst new file mode 100644 index 00000000000..6c1d5c6ee76 --- /dev/null +++ b/news/12417.doc.rst @@ -0,0 +1 @@ +Fix outdated pip install argument description \ No newline at end of file From fe10d368f64501cba0c00ad2adb40b846cd02c61 Mon Sep 17 00:00:00 2001 From: Qiming Xu <33349132+xqm32@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:25:56 +0800 Subject: [PATCH 10/12] Add end line --- news/12417.doc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/12417.doc.rst b/news/12417.doc.rst index 6c1d5c6ee76..43b487d26e3 100644 --- a/news/12417.doc.rst +++ b/news/12417.doc.rst @@ -1 +1 @@ -Fix outdated pip install argument description \ No newline at end of file +Fix outdated pip install argument description From d8ab6dc6c1f698d78135f12ebd960f55030679e5 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Tue, 28 Nov 2023 15:03:26 +0800 Subject: [PATCH 11/12] Clarify news fragment --- news/12417.doc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/12417.doc.rst b/news/12417.doc.rst index 43b487d26e3..efde79a5808 100644 --- a/news/12417.doc.rst +++ b/news/12417.doc.rst @@ -1 +1 @@ -Fix outdated pip install argument description +Fix outdated pip install argument description in documentation. From 83e41d90afd2bfe941b48e5f9f3ed22eab7d1146 Mon Sep 17 00:00:00 2001 From: Sander Van Balen Date: Sun, 10 Dec 2023 20:43:20 +0100 Subject: [PATCH 12/12] added second test case --- tests/functional/test_new_resolver.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/functional/test_new_resolver.py b/tests/functional/test_new_resolver.py index 1e7672a46ae..dfcf97ab132 100644 --- a/tests/functional/test_new_resolver.py +++ b/tests/functional/test_new_resolver.py @@ -2427,6 +2427,30 @@ def test_new_resolver_constraint_on_link_with_extra( script.assert_installed(pkg="1.0") +def test_new_resolver_constraint_on_link_with_extra_indirect( + script: PipTestEnvironment, +) -> None: + """ + Verify that installing works from a link with an extra if there is an indirect + dependency on that same package with the same extra (#12372). + """ + wheel_one: pathlib.Path = create_basic_wheel_for_package( + script, "pkg1", "1.0", extras={"ext": []} + ) + wheel_two: pathlib.Path = create_basic_wheel_for_package( + script, "pkg2", "1.0", depends=["pkg1[ext]==1.0"] + ) + + script.pip( + "install", + "--no-cache-dir", + # no index, no --find-links: only the explicit path + wheel_two, + f"{wheel_one}[ext]", + ) + script.assert_installed(pkg1="1.0", pkg2="1.0") + + def test_new_resolver_do_not_backtrack_on_build_failure( script: PipTestEnvironment, ) -> None: