From 8e2e1964a4f0a060f7299a96a911c9e116b2283d Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Fri, 23 Jul 2021 17:35:02 +0100 Subject: Blacken src/pip/_internal/commands/ Progresses the black formatting of the codebase further. --- src/pip/_internal/commands/__init__.py | 200 +++++++++++++++-------- src/pip/_internal/commands/cache.py | 87 +++++----- src/pip/_internal/commands/check.py | 10 +- src/pip/_internal/commands/completion.py | 49 +++--- src/pip/_internal/commands/configuration.py | 68 ++++---- src/pip/_internal/commands/debug.py | 88 +++++----- src/pip/_internal/commands/download.py | 15 +- src/pip/_internal/commands/freeze.py | 59 ++++--- src/pip/_internal/commands/hash.py | 22 +-- src/pip/_internal/commands/help.py | 2 +- src/pip/_internal/commands/index.py | 21 ++- src/pip/_internal/commands/install.py | 238 ++++++++++++++-------------- src/pip/_internal/commands/list.py | 141 ++++++++-------- src/pip/_internal/commands/search.py | 72 +++++---- src/pip/_internal/commands/show.py | 37 +++-- src/pip/_internal/commands/uninstall.py | 39 ++--- src/pip/_internal/commands/wheel.py | 40 ++--- 17 files changed, 653 insertions(+), 535 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/commands/__init__.py b/src/pip/_internal/commands/__init__.py index 8e94b38f7..dc724ad24 100644 --- a/src/pip/_internal/commands/__init__.py +++ b/src/pip/_internal/commands/__init__.py @@ -8,7 +8,7 @@ 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 @@ -18,72 +18,138 @@ CommandInfo = namedtuple('CommandInfo', 'module_path, class_name, summary') # 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', - "Inspect and manage pip's wheel cache.", - )), - ('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.', - )), -]) +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", + "Inspect and manage pip's wheel cache.", + ), + ), + ( + "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.", + ), + ), + ] +) 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..651321b3a 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,9 @@ 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 +85,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(""" + 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() + """ + ) + .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 +161,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 +186,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..2d08904ec 100644 --- a/src/pip/_internal/commands/configuration.py +++ b/src/pip/_internal/commands/configuration.py @@ -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..270abb35c 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..0d9d308e0 100644 --- a/src/pip/_internal/commands/download.py +++ b/src/pip/_internal/commands/download.py @@ -53,9 +53,12 @@ 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 ."), ) @@ -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..57025698e 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,46 @@ 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', + metavar="file", help="Use the order in the given requirements file and its " - "comments when generating output. This option can be " - "used multiple times.") + "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 +87,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] ...' + usage = "%prog [options] ..." 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..b34019333 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -86,87 +86,90 @@ 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 . ' - 'By default this will not replace existing files/folders in ' - '. Use --upgrade to replace existing packages in ' - 'with new versions.' + help="Install packages into . " + "By default this will not replace existing files/folders in " + ". Use --upgrade to replace existing packages in " + "with new versions.", ) cmdoptions.add_target_python_options(self.cmd_opts) self.cmd_opts.add_option( - '--user', - dest='use_user_site', - action='store_true', + "--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.)") + "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") + "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 +252,11 @@ 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 os.path.exists(options.target_dir) and not os.path.isdir( + options.target_dir + ): 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 +288,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 +325,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 +346,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 +363,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 +400,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 +418,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 +456,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 +478,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 +497,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 +559,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 +567,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 +586,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 +645,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 +658,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 +678,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 +687,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 +718,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..d8b234dab 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,86 @@ 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'), + choices=("columns", "freeze", "json"), help="Select the output format among: columns (default), freeze, " - "or json", + "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 +154,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 +192,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 +201,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 +227,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 +247,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 +265,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 +290,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 +339,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..3561b1297 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..2f7920514 100644 --- a/src/pip/_internal/commands/show.py +++ b/src/pip/_internal/commands/show.py @@ -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 @@ -75,36 +78,32 @@ 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())) def _files_from_installed_files(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) @@ -116,7 +115,7 @@ 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 = [] @@ -170,8 +169,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..baab51fd3 100644 --- a/src/pip/_internal/commands/uninstall.py +++ b/src/pip/_internal/commands/uninstall.py @@ -32,19 +32,22 @@ 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 +57,23 @@ 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 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 +83,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 , where the default is the " - "current working directory."), + help=( + "Build wheels into , 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 -- cgit v1.2.1 From b53fea1c66b5daa59919d894a89865b92156eff6 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Fri, 23 Jul 2021 17:40:31 +0100 Subject: Change commands_dict to a normal dictionary In Python 3.6+, `dict` is ordered now and we can use that. --- src/pip/_internal/commands/__init__.py | 231 +++++++++++++-------------------- 1 file changed, 90 insertions(+), 141 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/commands/__init__.py b/src/pip/_internal/commands/__init__.py index dc724ad24..c72f24f30 100644 --- a/src/pip/_internal/commands/__init__.py +++ b/src/pip/_internal/commands/__init__.py @@ -3,153 +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") -# 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", - "Inspect and manage pip's wheel cache.", - ), - ), - ( - "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.", - ), - ), - ] -) +# 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", + "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.", + ), +} def create_command(name: str, **kwargs: Any) -> Command: -- cgit v1.2.1 From 1a7037eaf39eba5ee53a4c693bf0b19a7cb9f5a5 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Fri, 23 Jul 2021 17:44:02 +0100 Subject: Concatenate same-line strings This is suboptimal behaviour in black that'll hopefully get fixed in the near future. --- src/pip/_internal/commands/cache.py | 18 ++++++++---------- src/pip/_internal/commands/debug.py | 2 +- src/pip/_internal/commands/install.py | 6 +++--- src/pip/_internal/commands/list.py | 5 ++--- 4 files changed, 14 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/commands/cache.py b/src/pip/_internal/commands/cache.py index 651321b3a..dc307ef20 100644 --- a/src/pip/_internal/commands/cache.py +++ b/src/pip/_internal/commands/cache.py @@ -59,9 +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 @@ -104,13 +102,13 @@ class CacheCommand(Command): 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} - """ + 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, diff --git a/src/pip/_internal/commands/debug.py b/src/pip/_internal/commands/debug.py index 270abb35c..d3f1f28de 100644 --- a/src/pip/_internal/commands/debug.py +++ b/src/pip/_internal/commands/debug.py @@ -131,7 +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) diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index b34019333..862d9f072 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -115,7 +115,7 @@ class InstallCommand(RequirementCommand): 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", @@ -158,7 +158,7 @@ class InstallCommand(RequirementCommand): "--force-reinstall", dest="force_reinstall", action="store_true", - help="Reinstall all packages even if they are already " "up-to-date.", + help="Reinstall all packages even if they are already up-to-date.", ) self.cmd_opts.add_option( @@ -256,7 +256,7 @@ class InstallCommand(RequirementCommand): options.target_dir ): 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 diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py index d8b234dab..abe6ef2fc 100644 --- a/src/pip/_internal/commands/list.py +++ b/src/pip/_internal/commands/list.py @@ -103,15 +103,14 @@ class ListCommand(IndexGroupCommand): dest="list_format", default="columns", choices=("columns", "freeze", "json"), - help="Select the output format among: columns (default), freeze, " - "or 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.", + help="List packages that are not dependencies of installed packages.", ) self.cmd_opts.add_option( -- cgit v1.2.1 From 80a45f5ae333128272085dbc516e37b37e2b18d6 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Fri, 23 Jul 2021 17:45:21 +0100 Subject: Reformat a conditional that isn't nicely formatted Black seems to still get confused about where it should break up multi-part if statements. --- src/pip/_internal/commands/install.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 862d9f072..8f88680fe 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -252,8 +252,11 @@ 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." -- cgit v1.2.1 From 6641fe74b767dd3ede3a26806e46cf6b2b2cb2da Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Fri, 23 Jul 2021 17:46:00 +0100 Subject: Use backticks to denote a command This looks better IMO. --- src/pip/_internal/commands/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/pip/_internal/commands/search.py b/src/pip/_internal/commands/search.py index 3561b1297..03ed925b2 100644 --- a/src/pip/_internal/commands/search.py +++ b/src/pip/_internal/commands/search.py @@ -125,7 +125,7 @@ def print_dist_installation_info(name: str, latest: str) -> None: if parse_version(latest).pre: write_output( "LATEST: %s (pre-release; install" - ' with "pip install --pre")', + " with `pip install --pre`)", latest, ) else: -- cgit v1.2.1 From dd64df05c08559d52abe193480fc9f42cf79a7fe Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Fri, 23 Jul 2021 17:46:52 +0100 Subject: Consistently stylise multi-line help strings This is singlehandedly the most satisfying part of all the formatting efforts. --- src/pip/_internal/commands/download.py | 2 +- src/pip/_internal/commands/freeze.py | 20 ++++++---- src/pip/_internal/commands/install.py | 65 ++++++++++++++++++++------------- src/pip/_internal/commands/uninstall.py | 6 ++- 4 files changed, 58 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/commands/download.py b/src/pip/_internal/commands/download.py index 0d9d308e0..2f6aac29e 100644 --- a/src/pip/_internal/commands/download.py +++ b/src/pip/_internal/commands/download.py @@ -60,7 +60,7 @@ class DownloadCommand(RequirementCommand): dest="download_dir", metavar="dir", default=os.curdir, - help=("Download packages into ."), + help="Download packages into .", ) cmdoptions.add_target_python_options(self.cmd_opts) diff --git a/src/pip/_internal/commands/freeze.py b/src/pip/_internal/commands/freeze.py index 57025698e..5fa6d39b2 100644 --- a/src/pip/_internal/commands/freeze.py +++ b/src/pip/_internal/commands/freeze.py @@ -30,9 +30,11 @@ class FreezeCommand(Command): 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.", + 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", @@ -40,8 +42,10 @@ class FreezeCommand(Command): 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", @@ -55,8 +59,10 @@ class FreezeCommand(Command): "--all", dest="freeze_all", action="store_true", - help="Do not skip these packages in the output:" - " {}".format(", ".join(DEV_PKGS)), + help=( + "Do not skip these packages in the output:" + " {}".format(", ".join(DEV_PKGS)) + ), ) self.cmd_opts.add_option( "--exclude-editable", diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 8f88680fe..a427c6c59 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -91,10 +91,12 @@ class InstallCommand(RequirementCommand): dest="target_dir", metavar="dir", default=None, - help="Install packages into . " - "By default this will not replace existing files/folders in " - ". Use --upgrade to replace existing packages in " - "with new versions.", + help=( + "Install packages into . " + "By default this will not replace existing files/folders in " + ". Use --upgrade to replace existing packages in " + "with new versions." + ), ) cmdoptions.add_target_python_options(self.cmd_opts) @@ -102,13 +104,18 @@ class InstallCommand(RequirementCommand): "--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.)", + 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", @@ -122,8 +129,10 @@ class InstallCommand(RequirementCommand): 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()) @@ -135,9 +144,11 @@ class InstallCommand(RequirementCommand): "--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.", + 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( @@ -145,13 +156,15 @@ class InstallCommand(RequirementCommand): 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).", + 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( @@ -166,10 +179,12 @@ class InstallCommand(RequirementCommand): "--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!", + 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()) diff --git a/src/pip/_internal/commands/uninstall.py b/src/pip/_internal/commands/uninstall.py index baab51fd3..d47b07eab 100644 --- a/src/pip/_internal/commands/uninstall.py +++ b/src/pip/_internal/commands/uninstall.py @@ -38,8 +38,10 @@ class UninstallCommand(Command, SessionCommandMixin): action="append", default=[], metavar="file", - help="Uninstall all the packages listed in the given requirements " - "file. This option can be used multiple times.", + help=( + "Uninstall all the packages listed in the given requirements " + "file. This option can be used multiple times." + ), ) self.cmd_opts.add_option( "-y", -- cgit v1.2.1 From 069b01932a7d64a81c708c6254cc93e1f89e6783 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Sat, 24 Jul 2021 09:47:54 +0100 Subject: Blacken src/pip/_internal/req Progresses the black formatting of the codebase further. --- src/pip/_internal/req/__init__.py | 16 +-- src/pip/_internal/req/constructors.py | 91 ++++++++------- src/pip/_internal/req/req_file.py | 64 +++++----- src/pip/_internal/req/req_install.py | 189 +++++++++++++++--------------- src/pip/_internal/req/req_set.py | 69 ++++++----- src/pip/_internal/req/req_tracker.py | 24 ++-- src/pip/_internal/req/req_uninstall.py | 208 +++++++++++++++++---------------- 7 files changed, 328 insertions(+), 333 deletions(-) (limited to 'src') 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\$\{(?P[A-Z0-9_]+)\})') +ENV_VAR_RE = re.compile(r"(?P\$\{(?P[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..0d93941a3 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 = '' + s = "" 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 -- cgit v1.2.1 From 239a30737277887fed9f609ae786a0fb80591be4 Mon Sep 17 00:00:00 2001 From: Guy Tuval Date: Sat, 24 Jul 2021 16:24:30 +0300 Subject: Error handling upon `uninstall` invalid parameter (#10171) Co-authored-by: Tzu-ping Chung --- src/pip/_internal/commands/uninstall.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src') diff --git a/src/pip/_internal/commands/uninstall.py b/src/pip/_internal/commands/uninstall.py index dc22b5d1d..c590627ea 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): """ @@ -58,6 +61,13 @@ class UninstallCommand(Command, SessionCommandMixin): ) 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, -- cgit v1.2.1 From 33b7f0cd9e0fff6bd532941a530d7f8b3472eb55 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sat, 24 Jul 2021 22:26:18 +0800 Subject: Remove deprecated sdist reinstall feature for 21.2 --- src/pip/_internal/resolution/resolvelib/resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') 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, ) -- cgit v1.2.1 From 3d25c5327d0887271958999ac44709728b5c4f5a Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sat, 24 Jul 2021 21:28:36 +0800 Subject: Bump for release --- src/pip/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/pip/__init__.py b/src/pip/__init__.py index 67722d05e..f43d29b2f 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.2" def main(args: Optional[List[str]] = None) -> int: -- cgit v1.2.1 From 765a4b40227653d22d30c85448ec343e542f41d7 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sat, 24 Jul 2021 21:28:37 +0800 Subject: Bump for development --- src/pip/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/pip/__init__.py b/src/pip/__init__.py index f43d29b2f..e8bb5c837 100644 --- a/src/pip/__init__.py +++ b/src/pip/__init__.py @@ -1,6 +1,6 @@ from typing import List, Optional -__version__ = "21.2" +__version__ = "21.3.dev0" def main(args: Optional[List[str]] = None) -> int: -- cgit v1.2.1 From bd41229cdced10d2b7c304a1ef2d61baad3c7da0 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sun, 25 Jul 2021 12:21:39 +0800 Subject: Bump for release --- src/pip/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/pip/__init__.py b/src/pip/__init__.py index e8bb5c837..7b2bc25ff 100644 --- a/src/pip/__init__.py +++ b/src/pip/__init__.py @@ -1,6 +1,6 @@ from typing import List, Optional -__version__ = "21.3.dev0" +__version__ = "21.2.1" def main(args: Optional[List[str]] = None) -> int: -- cgit v1.2.1 From bddd1bb4bbe790da5178fcf8faa0e16db5157949 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sun, 25 Jul 2021 12:21:39 +0800 Subject: Bump for development --- src/pip/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/pip/__init__.py b/src/pip/__init__.py index 7b2bc25ff..e8bb5c837 100644 --- a/src/pip/__init__.py +++ b/src/pip/__init__.py @@ -1,6 +1,6 @@ from typing import List, Optional -__version__ = "21.2.1" +__version__ = "21.3.dev0" def main(args: Optional[List[str]] = None) -> int: -- cgit v1.2.1 From 20629e467598211827767643df6f14da6bf8e7ed Mon Sep 17 00:00:00 2001 From: Dominic Davis-Foster Date: Mon, 26 Jul 2021 18:11:26 +0100 Subject: Patch tenacity to quote typing.NoReturn --- src/pip/_vendor/tenacity/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') 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 -- cgit v1.2.1 From 02fcce2ed53121594b0ec1a022ce851b019a7c0e Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Tue, 27 Jul 2021 09:27:26 +0800 Subject: Correctly ignore osx_framework_user mismatches The expected 'headers' path is generally: * distutils (old): $PREFIX/include/python3.8/$PROJECT * sysconfig (new): $PREFIX/include/$PROJECT So we should check whether the old value's parent is a pythonX.Y, and the new value's parent matches the old's second-level parent. --- src/pip/_internal/locations/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index ce94c1609..a18ade3f8 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -137,8 +137,8 @@ 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 -- cgit v1.2.1 From c106a0327692fa33b6d820e8b0219cf8247b89e9 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Tue, 27 Jul 2021 10:58:02 +0800 Subject: Kill location warning on Deb and RH system Python --- src/pip/_internal/locations/__init__.py | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'src') diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index ce94c1609..48ce4db31 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -41,6 +41,30 @@ else: _MISMATCH_LEVEL = logging.WARNING +@functools.lru_cache(maxsize=None) +def _looks_like_red_hat_patched() -> 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 as SCHEMES + + return ( + k in SCHEMES + and "lib64" in SCHEMES[k]["platlib"] + and SCHEMES[k]["platlib"].replace("lib64", "lib") == SCHEMES[k]["purelib"] + for k in ("unix_prefix", "unix_home") + ) + + +@functools.lru_cache(maxsize=None) +def _looks_like_debian_patched() -> bool: + """Debian adds two additional schemes.""" + from distutils.command.install import INSTALL_SCHEMES + + return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES + + def _default_base(*, user: bool) -> str: if user: base = sysconfig.get_config_var("userbase") @@ -143,6 +167,24 @@ def get_scheme( if skip_osx_framework_user_special_case: continue + # 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_patched(): + continue + + # 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) + and old_v.parts[1:3] == ("user", "local") + and new_v.parts[1] == "usr" + and new_v.parts[2] != "local" + and (_looks_like_red_hat_patched() or _looks_like_debian_patched()) + ) + if skip_linux_system_special_case: + continue + warned.append(_warn_if_mismatch(old_v, new_v, key=f"scheme.{k}")) if any(warned): -- cgit v1.2.1 From 92921f9a16799b88995105ee3e32984aeddf6358 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Tue, 27 Jul 2021 11:38:57 +0800 Subject: Suppress location warning on abiflag differences --- src/pip/_internal/locations/__init__.py | 54 +++++++++++++++++++++++++++------ src/pip/_internal/locations/base.py | 2 ++ 2 files changed, 47 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index 48ce4db31..bb8e9f8b5 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -4,9 +4,10 @@ import os import pathlib import sys import sysconfig -from typing import List, Optional +from typing import Dict, Iterator, List, Optional, Tuple from pip._internal.models.scheme import SCHEME_KEYS, Scheme +from pip._internal.utils.compat import WINDOWS from . import _distutils, _sysconfig from .base import ( @@ -41,18 +42,25 @@ else: _MISMATCH_LEVEL = logging.WARNING +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 _looks_like_red_hat_patched() -> 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 as SCHEMES + from distutils.command.install import INSTALL_SCHEMES # type: ignore - return ( - k in SCHEMES - and "lib64" in SCHEMES[k]["platlib"] - and SCHEMES[k]["platlib"].replace("lib64", "lib") == SCHEMES[k]["purelib"] + return all( + k in INSTALL_SCHEMES + and _looks_like_red_hat_patched_platlib_purelib(INSTALL_SCHEMES[k]) for k in ("unix_prefix", "unix_home") ) @@ -60,11 +68,27 @@ def _looks_like_red_hat_patched() -> bool: @functools.lru_cache(maxsize=None) def _looks_like_debian_patched() -> bool: """Debian adds two additional schemes.""" - from distutils.command.install import INSTALL_SCHEMES + from distutils.command.install import INSTALL_SCHEMES # type: ignore return "deb_system" in INSTALL_SCHEMES and "unix_local" in INSTALL_SCHEMES +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 + + def _default_base(*, user: bool) -> str: if user: base = sysconfig.get_config_var("userbase") @@ -177,14 +201,26 @@ def get_scheme( # instead of site-packages, but the /usr/local check should cover it. skip_linux_system_special_case = ( not (user or home or prefix) - and old_v.parts[1:3] == ("user", "local") + and old_v.parts[1:3] == ("usr", "local") + and len(new_v.parts) > 1 and new_v.parts[1] == "usr" - and new_v.parts[2] != "local" + and (len(new_v.parts) < 3 or new_v.parts[2] != "local") and (_looks_like_red_hat_patched() or _looks_like_debian_patched()) ) 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 + warned.append(_warn_if_mismatch(old_v, new_v, key=f"scheme.{k}")) if any(warned): 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")) -- cgit v1.2.1 From dac6068d9f44c5ce32a2569b772011e5033c836b Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Tue, 27 Jul 2021 12:20:28 +0800 Subject: Post a deprecation warning for distutils configs Since we can't do anything about them in the transition (CPython is dropping support for those entirely), there's nothing we can do but to tell users to not use them. This also accounts for Homebrew and Linuxbrew for now. Hopefully they will come up with better solutions that don't trigger the location mismatch warning. --- src/pip/_internal/locations/__init__.py | 50 ++++++++++++++++++++++++++----- src/pip/_internal/locations/_distutils.py | 25 +++++++++------- 2 files changed, 57 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index ce94c1609..0edde4277 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -7,6 +7,7 @@ import sysconfig from typing import List, Optional from pip._internal.models.scheme import SCHEME_KEYS, Scheme +from pip._internal.utils.deprecation import deprecated from . import _distutils, _sysconfig from .base import ( @@ -51,9 +52,7 @@ def _default_base(*, user: bool) -> str: @functools.lru_cache(maxsize=None) -def _warn_if_mismatch(old: pathlib.Path, new: pathlib.Path, *, key: str) -> bool: - if old == new: - return False +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 +60,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 @@ -109,12 +114,15 @@ def get_scheme( ) 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)) 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 @@ -143,10 +151,38 @@ def get_scheme( if skip_osx_framework_user_special_case: continue - warned.append(_warn_if_mismatch(old_v, new_v, key=f"scheme.{k}")) + warning_contexts.append((old_v, new_v, f"scheme.{k}")) - if any(warned): - _log_context(user=user, home=home, root=root, prefix=prefix) + 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 diff --git a/src/pip/_internal/locations/_distutils.py b/src/pip/_internal/locations/_distutils.py index 38742d1dd..6d7c09dd0 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 @@ -121,7 +124,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"], -- cgit v1.2.1 From dbf994e1607b60c1c66e768942ac2176bd8382d1 Mon Sep 17 00:00:00 2001 From: Ben Mares <15216687+maresb@users.noreply.github.com> Date: Wed, 28 Jul 2021 00:26:36 -0700 Subject: Add canned explanation about feature preview flag Co-authored-by: Tzu-ping Chung and Pradyun Gedam --- src/pip/_internal/utils/deprecation.py | 63 +++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 20 deletions(-) (limited to 'src') 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) -- cgit v1.2.1 From 1418b5fc046d74d3865688e117c6d0f1bac6f406 Mon Sep 17 00:00:00 2001 From: Ben Mares <15216687+maresb@users.noreply.github.com> Date: Wed, 7 Jul 2021 10:34:06 +0200 Subject: Improve the phrasing of in-tree-build deprecation Context is explained in the following comment: --- src/pip/_internal/operations/prepare.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index 247e63fc8..eddbac26d 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -216,11 +216,12 @@ 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", + "pip currently copies the source tree into a temporary directory " + "before building it. In the future, pip will build packages in-place " + "within the original source tree (\"in-tree build\"). Before the " + "default behavior changes, we recommend testing your packages by " + "adding the --use-feature=in-tree-build argument. Regarding the " + "current out-of-tree default build behavior,\n", replacement=None, gone_in="21.3", issue=7555 -- cgit v1.2.1 From a2252a3b332fa3cf9d8692ef7486e6c78af984e9 Mon Sep 17 00:00:00 2001 From: Ben Mares <15216687+maresb@users.noreply.github.com> Date: Tue, 27 Jul 2021 15:32:31 -0700 Subject: Switch deprecation message to use feature_flag --- src/pip/_internal/operations/prepare.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index eddbac26d..abf275644 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -216,15 +216,13 @@ def unpack_url( # be removed. if link.is_existing_dir(): deprecated( - "pip currently copies the source tree into a temporary directory " - "before building it. In the future, pip will build packages in-place " - "within the original source tree (\"in-tree build\"). Before the " - "default behavior changes, we recommend testing your packages by " - "adding the --use-feature=in-tree-build argument. Regarding the " - "current out-of-tree default build behavior,\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) -- cgit v1.2.1 From 89f6c918066174b62f4e20ef1c6ba79070c673a3 Mon Sep 17 00:00:00 2001 From: Ben Mares <15216687+maresb@users.noreply.github.com> Date: Thu, 29 Jul 2021 15:50:25 -0700 Subject: Improve style for multiline string Co-authored-by: Pradyun Gedam --- src/pip/_internal/operations/prepare.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index abf275644..a57c1116d 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -216,9 +216,11 @@ def unpack_url( # be removed. if link.is_existing_dir(): deprecated( - 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").', + 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", feature_flag="in-tree-build", -- cgit v1.2.1 From e8b5b585d316241a086d1a737804dd2728f15242 Mon Sep 17 00:00:00 2001 From: Ben Mares <15216687+maresb@users.noreply.github.com> Date: Thu, 29 Jul 2021 16:00:17 -0700 Subject: Fix long line --- src/pip/_internal/operations/prepare.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index a57c1116d..8bb5a6843 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -218,7 +218,8 @@ def unpack_url( deprecated( reason=( "pip copied the source tree into a temporary directory " - "before building it. This is changing so that packages are built in-place " + "before building it. This is changing so that packages " + "are built in-place " 'within the original source tree ("in-tree build").' ), replacement=None, -- cgit v1.2.1 From 95fa4e5a2a81bed6575f25609793f898eed6c950 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 30 Jul 2021 19:11:01 +0800 Subject: Respect the base's constraint for extra-ed package --- src/pip/_internal/resolution/resolvelib/provider.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'src') 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, ) -- cgit v1.2.1 From 6a228068fb239aaef4eba3b12a76e906adac8176 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sat, 31 Jul 2021 14:55:43 +0800 Subject: Correctly normalize relative paths for 'pip show' --- src/pip/_internal/commands/show.py | 56 ++++++++++++++++--- src/pip/_internal/metadata/base.py | 20 +++++++ src/pip/_internal/metadata/pkg_resources.py | 4 ++ src/pip/_internal/operations/install/legacy.py | 75 ++++++++++++++------------ 4 files changed, 114 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/commands/show.py b/src/pip/_internal/commands/show.py index fa80254fd..5b2de39e5 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 @@ -66,6 +66,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, @@ -100,14 +127,29 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: 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') 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: @@ -121,11 +163,11 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: 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 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 @@ -48,6 +48,10 @@ class Distribution(BaseDistribution): def location(self) -> Optional[str]: 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 -- cgit v1.2.1 From dcff9d927d2e54b148c1676b1efd64ddf33f20c5 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sat, 31 Jul 2021 22:52:36 +0800 Subject: Detect "the other" Red Hat patch for Cygwin --- src/pip/_internal/locations/__init__.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index be18f21bb..beb438c0e 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -4,11 +4,12 @@ import os import pathlib import sys import sysconfig -from typing import Dict, Iterator, List, Optional, Tuple +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 ( @@ -52,7 +53,7 @@ def _looks_like_red_hat_patched_platlib_purelib(scheme: Dict[str, str]) -> bool: @functools.lru_cache(maxsize=None) -def _looks_like_red_hat_patched() -> bool: +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. @@ -67,13 +68,33 @@ def _looks_like_red_hat_patched() -> bool: @functools.lru_cache(maxsize=None) -def _looks_like_debian_patched() -> bool: +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) @@ -201,19 +222,19 @@ def get_scheme( # 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_patched(): + if k == "platlib" and _looks_like_red_hat_lib(): continue # 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) + 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_patched() or _looks_like_debian_patched()) + and (_looks_like_red_hat_scheme() or _looks_like_debian_scheme()) ) if skip_linux_system_special_case: continue -- cgit v1.2.1 From 87aee20df2890a89f70eecbd7086262146a2e64a Mon Sep 17 00:00:00 2001 From: Sylvain Date: Sun, 1 Aug 2021 11:33:29 +0200 Subject: Fix get_preferred_scheme detection for CPython 3.10 alpha Co-authored-by: Sylvain Desodt Co-authored-by: Tzu-ping Chung --- src/pip/_internal/locations/_sysconfig.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/locations/_sysconfig.py b/src/pip/_internal/locations/_sysconfig.py index 0fc67843f..86dbee7e9 100644 --- a/src/pip/_internal/locations/_sysconfig.py +++ b/src/pip/_internal/locations/_sysconfig.py @@ -24,7 +24,7 @@ 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 _infer_prefix() -> str: @@ -41,8 +41,8 @@ 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 + if _PREFERRED_SCHEME_API: + return _PREFERRED_SCHEME_API("prefix") os_framework_global = is_osx_framework() and not running_under_virtualenv() if os_framework_global and "osx_framework_library" in _AVAILABLE_SCHEMES: return "osx_framework_library" @@ -61,8 +61,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 +76,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 -- cgit v1.2.1 From 0d5798210cf4f40aa01c6ca00f14f86bd580d9e6 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sun, 1 Aug 2021 16:11:54 +0800 Subject: More workaround for Debian's faulty sysconfig --- src/pip/_internal/locations/__init__.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'src') diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index be18f21bb..b15b473f2 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -277,10 +277,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 @@ -290,6 +309,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 -- cgit v1.2.1 From 051de2ff68cc9aa5dc16252de6d12b14fc1bb297 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Sun, 1 Aug 2021 18:00:02 +0800 Subject: Use posix_prefix for Apple's system Python --- src/pip/_internal/locations/_sysconfig.py | 39 +++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/locations/_sysconfig.py b/src/pip/_internal/locations/_sysconfig.py index 86dbee7e9..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 @@ -27,6 +27,32 @@ _AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names()) _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: """Try to find a prefix scheme for the current platform. @@ -43,8 +69,7 @@ def _infer_prefix() -> str: """ if _PREFERRED_SCHEME_API: return _PREFERRED_SCHEME_API("prefix") - os_framework_global = is_osx_framework() and not running_under_virtualenv() - if os_framework_global and "osx_framework_library" in _AVAILABLE_SCHEMES: + if _should_use_osx_framework_prefix(): return "osx_framework_library" implementation_suffixed = f"{sys.implementation.name}_{os.name}" if implementation_suffixed in _AVAILABLE_SCHEMES: @@ -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: -- cgit v1.2.1 From 21fd93df0ec242d436dffe1f74ec6fa3dc8e0831 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 2 Aug 2021 09:36:16 -0700 Subject: Remove Python 2 __nonzero__ method definitions --- src/pip/_internal/exceptions.py | 6 +----- src/pip/_internal/resolution/resolvelib/base.py | 5 +---- src/pip/_internal/resolution/resolvelib/found_candidates.py | 2 -- src/pip/_internal/utils/hashes.py | 6 +----- 4 files changed, 3 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index 8aacf8120..ee0b1da2c 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -189,13 +189,9 @@ class HashErrors(InstallationError): return '\n'.join(lines) return '' - def __nonzero__(self): - # type: () -> bool - return bool(self.errors) - def __bool__(self): # type: () -> bool - return self.__nonzero__() + return bool(self.errors) class HashError(InstallationError): 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/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): -- cgit v1.2.1 From 88703bbceee636578b99f6e8abffbbb8a2410ef1 Mon Sep 17 00:00:00 2001 From: Alex Hedges Date: Mon, 2 Aug 2021 23:58:53 -0400 Subject: Remove extra period in deprecation message --- src/pip/_internal/req/req_install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index 0d93941a3..615535f24 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -836,7 +836,7 @@ 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, -- cgit v1.2.1 From 9bd6eedfc70bbbfd4463f3fefa28f3b707a43093 Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Tue, 3 Aug 2021 20:01:07 -0500 Subject: Update configuration.py --- src/pip/_internal/commands/configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/pip/_internal/commands/configuration.py b/src/pip/_internal/commands/configuration.py index 6e47b8732..a4d6204b6 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. """ -- cgit v1.2.1 From fcadb92b9ea803337d7e304251f90ac3d8b21861 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Thu, 5 Aug 2021 17:03:50 +0800 Subject: Prevent returning relative paths for a location Our own override for the distutils implementation has a bug :) --- src/pip/_internal/locations/__init__.py | 13 +------------ src/pip/_internal/locations/_distutils.py | 13 ++++++++----- 2 files changed, 9 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/locations/__init__.py b/src/pip/_internal/locations/__init__.py index be18f21bb..32bbba2c5 100644 --- a/src/pip/_internal/locations/__init__.py +++ b/src/pip/_internal/locations/__init__.py @@ -90,15 +90,6 @@ def _fix_abiflags(parts: Tuple[str]) -> Iterator[str]: yield part -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 - - @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" @@ -161,11 +152,9 @@ def get_scheme( prefix=prefix, ) - base = prefix or home or _default_base(user=user) 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: diff --git a/src/pip/_internal/locations/_distutils.py b/src/pip/_internal/locations/_distutils.py index 6d7c09dd0..8f2c2f1ef 100644 --- a/src/pip/_internal/locations/_distutils.py +++ b/src/pip/_internal/locations/_distutils.py @@ -81,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()}", @@ -91,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 -- cgit v1.2.1 From a3b186acf8924520b5c80ea9d42ac41cd199d134 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 6 Aug 2021 16:20:59 +0800 Subject: Normalize sys.prefix for old virtualenv virtualenv<14 does not normalize sys.prefix correctly, so we need to do it on our own. --- src/pip/_internal/locations/_distutils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/locations/_distutils.py b/src/pip/_internal/locations/_distutils.py index 8f2c2f1ef..2ec79e65b 100644 --- a/src/pip/_internal/locations/_distutils.py +++ b/src/pip/_internal/locations/_distutils.py @@ -138,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: -- cgit v1.2.1 From fd443b5616c3898c2da34d91f7c8ab97178d684b Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Fri, 6 Aug 2021 17:17:35 +0800 Subject: Black is so inconsistent --- src/pip/_internal/commands/show.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/pip/_internal/commands/show.py b/src/pip/_internal/commands/show.py index 41deecaa4..0c763a308 100644 --- a/src/pip/_internal/commands/show.py +++ b/src/pip/_internal/commands/show.py @@ -146,8 +146,7 @@ def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]: 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 + _covert_legacy_entry(pathlib.Path(p).parts, info_rel.parts) for p in paths ) for query_name in query_names: -- cgit v1.2.1