summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHarutaka Kawamura <hkawamura0130@gmail.com>2021-08-06 18:59:56 +0900
committerGitHub <noreply@github.com>2021-08-06 18:59:56 +0900
commit75981eccd2a0c98fc43b260a5e08e6a110669767 (patch)
tree008307a64fb41b1fb0a7e32e536cf6e2150e60de /src
parent6d665fd065ab8ec6316d5586ed217d8d5c29f072 (diff)
parent0b9537822f6b1dda3b437b627e98e512bc8f994e (diff)
downloadpip-75981eccd2a0c98fc43b260a5e08e6a110669767.tar.gz
Merge branch 'main' into type-annotations-internal
Diffstat (limited to 'src')
-rw-r--r--src/pip/__init__.py2
-rw-r--r--src/pip/_internal/commands/__init__.py163
-rw-r--r--src/pip/_internal/commands/cache.py97
-rw-r--r--src/pip/_internal/commands/check.py10
-rw-r--r--src/pip/_internal/commands/completion.py49
-rw-r--r--src/pip/_internal/commands/configuration.py70
-rw-r--r--src/pip/_internal/commands/debug.py88
-rw-r--r--src/pip/_internal/commands/download.py17
-rw-r--r--src/pip/_internal/commands/freeze.py67
-rw-r--r--src/pip/_internal/commands/hash.py22
-rw-r--r--src/pip/_internal/commands/help.py2
-rw-r--r--src/pip/_internal/commands/index.py21
-rw-r--r--src/pip/_internal/commands/install.py260
-rw-r--r--src/pip/_internal/commands/list.py142
-rw-r--r--src/pip/_internal/commands/search.py72
-rw-r--r--src/pip/_internal/commands/show.py92
-rw-r--r--src/pip/_internal/commands/uninstall.py51
-rw-r--r--src/pip/_internal/commands/wheel.py40
-rw-r--r--src/pip/_internal/exceptions.py3
-rw-r--r--src/pip/_internal/locations/__init__.py185
-rw-r--r--src/pip/_internal/locations/_distutils.py49
-rw-r--r--src/pip/_internal/locations/_sysconfig.py53
-rw-r--r--src/pip/_internal/locations/base.py2
-rw-r--r--src/pip/_internal/metadata/base.py20
-rw-r--r--src/pip/_internal/metadata/pkg_resources.py4
-rw-r--r--src/pip/_internal/operations/install/legacy.py75
-rw-r--r--src/pip/_internal/operations/prepare.py14
-rw-r--r--src/pip/_internal/req/__init__.py16
-rw-r--r--src/pip/_internal/req/constructors.py91
-rw-r--r--src/pip/_internal/req/req_file.py64
-rw-r--r--src/pip/_internal/req/req_install.py189
-rw-r--r--src/pip/_internal/req/req_set.py69
-rw-r--r--src/pip/_internal/req/req_tracker.py24
-rw-r--r--src/pip/_internal/req/req_uninstall.py208
-rw-r--r--src/pip/_internal/resolution/resolvelib/base.py5
-rw-r--r--src/pip/_internal/resolution/resolvelib/found_candidates.py2
-rw-r--r--src/pip/_internal/resolution/resolvelib/provider.py17
-rw-r--r--src/pip/_internal/resolution/resolvelib/resolver.py2
-rw-r--r--src/pip/_internal/utils/deprecation.py63
-rw-r--r--src/pip/_internal/utils/hashes.py6
-rw-r--r--src/pip/_vendor/tenacity/__init__.py2
41 files changed, 1405 insertions, 1023 deletions
diff --git a/src/pip/__init__.py b/src/pip/__init__.py
index 67722d05e..e8bb5c837 100644
--- a/src/pip/__init__.py
+++ b/src/pip/__init__.py
@@ -1,6 +1,6 @@
from typing import List, Optional
-__version__ = "21.2.dev0"
+__version__ = "21.3.dev0"
def main(args: Optional[List[str]] = None) -> int:
diff --git a/src/pip/_internal/commands/__init__.py b/src/pip/_internal/commands/__init__.py
index 8e94b38f7..c72f24f30 100644
--- a/src/pip/_internal/commands/__init__.py
+++ b/src/pip/_internal/commands/__init__.py
@@ -3,87 +3,102 @@ Package containing all pip commands
"""
import importlib
-from collections import OrderedDict, namedtuple
+from collections import namedtuple
from typing import Any, Dict, Optional
from pip._internal.cli.base_command import Command
-CommandInfo = namedtuple('CommandInfo', 'module_path, class_name, summary')
+CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")
-# The ordering matters for help display.
-# Also, even though the module path starts with the same
-# "pip._internal.commands" prefix in each case, we include the full path
-# because it makes testing easier (specifically when modifying commands_dict
-# in test setup / teardown by adding info for a FakeCommand class defined
-# in a test-related module).
-# Finally, we need to pass an iterable of pairs here rather than a dict
-# so that the ordering won't be lost when using Python 2.7.
-commands_dict: Dict[str, CommandInfo] = OrderedDict([
- ('install', CommandInfo(
- 'pip._internal.commands.install', 'InstallCommand',
- 'Install packages.',
- )),
- ('download', CommandInfo(
- 'pip._internal.commands.download', 'DownloadCommand',
- 'Download packages.',
- )),
- ('uninstall', CommandInfo(
- 'pip._internal.commands.uninstall', 'UninstallCommand',
- 'Uninstall packages.',
- )),
- ('freeze', CommandInfo(
- 'pip._internal.commands.freeze', 'FreezeCommand',
- 'Output installed packages in requirements format.',
- )),
- ('list', CommandInfo(
- 'pip._internal.commands.list', 'ListCommand',
- 'List installed packages.',
- )),
- ('show', CommandInfo(
- 'pip._internal.commands.show', 'ShowCommand',
- 'Show information about installed packages.',
- )),
- ('check', CommandInfo(
- 'pip._internal.commands.check', 'CheckCommand',
- 'Verify installed packages have compatible dependencies.',
- )),
- ('config', CommandInfo(
- 'pip._internal.commands.configuration', 'ConfigurationCommand',
- 'Manage local and global configuration.',
- )),
- ('search', CommandInfo(
- 'pip._internal.commands.search', 'SearchCommand',
- 'Search PyPI for packages.',
- )),
- ('cache', CommandInfo(
- 'pip._internal.commands.cache', 'CacheCommand',
+# This dictionary does a bunch of heavy lifting for help output:
+# - Enables avoiding additional (costly) imports for presenting `--help`.
+# - The ordering matters for help display.
+#
+# Even though the module path starts with the same "pip._internal.commands"
+# prefix, the full path makes testing easier (specifically when modifying
+# `commands_dict` in test setup / teardown).
+commands_dict: Dict[str, CommandInfo] = {
+ "install": CommandInfo(
+ "pip._internal.commands.install",
+ "InstallCommand",
+ "Install packages.",
+ ),
+ "download": CommandInfo(
+ "pip._internal.commands.download",
+ "DownloadCommand",
+ "Download packages.",
+ ),
+ "uninstall": CommandInfo(
+ "pip._internal.commands.uninstall",
+ "UninstallCommand",
+ "Uninstall packages.",
+ ),
+ "freeze": CommandInfo(
+ "pip._internal.commands.freeze",
+ "FreezeCommand",
+ "Output installed packages in requirements format.",
+ ),
+ "list": CommandInfo(
+ "pip._internal.commands.list",
+ "ListCommand",
+ "List installed packages.",
+ ),
+ "show": CommandInfo(
+ "pip._internal.commands.show",
+ "ShowCommand",
+ "Show information about installed packages.",
+ ),
+ "check": CommandInfo(
+ "pip._internal.commands.check",
+ "CheckCommand",
+ "Verify installed packages have compatible dependencies.",
+ ),
+ "config": CommandInfo(
+ "pip._internal.commands.configuration",
+ "ConfigurationCommand",
+ "Manage local and global configuration.",
+ ),
+ "search": CommandInfo(
+ "pip._internal.commands.search",
+ "SearchCommand",
+ "Search PyPI for packages.",
+ ),
+ "cache": CommandInfo(
+ "pip._internal.commands.cache",
+ "CacheCommand",
"Inspect and manage pip's wheel cache.",
- )),
- ('index', CommandInfo(
- 'pip._internal.commands.index', 'IndexCommand',
+ ),
+ "index": CommandInfo(
+ "pip._internal.commands.index",
+ "IndexCommand",
"Inspect information available from package indexes.",
- )),
- ('wheel', CommandInfo(
- 'pip._internal.commands.wheel', 'WheelCommand',
- 'Build wheels from your requirements.',
- )),
- ('hash', CommandInfo(
- 'pip._internal.commands.hash', 'HashCommand',
- 'Compute hashes of package archives.',
- )),
- ('completion', CommandInfo(
- 'pip._internal.commands.completion', 'CompletionCommand',
- 'A helper command used for command completion.',
- )),
- ('debug', CommandInfo(
- 'pip._internal.commands.debug', 'DebugCommand',
- 'Show information useful for debugging.',
- )),
- ('help', CommandInfo(
- 'pip._internal.commands.help', 'HelpCommand',
- 'Show help for commands.',
- )),
-])
+ ),
+ "wheel": CommandInfo(
+ "pip._internal.commands.wheel",
+ "WheelCommand",
+ "Build wheels from your requirements.",
+ ),
+ "hash": CommandInfo(
+ "pip._internal.commands.hash",
+ "HashCommand",
+ "Compute hashes of package archives.",
+ ),
+ "completion": CommandInfo(
+ "pip._internal.commands.completion",
+ "CompletionCommand",
+ "A helper command used for command completion.",
+ ),
+ "debug": CommandInfo(
+ "pip._internal.commands.debug",
+ "DebugCommand",
+ "Show information useful for debugging.",
+ ),
+ "help": CommandInfo(
+ "pip._internal.commands.help",
+ "HelpCommand",
+ "Show help for commands.",
+ ),
+}
def create_command(name: str, **kwargs: Any) -> Command:
diff --git a/src/pip/_internal/commands/cache.py b/src/pip/_internal/commands/cache.py
index 3a5bb9c88..dc307ef20 100644
--- a/src/pip/_internal/commands/cache.py
+++ b/src/pip/_internal/commands/cache.py
@@ -39,12 +39,12 @@ class CacheCommand(Command):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '--format',
- action='store',
- dest='list_format',
+ "--format",
+ action="store",
+ dest="list_format",
default="human",
- choices=('human', 'abspath'),
- help="Select the output format among: human (default) or abspath"
+ choices=("human", "abspath"),
+ help="Select the output format among: human (default) or abspath",
)
self.parser.insert_option_group(0, self.cmd_opts)
@@ -59,8 +59,7 @@ class CacheCommand(Command):
}
if not options.cache_dir:
- logger.error("pip cache commands can not "
- "function since cache is disabled.")
+ logger.error("pip cache commands can not function since cache is disabled.")
return ERROR
# Determine action
@@ -84,69 +83,73 @@ class CacheCommand(Command):
def get_cache_dir(self, options: Values, args: List[Any]) -> None:
if args:
- raise CommandError('Too many arguments')
+ raise CommandError("Too many arguments")
logger.info(options.cache_dir)
def get_cache_info(self, options: Values, args: List[Any]) -> None:
if args:
- raise CommandError('Too many arguments')
+ raise CommandError("Too many arguments")
num_http_files = len(self._find_http_files(options))
- num_packages = len(self._find_wheels(options, '*'))
+ num_packages = len(self._find_wheels(options, "*"))
- http_cache_location = self._cache_dir(options, 'http')
- wheels_cache_location = self._cache_dir(options, 'wheels')
+ http_cache_location = self._cache_dir(options, "http")
+ wheels_cache_location = self._cache_dir(options, "wheels")
http_cache_size = filesystem.format_directory_size(http_cache_location)
- wheels_cache_size = filesystem.format_directory_size(
- wheels_cache_location
+ wheels_cache_size = filesystem.format_directory_size(wheels_cache_location)
+
+ message = (
+ textwrap.dedent(
+ """
+ Package index page cache location: {http_cache_location}
+ Package index page cache size: {http_cache_size}
+ Number of HTTP files: {num_http_files}
+ Wheels location: {wheels_cache_location}
+ Wheels size: {wheels_cache_size}
+ Number of wheels: {package_count}
+ """
+ )
+ .format(
+ http_cache_location=http_cache_location,
+ http_cache_size=http_cache_size,
+ num_http_files=num_http_files,
+ wheels_cache_location=wheels_cache_location,
+ package_count=num_packages,
+ wheels_cache_size=wheels_cache_size,
+ )
+ .strip()
)
- message = textwrap.dedent("""
- Package index page cache location: {http_cache_location}
- Package index page cache size: {http_cache_size}
- Number of HTTP files: {num_http_files}
- Wheels location: {wheels_cache_location}
- Wheels size: {wheels_cache_size}
- Number of wheels: {package_count}
- """).format(
- http_cache_location=http_cache_location,
- http_cache_size=http_cache_size,
- num_http_files=num_http_files,
- wheels_cache_location=wheels_cache_location,
- package_count=num_packages,
- wheels_cache_size=wheels_cache_size,
- ).strip()
-
logger.info(message)
def list_cache_items(self, options: Values, args: List[Any]) -> None:
if len(args) > 1:
- raise CommandError('Too many arguments')
+ raise CommandError("Too many arguments")
if args:
pattern = args[0]
else:
- pattern = '*'
+ pattern = "*"
files = self._find_wheels(options, pattern)
- if options.list_format == 'human':
+ if options.list_format == "human":
self.format_for_human(files)
else:
self.format_for_abspath(files)
def format_for_human(self, files: List[str]) -> None:
if not files:
- logger.info('Nothing cached.')
+ logger.info("Nothing cached.")
return
results = []
for filename in files:
wheel = os.path.basename(filename)
size = filesystem.format_file_size(filename)
- results.append(f' - {wheel} ({size})')
- logger.info('Cache contents:\n')
- logger.info('\n'.join(sorted(results)))
+ results.append(f" - {wheel} ({size})")
+ logger.info("Cache contents:\n")
+ logger.info("\n".join(sorted(results)))
def format_for_abspath(self, files: List[str]) -> None:
if not files:
@@ -156,23 +159,23 @@ class CacheCommand(Command):
for filename in files:
results.append(filename)
- logger.info('\n'.join(sorted(results)))
+ logger.info("\n".join(sorted(results)))
def remove_cache_items(self, options: Values, args: List[Any]) -> None:
if len(args) > 1:
- raise CommandError('Too many arguments')
+ raise CommandError("Too many arguments")
if not args:
- raise CommandError('Please provide a pattern')
+ raise CommandError("Please provide a pattern")
files = self._find_wheels(options, args[0])
# Only fetch http files if no specific pattern given
- if args[0] == '*':
+ if args[0] == "*":
files += self._find_http_files(options)
if not files:
- raise CommandError('No matching packages')
+ raise CommandError("No matching packages")
for filename in files:
os.unlink(filename)
@@ -181,19 +184,19 @@ class CacheCommand(Command):
def purge_cache(self, options: Values, args: List[Any]) -> None:
if args:
- raise CommandError('Too many arguments')
+ raise CommandError("Too many arguments")
- return self.remove_cache_items(options, ['*'])
+ return self.remove_cache_items(options, ["*"])
def _cache_dir(self, options: Values, subdir: str) -> str:
return os.path.join(options.cache_dir, subdir)
def _find_http_files(self, options: Values) -> List[str]:
- http_dir = self._cache_dir(options, 'http')
- return filesystem.find_files(http_dir, '*')
+ http_dir = self._cache_dir(options, "http")
+ return filesystem.find_files(http_dir, "*")
def _find_wheels(self, options: Values, pattern: str) -> List[str]:
- wheel_dir = self._cache_dir(options, 'wheels')
+ wheel_dir = self._cache_dir(options, "wheels")
# The wheel filename format, as specified in PEP 427, is:
# {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl
diff --git a/src/pip/_internal/commands/check.py b/src/pip/_internal/commands/check.py
index f9412a7a9..ec08928a9 100644
--- a/src/pip/_internal/commands/check.py
+++ b/src/pip/_internal/commands/check.py
@@ -29,7 +29,9 @@ class CheckCommand(Command):
for dependency in missing[project_name]:
write_output(
"%s %s requires %s, which is not installed.",
- project_name, version, dependency[0],
+ project_name,
+ version,
+ dependency[0],
)
for project_name in conflicting:
@@ -37,7 +39,11 @@ class CheckCommand(Command):
for dep_name, dep_version, req in conflicting[project_name]:
write_output(
"%s %s has requirement %s, but you have %s %s.",
- project_name, version, req, dep_name, dep_version,
+ project_name,
+ version,
+ req,
+ dep_name,
+ dep_version,
)
if missing or conflicting or parsing_probs:
diff --git a/src/pip/_internal/commands/completion.py b/src/pip/_internal/commands/completion.py
index 9ce7888ef..c0fb4caf8 100644
--- a/src/pip/_internal/commands/completion.py
+++ b/src/pip/_internal/commands/completion.py
@@ -12,7 +12,7 @@ BASE_COMPLETION = """
"""
COMPLETION_SCRIPTS = {
- 'bash': """
+ "bash": """
_pip_completion()
{{
COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\
@@ -21,7 +21,7 @@ COMPLETION_SCRIPTS = {
}}
complete -o default -F _pip_completion {prog}
""",
- 'zsh': """
+ "zsh": """
function _pip_completion {{
local words cword
read -Ac words
@@ -32,7 +32,7 @@ COMPLETION_SCRIPTS = {
}}
compctl -K _pip_completion {prog}
""",
- 'fish': """
+ "fish": """
function __fish_complete_pip
set -lx COMP_WORDS (commandline -o) ""
set -lx COMP_CWORD ( \\
@@ -53,39 +53,44 @@ class CompletionCommand(Command):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '--bash', '-b',
- action='store_const',
- const='bash',
- dest='shell',
- help='Emit completion code for bash')
+ "--bash",
+ "-b",
+ action="store_const",
+ const="bash",
+ dest="shell",
+ help="Emit completion code for bash",
+ )
self.cmd_opts.add_option(
- '--zsh', '-z',
- action='store_const',
- const='zsh',
- dest='shell',
- help='Emit completion code for zsh')
+ "--zsh",
+ "-z",
+ action="store_const",
+ const="zsh",
+ dest="shell",
+ help="Emit completion code for zsh",
+ )
self.cmd_opts.add_option(
- '--fish', '-f',
- action='store_const',
- const='fish',
- dest='shell',
- help='Emit completion code for fish')
+ "--fish",
+ "-f",
+ action="store_const",
+ const="fish",
+ dest="shell",
+ help="Emit completion code for fish",
+ )
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: List[str]) -> int:
"""Prints the completion code of the given shell"""
shells = COMPLETION_SCRIPTS.keys()
- shell_options = ['--' + shell for shell in sorted(shells)]
+ shell_options = ["--" + shell for shell in sorted(shells)]
if options.shell in shells:
script = textwrap.dedent(
- COMPLETION_SCRIPTS.get(options.shell, '').format(
- prog=get_prog())
+ COMPLETION_SCRIPTS.get(options.shell, "").format(prog=get_prog())
)
print(BASE_COMPLETION.format(script=script, shell=options.shell))
return SUCCESS
else:
sys.stderr.write(
- 'ERROR: You must pass {}\n' .format(' or '.join(shell_options))
+ "ERROR: You must pass {}\n".format(" or ".join(shell_options))
)
return SUCCESS
diff --git a/src/pip/_internal/commands/configuration.py b/src/pip/_internal/commands/configuration.py
index 6e47b8732..c6c74ed50 100644
--- a/src/pip/_internal/commands/configuration.py
+++ b/src/pip/_internal/commands/configuration.py
@@ -34,7 +34,7 @@ class ConfigurationCommand(Command):
If none of --user, --global and --site are passed, a virtual
environment configuration file is used if one is active and the file
- exists. Otherwise, all modifications happen on the to the user file by
+ exists. Otherwise, all modifications happen to the user file by
default.
"""
@@ -51,38 +51,38 @@ class ConfigurationCommand(Command):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '--editor',
- dest='editor',
- action='store',
+ "--editor",
+ dest="editor",
+ action="store",
default=None,
help=(
- 'Editor to use to edit the file. Uses VISUAL or EDITOR '
- 'environment variables if not provided.'
- )
+ "Editor to use to edit the file. Uses VISUAL or EDITOR "
+ "environment variables if not provided."
+ ),
)
self.cmd_opts.add_option(
- '--global',
- dest='global_file',
- action='store_true',
+ "--global",
+ dest="global_file",
+ action="store_true",
default=False,
- help='Use the system-wide configuration file only'
+ help="Use the system-wide configuration file only",
)
self.cmd_opts.add_option(
- '--user',
- dest='user_file',
- action='store_true',
+ "--user",
+ dest="user_file",
+ action="store_true",
default=False,
- help='Use the user configuration file only'
+ help="Use the user configuration file only",
)
self.cmd_opts.add_option(
- '--site',
- dest='site_file',
- action='store_true',
+ "--site",
+ dest="site_file",
+ action="store_true",
default=False,
- help='Use the current environment configuration file only'
+ help="Use the current environment configuration file only",
)
self.parser.insert_option_group(0, self.cmd_opts)
@@ -133,11 +133,15 @@ class ConfigurationCommand(Command):
return SUCCESS
def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]:
- file_options = [key for key, value in (
- (kinds.USER, options.user_file),
- (kinds.GLOBAL, options.global_file),
- (kinds.SITE, options.site_file),
- ) if value]
+ file_options = [
+ key
+ for key, value in (
+ (kinds.USER, options.user_file),
+ (kinds.GLOBAL, options.global_file),
+ (kinds.SITE, options.site_file),
+ )
+ if value
+ ]
if not file_options:
if not need_value:
@@ -194,24 +198,22 @@ class ConfigurationCommand(Command):
for fname in files:
with indent_log():
file_exists = os.path.exists(fname)
- write_output("%s, exists: %r",
- fname, file_exists)
+ write_output("%s, exists: %r", fname, file_exists)
if file_exists:
self.print_config_file_values(variant)
def print_config_file_values(self, variant: Kind) -> None:
"""Get key-value pairs from the file of a variant"""
- for name, value in self.configuration.\
- get_values_in_config(variant).items():
+ for name, value in self.configuration.get_values_in_config(variant).items():
with indent_log():
write_output("%s: %s", name, value)
def print_env_var_values(self) -> None:
"""Get key-values pairs present as environment variables"""
- write_output("%s:", 'env_var')
+ write_output("%s:", "env_var")
with indent_log():
for key, value in sorted(self.configuration.get_environ_vars()):
- env_var = f'PIP_{key.upper()}'
+ env_var = f"PIP_{key.upper()}"
write_output("%s=%r", env_var, value)
def open_in_editor(self, options: Values, args: List[str]) -> None:
@@ -225,16 +227,14 @@ class ConfigurationCommand(Command):
subprocess.check_call([editor, fname])
except subprocess.CalledProcessError as e:
raise PipError(
- "Editor Subprocess exited with exit code {}"
- .format(e.returncode)
+ "Editor Subprocess exited with exit code {}".format(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
- """
+ """Helper to make sure the command got the right number of arguments"""
if len(args) != n:
msg = (
- 'Got unexpected number of arguments, expected {}. '
+ "Got unexpected number of arguments, expected {}. "
'(example: "{} config {}")'
).format(n, get_prog(), example)
raise PipError(msg)
diff --git a/src/pip/_internal/commands/debug.py b/src/pip/_internal/commands/debug.py
index b316b67bd..d3f1f28de 100644
--- a/src/pip/_internal/commands/debug.py
+++ b/src/pip/_internal/commands/debug.py
@@ -24,52 +24,46 @@ logger = logging.getLogger(__name__)
def show_value(name: str, value: Any) -> None:
- logger.info('%s: %s', name, value)
+ logger.info("%s: %s", name, value)
def show_sys_implementation() -> None:
- logger.info('sys.implementation:')
+ logger.info("sys.implementation:")
implementation_name = sys.implementation.name
with indent_log():
- show_value('name', implementation_name)
+ show_value("name", implementation_name)
def create_vendor_txt_map() -> Dict[str, str]:
vendor_txt_path = os.path.join(
- os.path.dirname(pip_location),
- '_vendor',
- 'vendor.txt'
+ os.path.dirname(pip_location), "_vendor", "vendor.txt"
)
with open(vendor_txt_path) as f:
# Purge non version specifying lines.
# Also, remove any space prefix or suffixes (including comments).
- lines = [line.strip().split(' ', 1)[0]
- for line in f.readlines() if '==' in line]
+ lines = [
+ line.strip().split(" ", 1)[0] for line in f.readlines() if "==" in line
+ ]
# Transform into "module" -> version dict.
- return dict(line.split('==', 1) for line in lines) # type: ignore
+ return dict(line.split("==", 1) for line in lines) # type: ignore
def get_module_from_module_name(module_name: str) -> ModuleType:
# Module name can be uppercase in vendor.txt for some reason...
module_name = module_name.lower()
# PATCH: setuptools is actually only pkg_resources.
- if module_name == 'setuptools':
- module_name = 'pkg_resources'
-
- __import__(
- f'pip._vendor.{module_name}',
- globals(),
- locals(),
- level=0
- )
+ if module_name == "setuptools":
+ module_name = "pkg_resources"
+
+ __import__(f"pip._vendor.{module_name}", globals(), locals(), level=0)
return getattr(pip._vendor, module_name)
def get_vendor_version_from_module(module_name: str) -> Optional[str]:
module = get_module_from_module_name(module_name)
- version = getattr(module, '__version__', None)
+ version = getattr(module, "__version__", None)
if not version:
# Try to find version in debundled module info.
@@ -86,20 +80,24 @@ def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None:
a conflict or if the actual version could not be imported.
"""
for module_name, expected_version in vendor_txt_versions.items():
- extra_message = ''
+ extra_message = ""
actual_version = get_vendor_version_from_module(module_name)
if not actual_version:
- extra_message = ' (Unable to locate actual module version, using'\
- ' vendor.txt specified version)'
+ extra_message = (
+ " (Unable to locate actual module version, using"
+ " vendor.txt specified version)"
+ )
actual_version = expected_version
elif parse_version(actual_version) != parse_version(expected_version):
- extra_message = ' (CONFLICT: vendor.txt suggests version should'\
- ' be {})'.format(expected_version)
- logger.info('%s==%s%s', module_name, actual_version, extra_message)
+ extra_message = (
+ " (CONFLICT: vendor.txt suggests version should"
+ " be {})".format(expected_version)
+ )
+ logger.info("%s==%s%s", module_name, actual_version, extra_message)
def show_vendor_versions() -> None:
- logger.info('vendored library versions:')
+ logger.info("vendored library versions:")
vendor_txt_versions = create_vendor_txt_map()
with indent_log():
@@ -114,11 +112,11 @@ def show_tags(options: Values) -> None:
# Display the target options that were explicitly provided.
formatted_target = target_python.format_given()
- suffix = ''
+ suffix = ""
if formatted_target:
- suffix = f' (target: {formatted_target})'
+ suffix = f" (target: {formatted_target})"
- msg = 'Compatible tags: {}{}'.format(len(tags), suffix)
+ msg = "Compatible tags: {}{}".format(len(tags), suffix)
logger.info(msg)
if options.verbose < 1 and len(tags) > tag_limit:
@@ -133,8 +131,7 @@ def show_tags(options: Values) -> None:
if tags_limited:
msg = (
- '...\n'
- '[First {tag_limit} tags shown. Pass --verbose to show all.]'
+ "...\n[First {tag_limit} tags shown. Pass --verbose to show all.]"
).format(tag_limit=tag_limit)
logger.info(msg)
@@ -142,20 +139,20 @@ def show_tags(options: Values) -> None:
def ca_bundle_info(config: Configuration) -> str:
levels = set()
for key, _ in config.items():
- levels.add(key.split('.')[0])
+ levels.add(key.split(".")[0])
if not levels:
return "Not specified"
- levels_that_override_global = ['install', 'wheel', 'download']
+ levels_that_override_global = ["install", "wheel", "download"]
global_overriding_level = [
level for level in levels if level in levels_that_override_global
]
if not global_overriding_level:
- return 'global'
+ return "global"
- if 'global' in levels:
- levels.remove('global')
+ if "global" in levels:
+ levels.remove("global")
return ", ".join(levels)
@@ -180,20 +177,21 @@ class DebugCommand(Command):
"details, since the output and options of this command may "
"change without notice."
)
- show_value('pip version', get_pip_version())
- show_value('sys.version', sys.version)
- show_value('sys.executable', sys.executable)
- show_value('sys.getdefaultencoding', sys.getdefaultencoding())
- show_value('sys.getfilesystemencoding', sys.getfilesystemencoding())
+ show_value("pip version", get_pip_version())
+ show_value("sys.version", sys.version)
+ show_value("sys.executable", sys.executable)
+ show_value("sys.getdefaultencoding", sys.getdefaultencoding())
+ show_value("sys.getfilesystemencoding", sys.getfilesystemencoding())
show_value(
- 'locale.getpreferredencoding', locale.getpreferredencoding(),
+ "locale.getpreferredencoding",
+ locale.getpreferredencoding(),
)
- show_value('sys.platform', sys.platform)
+ show_value("sys.platform", sys.platform)
show_sys_implementation()
show_value("'cert' config value", ca_bundle_info(self.parser.config))
- show_value("REQUESTS_CA_BUNDLE", os.environ.get('REQUESTS_CA_BUNDLE'))
- show_value("CURL_CA_BUNDLE", os.environ.get('CURL_CA_BUNDLE'))
+ show_value("REQUESTS_CA_BUNDLE", os.environ.get("REQUESTS_CA_BUNDLE"))
+ show_value("CURL_CA_BUNDLE", os.environ.get("CURL_CA_BUNDLE"))
show_value("pip._vendor.certifi.where()", where())
show_value("pip._vendor.DEBUNDLED", pip._vendor.DEBUNDLED)
diff --git a/src/pip/_internal/commands/download.py b/src/pip/_internal/commands/download.py
index 230264591..2f6aac29e 100644
--- a/src/pip/_internal/commands/download.py
+++ b/src/pip/_internal/commands/download.py
@@ -53,11 +53,14 @@ class DownloadCommand(RequirementCommand):
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
self.cmd_opts.add_option(
- '-d', '--dest', '--destination-dir', '--destination-directory',
- dest='download_dir',
- metavar='dir',
+ "-d",
+ "--dest",
+ "--destination-dir",
+ "--destination-directory",
+ dest="download_dir",
+ metavar="dir",
default=os.curdir,
- help=("Download packages into <dir>."),
+ help="Download packages into <dir>.",
)
cmdoptions.add_target_python_options(self.cmd_opts)
@@ -123,9 +126,7 @@ class DownloadCommand(RequirementCommand):
self.trace_basic_info(finder)
- requirement_set = resolver.resolve(
- reqs, check_supported_wheels=True
- )
+ requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
downloaded: List[str] = []
for req in requirement_set.requirements.values():
@@ -134,6 +135,6 @@ class DownloadCommand(RequirementCommand):
preparer.save_linked_requirement(req)
downloaded.append(req.name)
if downloaded:
- write_output('Successfully downloaded %s', ' '.join(downloaded))
+ write_output("Successfully downloaded %s", " ".join(downloaded))
return SUCCESS
diff --git a/src/pip/_internal/commands/freeze.py b/src/pip/_internal/commands/freeze.py
index 1ccc87525..5fa6d39b2 100644
--- a/src/pip/_internal/commands/freeze.py
+++ b/src/pip/_internal/commands/freeze.py
@@ -8,7 +8,7 @@ from pip._internal.cli.status_codes import SUCCESS
from pip._internal.operations.freeze import freeze
from pip._internal.utils.compat import stdlib_pkgs
-DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'}
+DEV_PKGS = {"pip", "setuptools", "distribute", "wheel"}
class FreezeCommand(Command):
@@ -24,39 +24,52 @@ class FreezeCommand(Command):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-r', '--requirement',
- dest='requirements',
- action='append',
+ "-r",
+ "--requirement",
+ dest="requirements",
+ action="append",
default=[],
- metavar='file',
- help="Use the order in the given requirements file and its "
- "comments when generating output. This option can be "
- "used multiple times.")
+ metavar="file",
+ help=(
+ "Use the order in the given requirements file and its "
+ "comments when generating output. This option can be "
+ "used multiple times."
+ ),
+ )
self.cmd_opts.add_option(
- '-l', '--local',
- dest='local',
- action='store_true',
+ "-l",
+ "--local",
+ dest="local",
+ action="store_true",
default=False,
- help='If in a virtualenv that has global access, do not output '
- 'globally-installed packages.')
+ help=(
+ "If in a virtualenv that has global access, do not output "
+ "globally-installed packages."
+ ),
+ )
self.cmd_opts.add_option(
- '--user',
- dest='user',
- action='store_true',
+ "--user",
+ dest="user",
+ action="store_true",
default=False,
- help='Only output packages installed in user-site.')
+ help="Only output packages installed in user-site.",
+ )
self.cmd_opts.add_option(cmdoptions.list_path())
self.cmd_opts.add_option(
- '--all',
- dest='freeze_all',
- action='store_true',
- help='Do not skip these packages in the output:'
- ' {}'.format(', '.join(DEV_PKGS)))
+ "--all",
+ dest="freeze_all",
+ action="store_true",
+ help=(
+ "Do not skip these packages in the output:"
+ " {}".format(", ".join(DEV_PKGS))
+ ),
+ )
self.cmd_opts.add_option(
- '--exclude-editable',
- dest='exclude_editable',
- action='store_true',
- help='Exclude editable package from output.')
+ "--exclude-editable",
+ dest="exclude_editable",
+ action="store_true",
+ help="Exclude editable package from output.",
+ )
self.cmd_opts.add_option(cmdoptions.list_exclude())
self.parser.insert_option_group(0, self.cmd_opts)
@@ -80,5 +93,5 @@ class FreezeCommand(Command):
skip=skip,
exclude_editable=options.exclude_editable,
):
- sys.stdout.write(line + '\n')
+ sys.stdout.write(line + "\n")
return SUCCESS
diff --git a/src/pip/_internal/commands/hash.py b/src/pip/_internal/commands/hash.py
index 3e4c32f35..042dac813 100644
--- a/src/pip/_internal/commands/hash.py
+++ b/src/pip/_internal/commands/hash.py
@@ -20,18 +20,21 @@ class HashCommand(Command):
installs.
"""
- usage = '%prog [options] <file> ...'
+ usage = "%prog [options] <file> ..."
ignore_require_venv = True
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-a', '--algorithm',
- dest='algorithm',
+ "-a",
+ "--algorithm",
+ dest="algorithm",
choices=STRONG_HASHES,
- action='store',
+ action="store",
default=FAVORITE_HASH,
- help='The hash algorithm to use: one of {}'.format(
- ', '.join(STRONG_HASHES)))
+ help="The hash algorithm to use: one of {}".format(
+ ", ".join(STRONG_HASHES)
+ ),
+ )
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: List[str]) -> int:
@@ -41,14 +44,15 @@ class HashCommand(Command):
algorithm = options.algorithm
for path in args:
- write_output('%s:\n--hash=%s:%s',
- path, algorithm, _hash_of_file(path, algorithm))
+ write_output(
+ "%s:\n--hash=%s:%s", path, algorithm, _hash_of_file(path, algorithm)
+ )
return SUCCESS
def _hash_of_file(path: str, algorithm: str) -> str:
"""Return the hash digest of a file."""
- with open(path, 'rb') as archive:
+ with open(path, "rb") as archive:
hash = hashlib.new(algorithm)
for chunk in read_chunks(archive):
hash.update(chunk)
diff --git a/src/pip/_internal/commands/help.py b/src/pip/_internal/commands/help.py
index 811ce89d5..62066318b 100644
--- a/src/pip/_internal/commands/help.py
+++ b/src/pip/_internal/commands/help.py
@@ -33,7 +33,7 @@ class HelpCommand(Command):
if guess:
msg.append(f'maybe you meant "{guess}"')
- raise CommandError(' - '.join(msg))
+ raise CommandError(" - ".join(msg))
command = create_command(cmd_name)
command.parser.print_help()
diff --git a/src/pip/_internal/commands/index.py b/src/pip/_internal/commands/index.py
index c505464f6..508171e99 100644
--- a/src/pip/_internal/commands/index.py
+++ b/src/pip/_internal/commands/index.py
@@ -101,7 +101,7 @@ class IndexCommand(IndexGroupCommand):
def get_available_package_versions(self, options: Values, args: List[Any]) -> None:
if len(args) != 1:
- raise CommandError('You need to specify exactly one argument')
+ raise CommandError("You need to specify exactly one argument")
target_python = cmdoptions.make_target_python(options)
query = args[0]
@@ -115,25 +115,24 @@ class IndexCommand(IndexGroupCommand):
)
versions: Iterable[Union[LegacyVersion, Version]] = (
- candidate.version
- for candidate in finder.find_all_candidates(query)
+ candidate.version for candidate in finder.find_all_candidates(query)
)
if not options.pre:
# Remove prereleases
- versions = (version for version in versions
- if not version.is_prerelease)
+ versions = (
+ version for version in versions if not version.is_prerelease
+ )
versions = set(versions)
if not versions:
raise DistributionNotFound(
- 'No matching distribution found for {}'.format(query))
+ "No matching distribution found for {}".format(query)
+ )
- formatted_versions = [str(ver) for ver in sorted(
- versions, reverse=True)]
+ formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)]
latest = formatted_versions[0]
- write_output('{} ({})'.format(query, latest))
- write_output('Available versions: {}'.format(
- ', '.join(formatted_versions)))
+ write_output("{} ({})".format(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 02da0777a..a427c6c59 100644
--- a/src/pip/_internal/commands/install.py
+++ b/src/pip/_internal/commands/install.py
@@ -86,87 +86,105 @@ class InstallCommand(RequirementCommand):
self.cmd_opts.add_option(cmdoptions.editable())
self.cmd_opts.add_option(
- '-t', '--target',
- dest='target_dir',
- metavar='dir',
+ "-t",
+ "--target",
+ dest="target_dir",
+ metavar="dir",
default=None,
- help='Install packages into <dir>. '
- 'By default this will not replace existing files/folders in '
- '<dir>. Use --upgrade to replace existing packages in <dir> '
- 'with new versions.'
+ help=(
+ "Install packages into <dir>. "
+ "By default this will not replace existing files/folders in "
+ "<dir>. Use --upgrade to replace existing packages in <dir> "
+ "with new versions."
+ ),
)
cmdoptions.add_target_python_options(self.cmd_opts)
self.cmd_opts.add_option(
- '--user',
- dest='use_user_site',
- action='store_true',
- help="Install to the Python user install directory for your "
- "platform. Typically ~/.local/, or %APPDATA%\\Python on "
- "Windows. (See the Python documentation for site.USER_BASE "
- "for full details.)")
+ "--user",
+ dest="use_user_site",
+ action="store_true",
+ help=(
+ "Install to the Python user install directory for your "
+ "platform. Typically ~/.local/, or %APPDATA%\\Python on "
+ "Windows. (See the Python documentation for site.USER_BASE "
+ "for full details.)"
+ ),
+ )
self.cmd_opts.add_option(
- '--no-user',
- dest='use_user_site',
- action='store_false',
- help=SUPPRESS_HELP)
+ "--no-user",
+ dest="use_user_site",
+ action="store_false",
+ help=SUPPRESS_HELP,
+ )
self.cmd_opts.add_option(
- '--root',
- dest='root_path',
- metavar='dir',
+ "--root",
+ dest="root_path",
+ metavar="dir",
default=None,
- help="Install everything relative to this alternate root "
- "directory.")
+ help="Install everything relative to this alternate root directory.",
+ )
self.cmd_opts.add_option(
- '--prefix',
- dest='prefix_path',
- metavar='dir',
+ "--prefix",
+ dest="prefix_path",
+ metavar="dir",
default=None,
- help="Installation prefix where lib, bin and other top-level "
- "folders are placed")
+ help=(
+ "Installation prefix where lib, bin and other top-level "
+ "folders are placed"
+ ),
+ )
self.cmd_opts.add_option(cmdoptions.build_dir())
self.cmd_opts.add_option(cmdoptions.src())
self.cmd_opts.add_option(
- '-U', '--upgrade',
- dest='upgrade',
- action='store_true',
- help='Upgrade all specified packages to the newest available '
- 'version. The handling of dependencies depends on the '
- 'upgrade-strategy used.'
+ "-U",
+ "--upgrade",
+ dest="upgrade",
+ action="store_true",
+ help=(
+ "Upgrade all specified packages to the newest available "
+ "version. The handling of dependencies depends on the "
+ "upgrade-strategy used."
+ ),
)
self.cmd_opts.add_option(
- '--upgrade-strategy',
- dest='upgrade_strategy',
- default='only-if-needed',
- choices=['only-if-needed', 'eager'],
- help='Determines how dependency upgrading should be handled '
- '[default: %default]. '
- '"eager" - dependencies are upgraded regardless of '
- 'whether the currently installed version satisfies the '
- 'requirements of the upgraded package(s). '
- '"only-if-needed" - are upgraded only when they do not '
- 'satisfy the requirements of the upgraded package(s).'
+ "--upgrade-strategy",
+ dest="upgrade_strategy",
+ default="only-if-needed",
+ choices=["only-if-needed", "eager"],
+ help=(
+ "Determines how dependency upgrading should be handled "
+ "[default: %default]. "
+ '"eager" - dependencies are upgraded regardless of '
+ "whether the currently installed version satisfies the "
+ "requirements of the upgraded package(s). "
+ '"only-if-needed" - are upgraded only when they do not '
+ "satisfy the requirements of the upgraded package(s)."
+ ),
)
self.cmd_opts.add_option(
- '--force-reinstall',
- dest='force_reinstall',
- action='store_true',
- help='Reinstall all packages even if they are already '
- 'up-to-date.')
+ "--force-reinstall",
+ dest="force_reinstall",
+ action="store_true",
+ help="Reinstall all packages even if they are already up-to-date.",
+ )
self.cmd_opts.add_option(
- '-I', '--ignore-installed',
- dest='ignore_installed',
- action='store_true',
- help='Ignore the installed packages, overwriting them. '
- 'This can break your system if the existing package '
- 'is of a different version or was installed '
- 'with a different package manager!'
+ "-I",
+ "--ignore-installed",
+ dest="ignore_installed",
+ action="store_true",
+ help=(
+ "Ignore the installed packages, overwriting them. "
+ "This can break your system if the existing package "
+ "is of a different version or was installed "
+ "with a different package manager!"
+ ),
)
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
@@ -249,11 +267,14 @@ class InstallCommand(RequirementCommand):
if options.target_dir:
options.ignore_installed = True
options.target_dir = os.path.abspath(options.target_dir)
- if (os.path.exists(options.target_dir) and not
- os.path.isdir(options.target_dir)):
+ if (
+ # fmt: off
+ os.path.exists(options.target_dir) and
+ not os.path.isdir(options.target_dir)
+ # fmt: on
+ ):
raise CommandError(
- "Target path exists but is not a directory, will not "
- "continue."
+ "Target path exists but is not a directory, will not continue."
)
# Create a target directory for using with the target option
@@ -285,9 +306,7 @@ class InstallCommand(RequirementCommand):
try:
reqs = self.get_requirements(args, options, finder, session)
- reject_location_related_install_options(
- reqs, options.install_options
- )
+ reject_location_related_install_options(reqs, options.install_options)
preparer = self.make_requirement_preparer(
temp_build_dir=directory,
@@ -324,19 +343,14 @@ class InstallCommand(RequirementCommand):
# If we're not replacing an already installed pip,
# we're not modifying it.
modifying_pip = pip_req.satisfied_by is None
- protect_pip_from_modification_on_windows(
- modifying_pip=modifying_pip
- )
+ protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
- check_binary_allowed = get_check_binary_allowed(
- finder.format_control
- )
+ check_binary_allowed = get_check_binary_allowed(finder.format_control)
reqs_to_build = [
- r for r in requirement_set.requirements.values()
- if should_build_for_install_command(
- r, check_binary_allowed
- )
+ r
+ for r in requirement_set.requirements.values()
+ if should_build_for_install_command(r, check_binary_allowed)
]
_, build_failures = build(
@@ -350,8 +364,7 @@ class InstallCommand(RequirementCommand):
# If we're using PEP 517, we cannot do a direct install
# so we fail here.
pep517_build_failure_names: List[str] = [
- r.name # type: ignore
- for r in build_failures if r.use_pep517
+ r.name for r in build_failures if r.use_pep517 # type: ignore
]
if pep517_build_failure_names:
raise InstallationError(
@@ -368,15 +381,12 @@ class InstallCommand(RequirementCommand):
if not r.use_pep517:
r.legacy_install_reason = 8368
- to_install = resolver.get_installation_order(
- requirement_set
- )
+ to_install = resolver.get_installation_order(requirement_set)
# Check for conflicts in the package set we're installing.
conflicts: Optional[ConflictDetails] = None
should_warn_about_conflicts = (
- not options.ignore_dependencies and
- options.warn_about_conflicts
+ not options.ignore_dependencies and options.warn_about_conflicts
)
if should_warn_about_conflicts:
conflicts = self._determine_conflicts(to_install)
@@ -408,7 +418,7 @@ class InstallCommand(RequirementCommand):
)
env = get_environment(lib_locations)
- installed.sort(key=operator.attrgetter('name'))
+ installed.sort(key=operator.attrgetter("name"))
items = []
for result in installed:
item = result.name
@@ -426,16 +436,19 @@ class InstallCommand(RequirementCommand):
resolver_variant=self.determine_resolver_variant(options),
)
- installed_desc = ' '.join(items)
+ installed_desc = " ".join(items)
if installed_desc:
write_output(
- 'Successfully installed %s', installed_desc,
+ "Successfully installed %s",
+ installed_desc,
)
except OSError as error:
- show_traceback = (self.verbosity >= 1)
+ show_traceback = self.verbosity >= 1
message = create_os_error_message(
- error, show_traceback, options.use_user_site,
+ error,
+ show_traceback,
+ options.use_user_site,
)
logger.error(message, exc_info=show_traceback) # noqa
@@ -461,7 +474,7 @@ class InstallCommand(RequirementCommand):
# Checking both purelib and platlib directories for installed
# packages to be moved to target directory
- scheme = get_scheme('', home=target_temp_dir.path)
+ scheme = get_scheme("", home=target_temp_dir.path)
purelib_dir = scheme.purelib
platlib_dir = scheme.platlib
data_dir = scheme.data
@@ -483,18 +496,18 @@ class InstallCommand(RequirementCommand):
if os.path.exists(target_item_dir):
if not upgrade:
logger.warning(
- 'Target directory %s already exists. Specify '
- '--upgrade to force replacement.',
- target_item_dir
+ "Target directory %s already exists. Specify "
+ "--upgrade to force replacement.",
+ target_item_dir,
)
continue
if os.path.islink(target_item_dir):
logger.warning(
- 'Target directory %s already exists and is '
- 'a link. pip will not automatically replace '
- 'links, please remove if replacement is '
- 'desired.',
- target_item_dir
+ "Target directory %s already exists and is "
+ "a link. pip will not automatically replace "
+ "links, please remove if replacement is "
+ "desired.",
+ target_item_dir,
)
continue
if os.path.isdir(target_item_dir):
@@ -502,10 +515,7 @@ class InstallCommand(RequirementCommand):
else:
os.remove(target_item_dir)
- shutil.move(
- os.path.join(lib_dir, item),
- target_item_dir
- )
+ shutil.move(os.path.join(lib_dir, item), target_item_dir)
def _determine_conflicts(
self, to_install: List[InstallRequirement]
@@ -567,7 +577,7 @@ class InstallCommand(RequirementCommand):
requirement=req,
dep_name=dep_name,
dep_version=dep_version,
- you=("you" if resolver_variant == "2020-resolver" else "you'll")
+ you=("you" if resolver_variant == "2020-resolver" else "you'll"),
)
parts.append(message)
@@ -575,14 +585,14 @@ class InstallCommand(RequirementCommand):
def get_lib_location_guesses(
- user: bool = False,
- home: Optional[str] = None,
- root: Optional[str] = None,
- isolated: bool = False,
- prefix: Optional[str] = None
+ user: bool = False,
+ home: Optional[str] = None,
+ root: Optional[str] = None,
+ isolated: bool = False,
+ prefix: Optional[str] = None,
) -> List[str]:
scheme = get_scheme(
- '',
+ "",
user=user,
home=home,
root=root,
@@ -594,8 +604,8 @@ def get_lib_location_guesses(
def site_packages_writable(root: Optional[str], isolated: bool) -> bool:
return all(
- test_writable_dir(d) for d in set(
- get_lib_location_guesses(root=root, isolated=isolated))
+ test_writable_dir(d)
+ for d in set(get_lib_location_guesses(root=root, isolated=isolated))
)
@@ -653,8 +663,10 @@ def decide_user_install(
logger.debug("Non-user install because site-packages writeable")
return False
- logger.info("Defaulting to user installation because normal site-packages "
- "is not writeable")
+ logger.info(
+ "Defaulting to user installation because normal site-packages "
+ "is not writeable"
+ )
return True
@@ -664,6 +676,7 @@ def reject_location_related_install_options(
"""If any location-changing --install-option arguments were passed for
requirements or on the command-line, then show a deprecation warning.
"""
+
def format_options(option_names: Iterable[str]) -> List[str]:
return ["--{}".format(name.replace("_", "-")) for name in option_names]
@@ -683,9 +696,7 @@ def reject_location_related_install_options(
location_options = parse_distutils_args(options)
if location_options:
offenders.append(
- "{!r} from command line".format(
- format_options(location_options.keys())
- )
+ "{!r} from command line".format(format_options(location_options.keys()))
)
if not offenders:
@@ -694,9 +705,7 @@ def reject_location_related_install_options(
raise CommandError(
"Location-changing options found in --install-option: {}."
" This is unsupported, use pip-level options like --user,"
- " --prefix, --root, and --target instead.".format(
- "; ".join(offenders)
- )
+ " --prefix, --root, and --target instead.".format("; ".join(offenders))
)
@@ -727,18 +736,25 @@ def create_os_error_message(
permissions_part = "Check the permissions"
if not running_under_virtualenv() and not using_user_site:
- parts.extend([
- user_option_part, " or ",
- permissions_part.lower(),
- ])
+ parts.extend(
+ [
+ user_option_part,
+ " or ",
+ permissions_part.lower(),
+ ]
+ )
else:
parts.append(permissions_part)
parts.append(".\n")
# Suggest the user to enable Long Paths if path length is
# more than 260
- if (WINDOWS and error.errno == errno.ENOENT and error.filename and
- len(error.filename) > 260):
+ if (
+ WINDOWS
+ and error.errno == errno.ENOENT
+ and error.filename
+ and len(error.filename) > 260
+ ):
parts.append(
"HINT: This error might have occurred since "
"this system does not have Windows Long Path "
diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py
index 828889f49..abe6ef2fc 100644
--- a/src/pip/_internal/commands/list.py
+++ b/src/pip/_internal/commands/list.py
@@ -26,6 +26,7 @@ if TYPE_CHECKING:
These will be populated during ``get_outdated()``. This is dirty but
makes the rest of the code much cleaner.
"""
+
latest_version: DistributionVersion
latest_filetype: str
@@ -48,77 +49,85 @@ class ListCommand(IndexGroupCommand):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-o', '--outdated',
- action='store_true',
+ "-o",
+ "--outdated",
+ action="store_true",
default=False,
- help='List outdated packages')
+ help="List outdated packages",
+ )
self.cmd_opts.add_option(
- '-u', '--uptodate',
- action='store_true',
+ "-u",
+ "--uptodate",
+ action="store_true",
default=False,
- help='List uptodate packages')
+ help="List uptodate packages",
+ )
self.cmd_opts.add_option(
- '-e', '--editable',
- action='store_true',
+ "-e",
+ "--editable",
+ action="store_true",
default=False,
- help='List editable projects.')
+ help="List editable projects.",
+ )
self.cmd_opts.add_option(
- '-l', '--local',
- action='store_true',
+ "-l",
+ "--local",
+ action="store_true",
default=False,
- help=('If in a virtualenv that has global access, do not list '
- 'globally-installed packages.'),
+ help=(
+ "If in a virtualenv that has global access, do not list "
+ "globally-installed packages."
+ ),
)
self.cmd_opts.add_option(
- '--user',
- dest='user',
- action='store_true',
+ "--user",
+ dest="user",
+ action="store_true",
default=False,
- help='Only output packages installed in user-site.')
+ help="Only output packages installed in user-site.",
+ )
self.cmd_opts.add_option(cmdoptions.list_path())
self.cmd_opts.add_option(
- '--pre',
- action='store_true',
+ "--pre",
+ action="store_true",
default=False,
- help=("Include pre-release and development versions. By default, "
- "pip only finds stable versions."),
+ help=(
+ "Include pre-release and development versions. By default, "
+ "pip only finds stable versions."
+ ),
)
self.cmd_opts.add_option(
- '--format',
- action='store',
- dest='list_format',
+ "--format",
+ action="store",
+ dest="list_format",
default="columns",
- choices=('columns', 'freeze', 'json'),
- help="Select the output format among: columns (default), freeze, "
- "or json",
+ choices=("columns", "freeze", "json"),
+ help="Select the output format among: columns (default), freeze, or json",
)
self.cmd_opts.add_option(
- '--not-required',
- action='store_true',
- dest='not_required',
- help="List packages that are not dependencies of "
- "installed packages.",
+ "--not-required",
+ action="store_true",
+ dest="not_required",
+ help="List packages that are not dependencies of installed packages.",
)
self.cmd_opts.add_option(
- '--exclude-editable',
- action='store_false',
- dest='include_editable',
- help='Exclude editable package from output.',
+ "--exclude-editable",
+ action="store_false",
+ dest="include_editable",
+ help="Exclude editable package from output.",
)
self.cmd_opts.add_option(
- '--include-editable',
- action='store_true',
- dest='include_editable',
- help='Include editable package from output.',
+ "--include-editable",
+ action="store_true",
+ dest="include_editable",
+ help="Include editable package from output.",
default=True,
)
self.cmd_opts.add_option(cmdoptions.list_exclude())
- index_opts = cmdoptions.make_option_group(
- cmdoptions.index_group, self.parser
- )
+ index_opts = cmdoptions.make_option_group(cmdoptions.index_group, self.parser)
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, self.cmd_opts)
@@ -144,8 +153,7 @@ class ListCommand(IndexGroupCommand):
def run(self, options: Values, args: List[str]) -> int:
if options.outdated and options.uptodate:
- raise CommandError(
- "Options --outdated and --uptodate cannot be combined.")
+ raise CommandError("Options --outdated and --uptodate cannot be combined.")
cmdoptions.check_list_path_option(options)
@@ -183,7 +191,8 @@ class ListCommand(IndexGroupCommand):
self, packages: "_ProcessedDists", options: Values
) -> "_ProcessedDists":
return [
- dist for dist in self.iter_packages_latest_infos(packages, options)
+ dist
+ for dist in self.iter_packages_latest_infos(packages, options)
if dist.latest_version > dist.version
]
@@ -191,7 +200,8 @@ class ListCommand(IndexGroupCommand):
self, packages: "_ProcessedDists", options: Values
) -> "_ProcessedDists":
return [
- dist for dist in self.iter_packages_latest_infos(packages, options)
+ dist
+ for dist in self.iter_packages_latest_infos(packages, options)
if dist.latest_version == dist.version
]
@@ -216,13 +226,16 @@ class ListCommand(IndexGroupCommand):
finder = self._build_package_finder(options, session)
def latest_info(
- dist: "_DistWithLatestInfo"
+ dist: "_DistWithLatestInfo",
) -> Optional["_DistWithLatestInfo"]:
all_candidates = finder.find_all_candidates(dist.canonical_name)
if not options.pre:
# Remove prereleases
- all_candidates = [candidate for candidate in all_candidates
- if not candidate.version.is_prerelease]
+ all_candidates = [
+ candidate
+ for candidate in all_candidates
+ if not candidate.version.is_prerelease
+ ]
evaluator = finder.make_candidate_evaluator(
project_name=dist.canonical_name,
@@ -233,9 +246,9 @@ class ListCommand(IndexGroupCommand):
remote_version = best_candidate.version
if best_candidate.link.is_wheel:
- typ = 'wheel'
+ typ = "wheel"
else:
- typ = 'sdist'
+ typ = "sdist"
dist.latest_version = remote_version
dist.latest_filetype = typ
return dist
@@ -251,17 +264,18 @@ class ListCommand(IndexGroupCommand):
packages,
key=lambda dist: dist.canonical_name,
)
- if options.list_format == 'columns' and packages:
+ if options.list_format == "columns" and packages:
data, header = format_for_columns(packages, options)
self.output_package_listing_columns(data, header)
- elif options.list_format == 'freeze':
+ elif options.list_format == "freeze":
for dist in packages:
if options.verbose >= 1:
- write_output("%s==%s (%s)", dist.raw_name,
- dist.version, dist.location)
+ write_output(
+ "%s==%s (%s)", dist.raw_name, dist.version, dist.location
+ )
else:
write_output("%s==%s", dist.raw_name, dist.version)
- elif options.list_format == 'json':
+ elif options.list_format == "json":
write_output(format_for_json(packages, options))
def output_package_listing_columns(
@@ -275,7 +289,7 @@ class ListCommand(IndexGroupCommand):
# Create and add a separator.
if len(data) > 0:
- pkg_strings.insert(1, " ".join(map(lambda x: '-' * x, sizes)))
+ pkg_strings.insert(1, " ".join(map(lambda x: "-" * x, sizes)))
for val in pkg_strings:
write_output(val)
@@ -324,14 +338,14 @@ def format_for_json(packages: "_ProcessedDists", options: Values) -> str:
data = []
for dist in packages:
info = {
- 'name': dist.raw_name,
- 'version': str(dist.version),
+ "name": dist.raw_name,
+ "version": str(dist.version),
}
if options.verbose >= 1:
- info['location'] = dist.location or ""
- info['installer'] = dist.installer
+ info["location"] = dist.location or ""
+ info["installer"] = dist.installer
if options.outdated:
- info['latest_version'] = str(dist.latest_version)
- info['latest_filetype'] = dist.latest_filetype
+ info["latest_version"] = str(dist.latest_version)
+ info["latest_filetype"] = dist.latest_filetype
data.append(info)
return json.dumps(data)
diff --git a/src/pip/_internal/commands/search.py b/src/pip/_internal/commands/search.py
index 7a20ba1e4..03ed925b2 100644
--- a/src/pip/_internal/commands/search.py
+++ b/src/pip/_internal/commands/search.py
@@ -27,6 +27,7 @@ if TYPE_CHECKING:
summary: str
versions: List[str]
+
logger = logging.getLogger(__name__)
@@ -39,17 +40,19 @@ class SearchCommand(Command, SessionCommandMixin):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-i', '--index',
- dest='index',
- metavar='URL',
+ "-i",
+ "--index",
+ dest="index",
+ metavar="URL",
default=PyPI.pypi_url,
- help='Base URL of Python Package Index (default %default)')
+ help="Base URL of Python Package Index (default %default)",
+ )
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: List[str]) -> int:
if not args:
- raise CommandError('Missing required argument (search query).')
+ raise CommandError("Missing required argument (search query).")
query = args
pypi_hits = self.search(query, options)
hits = transform_hits(pypi_hits)
@@ -71,7 +74,7 @@ class SearchCommand(Command, SessionCommandMixin):
transport = PipXmlrpcTransport(index_url, session)
pypi = xmlrpc.client.ServerProxy(index_url, transport)
try:
- hits = pypi.search({'name': query, 'summary': query}, 'or')
+ hits = pypi.search({"name": query, "summary": query}, "or")
except xmlrpc.client.Fault as fault:
message = "XMLRPC request failed [code: {code}]\n{string}".format(
code=fault.faultCode,
@@ -90,22 +93,22 @@ def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]:
"""
packages: Dict[str, "TransformedHit"] = OrderedDict()
for hit in hits:
- name = hit['name']
- summary = hit['summary']
- version = hit['version']
+ name = hit["name"]
+ summary = hit["summary"]
+ version = hit["version"]
if name not in packages.keys():
packages[name] = {
- 'name': name,
- 'summary': summary,
- 'versions': [version],
+ "name": name,
+ "summary": summary,
+ "versions": [version],
}
else:
- packages[name]['versions'].append(version)
+ packages[name]["versions"].append(version)
# if this is the highest version, replace summary and score
- if version == highest_version(packages[name]['versions']):
- packages[name]['summary'] = summary
+ if version == highest_version(packages[name]["versions"]):
+ packages[name]["summary"] = summary
return list(packages.values())
@@ -116,14 +119,17 @@ def print_dist_installation_info(name: str, latest: str) -> None:
if dist is not None:
with indent_log():
if dist.version == latest:
- write_output('INSTALLED: %s (latest)', dist.version)
+ write_output("INSTALLED: %s (latest)", dist.version)
else:
- write_output('INSTALLED: %s', dist.version)
+ write_output("INSTALLED: %s", dist.version)
if parse_version(latest).pre:
- write_output('LATEST: %s (pre-release; install'
- ' with "pip install --pre")', latest)
+ write_output(
+ "LATEST: %s (pre-release; install"
+ " with `pip install --pre`)",
+ latest,
+ )
else:
- write_output('LATEST: %s', latest)
+ write_output("LATEST: %s", latest)
def print_results(
@@ -134,25 +140,29 @@ def print_results(
if not hits:
return
if name_column_width is None:
- name_column_width = max([
- len(hit['name']) + len(highest_version(hit.get('versions', ['-'])))
- for hit in hits
- ]) + 4
+ name_column_width = (
+ max(
+ [
+ len(hit["name"]) + len(highest_version(hit.get("versions", ["-"])))
+ for hit in hits
+ ]
+ )
+ + 4
+ )
for hit in hits:
- name = hit['name']
- summary = hit['summary'] or ''
- latest = highest_version(hit.get('versions', ['-']))
+ name = hit["name"]
+ summary = hit["summary"] or ""
+ latest = highest_version(hit.get("versions", ["-"]))
if terminal_width is not None:
target_width = terminal_width - name_column_width - 5
if target_width > 10:
# wrap and indent summary to fit terminal
summary_lines = textwrap.wrap(summary, target_width)
- summary = ('\n' + ' ' * (name_column_width + 3)).join(
- summary_lines)
+ summary = ("\n" + " " * (name_column_width + 3)).join(summary_lines)
- name_latest = f'{name} ({latest})'
- line = f'{name_latest:{name_column_width}} - {summary}'
+ name_latest = f"{name} ({latest})"
+ line = f"{name_latest:{name_column_width}} - {summary}"
try:
write_output(line)
print_dist_installation_info(name, latest)
diff --git a/src/pip/_internal/commands/show.py b/src/pip/_internal/commands/show.py
index fa80254fd..0c763a308 100644
--- a/src/pip/_internal/commands/show.py
+++ b/src/pip/_internal/commands/show.py
@@ -1,8 +1,8 @@
import csv
import logging
-import os
+import pathlib
from optparse import Values
-from typing import Iterator, List, NamedTuple, Optional
+from typing import Iterator, List, NamedTuple, Optional, Tuple
from pip._vendor.packaging.utils import canonicalize_name
@@ -27,23 +27,26 @@ class ShowCommand(Command):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-f', '--files',
- dest='files',
- action='store_true',
+ "-f",
+ "--files",
+ dest="files",
+ action="store_true",
default=False,
- help='Show the full list of installed files for each package.')
+ help="Show the full list of installed files for each package.",
+ )
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: List[str]) -> int:
if not args:
- logger.warning('ERROR: Please provide a package name or names.')
+ logger.warning("ERROR: Please provide a package name or names.")
return ERROR
query = args
results = search_packages_info(query)
if not print_results(
- results, list_files=options.files, verbose=options.verbose):
+ results, list_files=options.files, verbose=options.verbose
+ ):
return ERROR
return SUCCESS
@@ -66,6 +69,33 @@ class _PackageInfo(NamedTuple):
files: Optional[List[str]]
+def _covert_legacy_entry(entry: Tuple[str, ...], info: Tuple[str, ...]) -> str:
+ """Convert a legacy installed-files.txt path into modern RECORD path.
+
+ The legacy format stores paths relative to the info directory, while the
+ modern format stores paths relative to the package root, e.g. the
+ site-packages directory.
+
+ :param entry: Path parts of the installed-files.txt entry.
+ :param info: Path parts of the egg-info directory relative to package root.
+ :returns: The converted entry.
+
+ For best compatibility with symlinks, this does not use ``abspath()`` or
+ ``Path.resolve()``, but tries to work with path parts:
+
+ 1. While ``entry`` starts with ``..``, remove the equal amounts of parts
+ from ``info``; if ``info`` is empty, start appending ``..`` instead.
+ 2. Join the two directly.
+ """
+ while entry and entry[0] == "..":
+ if not info or info[-1] == "..":
+ info += ("..",)
+ else:
+ info = info[:-1]
+ entry = entry[1:]
+ return str(pathlib.Path(*info, *entry))
+
+
def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
"""
Gather details from installed distributions. Print distribution name,
@@ -75,39 +105,49 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
"""
env = get_default_environment()
- installed = {
- dist.canonical_name: dist
- for dist in env.iter_distributions()
- }
+ installed = {dist.canonical_name: dist for dist in env.iter_distributions()}
query_names = [canonicalize_name(name) for name in query]
missing = sorted(
[name for name, pkg in zip(query, query_names) if pkg not in installed]
)
if missing:
- logger.warning('Package(s) not found: %s', ', '.join(missing))
+ logger.warning("Package(s) not found: %s", ", ".join(missing))
def _get_requiring_packages(current_dist: BaseDistribution) -> List[str]:
return [
dist.metadata["Name"] or "UNKNOWN"
for dist in installed.values()
- if current_dist.canonical_name in {
- canonicalize_name(d.name) for d in dist.iter_dependencies()
- }
+ if current_dist.canonical_name
+ in {canonicalize_name(d.name) for d in dist.iter_dependencies()}
]
def _files_from_record(dist: BaseDistribution) -> Optional[Iterator[str]]:
try:
- text = dist.read_text('RECORD')
+ text = dist.read_text("RECORD")
except FileNotFoundError:
return None
- return (row[0] for row in csv.reader(text.splitlines()))
+ # This extra Path-str cast normalizes entries.
+ return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines()))
- def _files_from_installed_files(dist: BaseDistribution) -> Optional[Iterator[str]]:
+ def _files_from_legacy(dist: BaseDistribution) -> Optional[Iterator[str]]:
try:
- text = dist.read_text('installed-files.txt')
+ text = dist.read_text("installed-files.txt")
except FileNotFoundError:
return None
- return (p for p in text.splitlines(keepends=False) if p)
+ paths = (p for p in text.splitlines(keepends=False) if p)
+ root = dist.location
+ info = dist.info_directory
+ if root is None or info is None:
+ return paths
+ try:
+ info_rel = pathlib.Path(info).relative_to(root)
+ except ValueError: # info is not relative to root.
+ return paths
+ if not info_rel.parts: # info *is* root.
+ return paths
+ return (
+ _covert_legacy_entry(pathlib.Path(p).parts, info_rel.parts) for p in paths
+ )
for query_name in query_names:
try:
@@ -116,16 +156,16 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
continue
try:
- entry_points_text = dist.read_text('entry_points.txt')
+ entry_points_text = dist.read_text("entry_points.txt")
entry_points = entry_points_text.splitlines(keepends=False)
except FileNotFoundError:
entry_points = []
- files_iter = _files_from_record(dist) or _files_from_installed_files(dist)
+ files_iter = _files_from_record(dist) or _files_from_legacy(dist)
if files_iter is None:
files: Optional[List[str]] = None
else:
- files = sorted(os.path.relpath(p, dist.location) for p in files_iter)
+ files = sorted(files_iter)
metadata = dist.metadata
@@ -170,8 +210,8 @@ def print_results(
write_output("Author-email: %s", dist.author_email)
write_output("License: %s", dist.license)
write_output("Location: %s", dist.location)
- write_output("Requires: %s", ', '.join(dist.requires))
- write_output("Required-by: %s", ', '.join(dist.required_by))
+ write_output("Requires: %s", ", ".join(dist.requires))
+ write_output("Required-by: %s", ", ".join(dist.required_by))
if verbose:
write_output("Metadata-Version: %s", dist.metadata_version)
diff --git a/src/pip/_internal/commands/uninstall.py b/src/pip/_internal/commands/uninstall.py
index dc22b5d1d..bb9e8e6a3 100644
--- a/src/pip/_internal/commands/uninstall.py
+++ b/src/pip/_internal/commands/uninstall.py
@@ -1,3 +1,4 @@
+import logging
from optparse import Values
from typing import List
@@ -14,6 +15,8 @@ from pip._internal.req.constructors import (
)
from pip._internal.utils.misc import protect_pip_from_modification_on_windows
+logger = logging.getLogger(__name__)
+
class UninstallCommand(Command, SessionCommandMixin):
"""
@@ -32,19 +35,24 @@ class UninstallCommand(Command, SessionCommandMixin):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-r', '--requirement',
- dest='requirements',
- action='append',
+ "-r",
+ "--requirement",
+ dest="requirements",
+ action="append",
default=[],
- metavar='file',
- help='Uninstall all the packages listed in the given requirements '
- 'file. This option can be used multiple times.',
+ metavar="file",
+ help=(
+ "Uninstall all the packages listed in the given requirements "
+ "file. This option can be used multiple times."
+ ),
)
self.cmd_opts.add_option(
- '-y', '--yes',
- dest='yes',
- action='store_true',
- help="Don't ask for confirmation of uninstall deletions.")
+ "-y",
+ "--yes",
+ dest="yes",
+ action="store_true",
+ help="Don't ask for confirmation of uninstall deletions.",
+ )
self.parser.insert_option_group(0, self.cmd_opts)
@@ -54,24 +62,30 @@ class UninstallCommand(Command, SessionCommandMixin):
reqs_to_uninstall = {}
for name in args:
req = install_req_from_line(
- name, isolated=options.isolated_mode,
+ name,
+ isolated=options.isolated_mode,
)
if req.name:
reqs_to_uninstall[canonicalize_name(req.name)] = req
+ else:
+ logger.warning(
+ "Invalid requirement: %r ignored -"
+ " the uninstall command expects named"
+ " requirements.",
+ name,
+ )
for filename in options.requirements:
for parsed_req in parse_requirements(
- filename,
- options=options,
- session=session):
+ filename, options=options, session=session
+ ):
req = install_req_from_parsed_requirement(
- parsed_req,
- isolated=options.isolated_mode
+ parsed_req, isolated=options.isolated_mode
)
if req.name:
reqs_to_uninstall[canonicalize_name(req.name)] = req
if not reqs_to_uninstall:
raise InstallationError(
- f'You must give at least one requirement to {self.name} (see '
+ f"You must give at least one requirement to {self.name} (see "
f'"pip help {self.name}")'
)
@@ -81,7 +95,8 @@ class UninstallCommand(Command, SessionCommandMixin):
for req in reqs_to_uninstall.values():
uninstall_pathset = req.uninstall(
- auto_confirm=options.yes, verbose=self.verbosity > 0,
+ auto_confirm=options.yes,
+ verbose=self.verbosity > 0,
)
if uninstall_pathset:
uninstall_pathset.commit()
diff --git a/src/pip/_internal/commands/wheel.py b/src/pip/_internal/commands/wheel.py
index c8bf4e25d..1fb5007a7 100644
--- a/src/pip/_internal/commands/wheel.py
+++ b/src/pip/_internal/commands/wheel.py
@@ -43,12 +43,15 @@ class WheelCommand(RequirementCommand):
def add_options(self) -> None:
self.cmd_opts.add_option(
- '-w', '--wheel-dir',
- dest='wheel_dir',
- metavar='dir',
+ "-w",
+ "--wheel-dir",
+ dest="wheel_dir",
+ metavar="dir",
default=os.curdir,
- help=("Build wheels into <dir>, where the default is the "
- "current working directory."),
+ help=(
+ "Build wheels into <dir>, where the default is the "
+ "current working directory."
+ ),
)
self.cmd_opts.add_option(cmdoptions.no_binary())
self.cmd_opts.add_option(cmdoptions.only_binary())
@@ -66,9 +69,9 @@ class WheelCommand(RequirementCommand):
self.cmd_opts.add_option(cmdoptions.progress_bar())
self.cmd_opts.add_option(
- '--no-verify',
- dest='no_verify',
- action='store_true',
+ "--no-verify",
+ dest="no_verify",
+ action="store_true",
default=False,
help="Don't verify if built wheel is valid.",
)
@@ -77,11 +80,13 @@ class WheelCommand(RequirementCommand):
self.cmd_opts.add_option(cmdoptions.global_options())
self.cmd_opts.add_option(
- '--pre',
- action='store_true',
+ "--pre",
+ action="store_true",
default=False,
- help=("Include pre-release and development versions. By default, "
- "pip only finds stable versions."),
+ help=(
+ "Include pre-release and development versions. By default, "
+ "pip only finds stable versions."
+ ),
)
self.cmd_opts.add_option(cmdoptions.require_hashes())
@@ -137,9 +142,7 @@ class WheelCommand(RequirementCommand):
self.trace_basic_info(finder)
- requirement_set = resolver.resolve(
- reqs, check_supported_wheels=True
- )
+ requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
reqs_to_build: List[InstallRequirement] = []
for req in requirement_set.requirements.values():
@@ -165,12 +168,11 @@ class WheelCommand(RequirementCommand):
except OSError as e:
logger.warning(
"Building wheel for %s failed: %s",
- req.name, e,
+ req.name,
+ e,
)
build_failures.append(req)
if len(build_failures) != 0:
- raise CommandError(
- "Failed to build one or more wheels"
- )
+ raise CommandError("Failed to build one or more wheels")
return SUCCESS
diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py
index c1681a8e5..ee497df54 100644
--- a/src/pip/_internal/exceptions.py
+++ b/src/pip/_internal/exceptions.py
@@ -180,9 +180,6 @@ class HashErrors(InstallationError):
return '\n'.join(lines)
return ''
- def __nonzero__(self) -> bool:
- return bool(self.errors)
-
def __bool__(self) -> bool:
return self.__nonzero__()
diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py
index ce94c1609..2b7de8eed 100644
--- a/src/pip/_internal/locations/__init__.py
+++ b/src/pip/_internal/locations/__init__.py
@@ -4,9 +4,12 @@ import os
import pathlib
import sys
import sysconfig
-from typing import List, Optional
+from typing import Any, Dict, Iterator, List, Optional, Tuple
from pip._internal.models.scheme import SCHEME_KEYS, Scheme
+from pip._internal.utils.compat import WINDOWS
+from pip._internal.utils.deprecation import deprecated
+from pip._internal.utils.virtualenv import running_under_virtualenv
from . import _distutils, _sysconfig
from .base import (
@@ -41,19 +44,75 @@ else:
_MISMATCH_LEVEL = logging.WARNING
-def _default_base(*, user: bool) -> str:
- if user:
- base = sysconfig.get_config_var("userbase")
- else:
- base = sysconfig.get_config_var("base")
- assert base is not None
- return base
+def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool:
+ platlib = scheme["platlib"]
+ if "/lib64/" not in platlib:
+ return False
+ unpatched = platlib.replace("/lib64/", "/lib/")
+ return unpatched.replace("$platbase/", "$base/") == scheme["purelib"]
@functools.lru_cache(maxsize=None)
-def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool:
- if old == new:
- return False
+def _looks_like_red_hat_lib() -> bool:
+ """Red Hat patches platlib in unix_prefix and unix_home, but not purelib.
+
+ This is the only way I can see to tell a Red Hat-patched Python.
+ """
+ from distutils.command.install import INSTALL_SCHEMES # type: ignore
+
+ return all(
+ k in INSTALL_SCHEMES
+ and _looks_like_red_hat_patched_platlib_purelib(INSTALL_SCHEMES[k])
+ for k in ("unix_prefix", "unix_home")
+ )
+
+
+@functools.lru_cache(maxsize=None)
+def _looks_like_debian_scheme() -> bool:
+ """Debian adds two additional schemes."""
+ from distutils.command.install import INSTALL_SCHEMES # type: ignore
+
+ return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES
+
+
+@functools.lru_cache(maxsize=None)
+def _looks_like_red_hat_scheme() -> bool:
+ """Red Hat patches ``sys.prefix`` and ``sys.exec_prefix``.
+
+ Red Hat's ``00251-change-user-install-location.patch`` changes the install
+ command's ``prefix`` and ``exec_prefix`` to append ``"/local"``. This is
+ (fortunately?) done quite unconditionally, so we create a default command
+ object without any configuration to detect this.
+ """
+ from distutils.command.install import install
+ from distutils.dist import Distribution
+
+ cmd: Any = install(Distribution())
+ cmd.finalize_options()
+ return (
+ cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local"
+ and cmd.prefix == f"{os.path.normpath(sys.prefix)}/local"
+ )
+
+
+def _fix_abiflags(parts: Tuple[str]) -> Iterator[str]:
+ ldversion = sysconfig.get_config_var("LDVERSION")
+ abiflags: str = getattr(sys, "abiflags", None)
+
+ # LDVERSION does not end with sys.abiflags. Just return the path unchanged.
+ if not ldversion or not abiflags or not ldversion.endswith(abiflags):
+ yield from parts
+ return
+
+ # Strip sys.abiflags from LDVERSION-based path components.
+ for part in parts:
+ if part.endswith(ldversion):
+ part = part[: (0 - len(abiflags))]
+ yield part
+
+
+@functools.lru_cache(maxsize=None)
+def _warn_mismatched(old: pathlib.Path, new: pathlib.Path, *, key: str) -> None:
issue_url = "https://github.com/pypa/pip/issues/10151"
message = (
"Value for %s does not match. Please report this to <%s>"
@@ -61,6 +120,12 @@ def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool
"\nsysconfig: %s"
)
logger.log(_MISMATCH_LEVEL, message, key, issue_url, old, new)
+
+
+def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool:
+ if old == new:
+ return False
+ _warn_mismatched(old, new, key=key)
return True
@@ -108,13 +173,14 @@ def get_scheme(
prefix=prefix,
)
- base = prefix or home or _default_base(user=user)
- warned = []
+ warning_contexts = []
for k in SCHEME_KEYS:
- # Extra join because distutils can return relative paths.
- old_v = pathlib.Path(base, getattr(old, k))
+ old_v = pathlib.Path(getattr(old, k))
new_v = pathlib.Path(getattr(new, k))
+ if old_v == new_v:
+ continue
+
# distutils incorrectly put PyPy packages under ``site-packages/python``
# in the ``posix_home`` scheme, but PyPy devs said they expect the
# directory name to be ``pypy`` instead. So we treat this as a bug fix
@@ -137,16 +203,74 @@ def get_scheme(
user
and is_osx_framework()
and k == "headers"
- and old_v.parent == new_v
- and old_v.name.startswith("python")
+ and old_v.parent.parent == new_v.parent
+ and old_v.parent.name.startswith("python")
)
if skip_osx_framework_user_special_case:
continue
- warned.append(_warn_if_mismatch(old_v, new_v, key=f"scheme.{k}"))
+ # On Red Hat and derived Linux distributions, distutils is patched to
+ # use "lib64" instead of "lib" for platlib.
+ if k == "platlib" and _looks_like_red_hat_lib():
+ continue
- if any(warned):
- _log_context(user=user, home=home, root=root, prefix=prefix)
+ # Both Debian and Red Hat patch Python to place the system site under
+ # /usr/local instead of /usr. Debian also places lib in dist-packages
+ # instead of site-packages, but the /usr/local check should cover it.
+ skip_linux_system_special_case = (
+ not (user or home or prefix or running_under_virtualenv())
+ and old_v.parts[1:3] == ("usr", "local")
+ and len(new_v.parts) > 1
+ and new_v.parts[1] == "usr"
+ and (len(new_v.parts) < 3 or new_v.parts[2] != "local")
+ and (_looks_like_red_hat_scheme() or _looks_like_debian_scheme())
+ )
+ if skip_linux_system_special_case:
+ continue
+
+ # On Python 3.7 and earlier, sysconfig does not include sys.abiflags in
+ # the "pythonX.Y" part of the path, but distutils does.
+ skip_sysconfig_abiflag_bug = (
+ sys.version_info < (3, 8)
+ and not WINDOWS
+ and k in ("headers", "platlib", "purelib")
+ and tuple(_fix_abiflags(old_v.parts)) == new_v.parts
+ )
+ if skip_sysconfig_abiflag_bug:
+ continue
+
+ warning_contexts.append((old_v, new_v, f"scheme.{k}"))
+
+ if not warning_contexts:
+ return old
+
+ # Check if this path mismatch is caused by distutils config files. Those
+ # files will no longer work once we switch to sysconfig, so this raises a
+ # deprecation message for them.
+ default_old = _distutils.distutils_scheme(
+ dist_name,
+ user,
+ home,
+ root,
+ isolated,
+ prefix,
+ ignore_config_files=True,
+ )
+ if any(default_old[k] != getattr(old, k) for k in SCHEME_KEYS):
+ deprecated(
+ "Configuring installation scheme with distutils config files "
+ "is deprecated and will no longer work in the near future. If you "
+ "are using a Homebrew or Linuxbrew Python, please see discussion "
+ "at https://github.com/Homebrew/homebrew-core/issues/76621",
+ replacement=None,
+ gone_in=None,
+ )
+ return old
+
+ # Post warnings about this mismatch so user can report them back.
+ for old_v, new_v, key in warning_contexts:
+ _warn_mismatched(old_v, new_v, key=key)
+ _log_context(user=user, home=home, root=root, prefix=prefix)
return old
@@ -163,10 +287,29 @@ def get_bin_user() -> str:
return _sysconfig.get_scheme("", user=True).scripts
+def _looks_like_deb_system_dist_packages(value: str) -> bool:
+ """Check if the value is Debian's APT-controlled dist-packages.
+
+ Debian's ``distutils.sysconfig.get_python_lib()`` implementation returns the
+ default package path controlled by APT, but does not patch ``sysconfig`` to
+ do the same. This is similar to the bug worked around in ``get_scheme()``,
+ but here the default is ``deb_system`` instead of ``unix_local``. Ultimately
+ we can't do anything about this Debian bug, and this detection allows us to
+ skip the warning when needed.
+ """
+ if not _looks_like_debian_patched():
+ return False
+ if value == "/usr/lib/python3/dist-packages":
+ return True
+ return False
+
+
def get_purelib() -> str:
"""Return the default pure-Python lib location."""
old = _distutils.get_purelib()
new = _sysconfig.get_purelib()
+ if _looks_like_deb_system_dist_packages(old):
+ return old
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="purelib"):
_log_context()
return old
@@ -176,6 +319,8 @@ def get_platlib() -> str:
"""Return the default platform-shared lib location."""
old = _distutils.get_platlib()
new = _sysconfig.get_platlib()
+ if _looks_like_deb_system_dist_packages(old):
+ return old
if _warn_if_mismatch(pathlib.Path(old), pathlib.Path(new), key="platlib"):
_log_context()
return old
diff --git a/src/pip/_internal/locations/_distutils.py b/src/pip/_internal/locations/_distutils.py
index 38742d1dd..2ec79e65b 100644
--- a/src/pip/_internal/locations/_distutils.py
+++ b/src/pip/_internal/locations/_distutils.py
@@ -21,13 +21,15 @@ from .base import get_major_minor_version
logger = logging.getLogger(__name__)
-def _distutils_scheme(
+def distutils_scheme(
dist_name: str,
user: bool = False,
home: str = None,
root: str = None,
isolated: bool = False,
prefix: str = None,
+ *,
+ ignore_config_files: bool = False,
) -> Dict[str, str]:
"""
Return a distutils install scheme
@@ -39,15 +41,16 @@ def _distutils_scheme(
dist_args["script_args"] = ["--no-user-cfg"]
d = Distribution(dist_args)
- try:
- d.parse_config_files()
- except UnicodeDecodeError:
- # Typeshed does not include find_config_files() for some reason.
- paths = d.find_config_files() # type: ignore
- logger.warning(
- "Ignore distutils configs in %s due to encoding errors.",
- ", ".join(os.path.basename(p) for p in paths),
- )
+ if not ignore_config_files:
+ try:
+ d.parse_config_files()
+ except UnicodeDecodeError:
+ # Typeshed does not include find_config_files() for some reason.
+ paths = d.find_config_files() # type: ignore
+ logger.warning(
+ "Ignore distutils configs in %s due to encoding errors.",
+ ", ".join(os.path.basename(p) for p in paths),
+ )
obj: Optional[DistutilsCommand] = None
obj = d.get_command_obj("install", create=True)
assert obj is not None
@@ -78,8 +81,14 @@ def _distutils_scheme(
scheme.update(dict(purelib=i.install_lib, platlib=i.install_lib))
if running_under_virtualenv():
+ if home:
+ prefix = home
+ elif user:
+ prefix = i.install_userbase # type: ignore
+ else:
+ prefix = i.prefix
scheme["headers"] = os.path.join(
- i.prefix,
+ prefix,
"include",
"site",
f"python{get_major_minor_version()}",
@@ -88,10 +97,7 @@ def _distutils_scheme(
if root is not None:
path_no_drive = os.path.splitdrive(os.path.abspath(scheme["headers"]))[1]
- scheme["headers"] = os.path.join(
- root,
- path_no_drive[1:],
- )
+ scheme["headers"] = os.path.join(root, path_no_drive[1:])
return scheme
@@ -121,7 +127,7 @@ def get_scheme(
:param prefix: indicates to use the "prefix" scheme and provides the
base directory for the same
"""
- scheme = _distutils_scheme(dist_name, user, home, root, isolated, prefix)
+ scheme = distutils_scheme(dist_name, user, home, root, isolated, prefix)
return Scheme(
platlib=scheme["platlib"],
purelib=scheme["purelib"],
@@ -132,17 +138,20 @@ def get_scheme(
def get_bin_prefix() -> str:
+ # XXX: In old virtualenv versions, sys.prefix can contain '..' components,
+ # so we need to call normpath to eliminate them.
+ prefix = os.path.normpath(sys.prefix)
if WINDOWS:
- bin_py = os.path.join(sys.prefix, "Scripts")
+ bin_py = os.path.join(prefix, "Scripts")
# buildout uses 'bin' on Windows too?
if not os.path.exists(bin_py):
- bin_py = os.path.join(sys.prefix, "bin")
+ bin_py = os.path.join(prefix, "bin")
return bin_py
# Forcing to use /usr/local/bin for standard macOS framework installs
# Also log to ~/Library/Logs/ for use with the Console.app log viewer
- if sys.platform[:6] == "darwin" and sys.prefix[:16] == "/System/Library/":
+ if sys.platform[:6] == "darwin" and prefix[:16] == "/System/Library/":
return "/usr/local/bin"
- return os.path.join(sys.prefix, "bin")
+ return os.path.join(prefix, "bin")
def get_purelib() -> str:
diff --git a/src/pip/_internal/locations/_sysconfig.py b/src/pip/_internal/locations/_sysconfig.py
index 0fc67843f..5e141aa1b 100644
--- a/src/pip/_internal/locations/_sysconfig.py
+++ b/src/pip/_internal/locations/_sysconfig.py
@@ -15,8 +15,8 @@ logger = logging.getLogger(__name__)
# Notes on _infer_* functions.
-# Unfortunately ``_get_default_scheme()`` is private, so there's no way to
-# ask things like "what is the '_prefix' scheme on this platform". These
+# Unfortunately ``get_default_scheme()`` didn't exist before 3.10, so there's no
+# way to ask things like "what is the '_prefix' scheme on this platform". These
# functions try to answer that with some heuristics while accounting for ad-hoc
# platforms not covered by CPython's default sysconfig implementation. If the
# ad-hoc implementation does not fully implement sysconfig, we'll fall back to
@@ -24,7 +24,33 @@ logger = logging.getLogger(__name__)
_AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names())
-_HAS_PREFERRED_SCHEME_API = sys.version_info >= (3, 10)
+_PREFERRED_SCHEME_API = getattr(sysconfig, "get_preferred_scheme", None)
+
+
+def _should_use_osx_framework_prefix() -> bool:
+ """Check for Apple's ``osx_framework_library`` scheme.
+
+ Python distributed by Apple's Command Line Tools has this special scheme
+ that's used when:
+
+ * This is a framework build.
+ * We are installing into the system prefix.
+
+ This does not account for ``pip install --prefix`` (also means we're not
+ installing to the system prefix), which should use ``posix_prefix``, but
+ logic here means ``_infer_prefix()`` outputs ``osx_framework_library``. But
+ since ``prefix`` is not available for ``sysconfig.get_default_scheme()``,
+ which is the stdlib replacement for ``_infer_prefix()``, presumably Apple
+ wouldn't be able to magically switch between ``osx_framework_library`` and
+ ``posix_prefix``. ``_infer_prefix()`` returning ``osx_framework_library``
+ means its behavior is consistent whether we use the stdlib implementation
+ or our own, and we deal with this special case in ``get_scheme()`` instead.
+ """
+ return (
+ "osx_framework_library" in _AVAILABLE_SCHEMES
+ and not running_under_virtualenv()
+ and is_osx_framework()
+ )
def _infer_prefix() -> str:
@@ -41,10 +67,9 @@ def _infer_prefix() -> str:
If none of the above works, fall back to ``posix_prefix``.
"""
- if _HAS_PREFERRED_SCHEME_API:
- return sysconfig.get_preferred_scheme("prefix") # type: ignore
- os_framework_global = is_osx_framework() and not running_under_virtualenv()
- if os_framework_global and "osx_framework_library" in _AVAILABLE_SCHEMES:
+ if _PREFERRED_SCHEME_API:
+ return _PREFERRED_SCHEME_API("prefix")
+ if _should_use_osx_framework_prefix():
return "osx_framework_library"
implementation_suffixed = f"{sys.implementation.name}_{os.name}"
if implementation_suffixed in _AVAILABLE_SCHEMES:
@@ -61,8 +86,8 @@ def _infer_prefix() -> str:
def _infer_user() -> str:
"""Try to find a user scheme for the current platform."""
- if _HAS_PREFERRED_SCHEME_API:
- return sysconfig.get_preferred_scheme("user") # type: ignore
+ if _PREFERRED_SCHEME_API:
+ return _PREFERRED_SCHEME_API("user")
if is_osx_framework() and not running_under_virtualenv():
suffixed = "osx_framework_user"
else:
@@ -76,8 +101,8 @@ def _infer_user() -> str:
def _infer_home() -> str:
"""Try to find a home for the current platform."""
- if _HAS_PREFERRED_SCHEME_API:
- return sysconfig.get_preferred_scheme("home") # type: ignore
+ if _PREFERRED_SCHEME_API:
+ return _PREFERRED_SCHEME_API("home")
suffixed = f"{os.name}_home"
if suffixed in _AVAILABLE_SCHEMES:
return suffixed
@@ -130,6 +155,12 @@ def get_scheme(
else:
scheme_name = _infer_prefix()
+ # Special case: When installing into a custom prefix, use posix_prefix
+ # instead of osx_framework_library. See _should_use_osx_framework_prefix()
+ # docstring for details.
+ if prefix is not None and scheme_name == "osx_framework_library":
+ scheme_name = "posix_prefix"
+
if home is not None:
variables = {k: home for k in _HOME_KEYS}
elif prefix is not None:
diff --git a/src/pip/_internal/locations/base.py b/src/pip/_internal/locations/base.py
index 315527f07..86dad4a3a 100644
--- a/src/pip/_internal/locations/base.py
+++ b/src/pip/_internal/locations/base.py
@@ -1,3 +1,4 @@
+import functools
import os
import site
import sys
@@ -46,5 +47,6 @@ except AttributeError:
user_site = site.USER_SITE
+@functools.lru_cache(maxsize=None)
def is_osx_framework() -> bool:
return bool(sysconfig.get_config_var("PYTHONFRAMEWORK"))
diff --git a/src/pip/_internal/metadata/base.py b/src/pip/_internal/metadata/base.py
index b9c432d7b..9fdc123ce 100644
--- a/src/pip/_internal/metadata/base.py
+++ b/src/pip/_internal/metadata/base.py
@@ -57,6 +57,26 @@ class BaseDistribution(Protocol):
A string value is not necessarily a filesystem path, since distributions
can be loaded from other sources, e.g. arbitrary zip archives. ``None``
means the distribution is created in-memory.
+
+ Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If
+ this is a symbolic link, we want to preserve the relative path between
+ it and files in the distribution.
+ """
+ raise NotImplementedError()
+
+ @property
+ def info_directory(self) -> Optional[str]:
+ """Location of the .[egg|dist]-info directory.
+
+ Similarly to ``location``, a string value is not necessarily a
+ filesystem path. ``None`` means the distribution is created in-memory.
+
+ For a modern .dist-info installation on disk, this should be something
+ like ``{location}/{raw_name}-{version}.dist-info``.
+
+ Do not canonicalize this value with e.g. ``pathlib.Path.resolve()``. If
+ this is a symbolic link, we want to preserve the relative path between
+ it and other files in the distribution.
"""
raise NotImplementedError()
diff --git a/src/pip/_internal/metadata/pkg_resources.py b/src/pip/_internal/metadata/pkg_resources.py
index 0dab7e989..59460062e 100644
--- a/src/pip/_internal/metadata/pkg_resources.py
+++ b/src/pip/_internal/metadata/pkg_resources.py
@@ -49,6 +49,10 @@ class Distribution(BaseDistribution):
return self._dist.location
@property
+ def info_directory(self) -> Optional[str]:
+ return self._dist.egg_info
+
+ @property
def canonical_name(self) -> "NormalizedName":
return canonicalize_name(self._dist.project_name)
diff --git a/src/pip/_internal/operations/install/legacy.py b/src/pip/_internal/operations/install/legacy.py
index 41d0c1f9d..4cb24fe1a 100644
--- a/src/pip/_internal/operations/install/legacy.py
+++ b/src/pip/_internal/operations/install/legacy.py
@@ -25,6 +25,46 @@ class LegacyInstallFailure(Exception):
self.parent = sys.exc_info()
+def write_installed_files_from_setuptools_record(
+ record_lines: List[str],
+ root: Optional[str],
+ req_description: str,
+) -> None:
+ def prepend_root(path):
+ # type: (str) -> str
+ if root is None or not os.path.isabs(path):
+ return path
+ else:
+ return change_root(root, path)
+
+ for line in record_lines:
+ directory = os.path.dirname(line)
+ if directory.endswith('.egg-info'):
+ egg_info_dir = prepend_root(directory)
+ break
+ else:
+ message = (
+ "{} did not indicate that it installed an "
+ ".egg-info directory. Only setup.py projects "
+ "generating .egg-info directories are supported."
+ ).format(req_description)
+ raise InstallationError(message)
+
+ new_lines = []
+ for line in record_lines:
+ filename = line.strip()
+ if os.path.isdir(filename):
+ filename += os.path.sep
+ new_lines.append(
+ os.path.relpath(prepend_root(filename), egg_info_dir)
+ )
+ new_lines.sort()
+ ensure_dir(egg_info_dir)
+ inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt')
+ with open(inst_files_path, 'w') as f:
+ f.write('\n'.join(new_lines) + '\n')
+
+
def install(
install_options, # type: List[str]
global_options, # type: Sequence[str]
@@ -88,38 +128,5 @@ def install(
with open(record_filename) as f:
record_lines = f.read().splitlines()
- def prepend_root(path):
- # type: (str) -> str
- if root is None or not os.path.isabs(path):
- return path
- else:
- return change_root(root, path)
-
- for line in record_lines:
- directory = os.path.dirname(line)
- if directory.endswith('.egg-info'):
- egg_info_dir = prepend_root(directory)
- break
- else:
- message = (
- "{} did not indicate that it installed an "
- ".egg-info directory. Only setup.py projects "
- "generating .egg-info directories are supported."
- ).format(req_description)
- raise InstallationError(message)
-
- new_lines = []
- for line in record_lines:
- filename = line.strip()
- if os.path.isdir(filename):
- filename += os.path.sep
- new_lines.append(
- os.path.relpath(prepend_root(filename), egg_info_dir)
- )
- new_lines.sort()
- ensure_dir(egg_info_dir)
- inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt')
- with open(inst_files_path, 'w') as f:
- f.write('\n'.join(new_lines) + '\n')
-
+ write_installed_files_from_setuptools_record(record_lines, root, req_description)
return True
diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py
index 247e63fc8..8bb5a6843 100644
--- a/src/pip/_internal/operations/prepare.py
+++ b/src/pip/_internal/operations/prepare.py
@@ -216,14 +216,16 @@ def unpack_url(
# be removed.
if link.is_existing_dir():
deprecated(
- "A future pip version will change local packages to be built "
- "in-place without first copying to a temporary directory. "
- "We recommend you use --use-feature=in-tree-build to test "
- "your packages with this new behavior before it becomes the "
- "default.\n",
+ reason=(
+ "pip copied the source tree into a temporary directory "
+ "before building it. This is changing so that packages "
+ "are built in-place "
+ 'within the original source tree ("in-tree build").'
+ ),
replacement=None,
gone_in="21.3",
- issue=7555
+ feature_flag="in-tree-build",
+ issue=7555,
)
if os.path.isdir(location):
rmtree(location)
diff --git a/src/pip/_internal/req/__init__.py b/src/pip/_internal/req/__init__.py
index aaea748dc..70dea27a6 100644
--- a/src/pip/_internal/req/__init__.py
+++ b/src/pip/_internal/req/__init__.py
@@ -9,8 +9,10 @@ from .req_install import InstallRequirement
from .req_set import RequirementSet
__all__ = [
- "RequirementSet", "InstallRequirement",
- "parse_requirements", "install_given_reqs",
+ "RequirementSet",
+ "InstallRequirement",
+ "parse_requirements",
+ "install_given_reqs",
]
logger = logging.getLogger(__name__)
@@ -52,8 +54,8 @@ def install_given_reqs(
if to_install:
logger.info(
- 'Installing collected packages: %s',
- ', '.join(to_install.keys()),
+ "Installing collected packages: %s",
+ ", ".join(to_install.keys()),
)
installed = []
@@ -61,11 +63,9 @@ def install_given_reqs(
with indent_log():
for req_name, requirement in to_install.items():
if requirement.should_reinstall:
- logger.info('Attempting uninstall: %s', req_name)
+ logger.info("Attempting uninstall: %s", req_name)
with indent_log():
- uninstalled_pathset = requirement.uninstall(
- auto_confirm=True
- )
+ uninstalled_pathset = requirement.uninstall(auto_confirm=True)
else:
uninstalled_pathset = None
diff --git a/src/pip/_internal/req/constructors.py b/src/pip/_internal/req/constructors.py
index d0f5b4248..b9b18139a 100644
--- a/src/pip/_internal/req/constructors.py
+++ b/src/pip/_internal/req/constructors.py
@@ -31,8 +31,9 @@ from pip._internal.utils.urls import path_to_url
from pip._internal.vcs import is_url, vcs
__all__ = [
- "install_req_from_editable", "install_req_from_line",
- "parse_editable"
+ "install_req_from_editable",
+ "install_req_from_line",
+ "parse_editable",
]
logger = logging.getLogger(__name__)
@@ -40,7 +41,7 @@ operators = Specifier._operators.keys()
def _strip_extras(path: str) -> Tuple[str, Optional[str]]:
- m = re.match(r'^(.+)(\[[^\]]+\])$', path)
+ m = re.match(r"^(.+)(\[[^\]]+\])$", path)
extras = None
if m:
path_no_extras = m.group(1)
@@ -74,26 +75,25 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
url_no_extras, extras = _strip_extras(url)
if os.path.isdir(url_no_extras):
- setup_py = os.path.join(url_no_extras, 'setup.py')
- setup_cfg = os.path.join(url_no_extras, 'setup.cfg')
+ setup_py = os.path.join(url_no_extras, "setup.py")
+ setup_cfg = os.path.join(url_no_extras, "setup.cfg")
if not os.path.exists(setup_py) and not os.path.exists(setup_cfg):
msg = (
'File "setup.py" or "setup.cfg" not found. Directory cannot be '
- 'installed in editable mode: {}'
- .format(os.path.abspath(url_no_extras))
+ "installed in editable mode: {}".format(os.path.abspath(url_no_extras))
)
pyproject_path = make_pyproject_path(url_no_extras)
if os.path.isfile(pyproject_path):
msg += (
'\n(A "pyproject.toml" file was found, but editable '
- 'mode currently requires a setuptools-based build.)'
+ "mode currently requires a setuptools-based build.)"
)
raise InstallationError(msg)
# Treating it as code that has already been checked out
url_no_extras = path_to_url(url_no_extras)
- if url_no_extras.lower().startswith('file:'):
+ if url_no_extras.lower().startswith("file:"):
package_name = Link(url_no_extras).egg_fragment
if extras:
return (
@@ -105,8 +105,8 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
return package_name, url_no_extras, set()
for version_control in vcs:
- if url.lower().startswith(f'{version_control}:'):
- url = f'{version_control}+{url}'
+ if url.lower().startswith(f"{version_control}:"):
+ url = f"{version_control}+{url}"
break
link = Link(url)
@@ -114,9 +114,9 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
if not link.is_vcs:
backends = ", ".join(vcs.all_schemes)
raise InstallationError(
- f'{editable_req} is not a valid editable requirement. '
- f'It should either be a path to a local project or a VCS URL '
- f'(beginning with {backends}).'
+ f"{editable_req} is not a valid editable requirement. "
+ f"It should either be a path to a local project or a VCS URL "
+ f"(beginning with {backends})."
)
package_name = link.egg_fragment
@@ -150,9 +150,7 @@ def deduce_helpful_msg(req: str) -> str:
" the packages specified within it."
).format(req)
except RequirementParseError:
- logger.debug(
- "Cannot parse '%s' as requirements file", req, exc_info=True
- )
+ logger.debug("Cannot parse '%s' as requirements file", req, exc_info=True)
else:
msg += f" File '{req}' does not exist."
return msg
@@ -160,11 +158,11 @@ def deduce_helpful_msg(req: str) -> str:
class RequirementParts:
def __init__(
- self,
- requirement: Optional[Requirement],
- link: Optional[Link],
- markers: Optional[Marker],
- extras: Set[str],
+ self,
+ requirement: Optional[Requirement],
+ link: Optional[Link],
+ markers: Optional[Marker],
+ extras: Set[str],
):
self.requirement = requirement
self.link = link
@@ -258,24 +256,23 @@ def _get_url_from_path(path: str, name: str) -> Optional[str]:
return None
if os.path.isfile(path):
return path_to_url(path)
- urlreq_parts = name.split('@', 1)
+ urlreq_parts = name.split("@", 1)
if len(urlreq_parts) >= 2 and not _looks_like_path(urlreq_parts[0]):
# If the path contains '@' and the part before it does not look
# like a path, try to treat it as a PEP 440 URL req instead.
return None
logger.warning(
- 'Requirement %r looks like a filename, but the '
- 'file does not exist',
- name
+ "Requirement %r looks like a filename, but the file does not exist",
+ name,
)
return path_to_url(path)
def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementParts:
if is_url(name):
- marker_sep = '; '
+ marker_sep = "; "
else:
- marker_sep = ';'
+ marker_sep = ";"
if marker_sep in name:
name, markers_as_string = name.split(marker_sep, 1)
markers_as_string = markers_as_string.strip()
@@ -302,9 +299,8 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
# it's a local file, dir, or url
if link:
# Handle relative file URLs
- if link.scheme == 'file' and re.search(r'\.\./', link.url):
- link = Link(
- path_to_url(os.path.normpath(os.path.abspath(link.path))))
+ if link.scheme == "file" and re.search(r"\.\./", link.url):
+ link = Link(path_to_url(os.path.normpath(os.path.abspath(link.path))))
# wheel file
if link.is_wheel:
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
@@ -323,7 +319,7 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
def with_source(text: str) -> str:
if not line_source:
return text
- return f'{text} (from {line_source})'
+ return f"{text} (from {line_source})"
def _parse_req_string(req_as_string: str) -> Requirement:
try:
@@ -332,16 +328,15 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
if os.path.sep in req_as_string:
add_msg = "It looks like a path."
add_msg += deduce_helpful_msg(req_as_string)
- elif ('=' in req_as_string and
- not any(op in req_as_string for op in operators)):
+ elif "=" in req_as_string and not any(
+ op in req_as_string for op in operators
+ ):
add_msg = "= is not a valid operator. Did you mean == ?"
else:
- add_msg = ''
- msg = with_source(
- f'Invalid requirement: {req_as_string!r}'
- )
+ add_msg = ""
+ msg = with_source(f"Invalid requirement: {req_as_string!r}")
if add_msg:
- msg += f'\nHint: {add_msg}'
+ msg += f"\nHint: {add_msg}"
raise InstallationError(msg)
else:
# Deprecate extras after specifiers: "name>=1.0[extras]"
@@ -350,7 +345,7 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
# RequirementParts
for spec in req.specifier:
spec_str = str(spec)
- if spec_str.endswith(']'):
+ if spec_str.endswith("]"):
msg = f"Extras after version '{spec_str}'."
raise InstallationError(msg)
return req
@@ -382,8 +377,12 @@ def install_req_from_line(
parts = parse_req_from_line(name, line_source)
return InstallRequirement(
- parts.requirement, comes_from, link=parts.link, markers=parts.markers,
- use_pep517=use_pep517, isolated=isolated,
+ parts.requirement,
+ comes_from,
+ link=parts.link,
+ markers=parts.markers,
+ use_pep517=use_pep517,
+ isolated=isolated,
install_options=options.get("install_options", []) if options else [],
global_options=options.get("global_options", []) if options else [],
hash_options=options.get("hashes", {}) if options else {},
@@ -409,8 +408,12 @@ def install_req_from_req_string(
PyPI.file_storage_domain,
TestPyPI.file_storage_domain,
]
- if (req.url and comes_from and comes_from.link and
- comes_from.link.netloc in domains_not_allowed):
+ if (
+ req.url
+ and comes_from
+ and comes_from.link
+ and comes_from.link.netloc in domains_not_allowed
+ ):
# Explicitly disallow pypi packages that depend on external urls
raise InstallationError(
"Packages installed from PyPI cannot depend on packages "
diff --git a/src/pip/_internal/req/req_file.py b/src/pip/_internal/req/req_file.py
index 01c6cf679..b392989bf 100644
--- a/src/pip/_internal/req/req_file.py
+++ b/src/pip/_internal/req/req_file.py
@@ -25,20 +25,20 @@ if TYPE_CHECKING:
from pip._internal.index.package_finder import PackageFinder
-__all__ = ['parse_requirements']
+__all__ = ["parse_requirements"]
ReqFileLines = Iterator[Tuple[int, str]]
LineParser = Callable[[str], Tuple[str, Values]]
-SCHEME_RE = re.compile(r'^(http|https|file):', re.I)
-COMMENT_RE = re.compile(r'(^|\s+)#.*$')
+SCHEME_RE = re.compile(r"^(http|https|file):", re.I)
+COMMENT_RE = re.compile(r"(^|\s+)#.*$")
# Matches environment variable-style values in '${MY_VARIABLE_1}' with the
# variable name consisting of only uppercase letters, digits or the '_'
# (underscore). This follows the POSIX standard defined in IEEE Std 1003.1,
# 2013 Edition.
-ENV_VAR_RE = re.compile(r'(?P<var>\$\{(?P<name>[A-Z0-9_]+)\})')
+ENV_VAR_RE = re.compile(r"(?P<var>\$\{(?P<name>[A-Z0-9_]+)\})")
SUPPORTED_OPTIONS: List[Callable[..., optparse.Option]] = [
cmdoptions.index_url,
@@ -134,10 +134,7 @@ def parse_requirements(
for parsed_line in parser.parse(filename, constraint):
parsed_req = handle_line(
- parsed_line,
- options=options,
- finder=finder,
- session=session
+ parsed_line, options=options, finder=finder, session=session
)
if parsed_req is not None:
yield parsed_req
@@ -161,8 +158,10 @@ def handle_requirement_line(
) -> ParsedRequirement:
# preserve for the nested code path
- line_comes_from = '{} {} (line {})'.format(
- '-c' if line.constraint else '-r', line.filename, line.lineno,
+ line_comes_from = "{} {} (line {})".format(
+ "-c" if line.constraint else "-r",
+ line.filename,
+ line.lineno,
)
assert line.is_requirement
@@ -187,7 +186,7 @@ def handle_requirement_line(
if dest in line.opts.__dict__ and line.opts.__dict__[dest]:
req_options[dest] = line.opts.__dict__[dest]
- line_source = f'line {line.lineno} of {line.filename}'
+ line_source = f"line {line.lineno} of {line.filename}"
return ParsedRequirement(
requirement=line.requirement,
is_editable=line.is_editable,
@@ -213,8 +212,7 @@ def handle_option_line(
options.require_hashes = opts.require_hashes
if opts.features_enabled:
options.features_enabled.extend(
- f for f in opts.features_enabled
- if f not in options.features_enabled
+ f for f in opts.features_enabled if f not in options.features_enabled
)
# set finder options
@@ -256,7 +254,7 @@ def handle_option_line(
if session:
for host in opts.trusted_hosts or []:
- source = f'line {lineno} of {filename}'
+ source = f"line {lineno} of {filename}"
session.add_trusted_host(host, source=source)
@@ -314,17 +312,15 @@ class RequirementsFileParser:
self._line_parser = line_parser
def parse(self, filename: str, constraint: bool) -> Iterator[ParsedLine]:
- """Parse a given file, yielding parsed lines.
- """
+ """Parse a given file, yielding parsed lines."""
yield from self._parse_and_recurse(filename, constraint)
def _parse_and_recurse(
self, filename: str, constraint: bool
) -> Iterator[ParsedLine]:
for line in self._parse_file(filename, constraint):
- if (
- not line.is_requirement and
- (line.opts.requirements or line.opts.constraints)
+ if not line.is_requirement and (
+ line.opts.requirements or line.opts.constraints
):
# parse a nested requirements file
if line.opts.requirements:
@@ -342,7 +338,8 @@ class RequirementsFileParser:
elif not SCHEME_RE.search(req_path):
# do a join so relative paths work
req_path = os.path.join(
- os.path.dirname(filename), req_path,
+ os.path.dirname(filename),
+ req_path,
)
yield from self._parse_and_recurse(req_path, nested_constraint)
@@ -359,7 +356,7 @@ class RequirementsFileParser:
args_str, opts = self._line_parser(line)
except OptionParsingError as e:
# add offending line
- msg = f'Invalid requirement: {line}\n{e.msg}'
+ msg = f"Invalid requirement: {line}\n{e.msg}"
raise RequirementsFileParseError(msg)
yield ParsedLine(
@@ -395,16 +392,16 @@ def break_args_options(line: str) -> Tuple[str, str]:
(and then optparse) the options, not the args. args can contain markers
which are corrupted by shlex.
"""
- tokens = line.split(' ')
+ tokens = line.split(" ")
args = []
options = tokens[:]
for token in tokens:
- if token.startswith('-') or token.startswith('--'):
+ if token.startswith("-") or token.startswith("--"):
break
else:
args.append(token)
options.pop(0)
- return ' '.join(args), ' '.join(options)
+ return " ".join(args), " ".join(options)
class OptionParsingError(Exception):
@@ -427,6 +424,7 @@ def build_parser() -> optparse.OptionParser:
# that in our own exception.
def parser_exit(self: Any, msg: str) -> "NoReturn":
raise OptionParsingError(msg)
+
# NOTE: mypy disallows assigning to a method
# https://github.com/python/mypy/issues/2427
parser.exit = parser_exit # type: ignore
@@ -441,26 +439,26 @@ def join_lines(lines_enum: ReqFileLines) -> ReqFileLines:
primary_line_number = None
new_line: List[str] = []
for line_number, line in lines_enum:
- if not line.endswith('\\') or COMMENT_RE.match(line):
+ if not line.endswith("\\") or COMMENT_RE.match(line):
if COMMENT_RE.match(line):
# this ensures comments are always matched later
- line = ' ' + line
+ line = " " + line
if new_line:
new_line.append(line)
assert primary_line_number is not None
- yield primary_line_number, ''.join(new_line)
+ yield primary_line_number, "".join(new_line)
new_line = []
else:
yield line_number, line
else:
if not new_line:
primary_line_number = line_number
- new_line.append(line.strip('\\'))
+ new_line.append(line.strip("\\"))
# last line contains \
if new_line:
assert primary_line_number is not None
- yield primary_line_number, ''.join(new_line)
+ yield primary_line_number, "".join(new_line)
# TODO: handle space after '\'.
@@ -470,7 +468,7 @@ def ignore_comments(lines_enum: ReqFileLines) -> ReqFileLines:
Strips comments and filter empty lines.
"""
for line_number, line in lines_enum:
- line = COMMENT_RE.sub('', line)
+ line = COMMENT_RE.sub("", line)
line = line.strip()
if line:
yield line_number, line
@@ -514,15 +512,15 @@ def get_file_content(url: str, session: PipSession) -> Tuple[str, str]:
scheme = get_url_scheme(url)
# Pip has special support for file:// URLs (LocalFSAdapter).
- if scheme in ['http', 'https', 'file']:
+ if scheme in ["http", "https", "file"]:
resp = session.get(url)
raise_for_status(resp)
return resp.url, resp.text
# Assume this is a bare path.
try:
- with open(url, 'rb') as f:
+ with open(url, "rb") as f:
content = auto_decode(f.read())
except OSError as exc:
- raise InstallationError(f'Could not open requirements file: {exc}')
+ raise InstallationError(f"Could not open requirements file: {exc}")
return url, content
diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py
index 6a42a5109..615535f24 100644
--- a/src/pip/_internal/req/req_install.py
+++ b/src/pip/_internal/req/req_install.py
@@ -122,9 +122,7 @@ class InstallRequirement:
if self.editable:
assert link
if link.is_file:
- self.source_dir = os.path.normpath(
- os.path.abspath(link.file_path)
- )
+ self.source_dir = os.path.normpath(os.path.abspath(link.file_path))
if link is None and req and req.url:
# PEP 508 URL requirement
@@ -140,9 +138,7 @@ class InstallRequirement:
if extras:
self.extras = extras
elif req:
- self.extras = {
- pkg_resources.safe_extra(extra) for extra in req.extras
- }
+ self.extras = {pkg_resources.safe_extra(extra) for extra in req.extras}
else:
self.extras = set()
if markers is None and req:
@@ -202,36 +198,34 @@ class InstallRequirement:
if self.req:
s = str(self.req)
if self.link:
- s += ' from {}'.format(redact_auth_from_url(self.link.url))
+ s += " from {}".format(redact_auth_from_url(self.link.url))
elif self.link:
s = redact_auth_from_url(self.link.url)
else:
- s = '<InstallRequirement>'
+ s = "<InstallRequirement>"
if self.satisfied_by is not None:
- s += ' in {}'.format(display_path(self.satisfied_by.location))
+ s += " in {}".format(display_path(self.satisfied_by.location))
if self.comes_from:
if isinstance(self.comes_from, str):
comes_from: Optional[str] = self.comes_from
else:
comes_from = self.comes_from.from_path()
if comes_from:
- s += f' (from {comes_from})'
+ s += f" (from {comes_from})"
return s
def __repr__(self) -> str:
- return '<{} object: {} editable={!r}>'.format(
- self.__class__.__name__, str(self), self.editable)
+ return "<{} object: {} editable={!r}>".format(
+ self.__class__.__name__, str(self), self.editable
+ )
def format_debug(self) -> str:
- """An un-tested helper for getting state, for debugging.
- """
+ """An un-tested helper for getting state, for debugging."""
attributes = vars(self)
names = sorted(attributes)
- state = (
- "{}={!r}".format(attr, attributes[attr]) for attr in sorted(names)
- )
- return '<{name} object: {{{state}}}>'.format(
+ state = ("{}={!r}".format(attr, attributes[attr]) for attr in sorted(names))
+ return "<{name} object: {{{state}}}>".format(
name=self.__class__.__name__,
state=", ".join(state),
)
@@ -254,18 +248,17 @@ class InstallRequirement:
For example, some-package==1.2 is pinned; some-package>1.2 is not.
"""
specifiers = self.specifier
- return (len(specifiers) == 1 and
- next(iter(specifiers)).operator in {'==', '==='})
+ return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="}
def match_markers(self, extras_requested: Optional[Iterable[str]] = None) -> bool:
if not extras_requested:
# Provide an extra to safely evaluate the markers
# without matching any extra
- extras_requested = ('',)
+ extras_requested = ("",)
if self.markers is not None:
return any(
- self.markers.evaluate({'extra': extra})
- for extra in extras_requested)
+ self.markers.evaluate({"extra": extra}) for extra in extras_requested
+ )
else:
return True
@@ -301,8 +294,7 @@ class InstallRequirement:
return Hashes(good_hashes)
def from_path(self) -> Optional[str]:
- """Format a nice indicator to show where this "comes from"
- """
+ """Format a nice indicator to show where this "comes from" """
if self.req is None:
return None
s = str(self.req)
@@ -312,7 +304,7 @@ class InstallRequirement:
else:
comes_from = self.comes_from.from_path()
if comes_from:
- s += '->' + comes_from
+ s += "->" + comes_from
return s
def ensure_build_location(
@@ -345,7 +337,7 @@ class InstallRequirement:
# FIXME: Is there a better place to create the build_dir? (hg and bzr
# need this)
if not os.path.exists(build_dir):
- logger.debug('Creating directory %s', build_dir)
+ logger.debug("Creating directory %s", build_dir)
os.makedirs(build_dir)
actual_build_dir = os.path.join(build_dir, dir_name)
# `None` indicates that we respect the globally-configured deletion
@@ -359,8 +351,7 @@ class InstallRequirement:
).path
def _set_requirement(self) -> None:
- """Set requirement after generating metadata.
- """
+ """Set requirement after generating metadata."""
assert self.req is None
assert self.metadata is not None
assert self.source_dir is not None
@@ -372,11 +363,13 @@ class InstallRequirement:
op = "==="
self.req = Requirement(
- "".join([
- self.metadata["Name"],
- op,
- self.metadata["Version"],
- ])
+ "".join(
+ [
+ self.metadata["Name"],
+ op,
+ self.metadata["Version"],
+ ]
+ )
)
def warn_on_mismatching_name(self) -> None:
@@ -387,10 +380,12 @@ class InstallRequirement:
# If we're here, there's a mismatch. Log a warning about it.
logger.warning(
- 'Generating metadata for package %s '
- 'produced metadata for project name %s. Fix your '
- '#egg=%s fragments.',
- self.name, metadata_name, self.name
+ "Generating metadata for package %s "
+ "produced metadata for project name %s. Fix your "
+ "#egg=%s fragments.",
+ self.name,
+ metadata_name,
+ self.name,
)
self.req = Requirement(metadata_name)
@@ -411,20 +406,22 @@ class InstallRequirement:
# parses the version instead.
existing_version = existing_dist.version
version_compatible = (
- existing_version is not None and
- self.req.specifier.contains(existing_version, prereleases=True)
+ existing_version is not None
+ and self.req.specifier.contains(existing_version, prereleases=True)
)
if not version_compatible:
self.satisfied_by = None
if use_user_site:
if dist_in_usersite(existing_dist):
self.should_reinstall = True
- elif (running_under_virtualenv() and
- dist_in_site_packages(existing_dist)):
+ elif running_under_virtualenv() and dist_in_site_packages(
+ existing_dist
+ ):
raise InstallationError(
"Will not install to the user site because it will "
"lack sys.path precedence to {} in {}".format(
- existing_dist.project_name, existing_dist.location)
+ existing_dist.project_name, existing_dist.location
+ )
)
else:
self.should_reinstall = True
@@ -448,13 +445,13 @@ class InstallRequirement:
@property
def unpacked_source_directory(self) -> str:
return os.path.join(
- self.source_dir,
- self.link and self.link.subdirectory_fragment or '')
+ self.source_dir, self.link and self.link.subdirectory_fragment or ""
+ )
@property
def setup_py_path(self) -> str:
assert self.source_dir, f"No source dir for {self}"
- setup_py = os.path.join(self.unpacked_source_directory, 'setup.py')
+ setup_py = os.path.join(self.unpacked_source_directory, "setup.py")
return setup_py
@@ -472,10 +469,7 @@ class InstallRequirement:
follow the PEP 517 or legacy (setup.py) code path.
"""
pyproject_toml_data = load_pyproject_toml(
- self.use_pep517,
- self.pyproject_toml_path,
- self.setup_py_path,
- str(self)
+ self.use_pep517, self.pyproject_toml_path, self.setup_py_path, str(self)
)
if pyproject_toml_data is None:
@@ -487,12 +481,13 @@ class InstallRequirement:
self.requirements_to_check = check
self.pyproject_requires = requires
self.pep517_backend = Pep517HookCaller(
- self.unpacked_source_directory, backend, backend_path=backend_path,
+ self.unpacked_source_directory,
+ backend,
+ backend_path=backend_path,
)
def _generate_metadata(self) -> str:
- """Invokes metadata generator functions, with the required arguments.
- """
+ """Invokes metadata generator functions, with the required arguments."""
if not self.use_pep517:
assert self.unpacked_source_directory
@@ -506,7 +501,7 @@ class InstallRequirement:
setup_py_path=self.setup_py_path,
source_dir=self.unpacked_source_directory,
isolated=self.isolated,
- details=self.name or f"from {self.link}"
+ details=self.name or f"from {self.link}",
)
assert self.pep517_backend is not None
@@ -537,7 +532,7 @@ class InstallRequirement:
@property
def metadata(self) -> Any:
- if not hasattr(self, '_metadata'):
+ if not hasattr(self, "_metadata"):
self._metadata = get_metadata(self.get_dist())
return self._metadata
@@ -547,16 +542,16 @@ class InstallRequirement:
def assert_source_matches_version(self) -> None:
assert self.source_dir
- version = self.metadata['version']
+ version = self.metadata["version"]
if self.req.specifier and version not in self.req.specifier:
logger.warning(
- 'Requested %s, but installing version %s',
+ "Requested %s, but installing version %s",
self,
version,
)
else:
logger.debug(
- 'Source in %s has version %s, which satisfies requirement %s',
+ "Source in %s has version %s, which satisfies requirement %s",
display_path(self.source_dir),
version,
self,
@@ -589,14 +584,13 @@ class InstallRequirement:
def update_editable(self) -> None:
if not self.link:
logger.debug(
- "Cannot update repository at %s; repository location is "
- "unknown",
+ "Cannot update repository at %s; repository location is unknown",
self.source_dir,
)
return
assert self.editable
assert self.source_dir
- if self.link.scheme == 'file':
+ if self.link.scheme == "file":
# Static paths don't get updated
return
vcs_backend = vcs.get_backend_for_scheme(self.link.scheme)
@@ -627,25 +621,24 @@ class InstallRequirement:
if not dist:
logger.warning("Skipping %s as it is not installed.", self.name)
return None
- logger.info('Found existing installation: %s', dist)
+ logger.info("Found existing installation: %s", dist)
uninstalled_pathset = UninstallPathSet.from_dist(dist)
uninstalled_pathset.remove(auto_confirm, verbose)
return uninstalled_pathset
def _get_archive_name(self, path: str, parentdir: str, rootdir: str) -> str:
-
def _clean_zip_name(name: str, prefix: str) -> str:
- assert name.startswith(prefix + os.path.sep), (
- f"name {name!r} doesn't start with prefix {prefix!r}"
- )
- name = name[len(prefix) + 1:]
- name = name.replace(os.path.sep, '/')
+ assert name.startswith(
+ prefix + os.path.sep
+ ), f"name {name!r} doesn't start with prefix {prefix!r}"
+ name = name[len(prefix) + 1 :]
+ name = name.replace(os.path.sep, "/")
return name
path = os.path.join(parentdir, path)
name = _clean_zip_name(path, rootdir)
- return self.name + '/' + name
+ return self.name + "/" + name
def archive(self, build_dir: Optional[str]) -> None:
"""Saves archive to provided build_dir.
@@ -657,57 +650,62 @@ class InstallRequirement:
return
create_archive = True
- archive_name = '{}-{}.zip'.format(self.name, self.metadata["version"])
+ archive_name = "{}-{}.zip".format(self.name, self.metadata["version"])
archive_path = os.path.join(build_dir, archive_name)
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)),
- ('i', 'w', 'b', 'a'))
- if response == 'i':
+ "The file {} exists. (i)gnore, (w)ipe, "
+ "(b)ackup, (a)bort ".format(display_path(archive_path)),
+ ("i", "w", "b", "a"),
+ )
+ if response == "i":
create_archive = False
- elif response == 'w':
- logger.warning('Deleting %s', display_path(archive_path))
+ elif response == "w":
+ logger.warning("Deleting %s", display_path(archive_path))
os.remove(archive_path)
- elif response == 'b':
+ elif response == "b":
dest_file = backup_dir(archive_path)
logger.warning(
- 'Backing up %s to %s',
+ "Backing up %s to %s",
display_path(archive_path),
display_path(dest_file),
)
shutil.move(archive_path, dest_file)
- elif response == 'a':
+ elif response == "a":
sys.exit(-1)
if not create_archive:
return
zip_output = zipfile.ZipFile(
- archive_path, 'w', zipfile.ZIP_DEFLATED, allowZip64=True,
+ archive_path,
+ "w",
+ zipfile.ZIP_DEFLATED,
+ allowZip64=True,
)
with zip_output:
- dir = os.path.normcase(
- os.path.abspath(self.unpacked_source_directory)
- )
+ dir = os.path.normcase(os.path.abspath(self.unpacked_source_directory))
for dirpath, dirnames, filenames in os.walk(dir):
for dirname in dirnames:
dir_arcname = self._get_archive_name(
- dirname, parentdir=dirpath, rootdir=dir,
+ dirname,
+ parentdir=dirpath,
+ rootdir=dir,
)
- zipdir = zipfile.ZipInfo(dir_arcname + '/')
+ zipdir = zipfile.ZipInfo(dir_arcname + "/")
zipdir.external_attr = 0x1ED << 16 # 0o755
- zip_output.writestr(zipdir, '')
+ zip_output.writestr(zipdir, "")
for filename in filenames:
file_arcname = self._get_archive_name(
- filename, parentdir=dirpath, rootdir=dir,
+ filename,
+ parentdir=dirpath,
+ rootdir=dir,
)
filename = os.path.join(dirpath, filename)
zip_output.write(filename, file_arcname)
- logger.info('Saved %s', display_path(archive_path))
+ logger.info("Saved %s", display_path(archive_path))
def install(
self,
@@ -718,7 +716,7 @@ class InstallRequirement:
prefix: Optional[str] = None,
warn_script_location: bool = True,
use_user_site: bool = False,
- pycompile: bool = True
+ pycompile: bool = True,
) -> None:
scheme = get_scheme(
self.name,
@@ -808,8 +806,9 @@ class InstallRequirement:
deprecated(
reason=(
"{} was installed using the legacy 'setup.py install' "
- "method, because a wheel could not be built for it.".
- format(self.name)
+ "method, because a wheel could not be built for it.".format(
+ self.name
+ )
),
replacement="to fix the wheel build issue reported above",
gone_in=None,
@@ -837,12 +836,10 @@ def check_invalid_constraint_type(req: InstallRequirement) -> str:
"undocumented. The new implementation of the resolver no "
"longer supports these forms."
),
- replacement=(
- "replacing the constraint with a requirement."
- ),
+ replacement="replacing the constraint with a requirement",
# No plan yet for when the new resolver becomes default
gone_in=None,
- issue=8210
+ issue=8210,
)
return problem
diff --git a/src/pip/_internal/req/req_set.py b/src/pip/_internal/req/req_set.py
index 39a2b01cd..6626c37e2 100644
--- a/src/pip/_internal/req/req_set.py
+++ b/src/pip/_internal/req/req_set.py
@@ -13,10 +13,8 @@ logger = logging.getLogger(__name__)
class RequirementSet:
-
def __init__(self, check_supported_wheels: bool = True) -> None:
- """Create a RequirementSet.
- """
+ """Create a RequirementSet."""
self.requirements: Dict[str, InstallRequirement] = OrderedDict()
self.check_supported_wheels = check_supported_wheels
@@ -28,7 +26,7 @@ class RequirementSet:
(req for req in self.requirements.values() if not req.comes_from),
key=lambda req: canonicalize_name(req.name or ""),
)
- return ' '.join(str(req.req) for req in requirements)
+ return " ".join(str(req.req) for req in requirements)
def __repr__(self) -> str:
requirements = sorted(
@@ -36,11 +34,11 @@ class RequirementSet:
key=lambda req: canonicalize_name(req.name or ""),
)
- format_string = '<{classname} object; {count} requirement(s): {reqs}>'
+ format_string = "<{classname} object; {count} requirement(s): {reqs}>"
return format_string.format(
classname=self.__class__.__name__,
count=len(requirements),
- reqs=', '.join(str(req.req) for req in requirements),
+ reqs=", ".join(str(req.req) for req in requirements),
)
def add_unnamed_requirement(self, install_req: InstallRequirement) -> None:
@@ -57,7 +55,7 @@ class RequirementSet:
self,
install_req: InstallRequirement,
parent_req_name: Optional[str] = None,
- extras_requested: Optional[Iterable[str]] = None
+ extras_requested: Optional[Iterable[str]] = None,
) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
"""Add install_req as a requirement to install.
@@ -77,7 +75,8 @@ class RequirementSet:
if not install_req.match_markers(extras_requested):
logger.info(
"Ignoring %s: markers '%s' don't match your environment",
- install_req.name, install_req.markers,
+ install_req.name,
+ install_req.markers,
)
return [], None
@@ -88,16 +87,17 @@ class RequirementSet:
if install_req.link and install_req.link.is_wheel:
wheel = Wheel(install_req.link.filename)
tags = compatibility_tags.get_supported()
- if (self.check_supported_wheels and not wheel.supported(tags)):
+ if self.check_supported_wheels and not wheel.supported(tags):
raise InstallationError(
"{} is not a supported wheel on this platform.".format(
- wheel.filename)
+ wheel.filename
+ )
)
# This next bit is really a sanity check.
- assert not install_req.user_supplied or parent_req_name is None, (
- "a user supplied req shouldn't have a parent"
- )
+ assert (
+ not install_req.user_supplied or parent_req_name is None
+ ), "a user supplied req shouldn't have a parent"
# Unnamed requirements are scanned again and the requirement won't be
# added as a dependency until after scanning.
@@ -107,23 +107,25 @@ class RequirementSet:
try:
existing_req: Optional[InstallRequirement] = self.get_requirement(
- install_req.name)
+ install_req.name
+ )
except KeyError:
existing_req = None
has_conflicting_requirement = (
- parent_req_name is None and
- existing_req and
- not existing_req.constraint and
- existing_req.extras == install_req.extras and
- existing_req.req and
- install_req.req and
- existing_req.req.specifier != install_req.req.specifier
+ parent_req_name is None
+ and existing_req
+ and not existing_req.constraint
+ and existing_req.extras == install_req.extras
+ and existing_req.req
+ and install_req.req
+ and existing_req.req.specifier != install_req.req.specifier
)
if has_conflicting_requirement:
raise InstallationError(
- "Double requirement given: {} (already in {}, name={!r})"
- .format(install_req, existing_req, install_req.name)
+ "Double requirement given: {} (already in {}, name={!r})".format(
+ install_req, existing_req, install_req.name
+ )
)
# When no existing requirement exists, add the requirement as a
@@ -138,12 +140,8 @@ class RequirementSet:
if install_req.constraint or not existing_req.constraint:
return [], existing_req
- does_not_satisfy_constraint = (
- install_req.link and
- not (
- existing_req.link and
- install_req.link.path == existing_req.link.path
- )
+ does_not_satisfy_constraint = install_req.link and not (
+ existing_req.link and install_req.link.path == existing_req.link.path
)
if does_not_satisfy_constraint:
raise InstallationError(
@@ -158,12 +156,13 @@ class RequirementSet:
# mark the existing object as such.
if install_req.user_supplied:
existing_req.user_supplied = True
- existing_req.extras = tuple(sorted(
- set(existing_req.extras) | set(install_req.extras)
- ))
+ existing_req.extras = tuple(
+ sorted(set(existing_req.extras) | set(install_req.extras))
+ )
logger.debug(
"Setting %s extras to: %s",
- existing_req, existing_req.extras,
+ existing_req,
+ existing_req.extras,
)
# Return the existing requirement for addition to the parent and
# scanning again.
@@ -173,8 +172,8 @@ class RequirementSet:
project_name = canonicalize_name(name)
return (
- project_name in self.requirements and
- not self.requirements[project_name].constraint
+ project_name in self.requirements
+ and not self.requirements[project_name].constraint
)
def get_requirement(self, name: str) -> InstallRequirement:
diff --git a/src/pip/_internal/req/req_tracker.py b/src/pip/_internal/req/req_tracker.py
index 27c6baf43..24d3c5303 100644
--- a/src/pip/_internal/req/req_tracker.py
+++ b/src/pip/_internal/req/req_tracker.py
@@ -40,12 +40,10 @@ def update_env_context_manager(**changes: str) -> Iterator[None]:
@contextlib.contextmanager
def get_requirement_tracker() -> Iterator["RequirementTracker"]:
- root = os.environ.get('PIP_REQ_TRACKER')
+ root = os.environ.get("PIP_REQ_TRACKER")
with contextlib.ExitStack() as ctx:
if root is None:
- root = ctx.enter_context(
- TempDirectory(kind='req-tracker')
- ).path
+ root = ctx.enter_context(TempDirectory(kind="req-tracker")).path
ctx.enter_context(update_env_context_manager(PIP_REQ_TRACKER=root))
logger.debug("Initialized build tracking at %s", root)
@@ -54,7 +52,6 @@ def get_requirement_tracker() -> Iterator["RequirementTracker"]:
class RequirementTracker:
-
def __init__(self, root: str) -> None:
self._root = root
self._entries: Set[InstallRequirement] = set()
@@ -68,7 +65,7 @@ class RequirementTracker:
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
- exc_tb: Optional[TracebackType]
+ exc_tb: Optional[TracebackType],
) -> None:
self.cleanup()
@@ -77,8 +74,7 @@ class RequirementTracker:
return os.path.join(self._root, hashed)
def add(self, req: InstallRequirement) -> None:
- """Add an InstallRequirement to build tracking.
- """
+ """Add an InstallRequirement to build tracking."""
assert req.link
# Get the file to write information about this requirement.
@@ -92,30 +88,28 @@ class RequirementTracker:
except FileNotFoundError:
pass
else:
- message = '{} is already being built: {}'.format(
- req.link, contents)
+ message = "{} is already being built: {}".format(req.link, contents)
raise LookupError(message)
# If we're here, req should really not be building already.
assert req not in self._entries
# Start tracking this requirement.
- with open(entry_path, 'w', encoding="utf-8") as fp:
+ with open(entry_path, "w", encoding="utf-8") as fp:
fp.write(str(req))
self._entries.add(req)
- logger.debug('Added %s to build tracker %r', req, self._root)
+ logger.debug("Added %s to build tracker %r", req, self._root)
def remove(self, req: InstallRequirement) -> None:
- """Remove an InstallRequirement from build tracking.
- """
+ """Remove an InstallRequirement from build tracking."""
assert req.link
# Delete the created file and the corresponding entries.
os.unlink(self._entry_path(req.link))
self._entries.remove(req)
- logger.debug('Removed %s from build tracker %r', req, self._root)
+ logger.debug("Removed %s from build tracker %r", req, self._root)
def cleanup(self) -> None:
for req in set(self._entries):
diff --git a/src/pip/_internal/req/req_uninstall.py b/src/pip/_internal/req/req_uninstall.py
index 0c51c8467..ef7352f7b 100644
--- a/src/pip/_internal/req/req_uninstall.py
+++ b/src/pip/_internal/req/req_uninstall.py
@@ -40,12 +40,12 @@ def _script_names(dist: Distribution, script_name: str, is_gui: bool) -> List[st
exe_name = os.path.join(bin_dir, script_name)
paths_to_remove = [exe_name]
if WINDOWS:
- paths_to_remove.append(exe_name + '.exe')
- paths_to_remove.append(exe_name + '.exe.manifest')
+ paths_to_remove.append(exe_name + ".exe")
+ paths_to_remove.append(exe_name + ".exe.manifest")
if is_gui:
- paths_to_remove.append(exe_name + '-script.pyw')
+ paths_to_remove.append(exe_name + "-script.pyw")
else:
- paths_to_remove.append(exe_name + '-script.py')
+ paths_to_remove.append(exe_name + "-script.py")
return paths_to_remove
@@ -57,6 +57,7 @@ def _unique(fn: Callable[..., Iterator[Any]]) -> Callable[..., Iterator[Any]]:
if item not in seen:
seen.add(item)
yield item
+
return unique
@@ -76,29 +77,31 @@ def uninstallation_paths(dist: Distribution) -> Iterator[str]:
https://packaging.python.org/specifications/recording-installed-packages/
"""
try:
- r = csv.reader(dist.get_metadata_lines('RECORD'))
+ r = csv.reader(dist.get_metadata_lines("RECORD"))
except FileNotFoundError as missing_record_exception:
- msg = 'Cannot uninstall {dist}, RECORD file not found.'.format(dist=dist)
+ msg = "Cannot uninstall {dist}, RECORD file not found.".format(dist=dist)
try:
- installer = next(dist.get_metadata_lines('INSTALLER'))
- if not installer or installer == 'pip':
+ installer = next(dist.get_metadata_lines("INSTALLER"))
+ if not installer or installer == "pip":
raise ValueError()
except (OSError, StopIteration, ValueError):
- dep = '{}=={}'.format(dist.project_name, dist.version)
- msg += (" You might be able to recover from this via: "
- "'pip install --force-reinstall --no-deps {}'.".format(dep))
+ dep = "{}=={}".format(dist.project_name, dist.version)
+ msg += (
+ " You might be able to recover from this via: "
+ "'pip install --force-reinstall --no-deps {}'.".format(dep)
+ )
else:
- msg += ' Hint: The package was installed by {}.'.format(installer)
+ msg += " Hint: The package was installed by {}.".format(installer)
raise UninstallationError(msg) from missing_record_exception
for row in r:
path = os.path.join(dist.location, row[0])
yield path
- if path.endswith('.py'):
+ if path.endswith(".py"):
dn, fn = os.path.split(path)
base = fn[:-3]
- path = os.path.join(dn, base + '.pyc')
+ path = os.path.join(dn, base + ".pyc")
yield path
- path = os.path.join(dn, base + '.pyo')
+ path = os.path.join(dn, base + ".pyo")
yield path
@@ -112,8 +115,8 @@ def compact(paths: Iterable[str]) -> Set[str]:
short_paths: Set[str] = set()
for path in sorted(paths, key=len):
should_skip = any(
- path.startswith(shortpath.rstrip("*")) and
- path[len(shortpath.rstrip("*").rstrip(sep))] == sep
+ path.startswith(shortpath.rstrip("*"))
+ and path[len(shortpath.rstrip("*").rstrip(sep))] == sep
for shortpath in short_paths
)
if not should_skip:
@@ -136,18 +139,15 @@ def compress_for_rename(paths: Iterable[str]) -> Set[str]:
return os.path.normcase(os.path.join(*a))
for root in unchecked:
- if any(os.path.normcase(root).startswith(w)
- for w in wildcards):
+ if any(os.path.normcase(root).startswith(w) for w in wildcards):
# This directory has already been handled.
continue
all_files: Set[str] = set()
all_subdirs: Set[str] = set()
for dirname, subdirs, files in os.walk(root):
- all_subdirs.update(norm_join(root, dirname, d)
- for d in subdirs)
- all_files.update(norm_join(root, dirname, f)
- for f in files)
+ all_subdirs.update(norm_join(root, dirname, d) for d in subdirs)
+ all_files.update(norm_join(root, dirname, f) for f in files)
# If all the files we found are in our remaining set of files to
# remove, then remove them from the latter set and add a wildcard
# for the directory.
@@ -196,14 +196,14 @@ def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str
continue
file_ = os.path.join(dirpath, fname)
- if (os.path.isfile(file_) and
- os.path.normcase(file_) not in _normcased_files):
+ if (
+ os.path.isfile(file_)
+ and os.path.normcase(file_) not in _normcased_files
+ ):
# We are skipping this file. Add it to the set.
will_skip.add(file_)
- will_remove = files | {
- os.path.join(folder, "*") for folder in folders
- }
+ will_remove = files | {os.path.join(folder, "*") for folder in folders}
return will_remove, will_skip
@@ -211,6 +211,7 @@ def compress_for_output_listing(paths: Iterable[str]) -> Tuple[Set[str], Set[str
class StashedUninstallPathSet:
"""A set of file rename operations to stash files while
tentatively uninstalling them."""
+
def __init__(self) -> None:
# Mapping from source file root to [Adjacent]TempDirectory
# for files under that directory.
@@ -252,7 +253,7 @@ class StashedUninstallPathSet:
else:
# Did not find any suitable root
head = os.path.dirname(path)
- save_dir = TempDirectory(kind='uninstall')
+ save_dir = TempDirectory(kind="uninstall")
self._save_dirs[head] = save_dir
relpath = os.path.relpath(path, head)
@@ -271,7 +272,7 @@ class StashedUninstallPathSet:
new_path = self._get_file_stash(path)
self._moves.append((path, new_path))
- if (path_is_dir and os.path.isdir(new_path)):
+ if path_is_dir and os.path.isdir(new_path):
# If we're moving a directory, we need to
# remove the destination first or else it will be
# moved to inside the existing directory.
@@ -295,7 +296,7 @@ class StashedUninstallPathSet:
for new_path, path in self._moves:
try:
- logger.debug('Replacing %s from %s', new_path, path)
+ logger.debug("Replacing %s from %s", new_path, path)
if os.path.isfile(new_path) or os.path.islink(new_path):
os.unlink(new_path)
elif os.path.isdir(new_path):
@@ -315,6 +316,7 @@ class StashedUninstallPathSet:
class UninstallPathSet:
"""A set of file paths to be removed in the uninstallation of a
requirement."""
+
def __init__(self, dist: Distribution) -> None:
self.paths: Set[str] = set()
self._refuse: Set[str] = set()
@@ -346,7 +348,7 @@ class UninstallPathSet:
# __pycache__ files can show up after 'installed-files.txt' is created,
# due to imports
- if os.path.splitext(path)[1] == '.py':
+ if os.path.splitext(path)[1] == ".py":
self.add(cache_from_source(path))
def add_pth(self, pth_file: str, entry: str) -> None:
@@ -369,10 +371,8 @@ class UninstallPathSet:
)
return
- dist_name_version = (
- self.dist.project_name + "-" + self.dist.version
- )
- logger.info('Uninstalling %s:', dist_name_version)
+ dist_name_version = self.dist.project_name + "-" + self.dist.version
+ logger.info("Uninstalling %s:", dist_name_version)
with indent_log():
if auto_confirm or self._allowed_to_proceed(verbose):
@@ -382,16 +382,15 @@ class UninstallPathSet:
for path in sorted(compact(for_rename)):
moved.stash(path)
- logger.verbose('Removing file or directory %s', path)
+ logger.verbose("Removing file or directory %s", path)
for pth in self.pth.values():
pth.remove()
- logger.info('Successfully uninstalled %s', dist_name_version)
+ logger.info("Successfully uninstalled %s", dist_name_version)
def _allowed_to_proceed(self, verbose: bool) -> bool:
- """Display which files would be deleted and prompt for confirmation
- """
+ """Display which files would be deleted and prompt for confirmation"""
def _display(msg: str, paths: Iterable[str]) -> None:
if not paths:
@@ -410,13 +409,13 @@ class UninstallPathSet:
will_remove = set(self.paths)
will_skip = set()
- _display('Would remove:', will_remove)
- _display('Would not remove (might be manually added):', will_skip)
- _display('Would not remove (outside of prefix):', self._refuse)
+ _display("Would remove:", will_remove)
+ _display("Would not remove (might be manually added):", will_skip)
+ _display("Would not remove (outside of prefix):", self._refuse)
if verbose:
- _display('Will actually move:', compress_for_rename(self.paths))
+ _display("Will actually move:", compress_for_rename(self.paths))
- return ask('Proceed (Y/n)? ', ('y', 'n', '')) != 'n'
+ return ask("Proceed (Y/n)? ", ("y", "n", "")) != "n"
def rollback(self) -> None:
"""Rollback the changes previously made by remove()."""
@@ -426,7 +425,7 @@ class UninstallPathSet:
self.dist.project_name,
)
return
- logger.info('Rolling back uninstall of %s', self.dist.project_name)
+ logger.info("Rolling back uninstall of %s", self.dist.project_name)
self._moved_paths.rollback()
for pth in self.pth.values():
pth.rollback()
@@ -447,9 +446,11 @@ class UninstallPathSet:
)
return cls(dist)
- if dist_path in {p for p in {sysconfig.get_path("stdlib"),
- sysconfig.get_path("platstdlib")}
- if p}:
+ if dist_path in {
+ p
+ for p in {sysconfig.get_path("stdlib"), sysconfig.get_path("platstdlib")}
+ if p
+ }:
logger.info(
"Not uninstalling %s at %s, as it is in the standard library.",
dist.key,
@@ -459,43 +460,47 @@ class UninstallPathSet:
paths_to_remove = cls(dist)
develop_egg_link = egg_link_path(dist)
- develop_egg_link_egg_info = '{}.egg-info'.format(
- pkg_resources.to_filename(dist.project_name))
+ develop_egg_link_egg_info = "{}.egg-info".format(
+ pkg_resources.to_filename(dist.project_name)
+ )
egg_info_exists = dist.egg_info and os.path.exists(dist.egg_info)
# Special case for distutils installed package
- distutils_egg_info = getattr(dist._provider, 'path', None)
+ distutils_egg_info = getattr(dist._provider, "path", None)
# Uninstall cases order do matter as in the case of 2 installs of the
# same package, pip needs to uninstall the currently detected version
- if (egg_info_exists and dist.egg_info.endswith('.egg-info') and
- not dist.egg_info.endswith(develop_egg_link_egg_info)):
+ if (
+ egg_info_exists
+ and dist.egg_info.endswith(".egg-info")
+ and not dist.egg_info.endswith(develop_egg_link_egg_info)
+ ):
# if dist.egg_info.endswith(develop_egg_link_egg_info), we
# are in fact in the develop_egg_link case
paths_to_remove.add(dist.egg_info)
- if dist.has_metadata('installed-files.txt'):
+ if dist.has_metadata("installed-files.txt"):
for installed_file in dist.get_metadata(
- 'installed-files.txt').splitlines():
- path = os.path.normpath(
- os.path.join(dist.egg_info, installed_file)
- )
+ "installed-files.txt"
+ ).splitlines():
+ path = os.path.normpath(os.path.join(dist.egg_info, installed_file))
paths_to_remove.add(path)
# FIXME: need a test for this elif block
# occurs with --single-version-externally-managed/--record outside
# of pip
- elif dist.has_metadata('top_level.txt'):
- if dist.has_metadata('namespace_packages.txt'):
- namespaces = dist.get_metadata('namespace_packages.txt')
+ elif dist.has_metadata("top_level.txt"):
+ if dist.has_metadata("namespace_packages.txt"):
+ namespaces = dist.get_metadata("namespace_packages.txt")
else:
namespaces = []
for top_level_pkg in [
- p for p
- in dist.get_metadata('top_level.txt').splitlines()
- if p and p not in namespaces]:
+ p
+ for p in dist.get_metadata("top_level.txt").splitlines()
+ if p and p not in namespaces
+ ]:
path = os.path.join(dist.location, top_level_pkg)
paths_to_remove.add(path)
- paths_to_remove.add(path + '.py')
- paths_to_remove.add(path + '.pyc')
- paths_to_remove.add(path + '.pyo')
+ paths_to_remove.add(path + ".py")
+ paths_to_remove.add(path + ".pyc")
+ paths_to_remove.add(path + ".pyo")
elif distutils_egg_info:
raise UninstallationError(
@@ -506,17 +511,18 @@ class UninstallPathSet:
)
)
- elif dist.location.endswith('.egg'):
+ elif dist.location.endswith(".egg"):
# package installed by easy_install
# We cannot match on dist.egg_name because it can slightly vary
# i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg
paths_to_remove.add(dist.location)
easy_install_egg = os.path.split(dist.location)[1]
- easy_install_pth = os.path.join(os.path.dirname(dist.location),
- 'easy-install.pth')
- paths_to_remove.add_pth(easy_install_pth, './' + easy_install_egg)
+ easy_install_pth = os.path.join(
+ os.path.dirname(dist.location), "easy-install.pth"
+ )
+ paths_to_remove.add_pth(easy_install_pth, "./" + easy_install_egg)
- elif egg_info_exists and dist.egg_info.endswith('.dist-info'):
+ elif egg_info_exists and dist.egg_info.endswith(".dist-info"):
for path in uninstallation_paths(dist):
paths_to_remove.add(path)
@@ -524,40 +530,42 @@ class UninstallPathSet:
# develop egg
with open(develop_egg_link) as fh:
link_pointer = os.path.normcase(fh.readline().strip())
- assert (link_pointer == dist.location), (
- 'Egg-link {} does not match installed location of {} '
- '(at {})'.format(
- link_pointer, dist.project_name, dist.location)
+ assert (
+ link_pointer == dist.location
+ ), "Egg-link {} does not match installed location of {} (at {})".format(
+ link_pointer, dist.project_name, dist.location
)
paths_to_remove.add(develop_egg_link)
- easy_install_pth = os.path.join(os.path.dirname(develop_egg_link),
- 'easy-install.pth')
+ easy_install_pth = os.path.join(
+ os.path.dirname(develop_egg_link), "easy-install.pth"
+ )
paths_to_remove.add_pth(easy_install_pth, dist.location)
else:
logger.debug(
- 'Not sure how to uninstall: %s - Check: %s',
- dist, dist.location,
+ "Not sure how to uninstall: %s - Check: %s",
+ dist,
+ dist.location,
)
# find distutils scripts= scripts
- if dist.has_metadata('scripts') and dist.metadata_isdir('scripts'):
- for script in dist.metadata_listdir('scripts'):
+ if dist.has_metadata("scripts") and dist.metadata_isdir("scripts"):
+ for script in dist.metadata_listdir("scripts"):
if dist_in_usersite(dist):
bin_dir = get_bin_user()
else:
bin_dir = get_bin_prefix()
paths_to_remove.add(os.path.join(bin_dir, script))
if WINDOWS:
- paths_to_remove.add(os.path.join(bin_dir, script) + '.bat')
+ paths_to_remove.add(os.path.join(bin_dir, script) + ".bat")
# find console_scripts
_scripts_to_remove = []
- console_scripts = dist.get_entry_map(group='console_scripts')
+ console_scripts = dist.get_entry_map(group="console_scripts")
for name in console_scripts.keys():
_scripts_to_remove.extend(_script_names(dist, name, False))
# find gui_scripts
- gui_scripts = dist.get_entry_map(group='gui_scripts')
+ gui_scripts = dist.get_entry_map(group="gui_scripts")
for name in gui_scripts.keys():
_scripts_to_remove.extend(_script_names(dist, name, True))
@@ -585,45 +593,41 @@ class UninstallPthEntries:
# have more than "\\sever\share". Valid examples: "\\server\share\" or
# "\\server\share\folder".
if WINDOWS and not os.path.splitdrive(entry)[0]:
- entry = entry.replace('\\', '/')
+ entry = entry.replace("\\", "/")
self.entries.add(entry)
def remove(self) -> None:
- logger.verbose('Removing pth entries from %s:', self.file)
+ logger.verbose("Removing pth entries from %s:", self.file)
# If the file doesn't exist, log a warning and return
if not os.path.isfile(self.file):
- logger.warning(
- "Cannot remove entries from nonexistent file %s", self.file
- )
+ logger.warning("Cannot remove entries from nonexistent file %s", self.file)
return
- with open(self.file, 'rb') as fh:
+ with open(self.file, "rb") as fh:
# windows uses '\r\n' with py3k, but uses '\n' with py2.x
lines = fh.readlines()
self._saved_lines = lines
- if any(b'\r\n' in line for line in lines):
- endline = '\r\n'
+ if any(b"\r\n" in line for line in lines):
+ endline = "\r\n"
else:
- endline = '\n'
+ endline = "\n"
# handle missing trailing newline
if lines and not lines[-1].endswith(endline.encode("utf-8")):
lines[-1] = lines[-1] + endline.encode("utf-8")
for entry in self.entries:
try:
- logger.verbose('Removing entry: %s', entry)
+ logger.verbose("Removing entry: %s", entry)
lines.remove((entry + endline).encode("utf-8"))
except ValueError:
pass
- with open(self.file, 'wb') as fh:
+ with open(self.file, "wb") as fh:
fh.writelines(lines)
def rollback(self) -> bool:
if self._saved_lines is None:
- logger.error(
- 'Cannot roll back changes to %s, none were made', self.file
- )
+ logger.error("Cannot roll back changes to %s, none were made", self.file)
return False
- logger.debug('Rolling %s back to previous state', self.file)
- with open(self.file, 'wb') as fh:
+ logger.debug("Rolling %s back to previous state", self.file)
+ with open(self.file, "wb") as fh:
fh.writelines(self._saved_lines)
return True
diff --git a/src/pip/_internal/resolution/resolvelib/base.py b/src/pip/_internal/resolution/resolvelib/base.py
index 7f258c574..b206692a0 100644
--- a/src/pip/_internal/resolution/resolvelib/base.py
+++ b/src/pip/_internal/resolution/resolvelib/base.py
@@ -36,11 +36,8 @@ class Constraint:
links = frozenset([ireq.link]) if ireq.link else frozenset()
return Constraint(ireq.specifier, ireq.hashes(trust_internet=False), links)
- def __nonzero__(self) -> bool:
- return bool(self.specifier) or bool(self.hashes) or bool(self.links)
-
def __bool__(self) -> bool:
- return self.__nonzero__()
+ return bool(self.specifier) or bool(self.hashes) or bool(self.links)
def __and__(self, other: InstallRequirement) -> "Constraint":
if not isinstance(other, InstallRequirement):
diff --git a/src/pip/_internal/resolution/resolvelib/found_candidates.py b/src/pip/_internal/resolution/resolvelib/found_candidates.py
index d2fa5ef55..f28df6481 100644
--- a/src/pip/_internal/resolution/resolvelib/found_candidates.py
+++ b/src/pip/_internal/resolution/resolvelib/found_candidates.py
@@ -138,5 +138,3 @@ class FoundCandidates(collections_abc.Sequence):
if self._prefers_installed and self._installed:
return True
return any(self)
-
- __nonzero__ = __bool__ # XXX: Python 2.
diff --git a/src/pip/_internal/resolution/resolvelib/provider.py b/src/pip/_internal/resolution/resolvelib/provider.py
index c86fdc31f..632854d3b 100644
--- a/src/pip/_internal/resolution/resolvelib/provider.py
+++ b/src/pip/_internal/resolution/resolvelib/provider.py
@@ -143,6 +143,21 @@ class PipProvider(_ProviderBase):
identifier,
)
+ def _get_constraint(self, identifier: str) -> Constraint:
+ if identifier in self._constraints:
+ return self._constraints[identifier]
+
+ # HACK: Theoratically we should check whether this identifier is a valid
+ # "NAME[EXTRAS]" format, and parse out the name part with packaging or
+ # some regular expression. But since pip's resolver only spits out
+ # three kinds of identifiers: normalized PEP 503 names, normalized names
+ # plus extras, and Requires-Python, we can cheat a bit here.
+ name, open_bracket, _ = identifier.partition("[")
+ if open_bracket and name in self._constraints:
+ return self._constraints[name]
+
+ return Constraint.empty()
+
def find_matches(
self,
identifier: str,
@@ -169,7 +184,7 @@ class PipProvider(_ProviderBase):
return self._factory.find_candidates(
identifier=identifier,
requirements=requirements,
- constraint=self._constraints.get(identifier, Constraint.empty()),
+ constraint=self._get_constraint(identifier),
prefers_installed=(not _eligible_for_upgrade(identifier)),
incompatibilities=incompatibilities,
)
diff --git a/src/pip/_internal/resolution/resolvelib/resolver.py b/src/pip/_internal/resolution/resolvelib/resolver.py
index 6de6636a0..f89afaf43 100644
--- a/src/pip/_internal/resolution/resolvelib/resolver.py
+++ b/src/pip/_internal/resolution/resolvelib/resolver.py
@@ -151,7 +151,7 @@ class Resolver(BaseResolver):
deprecated(
reason=reason,
replacement=replacement,
- gone_in="21.2",
+ gone_in="21.3",
issue=8711,
)
diff --git a/src/pip/_internal/utils/deprecation.py b/src/pip/_internal/utils/deprecation.py
index 57dbdbdca..a163395c4 100644
--- a/src/pip/_internal/utils/deprecation.py
+++ b/src/pip/_internal/utils/deprecation.py
@@ -11,6 +11,15 @@ from pip._vendor.packaging.version import parse
from pip import __version__ as current_version
DEPRECATION_MSG_PREFIX = "DEPRECATION: "
+DEPRECATION_MESSAGE = DEPRECATION_MSG_PREFIX + "{reason}"
+GONE_IN_MESSAGE_FUTURE = "pip {gone_in} will enforce this behavior change."
+GONE_IN_MESSAGE_PAST = "This behavior change has been enforced since pip {gone_in}."
+REPLACEMENT_MESSAGE = "A possible replacement is {replacement}."
+FEATURE_FLAG_MESSAGE = (
+ "You can temporarily use the flag --use-feature={feature_flag} "
+ "to test the upcoming behavior."
+)
+ISSUE_MESSAGE = "Discussion can be found at https://github.com/pypa/pip/issues/{issue}."
class PipDeprecationWarning(Warning):
@@ -56,20 +65,24 @@ def deprecated(
reason: str,
replacement: Optional[str],
gone_in: Optional[str],
+ feature_flag: Optional[str] = None,
issue: Optional[int] = None,
) -> None:
"""Helper to deprecate existing functionality.
reason:
Textual reason shown to the user about why this functionality has
- been deprecated.
+ been deprecated. Should be a complete sentence.
replacement:
Textual suggestion shown to the user about what alternative
functionality they can use.
gone_in:
The version of pip does this functionality should get removed in.
- Raises errors if pip's current version is greater than or equal to
+ Raises an error if pip's current version is greater than or equal to
this.
+ feature_flag:
+ Command-line flag of the form --use-feature={feature_flag} for testing
+ upcoming functionality.
issue:
Issue number on the tracker that would serve as a useful place for
users to find related discussion and provide feedback.
@@ -77,28 +90,38 @@ def deprecated(
Always pass replacement, gone_in and issue as keyword arguments for clarity
at the call site.
"""
-
+ # Determine whether or not the feature is already gone in this version.
+ is_gone = gone_in is not None and parse(current_version) >= parse(gone_in)
+ # Allow variable substitutions within the "reason" variable.
+ formatted_reason = reason.format(gone_in=gone_in)
# Construct a nice message.
# This is eagerly formatted as we want it to get logged as if someone
# typed this entire message out.
+ formatted_deprecation_message = DEPRECATION_MESSAGE.format(reason=formatted_reason)
+ gone_in_message = GONE_IN_MESSAGE_PAST if is_gone else GONE_IN_MESSAGE_FUTURE
+ formatted_gone_in_message = (
+ gone_in_message.format(gone_in=gone_in) if gone_in else None
+ )
+ formatted_replacement_message = (
+ REPLACEMENT_MESSAGE.format(replacement=replacement) if replacement else None
+ )
+ formatted_feature_flag_message = (
+ None
+ if is_gone or not feature_flag
+ else FEATURE_FLAG_MESSAGE.format(feature_flag=feature_flag)
+ )
+ formatted_issue_message = ISSUE_MESSAGE.format(issue=issue) if issue else None
sentences = [
- (reason, DEPRECATION_MSG_PREFIX + "{}"),
- (gone_in, "pip {} will remove support for this functionality."),
- (replacement, "A possible replacement is {}."),
- (
- issue,
- (
- "You can find discussion regarding this at "
- "https://github.com/pypa/pip/issues/{}."
- ),
- ),
+ formatted_deprecation_message,
+ formatted_gone_in_message,
+ formatted_replacement_message,
+ formatted_feature_flag_message,
+ formatted_issue_message,
]
- message = " ".join(
- template.format(val) for val, template in sentences if val is not None
- )
+ message = " ".join(sentence for sentence in sentences if sentence)
- # Raise as an error if it has to be removed.
- if gone_in is not None and parse(current_version) >= parse(gone_in):
+ # Raise as an error if the functionality is gone.
+ if is_gone:
raise PipDeprecationWarning(message)
-
- warnings.warn(message, category=PipDeprecationWarning, stacklevel=2)
+ else:
+ warnings.warn(message, category=PipDeprecationWarning, stacklevel=2)
diff --git a/src/pip/_internal/utils/hashes.py b/src/pip/_internal/utils/hashes.py
index 3d20b8d02..f011f6591 100644
--- a/src/pip/_internal/utils/hashes.py
+++ b/src/pip/_internal/utils/hashes.py
@@ -117,15 +117,11 @@ class Hashes:
with open(path, "rb") as file:
return self.check_against_file(file)
- def __nonzero__(self):
+ def __bool__(self):
# type: () -> bool
"""Return whether I know any known-good hashes."""
return bool(self._allowed)
- def __bool__(self):
- # type: () -> bool
- return self.__nonzero__()
-
def __eq__(self, other):
# type: (object) -> bool
if not isinstance(other, Hashes):
diff --git a/src/pip/_vendor/tenacity/__init__.py b/src/pip/_vendor/tenacity/__init__.py
index f984eec4e..086ad46e1 100644
--- a/src/pip/_vendor/tenacity/__init__.py
+++ b/src/pip/_vendor/tenacity/__init__.py
@@ -190,7 +190,7 @@ class RetryError(Exception):
self.last_attempt = last_attempt
super().__init__(last_attempt)
- def reraise(self) -> t.NoReturn:
+ def reraise(self) -> "t.NoReturn":
if self.last_attempt.failed:
raise self.last_attempt.result()
raise self