mirror of
				https://forge.chapril.org/tykayn/orgmode-to-gemini-blog
				synced 2025-10-09 17:02:45 +02:00 
			
		
		
		
	move on index build and linking previous and next articles
This commit is contained in:
		
							parent
							
								
									7d221d970a
								
							
						
					
					
						commit
						16b93f380e
					
				
					 1711 changed files with 231792 additions and 838 deletions
				
			
		|  | @ -0,0 +1,132 @@ | |||
| """ | ||||
| Package containing all pip commands | ||||
| """ | ||||
| 
 | ||||
| import importlib | ||||
| 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") | ||||
| 
 | ||||
| # 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.", | ||||
|     ), | ||||
|     "inspect": CommandInfo( | ||||
|         "pip._internal.commands.inspect", | ||||
|         "InspectCommand", | ||||
|         "Inspect the python environment.", | ||||
|     ), | ||||
|     "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: | ||||
|     """ | ||||
|     Create an instance of the Command class with the given name. | ||||
|     """ | ||||
|     module_path, class_name, summary = commands_dict[name] | ||||
|     module = importlib.import_module(module_path) | ||||
|     command_class = getattr(module, class_name) | ||||
|     command = command_class(name=name, summary=summary, **kwargs) | ||||
| 
 | ||||
|     return command | ||||
| 
 | ||||
| 
 | ||||
| def get_similar_commands(name: str) -> Optional[str]: | ||||
|     """Command name auto-correct.""" | ||||
|     from difflib import get_close_matches | ||||
| 
 | ||||
|     name = name.lower() | ||||
| 
 | ||||
|     close_commands = get_close_matches(name, commands_dict.keys()) | ||||
| 
 | ||||
|     if close_commands: | ||||
|         return close_commands[0] | ||||
|     else: | ||||
|         return None | ||||
|  | @ -0,0 +1,228 @@ | |||
| import os | ||||
| import textwrap | ||||
| from optparse import Values | ||||
| from typing import Any, List | ||||
| 
 | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.status_codes import ERROR, SUCCESS | ||||
| from pip._internal.exceptions import CommandError, PipError | ||||
| from pip._internal.utils import filesystem | ||||
| from pip._internal.utils.logging import getLogger | ||||
| from pip._internal.utils.misc import format_size | ||||
| 
 | ||||
| logger = getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class CacheCommand(Command): | ||||
|     """ | ||||
|     Inspect and manage pip's wheel cache. | ||||
| 
 | ||||
|     Subcommands: | ||||
| 
 | ||||
|     - dir: Show the cache directory. | ||||
|     - info: Show information about the cache. | ||||
|     - list: List filenames of packages stored in the cache. | ||||
|     - remove: Remove one or more package from the cache. | ||||
|     - purge: Remove all items from the cache. | ||||
| 
 | ||||
|     ``<pattern>`` can be a glob expression or a package name. | ||||
|     """ | ||||
| 
 | ||||
|     ignore_require_venv = True | ||||
|     usage = """ | ||||
|         %prog dir | ||||
|         %prog info | ||||
|         %prog list [<pattern>] [--format=[human, abspath]] | ||||
|         %prog remove <pattern> | ||||
|         %prog purge | ||||
|     """ | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option( | ||||
|             "--format", | ||||
|             action="store", | ||||
|             dest="list_format", | ||||
|             default="human", | ||||
|             choices=("human", "abspath"), | ||||
|             help="Select the output format among: human (default) or abspath", | ||||
|         ) | ||||
| 
 | ||||
|         self.parser.insert_option_group(0, self.cmd_opts) | ||||
| 
 | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         handlers = { | ||||
|             "dir": self.get_cache_dir, | ||||
|             "info": self.get_cache_info, | ||||
|             "list": self.list_cache_items, | ||||
|             "remove": self.remove_cache_items, | ||||
|             "purge": self.purge_cache, | ||||
|         } | ||||
| 
 | ||||
|         if not options.cache_dir: | ||||
|             logger.error("pip cache commands can not function since cache is disabled.") | ||||
|             return ERROR | ||||
| 
 | ||||
|         # Determine action | ||||
|         if not args or args[0] not in handlers: | ||||
|             logger.error( | ||||
|                 "Need an action (%s) to perform.", | ||||
|                 ", ".join(sorted(handlers)), | ||||
|             ) | ||||
|             return ERROR | ||||
| 
 | ||||
|         action = args[0] | ||||
| 
 | ||||
|         # Error handling happens here, not in the action-handlers. | ||||
|         try: | ||||
|             handlers[action](options, args[1:]) | ||||
|         except PipError as e: | ||||
|             logger.error(e.args[0]) | ||||
|             return ERROR | ||||
| 
 | ||||
|         return SUCCESS | ||||
| 
 | ||||
|     def get_cache_dir(self, options: Values, args: List[Any]) -> None: | ||||
|         if args: | ||||
|             raise CommandError("Too many arguments") | ||||
| 
 | ||||
|         logger.info(options.cache_dir) | ||||
| 
 | ||||
|     def get_cache_info(self, options: Values, args: List[Any]) -> None: | ||||
|         if args: | ||||
|             raise CommandError("Too many arguments") | ||||
| 
 | ||||
|         num_http_files = len(self._find_http_files(options)) | ||||
|         num_packages = len(self._find_wheels(options, "*")) | ||||
| 
 | ||||
|         http_cache_location = self._cache_dir(options, "http-v2") | ||||
|         old_http_cache_location = self._cache_dir(options, "http") | ||||
|         wheels_cache_location = self._cache_dir(options, "wheels") | ||||
|         http_cache_size = filesystem.format_size( | ||||
|             filesystem.directory_size(http_cache_location) | ||||
|             + filesystem.directory_size(old_http_cache_location) | ||||
|         ) | ||||
|         wheels_cache_size = filesystem.format_directory_size(wheels_cache_location) | ||||
| 
 | ||||
|         message = ( | ||||
|             textwrap.dedent( | ||||
|                 """ | ||||
|                     Package index page cache location (pip v23.3+): {http_cache_location} | ||||
|                     Package index page cache location (older pips): {old_http_cache_location} | ||||
|                     Package index page cache size: {http_cache_size} | ||||
|                     Number of HTTP files: {num_http_files} | ||||
|                     Locally built wheels location: {wheels_cache_location} | ||||
|                     Locally built wheels size: {wheels_cache_size} | ||||
|                     Number of locally built wheels: {package_count} | ||||
|                 """  # noqa: E501 | ||||
|             ) | ||||
|             .format( | ||||
|                 http_cache_location=http_cache_location, | ||||
|                 old_http_cache_location=old_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") | ||||
| 
 | ||||
|         if args: | ||||
|             pattern = args[0] | ||||
|         else: | ||||
|             pattern = "*" | ||||
| 
 | ||||
|         files = self._find_wheels(options, pattern) | ||||
|         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("No locally built wheels 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))) | ||||
| 
 | ||||
|     def format_for_abspath(self, files: List[str]) -> None: | ||||
|         if files: | ||||
|             logger.info("\n".join(sorted(files))) | ||||
| 
 | ||||
|     def remove_cache_items(self, options: Values, args: List[Any]) -> None: | ||||
|         if len(args) > 1: | ||||
|             raise CommandError("Too many arguments") | ||||
| 
 | ||||
|         if not args: | ||||
|             raise CommandError("Please provide a pattern") | ||||
| 
 | ||||
|         files = self._find_wheels(options, args[0]) | ||||
| 
 | ||||
|         no_matching_msg = "No matching packages" | ||||
|         if args[0] == "*": | ||||
|             # Only fetch http files if no specific pattern given | ||||
|             files += self._find_http_files(options) | ||||
|         else: | ||||
|             # Add the pattern to the log message | ||||
|             no_matching_msg += f' for pattern "{args[0]}"' | ||||
| 
 | ||||
|         if not files: | ||||
|             logger.warning(no_matching_msg) | ||||
| 
 | ||||
|         bytes_removed = 0 | ||||
|         for filename in files: | ||||
|             bytes_removed += os.stat(filename).st_size | ||||
|             os.unlink(filename) | ||||
|             logger.verbose("Removed %s", filename) | ||||
|         logger.info("Files removed: %s (%s)", len(files), format_size(bytes_removed)) | ||||
| 
 | ||||
|     def purge_cache(self, options: Values, args: List[Any]) -> None: | ||||
|         if args: | ||||
|             raise CommandError("Too many arguments") | ||||
| 
 | ||||
|         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]: | ||||
|         old_http_dir = self._cache_dir(options, "http") | ||||
|         new_http_dir = self._cache_dir(options, "http-v2") | ||||
|         return filesystem.find_files(old_http_dir, "*") + filesystem.find_files( | ||||
|             new_http_dir, "*" | ||||
|         ) | ||||
| 
 | ||||
|     def _find_wheels(self, options: Values, pattern: str) -> List[str]: | ||||
|         wheel_dir = self._cache_dir(options, "wheels") | ||||
| 
 | ||||
|         # The wheel filename format, as specified in PEP 427, is: | ||||
|         #     {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl | ||||
|         # | ||||
|         # Additionally, non-alphanumeric values in the distribution are | ||||
|         # normalized to underscores (_), meaning hyphens can never occur | ||||
|         # before `-{version}`. | ||||
|         # | ||||
|         # Given that information: | ||||
|         # - If the pattern we're given contains a hyphen (-), the user is | ||||
|         #   providing at least the version. Thus, we can just append `*.whl` | ||||
|         #   to match the rest of it. | ||||
|         # - If the pattern we're given doesn't contain a hyphen (-), the | ||||
|         #   user is only providing the name. Thus, we append `-*.whl` to | ||||
|         #   match the hyphen before the version, followed by anything else. | ||||
|         # | ||||
|         # PEP 427: https://www.python.org/dev/peps/pep-0427/ | ||||
|         pattern = pattern + ("*.whl" if "-" in pattern else "-*.whl") | ||||
| 
 | ||||
|         return filesystem.find_files(wheel_dir, pattern) | ||||
|  | @ -0,0 +1,67 @@ | |||
| import logging | ||||
| from optparse import Values | ||||
| from typing import List | ||||
| 
 | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.status_codes import ERROR, SUCCESS | ||||
| from pip._internal.metadata import get_default_environment | ||||
| from pip._internal.operations.check import ( | ||||
|     check_package_set, | ||||
|     check_unsupported, | ||||
|     create_package_set_from_installed, | ||||
| ) | ||||
| from pip._internal.utils.compatibility_tags import get_supported | ||||
| from pip._internal.utils.misc import write_output | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class CheckCommand(Command): | ||||
|     """Verify installed packages have compatible dependencies.""" | ||||
| 
 | ||||
|     ignore_require_venv = True | ||||
|     usage = """ | ||||
|       %prog [options]""" | ||||
| 
 | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         package_set, parsing_probs = create_package_set_from_installed() | ||||
|         missing, conflicting = check_package_set(package_set) | ||||
|         unsupported = list( | ||||
|             check_unsupported( | ||||
|                 get_default_environment().iter_installed_distributions(), | ||||
|                 get_supported(), | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         for project_name in missing: | ||||
|             version = package_set[project_name].version | ||||
|             for dependency in missing[project_name]: | ||||
|                 write_output( | ||||
|                     "%s %s requires %s, which is not installed.", | ||||
|                     project_name, | ||||
|                     version, | ||||
|                     dependency[0], | ||||
|                 ) | ||||
| 
 | ||||
|         for project_name in conflicting: | ||||
|             version = package_set[project_name].version | ||||
|             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, | ||||
|                 ) | ||||
|         for package in unsupported: | ||||
|             write_output( | ||||
|                 "%s %s is not supported on this platform", | ||||
|                 package.raw_name, | ||||
|                 package.version, | ||||
|             ) | ||||
|         if missing or conflicting or parsing_probs or unsupported: | ||||
|             return ERROR | ||||
|         else: | ||||
|             write_output("No broken requirements found.") | ||||
|             return SUCCESS | ||||
|  | @ -0,0 +1,130 @@ | |||
| import sys | ||||
| import textwrap | ||||
| from optparse import Values | ||||
| from typing import List | ||||
| 
 | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.status_codes import SUCCESS | ||||
| from pip._internal.utils.misc import get_prog | ||||
| 
 | ||||
| BASE_COMPLETION = """ | ||||
| # pip {shell} completion start{script}# pip {shell} completion end | ||||
| """ | ||||
| 
 | ||||
| COMPLETION_SCRIPTS = { | ||||
|     "bash": """ | ||||
|         _pip_completion() | ||||
|         {{ | ||||
|             COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\ | ||||
|                            COMP_CWORD=$COMP_CWORD \\ | ||||
|                            PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) ) | ||||
|         }} | ||||
|         complete -o default -F _pip_completion {prog} | ||||
|     """, | ||||
|     "zsh": """ | ||||
|         #compdef -P pip[0-9.]# | ||||
|         __pip() {{ | ||||
|           compadd $( COMP_WORDS="$words[*]" \\ | ||||
|                      COMP_CWORD=$((CURRENT-1)) \\ | ||||
|                      PIP_AUTO_COMPLETE=1 $words[1] 2>/dev/null ) | ||||
|         }} | ||||
|         if [[ $zsh_eval_context[-1] == loadautofunc ]]; then | ||||
|           # autoload from fpath, call function directly | ||||
|           __pip "$@" | ||||
|         else | ||||
|           # eval/source/. command, register function for later | ||||
|           compdef __pip -P 'pip[0-9.]#' | ||||
|         fi | ||||
|     """, | ||||
|     "fish": """ | ||||
|         function __fish_complete_pip | ||||
|             set -lx COMP_WORDS (commandline -o) "" | ||||
|             set -lx COMP_CWORD ( \\ | ||||
|                 math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\ | ||||
|             ) | ||||
|             set -lx PIP_AUTO_COMPLETE 1 | ||||
|             string split \\  -- (eval $COMP_WORDS[1]) | ||||
|         end | ||||
|         complete -fa "(__fish_complete_pip)" -c {prog} | ||||
|     """, | ||||
|     "powershell": """ | ||||
|         if ((Test-Path Function:\\TabExpansion) -and -not ` | ||||
|             (Test-Path Function:\\_pip_completeBackup)) {{ | ||||
|             Rename-Item Function:\\TabExpansion _pip_completeBackup | ||||
|         }} | ||||
|         function TabExpansion($line, $lastWord) {{ | ||||
|             $lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart() | ||||
|             if ($lastBlock.StartsWith("{prog} ")) {{ | ||||
|                 $Env:COMP_WORDS=$lastBlock | ||||
|                 $Env:COMP_CWORD=$lastBlock.Split().Length - 1 | ||||
|                 $Env:PIP_AUTO_COMPLETE=1 | ||||
|                 (& {prog}).Split() | ||||
|                 Remove-Item Env:COMP_WORDS | ||||
|                 Remove-Item Env:COMP_CWORD | ||||
|                 Remove-Item Env:PIP_AUTO_COMPLETE | ||||
|             }} | ||||
|             elseif (Test-Path Function:\\_pip_completeBackup) {{ | ||||
|                 # Fall back on existing tab expansion | ||||
|                 _pip_completeBackup $line $lastWord | ||||
|             }} | ||||
|         }} | ||||
|     """, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class CompletionCommand(Command): | ||||
|     """A helper command to be used for command completion.""" | ||||
| 
 | ||||
|     ignore_require_venv = True | ||||
| 
 | ||||
|     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", | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "--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", | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "--powershell", | ||||
|             "-p", | ||||
|             action="store_const", | ||||
|             const="powershell", | ||||
|             dest="shell", | ||||
|             help="Emit completion code for powershell", | ||||
|         ) | ||||
| 
 | ||||
|         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)] | ||||
|         if options.shell in shells: | ||||
|             script = textwrap.dedent( | ||||
|                 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)) | ||||
|             ) | ||||
|             return SUCCESS | ||||
|  | @ -0,0 +1,280 @@ | |||
| import logging | ||||
| import os | ||||
| import subprocess | ||||
| from optparse import Values | ||||
| from typing import Any, List, Optional | ||||
| 
 | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.status_codes import ERROR, SUCCESS | ||||
| from pip._internal.configuration import ( | ||||
|     Configuration, | ||||
|     Kind, | ||||
|     get_configuration_files, | ||||
|     kinds, | ||||
| ) | ||||
| from pip._internal.exceptions import PipError | ||||
| from pip._internal.utils.logging import indent_log | ||||
| from pip._internal.utils.misc import get_prog, write_output | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class ConfigurationCommand(Command): | ||||
|     """ | ||||
|     Manage local and global configuration. | ||||
| 
 | ||||
|     Subcommands: | ||||
| 
 | ||||
|     - list: List the active configuration (or from the file specified) | ||||
|     - edit: Edit the configuration file in an editor | ||||
|     - get: Get the value associated with command.option | ||||
|     - set: Set the command.option=value | ||||
|     - unset: Unset the value associated with command.option | ||||
|     - debug: List the configuration files and values defined under them | ||||
| 
 | ||||
|     Configuration keys should be dot separated command and option name, | ||||
|     with the special prefix "global" affecting any command. For example, | ||||
|     "pip config set global.index-url https://example.org/" would configure | ||||
|     the index url for all commands, but "pip config set download.timeout 10" | ||||
|     would configure a 10 second timeout only for "pip download" commands. | ||||
| 
 | ||||
|     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 to the user file by | ||||
|     default. | ||||
|     """ | ||||
| 
 | ||||
|     ignore_require_venv = True | ||||
|     usage = """ | ||||
|         %prog [<file-option>] list | ||||
|         %prog [<file-option>] [--editor <editor-path>] edit | ||||
| 
 | ||||
|         %prog [<file-option>] get command.option | ||||
|         %prog [<file-option>] set command.option value | ||||
|         %prog [<file-option>] unset command.option | ||||
|         %prog [<file-option>] debug | ||||
|     """ | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option( | ||||
|             "--editor", | ||||
|             dest="editor", | ||||
|             action="store", | ||||
|             default=None, | ||||
|             help=( | ||||
|                 "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", | ||||
|             default=False, | ||||
|             help="Use the system-wide configuration file only", | ||||
|         ) | ||||
| 
 | ||||
|         self.cmd_opts.add_option( | ||||
|             "--user", | ||||
|             dest="user_file", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             help="Use the user configuration file only", | ||||
|         ) | ||||
| 
 | ||||
|         self.cmd_opts.add_option( | ||||
|             "--site", | ||||
|             dest="site_file", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             help="Use the current environment configuration file only", | ||||
|         ) | ||||
| 
 | ||||
|         self.parser.insert_option_group(0, self.cmd_opts) | ||||
| 
 | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         handlers = { | ||||
|             "list": self.list_values, | ||||
|             "edit": self.open_in_editor, | ||||
|             "get": self.get_name, | ||||
|             "set": self.set_name_value, | ||||
|             "unset": self.unset_name, | ||||
|             "debug": self.list_config_values, | ||||
|         } | ||||
| 
 | ||||
|         # Determine action | ||||
|         if not args or args[0] not in handlers: | ||||
|             logger.error( | ||||
|                 "Need an action (%s) to perform.", | ||||
|                 ", ".join(sorted(handlers)), | ||||
|             ) | ||||
|             return ERROR | ||||
| 
 | ||||
|         action = args[0] | ||||
| 
 | ||||
|         # Determine which configuration files are to be loaded | ||||
|         #    Depends on whether the command is modifying. | ||||
|         try: | ||||
|             load_only = self._determine_file( | ||||
|                 options, need_value=(action in ["get", "set", "unset", "edit"]) | ||||
|             ) | ||||
|         except PipError as e: | ||||
|             logger.error(e.args[0]) | ||||
|             return ERROR | ||||
| 
 | ||||
|         # Load a new configuration | ||||
|         self.configuration = Configuration( | ||||
|             isolated=options.isolated_mode, load_only=load_only | ||||
|         ) | ||||
|         self.configuration.load() | ||||
| 
 | ||||
|         # Error handling happens here, not in the action-handlers. | ||||
|         try: | ||||
|             handlers[action](options, args[1:]) | ||||
|         except PipError as e: | ||||
|             logger.error(e.args[0]) | ||||
|             return ERROR | ||||
| 
 | ||||
|         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 | ||||
|         ] | ||||
| 
 | ||||
|         if not file_options: | ||||
|             if not need_value: | ||||
|                 return None | ||||
|             # Default to user, unless there's a site file. | ||||
|             elif any( | ||||
|                 os.path.exists(site_config_file) | ||||
|                 for site_config_file in get_configuration_files()[kinds.SITE] | ||||
|             ): | ||||
|                 return kinds.SITE | ||||
|             else: | ||||
|                 return kinds.USER | ||||
|         elif len(file_options) == 1: | ||||
|             return file_options[0] | ||||
| 
 | ||||
|         raise PipError( | ||||
|             "Need exactly one file to operate upon " | ||||
|             "(--user, --site, --global) to perform." | ||||
|         ) | ||||
| 
 | ||||
|     def list_values(self, options: Values, args: List[str]) -> None: | ||||
|         self._get_n_args(args, "list", n=0) | ||||
| 
 | ||||
|         for key, value in sorted(self.configuration.items()): | ||||
|             write_output("%s=%r", key, value) | ||||
| 
 | ||||
|     def get_name(self, options: Values, args: List[str]) -> None: | ||||
|         key = self._get_n_args(args, "get [name]", n=1) | ||||
|         value = self.configuration.get_value(key) | ||||
| 
 | ||||
|         write_output("%s", value) | ||||
| 
 | ||||
|     def set_name_value(self, options: Values, args: List[str]) -> None: | ||||
|         key, value = self._get_n_args(args, "set [name] [value]", n=2) | ||||
|         self.configuration.set_value(key, value) | ||||
| 
 | ||||
|         self._save_configuration() | ||||
| 
 | ||||
|     def unset_name(self, options: Values, args: List[str]) -> None: | ||||
|         key = self._get_n_args(args, "unset [name]", n=1) | ||||
|         self.configuration.unset_value(key) | ||||
| 
 | ||||
|         self._save_configuration() | ||||
| 
 | ||||
|     def list_config_values(self, options: Values, args: List[str]) -> None: | ||||
|         """List config key-value pairs across different config files""" | ||||
|         self._get_n_args(args, "debug", n=0) | ||||
| 
 | ||||
|         self.print_env_var_values() | ||||
|         # Iterate over config files and print if they exist, and the | ||||
|         # key-value pairs present in them if they do | ||||
|         for variant, files in sorted(self.configuration.iter_config_files()): | ||||
|             write_output("%s:", variant) | ||||
|             for fname in files: | ||||
|                 with indent_log(): | ||||
|                     file_exists = os.path.exists(fname) | ||||
|                     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(): | ||||
|             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") | ||||
|         with indent_log(): | ||||
|             for key, value in sorted(self.configuration.get_environ_vars()): | ||||
|                 env_var = f"PIP_{key.upper()}" | ||||
|                 write_output("%s=%r", env_var, value) | ||||
| 
 | ||||
|     def open_in_editor(self, options: Values, args: List[str]) -> None: | ||||
|         editor = self._determine_editor(options) | ||||
| 
 | ||||
|         fname = self.configuration.get_file_to_edit() | ||||
|         if fname is None: | ||||
|             raise PipError("Could not determine appropriate file.") | ||||
|         elif '"' in fname: | ||||
|             # This shouldn't happen, unless we see a username like that. | ||||
|             # If that happens, we'd appreciate a pull request fixing this. | ||||
|             raise PipError( | ||||
|                 f'Can not open an editor for a file name containing "\n{fname}' | ||||
|             ) | ||||
| 
 | ||||
|         try: | ||||
|             subprocess.check_call(f'{editor} "{fname}"', shell=True) | ||||
|         except FileNotFoundError as e: | ||||
|             if not e.filename: | ||||
|                 e.filename = editor | ||||
|             raise | ||||
|         except subprocess.CalledProcessError as e: | ||||
|             raise PipError(f"Editor Subprocess exited with exit code {e.returncode}") | ||||
| 
 | ||||
|     def _get_n_args(self, args: List[str], example: str, n: int) -> Any: | ||||
|         """Helper to make sure the command got the right number of arguments""" | ||||
|         if len(args) != n: | ||||
|             msg = ( | ||||
|                 f"Got unexpected number of arguments, expected {n}. " | ||||
|                 f'(example: "{get_prog()} config {example}")' | ||||
|             ) | ||||
|             raise PipError(msg) | ||||
| 
 | ||||
|         if n == 1: | ||||
|             return args[0] | ||||
|         else: | ||||
|             return args | ||||
| 
 | ||||
|     def _save_configuration(self) -> None: | ||||
|         # We successfully ran a modifying command. Need to save the | ||||
|         # configuration. | ||||
|         try: | ||||
|             self.configuration.save() | ||||
|         except Exception: | ||||
|             logger.exception( | ||||
|                 "Unable to save configuration. Please report this as a bug." | ||||
|             ) | ||||
|             raise PipError("Internal Error.") | ||||
| 
 | ||||
|     def _determine_editor(self, options: Values) -> str: | ||||
|         if options.editor is not None: | ||||
|             return options.editor | ||||
|         elif "VISUAL" in os.environ: | ||||
|             return os.environ["VISUAL"] | ||||
|         elif "EDITOR" in os.environ: | ||||
|             return os.environ["EDITOR"] | ||||
|         else: | ||||
|             raise PipError("Could not determine editor to use.") | ||||
|  | @ -0,0 +1,201 @@ | |||
| import locale | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
| from optparse import Values | ||||
| from types import ModuleType | ||||
| from typing import Any, Dict, List, Optional | ||||
| 
 | ||||
| import pip._vendor | ||||
| from pip._vendor.certifi import where | ||||
| from pip._vendor.packaging.version import parse as parse_version | ||||
| 
 | ||||
| from pip._internal.cli import cmdoptions | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.cmdoptions import make_target_python | ||||
| from pip._internal.cli.status_codes import SUCCESS | ||||
| from pip._internal.configuration import Configuration | ||||
| from pip._internal.metadata import get_environment | ||||
| from pip._internal.utils.compat import open_text_resource | ||||
| from pip._internal.utils.logging import indent_log | ||||
| from pip._internal.utils.misc import get_pip_version | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| def show_value(name: str, value: Any) -> None: | ||||
|     logger.info("%s: %s", name, value) | ||||
| 
 | ||||
| 
 | ||||
| def show_sys_implementation() -> None: | ||||
|     logger.info("sys.implementation:") | ||||
|     implementation_name = sys.implementation.name | ||||
|     with indent_log(): | ||||
|         show_value("name", implementation_name) | ||||
| 
 | ||||
| 
 | ||||
| def create_vendor_txt_map() -> Dict[str, str]: | ||||
|     with open_text_resource("pip._vendor", "vendor.txt") as f: | ||||
|         # Purge non version specifying lines. | ||||
|         # Also, remove any space prefix or suffixes (including comments). | ||||
|         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) | ||||
| 
 | ||||
| 
 | ||||
| def get_module_from_module_name(module_name: str) -> Optional[ModuleType]: | ||||
|     # Module name can be uppercase in vendor.txt for some reason... | ||||
|     module_name = module_name.lower().replace("-", "_") | ||||
|     # PATCH: setuptools is actually only pkg_resources. | ||||
|     if module_name == "setuptools": | ||||
|         module_name = "pkg_resources" | ||||
| 
 | ||||
|     try: | ||||
|         __import__(f"pip._vendor.{module_name}", globals(), locals(), level=0) | ||||
|         return getattr(pip._vendor, module_name) | ||||
|     except ImportError: | ||||
|         # We allow 'truststore' to fail to import due | ||||
|         # to being unavailable on Python 3.9 and earlier. | ||||
|         if module_name == "truststore" and sys.version_info < (3, 10): | ||||
|             return None | ||||
|         raise | ||||
| 
 | ||||
| 
 | ||||
| def get_vendor_version_from_module(module_name: str) -> Optional[str]: | ||||
|     module = get_module_from_module_name(module_name) | ||||
|     version = getattr(module, "__version__", None) | ||||
| 
 | ||||
|     if module and not version: | ||||
|         # Try to find version in debundled module info. | ||||
|         assert module.__file__ is not None | ||||
|         env = get_environment([os.path.dirname(module.__file__)]) | ||||
|         dist = env.get_distribution(module_name) | ||||
|         if dist: | ||||
|             version = str(dist.version) | ||||
| 
 | ||||
|     return version | ||||
| 
 | ||||
| 
 | ||||
| def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None: | ||||
|     """Log the actual version and print extra info if there is | ||||
|     a conflict or if the actual version could not be imported. | ||||
|     """ | ||||
|     for module_name, expected_version in vendor_txt_versions.items(): | ||||
|         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)" | ||||
|             ) | ||||
|             actual_version = expected_version | ||||
|         elif parse_version(actual_version) != parse_version(expected_version): | ||||
|             extra_message = ( | ||||
|                 " (CONFLICT: vendor.txt suggests version should" | ||||
|                 f" be {expected_version})" | ||||
|             ) | ||||
|         logger.info("%s==%s%s", module_name, actual_version, extra_message) | ||||
| 
 | ||||
| 
 | ||||
| def show_vendor_versions() -> None: | ||||
|     logger.info("vendored library versions:") | ||||
| 
 | ||||
|     vendor_txt_versions = create_vendor_txt_map() | ||||
|     with indent_log(): | ||||
|         show_actual_vendor_versions(vendor_txt_versions) | ||||
| 
 | ||||
| 
 | ||||
| def show_tags(options: Values) -> None: | ||||
|     tag_limit = 10 | ||||
| 
 | ||||
|     target_python = make_target_python(options) | ||||
|     tags = target_python.get_sorted_tags() | ||||
| 
 | ||||
|     # Display the target options that were explicitly provided. | ||||
|     formatted_target = target_python.format_given() | ||||
|     suffix = "" | ||||
|     if formatted_target: | ||||
|         suffix = f" (target: {formatted_target})" | ||||
| 
 | ||||
|     msg = f"Compatible tags: {len(tags)}{suffix}" | ||||
|     logger.info(msg) | ||||
| 
 | ||||
|     if options.verbose < 1 and len(tags) > tag_limit: | ||||
|         tags_limited = True | ||||
|         tags = tags[:tag_limit] | ||||
|     else: | ||||
|         tags_limited = False | ||||
| 
 | ||||
|     with indent_log(): | ||||
|         for tag in tags: | ||||
|             logger.info(str(tag)) | ||||
| 
 | ||||
|         if tags_limited: | ||||
|             msg = f"...\n[First {tag_limit} tags shown. Pass --verbose to show all.]" | ||||
|             logger.info(msg) | ||||
| 
 | ||||
| 
 | ||||
| def ca_bundle_info(config: Configuration) -> str: | ||||
|     levels = {key.split(".", 1)[0] for key, _ in config.items()} | ||||
|     if not levels: | ||||
|         return "Not specified" | ||||
| 
 | ||||
|     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" | ||||
| 
 | ||||
|     if "global" in levels: | ||||
|         levels.remove("global") | ||||
|     return ", ".join(levels) | ||||
| 
 | ||||
| 
 | ||||
| class DebugCommand(Command): | ||||
|     """ | ||||
|     Display debug information. | ||||
|     """ | ||||
| 
 | ||||
|     usage = """ | ||||
|       %prog <options>""" | ||||
|     ignore_require_venv = True | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         cmdoptions.add_target_python_options(self.cmd_opts) | ||||
|         self.parser.insert_option_group(0, self.cmd_opts) | ||||
|         self.parser.config.load() | ||||
| 
 | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         logger.warning( | ||||
|             "This command is only meant for debugging. " | ||||
|             "Do not use this with automation for parsing and getting these " | ||||
|             "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( | ||||
|             "locale.getpreferredencoding", | ||||
|             locale.getpreferredencoding(), | ||||
|         ) | ||||
|         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("pip._vendor.certifi.where()", where()) | ||||
|         show_value("pip._vendor.DEBUNDLED", pip._vendor.DEBUNDLED) | ||||
| 
 | ||||
|         show_vendor_versions() | ||||
| 
 | ||||
|         show_tags(options) | ||||
| 
 | ||||
|         return SUCCESS | ||||
|  | @ -0,0 +1,146 @@ | |||
| import logging | ||||
| import os | ||||
| from optparse import Values | ||||
| from typing import List | ||||
| 
 | ||||
| from pip._internal.cli import cmdoptions | ||||
| from pip._internal.cli.cmdoptions import make_target_python | ||||
| from pip._internal.cli.req_command import RequirementCommand, with_cleanup | ||||
| from pip._internal.cli.status_codes import SUCCESS | ||||
| from pip._internal.operations.build.build_tracker import get_build_tracker | ||||
| from pip._internal.req.req_install import check_legacy_setup_py_options | ||||
| from pip._internal.utils.misc import ensure_dir, normalize_path, write_output | ||||
| from pip._internal.utils.temp_dir import TempDirectory | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class DownloadCommand(RequirementCommand): | ||||
|     """ | ||||
|     Download packages from: | ||||
| 
 | ||||
|     - PyPI (and other indexes) using requirement specifiers. | ||||
|     - VCS project urls. | ||||
|     - Local project directories. | ||||
|     - Local or remote source archives. | ||||
| 
 | ||||
|     pip also supports downloading from "requirements files", which provide | ||||
|     an easy way to specify a whole environment to be downloaded. | ||||
|     """ | ||||
| 
 | ||||
|     usage = """ | ||||
|       %prog [options] <requirement specifier> [package-index-options] ... | ||||
|       %prog [options] -r <requirements file> [package-index-options] ... | ||||
|       %prog [options] <vcs project url> ... | ||||
|       %prog [options] <local project path> ... | ||||
|       %prog [options] <archive url/path> ...""" | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option(cmdoptions.constraints()) | ||||
|         self.cmd_opts.add_option(cmdoptions.requirements()) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_deps()) | ||||
|         self.cmd_opts.add_option(cmdoptions.global_options()) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_binary()) | ||||
|         self.cmd_opts.add_option(cmdoptions.only_binary()) | ||||
|         self.cmd_opts.add_option(cmdoptions.prefer_binary()) | ||||
|         self.cmd_opts.add_option(cmdoptions.src()) | ||||
|         self.cmd_opts.add_option(cmdoptions.pre()) | ||||
|         self.cmd_opts.add_option(cmdoptions.require_hashes()) | ||||
|         self.cmd_opts.add_option(cmdoptions.progress_bar()) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_build_isolation()) | ||||
|         self.cmd_opts.add_option(cmdoptions.use_pep517()) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_use_pep517()) | ||||
|         self.cmd_opts.add_option(cmdoptions.check_build_deps()) | ||||
|         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", | ||||
|             default=os.curdir, | ||||
|             help="Download packages into <dir>.", | ||||
|         ) | ||||
| 
 | ||||
|         cmdoptions.add_target_python_options(self.cmd_opts) | ||||
| 
 | ||||
|         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) | ||||
| 
 | ||||
|     @with_cleanup | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         options.ignore_installed = True | ||||
|         # editable doesn't really make sense for `pip download`, but the bowels | ||||
|         # of the RequirementSet code require that property. | ||||
|         options.editables = [] | ||||
| 
 | ||||
|         cmdoptions.check_dist_restriction(options) | ||||
| 
 | ||||
|         options.download_dir = normalize_path(options.download_dir) | ||||
|         ensure_dir(options.download_dir) | ||||
| 
 | ||||
|         session = self.get_default_session(options) | ||||
| 
 | ||||
|         target_python = make_target_python(options) | ||||
|         finder = self._build_package_finder( | ||||
|             options=options, | ||||
|             session=session, | ||||
|             target_python=target_python, | ||||
|             ignore_requires_python=options.ignore_requires_python, | ||||
|         ) | ||||
| 
 | ||||
|         build_tracker = self.enter_context(get_build_tracker()) | ||||
| 
 | ||||
|         directory = TempDirectory( | ||||
|             delete=not options.no_clean, | ||||
|             kind="download", | ||||
|             globally_managed=True, | ||||
|         ) | ||||
| 
 | ||||
|         reqs = self.get_requirements(args, options, finder, session) | ||||
|         check_legacy_setup_py_options(options, reqs) | ||||
| 
 | ||||
|         preparer = self.make_requirement_preparer( | ||||
|             temp_build_dir=directory, | ||||
|             options=options, | ||||
|             build_tracker=build_tracker, | ||||
|             session=session, | ||||
|             finder=finder, | ||||
|             download_dir=options.download_dir, | ||||
|             use_user_site=False, | ||||
|             verbosity=self.verbosity, | ||||
|         ) | ||||
| 
 | ||||
|         resolver = self.make_resolver( | ||||
|             preparer=preparer, | ||||
|             finder=finder, | ||||
|             options=options, | ||||
|             ignore_requires_python=options.ignore_requires_python, | ||||
|             use_pep517=options.use_pep517, | ||||
|             py_version_info=options.python_version, | ||||
|         ) | ||||
| 
 | ||||
|         self.trace_basic_info(finder) | ||||
| 
 | ||||
|         requirement_set = resolver.resolve(reqs, check_supported_wheels=True) | ||||
| 
 | ||||
|         downloaded: List[str] = [] | ||||
|         for req in requirement_set.requirements.values(): | ||||
|             if req.satisfied_by is None: | ||||
|                 assert req.name is not None | ||||
|                 preparer.save_linked_requirement(req) | ||||
|                 downloaded.append(req.name) | ||||
| 
 | ||||
|         preparer.prepare_linked_requirements_more(requirement_set.requirements.values()) | ||||
| 
 | ||||
|         if downloaded: | ||||
|             write_output("Successfully downloaded %s", " ".join(downloaded)) | ||||
| 
 | ||||
|         return SUCCESS | ||||
|  | @ -0,0 +1,109 @@ | |||
| import sys | ||||
| from optparse import Values | ||||
| from typing import AbstractSet, List | ||||
| 
 | ||||
| from pip._internal.cli import cmdoptions | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.status_codes import SUCCESS | ||||
| from pip._internal.operations.freeze import freeze | ||||
| from pip._internal.utils.compat import stdlib_pkgs | ||||
| 
 | ||||
| 
 | ||||
| def _should_suppress_build_backends() -> bool: | ||||
|     return sys.version_info < (3, 12) | ||||
| 
 | ||||
| 
 | ||||
| def _dev_pkgs() -> AbstractSet[str]: | ||||
|     pkgs = {"pip"} | ||||
| 
 | ||||
|     if _should_suppress_build_backends(): | ||||
|         pkgs |= {"setuptools", "distribute", "wheel"} | ||||
| 
 | ||||
|     return pkgs | ||||
| 
 | ||||
| 
 | ||||
| class FreezeCommand(Command): | ||||
|     """ | ||||
|     Output installed packages in requirements format. | ||||
| 
 | ||||
|     packages are listed in a case-insensitive sorted order. | ||||
|     """ | ||||
| 
 | ||||
|     ignore_require_venv = True | ||||
|     usage = """ | ||||
|       %prog [options]""" | ||||
|     log_streams = ("ext://sys.stderr", "ext://sys.stderr") | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option( | ||||
|             "-r", | ||||
|             "--requirement", | ||||
|             dest="requirements", | ||||
|             action="append", | ||||
|             default=[], | ||||
|             metavar="file", | ||||
|             help=( | ||||
|                 "Use the order in the given requirements file and its " | ||||
|                 "comments when generating output. This option can be " | ||||
|                 "used multiple times." | ||||
|             ), | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "-l", | ||||
|             "--local", | ||||
|             dest="local", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             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", | ||||
|             default=False, | ||||
|             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())) | ||||
|             ), | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "--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) | ||||
| 
 | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         skip = set(stdlib_pkgs) | ||||
|         if not options.freeze_all: | ||||
|             skip.update(_dev_pkgs()) | ||||
| 
 | ||||
|         if options.excludes: | ||||
|             skip.update(options.excludes) | ||||
| 
 | ||||
|         cmdoptions.check_list_path_option(options) | ||||
| 
 | ||||
|         for line in freeze( | ||||
|             requirement=options.requirements, | ||||
|             local_only=options.local, | ||||
|             user_only=options.user, | ||||
|             paths=options.path, | ||||
|             isolated=options.isolated_mode, | ||||
|             skip=skip, | ||||
|             exclude_editable=options.exclude_editable, | ||||
|         ): | ||||
|             sys.stdout.write(line + "\n") | ||||
|         return SUCCESS | ||||
|  | @ -0,0 +1,59 @@ | |||
| import hashlib | ||||
| import logging | ||||
| import sys | ||||
| from optparse import Values | ||||
| from typing import List | ||||
| 
 | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.status_codes import ERROR, SUCCESS | ||||
| from pip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES | ||||
| from pip._internal.utils.misc import read_chunks, write_output | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class HashCommand(Command): | ||||
|     """ | ||||
|     Compute a hash of a local package archive. | ||||
| 
 | ||||
|     These can be used with --hash in a requirements file to do repeatable | ||||
|     installs. | ||||
|     """ | ||||
| 
 | ||||
|     usage = "%prog [options] <file> ..." | ||||
|     ignore_require_venv = True | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option( | ||||
|             "-a", | ||||
|             "--algorithm", | ||||
|             dest="algorithm", | ||||
|             choices=STRONG_HASHES, | ||||
|             action="store", | ||||
|             default=FAVORITE_HASH, | ||||
|             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: | ||||
|         if not args: | ||||
|             self.parser.print_usage(sys.stderr) | ||||
|             return ERROR | ||||
| 
 | ||||
|         algorithm = options.algorithm | ||||
|         for path in args: | ||||
|             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: | ||||
|         hash = hashlib.new(algorithm) | ||||
|         for chunk in read_chunks(archive): | ||||
|             hash.update(chunk) | ||||
|     return hash.hexdigest() | ||||
|  | @ -0,0 +1,41 @@ | |||
| from optparse import Values | ||||
| from typing import List | ||||
| 
 | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.status_codes import SUCCESS | ||||
| from pip._internal.exceptions import CommandError | ||||
| 
 | ||||
| 
 | ||||
| class HelpCommand(Command): | ||||
|     """Show help for commands""" | ||||
| 
 | ||||
|     usage = """ | ||||
|       %prog <command>""" | ||||
|     ignore_require_venv = True | ||||
| 
 | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         from pip._internal.commands import ( | ||||
|             commands_dict, | ||||
|             create_command, | ||||
|             get_similar_commands, | ||||
|         ) | ||||
| 
 | ||||
|         try: | ||||
|             # 'pip help' with no args is handled by pip.__init__.parseopt() | ||||
|             cmd_name = args[0]  # the command we need help for | ||||
|         except IndexError: | ||||
|             return SUCCESS | ||||
| 
 | ||||
|         if cmd_name not in commands_dict: | ||||
|             guess = get_similar_commands(cmd_name) | ||||
| 
 | ||||
|             msg = [f'unknown command "{cmd_name}"'] | ||||
|             if guess: | ||||
|                 msg.append(f'maybe you meant "{guess}"') | ||||
| 
 | ||||
|             raise CommandError(" - ".join(msg)) | ||||
| 
 | ||||
|         command = create_command(cmd_name) | ||||
|         command.parser.print_help() | ||||
| 
 | ||||
|         return SUCCESS | ||||
|  | @ -0,0 +1,139 @@ | |||
| import logging | ||||
| from optparse import Values | ||||
| from typing import Any, Iterable, List, Optional | ||||
| 
 | ||||
| from pip._vendor.packaging.version import Version | ||||
| 
 | ||||
| from pip._internal.cli import cmdoptions | ||||
| from pip._internal.cli.req_command import IndexGroupCommand | ||||
| from pip._internal.cli.status_codes import ERROR, SUCCESS | ||||
| from pip._internal.commands.search import print_dist_installation_info | ||||
| from pip._internal.exceptions import CommandError, DistributionNotFound, PipError | ||||
| from pip._internal.index.collector import LinkCollector | ||||
| from pip._internal.index.package_finder import PackageFinder | ||||
| from pip._internal.models.selection_prefs import SelectionPreferences | ||||
| from pip._internal.models.target_python import TargetPython | ||||
| from pip._internal.network.session import PipSession | ||||
| from pip._internal.utils.misc import write_output | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class IndexCommand(IndexGroupCommand): | ||||
|     """ | ||||
|     Inspect information available from package indexes. | ||||
|     """ | ||||
| 
 | ||||
|     ignore_require_venv = True | ||||
|     usage = """ | ||||
|         %prog versions <package> | ||||
|     """ | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         cmdoptions.add_target_python_options(self.cmd_opts) | ||||
| 
 | ||||
|         self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) | ||||
|         self.cmd_opts.add_option(cmdoptions.pre()) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_binary()) | ||||
|         self.cmd_opts.add_option(cmdoptions.only_binary()) | ||||
| 
 | ||||
|         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) | ||||
| 
 | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         handlers = { | ||||
|             "versions": self.get_available_package_versions, | ||||
|         } | ||||
| 
 | ||||
|         logger.warning( | ||||
|             "pip index is currently an experimental command. " | ||||
|             "It may be removed/changed in a future release " | ||||
|             "without prior warning." | ||||
|         ) | ||||
| 
 | ||||
|         # Determine action | ||||
|         if not args or args[0] not in handlers: | ||||
|             logger.error( | ||||
|                 "Need an action (%s) to perform.", | ||||
|                 ", ".join(sorted(handlers)), | ||||
|             ) | ||||
|             return ERROR | ||||
| 
 | ||||
|         action = args[0] | ||||
| 
 | ||||
|         # Error handling happens here, not in the action-handlers. | ||||
|         try: | ||||
|             handlers[action](options, args[1:]) | ||||
|         except PipError as e: | ||||
|             logger.error(e.args[0]) | ||||
|             return ERROR | ||||
| 
 | ||||
|         return SUCCESS | ||||
| 
 | ||||
|     def _build_package_finder( | ||||
|         self, | ||||
|         options: Values, | ||||
|         session: PipSession, | ||||
|         target_python: Optional[TargetPython] = None, | ||||
|         ignore_requires_python: Optional[bool] = None, | ||||
|     ) -> PackageFinder: | ||||
|         """ | ||||
|         Create a package finder appropriate to the index command. | ||||
|         """ | ||||
|         link_collector = LinkCollector.create(session, options=options) | ||||
| 
 | ||||
|         # Pass allow_yanked=False to ignore yanked versions. | ||||
|         selection_prefs = SelectionPreferences( | ||||
|             allow_yanked=False, | ||||
|             allow_all_prereleases=options.pre, | ||||
|             ignore_requires_python=ignore_requires_python, | ||||
|         ) | ||||
| 
 | ||||
|         return PackageFinder.create( | ||||
|             link_collector=link_collector, | ||||
|             selection_prefs=selection_prefs, | ||||
|             target_python=target_python, | ||||
|         ) | ||||
| 
 | ||||
|     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") | ||||
| 
 | ||||
|         target_python = cmdoptions.make_target_python(options) | ||||
|         query = args[0] | ||||
| 
 | ||||
|         with self._build_session(options) as session: | ||||
|             finder = self._build_package_finder( | ||||
|                 options=options, | ||||
|                 session=session, | ||||
|                 target_python=target_python, | ||||
|                 ignore_requires_python=options.ignore_requires_python, | ||||
|             ) | ||||
| 
 | ||||
|             versions: Iterable[Version] = ( | ||||
|                 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 = set(versions) | ||||
| 
 | ||||
|             if not versions: | ||||
|                 raise DistributionNotFound( | ||||
|                     f"No matching distribution found for {query}" | ||||
|                 ) | ||||
| 
 | ||||
|             formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)] | ||||
|             latest = formatted_versions[0] | ||||
| 
 | ||||
|         write_output(f"{query} ({latest})") | ||||
|         write_output("Available versions: {}".format(", ".join(formatted_versions))) | ||||
|         print_dist_installation_info(query, latest) | ||||
|  | @ -0,0 +1,92 @@ | |||
| import logging | ||||
| from optparse import Values | ||||
| from typing import Any, Dict, List | ||||
| 
 | ||||
| from pip._vendor.packaging.markers import default_environment | ||||
| from pip._vendor.rich import print_json | ||||
| 
 | ||||
| from pip import __version__ | ||||
| from pip._internal.cli import cmdoptions | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.status_codes import SUCCESS | ||||
| from pip._internal.metadata import BaseDistribution, get_environment | ||||
| from pip._internal.utils.compat import stdlib_pkgs | ||||
| from pip._internal.utils.urls import path_to_url | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class InspectCommand(Command): | ||||
|     """ | ||||
|     Inspect the content of a Python environment and produce a report in JSON format. | ||||
|     """ | ||||
| 
 | ||||
|     ignore_require_venv = True | ||||
|     usage = """ | ||||
|       %prog [options]""" | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option( | ||||
|             "--local", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             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", | ||||
|             default=False, | ||||
|             help="Only output packages installed in user-site.", | ||||
|         ) | ||||
|         self.cmd_opts.add_option(cmdoptions.list_path()) | ||||
|         self.parser.insert_option_group(0, self.cmd_opts) | ||||
| 
 | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         cmdoptions.check_list_path_option(options) | ||||
|         dists = get_environment(options.path).iter_installed_distributions( | ||||
|             local_only=options.local, | ||||
|             user_only=options.user, | ||||
|             skip=set(stdlib_pkgs), | ||||
|         ) | ||||
|         output = { | ||||
|             "version": "1", | ||||
|             "pip_version": __version__, | ||||
|             "installed": [self._dist_to_dict(dist) for dist in dists], | ||||
|             "environment": default_environment(), | ||||
|             # TODO tags? scheme? | ||||
|         } | ||||
|         print_json(data=output) | ||||
|         return SUCCESS | ||||
| 
 | ||||
|     def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]: | ||||
|         res: Dict[str, Any] = { | ||||
|             "metadata": dist.metadata_dict, | ||||
|             "metadata_location": dist.info_location, | ||||
|         } | ||||
|         # direct_url. Note that we don't have download_info (as in the installation | ||||
|         # report) since it is not recorded in installed metadata. | ||||
|         direct_url = dist.direct_url | ||||
|         if direct_url is not None: | ||||
|             res["direct_url"] = direct_url.to_dict() | ||||
|         else: | ||||
|             # Emulate direct_url for legacy editable installs. | ||||
|             editable_project_location = dist.editable_project_location | ||||
|             if editable_project_location is not None: | ||||
|                 res["direct_url"] = { | ||||
|                     "url": path_to_url(editable_project_location), | ||||
|                     "dir_info": { | ||||
|                         "editable": True, | ||||
|                     }, | ||||
|                 } | ||||
|         # installer | ||||
|         installer = dist.installer | ||||
|         if dist.installer: | ||||
|             res["installer"] = installer | ||||
|         # requested | ||||
|         if dist.installed_with_dist_info: | ||||
|             res["requested"] = dist.requested | ||||
|         return res | ||||
|  | @ -0,0 +1,784 @@ | |||
| import errno | ||||
| import json | ||||
| import operator | ||||
| import os | ||||
| import shutil | ||||
| import site | ||||
| from optparse import SUPPRESS_HELP, Values | ||||
| from typing import List, Optional | ||||
| 
 | ||||
| from pip._vendor.packaging.utils import canonicalize_name | ||||
| from pip._vendor.rich import print_json | ||||
| 
 | ||||
| # Eagerly import self_outdated_check to avoid crashes. Otherwise, | ||||
| # this module would be imported *after* pip was replaced, resulting | ||||
| # in crashes if the new self_outdated_check module was incompatible | ||||
| # with the rest of pip that's already imported, or allowing a | ||||
| # wheel to execute arbitrary code on install by replacing | ||||
| # self_outdated_check. | ||||
| import pip._internal.self_outdated_check  # noqa: F401 | ||||
| from pip._internal.cache import WheelCache | ||||
| from pip._internal.cli import cmdoptions | ||||
| from pip._internal.cli.cmdoptions import make_target_python | ||||
| from pip._internal.cli.req_command import ( | ||||
|     RequirementCommand, | ||||
|     with_cleanup, | ||||
| ) | ||||
| from pip._internal.cli.status_codes import ERROR, SUCCESS | ||||
| from pip._internal.exceptions import CommandError, InstallationError | ||||
| from pip._internal.locations import get_scheme | ||||
| from pip._internal.metadata import get_environment | ||||
| from pip._internal.models.installation_report import InstallationReport | ||||
| from pip._internal.operations.build.build_tracker import get_build_tracker | ||||
| from pip._internal.operations.check import ConflictDetails, check_install_conflicts | ||||
| from pip._internal.req import install_given_reqs | ||||
| from pip._internal.req.req_install import ( | ||||
|     InstallRequirement, | ||||
|     check_legacy_setup_py_options, | ||||
| ) | ||||
| from pip._internal.utils.compat import WINDOWS | ||||
| from pip._internal.utils.filesystem import test_writable_dir | ||||
| from pip._internal.utils.logging import getLogger | ||||
| from pip._internal.utils.misc import ( | ||||
|     check_externally_managed, | ||||
|     ensure_dir, | ||||
|     get_pip_version, | ||||
|     protect_pip_from_modification_on_windows, | ||||
|     warn_if_run_as_root, | ||||
|     write_output, | ||||
| ) | ||||
| from pip._internal.utils.temp_dir import TempDirectory | ||||
| from pip._internal.utils.virtualenv import ( | ||||
|     running_under_virtualenv, | ||||
|     virtualenv_no_global, | ||||
| ) | ||||
| from pip._internal.wheel_builder import build, should_build_for_install_command | ||||
| 
 | ||||
| logger = getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class InstallCommand(RequirementCommand): | ||||
|     """ | ||||
|     Install packages from: | ||||
| 
 | ||||
|     - PyPI (and other indexes) using requirement specifiers. | ||||
|     - VCS project urls. | ||||
|     - Local project directories. | ||||
|     - Local or remote source archives. | ||||
| 
 | ||||
|     pip also supports installing from "requirements files", which provide | ||||
|     an easy way to specify a whole environment to be installed. | ||||
|     """ | ||||
| 
 | ||||
|     usage = """ | ||||
|       %prog [options] <requirement specifier> [package-index-options] ... | ||||
|       %prog [options] -r <requirements file> [package-index-options] ... | ||||
|       %prog [options] [-e] <vcs project url> ... | ||||
|       %prog [options] [-e] <local project path> ... | ||||
|       %prog [options] <archive url/path> ...""" | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option(cmdoptions.requirements()) | ||||
|         self.cmd_opts.add_option(cmdoptions.constraints()) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_deps()) | ||||
|         self.cmd_opts.add_option(cmdoptions.pre()) | ||||
| 
 | ||||
|         self.cmd_opts.add_option(cmdoptions.editable()) | ||||
|         self.cmd_opts.add_option( | ||||
|             "--dry-run", | ||||
|             action="store_true", | ||||
|             dest="dry_run", | ||||
|             default=False, | ||||
|             help=( | ||||
|                 "Don't actually install anything, just print what would be. " | ||||
|                 "Can be used in combination with --ignore-installed " | ||||
|                 "to 'resolve' the requirements." | ||||
|             ), | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "-t", | ||||
|             "--target", | ||||
|             dest="target_dir", | ||||
|             metavar="dir", | ||||
|             default=None, | ||||
|             help=( | ||||
|                 "Install packages into <dir>. " | ||||
|                 "By default this will not replace existing files/folders in " | ||||
|                 "<dir>. Use --upgrade to replace existing packages in <dir> " | ||||
|                 "with new versions." | ||||
|             ), | ||||
|         ) | ||||
|         cmdoptions.add_target_python_options(self.cmd_opts) | ||||
| 
 | ||||
|         self.cmd_opts.add_option( | ||||
|             "--user", | ||||
|             dest="use_user_site", | ||||
|             action="store_true", | ||||
|             help=( | ||||
|                 "Install to the Python user install directory for your " | ||||
|                 "platform. Typically ~/.local/, or %APPDATA%\\Python on " | ||||
|                 "Windows. (See the Python documentation for site.USER_BASE " | ||||
|                 "for full details.)" | ||||
|             ), | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "--no-user", | ||||
|             dest="use_user_site", | ||||
|             action="store_false", | ||||
|             help=SUPPRESS_HELP, | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "--root", | ||||
|             dest="root_path", | ||||
|             metavar="dir", | ||||
|             default=None, | ||||
|             help="Install everything relative to this alternate root directory.", | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "--prefix", | ||||
|             dest="prefix_path", | ||||
|             metavar="dir", | ||||
|             default=None, | ||||
|             help=( | ||||
|                 "Installation prefix where lib, bin and other top-level " | ||||
|                 "folders are placed. Note that the resulting installation may " | ||||
|                 "contain scripts and other resources which reference the " | ||||
|                 "Python interpreter of pip, and not that of ``--prefix``. " | ||||
|                 "See also the ``--python`` option if the intention is to " | ||||
|                 "install packages into another (possibly pip-free) " | ||||
|                 "environment." | ||||
|             ), | ||||
|         ) | ||||
| 
 | ||||
|         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." | ||||
|             ), | ||||
|         ) | ||||
| 
 | ||||
|         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)." | ||||
|             ), | ||||
|         ) | ||||
| 
 | ||||
|         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.", | ||||
|         ) | ||||
| 
 | ||||
|         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!" | ||||
|             ), | ||||
|         ) | ||||
| 
 | ||||
|         self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_build_isolation()) | ||||
|         self.cmd_opts.add_option(cmdoptions.use_pep517()) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_use_pep517()) | ||||
|         self.cmd_opts.add_option(cmdoptions.check_build_deps()) | ||||
|         self.cmd_opts.add_option(cmdoptions.override_externally_managed()) | ||||
| 
 | ||||
|         self.cmd_opts.add_option(cmdoptions.config_settings()) | ||||
|         self.cmd_opts.add_option(cmdoptions.global_options()) | ||||
| 
 | ||||
|         self.cmd_opts.add_option( | ||||
|             "--compile", | ||||
|             action="store_true", | ||||
|             dest="compile", | ||||
|             default=True, | ||||
|             help="Compile Python source files to bytecode", | ||||
|         ) | ||||
| 
 | ||||
|         self.cmd_opts.add_option( | ||||
|             "--no-compile", | ||||
|             action="store_false", | ||||
|             dest="compile", | ||||
|             help="Do not compile Python source files to bytecode", | ||||
|         ) | ||||
| 
 | ||||
|         self.cmd_opts.add_option( | ||||
|             "--no-warn-script-location", | ||||
|             action="store_false", | ||||
|             dest="warn_script_location", | ||||
|             default=True, | ||||
|             help="Do not warn when installing scripts outside PATH", | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "--no-warn-conflicts", | ||||
|             action="store_false", | ||||
|             dest="warn_about_conflicts", | ||||
|             default=True, | ||||
|             help="Do not warn about broken dependencies", | ||||
|         ) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_binary()) | ||||
|         self.cmd_opts.add_option(cmdoptions.only_binary()) | ||||
|         self.cmd_opts.add_option(cmdoptions.prefer_binary()) | ||||
|         self.cmd_opts.add_option(cmdoptions.require_hashes()) | ||||
|         self.cmd_opts.add_option(cmdoptions.progress_bar()) | ||||
|         self.cmd_opts.add_option(cmdoptions.root_user_action()) | ||||
| 
 | ||||
|         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) | ||||
| 
 | ||||
|         self.cmd_opts.add_option( | ||||
|             "--report", | ||||
|             dest="json_report_file", | ||||
|             metavar="file", | ||||
|             default=None, | ||||
|             help=( | ||||
|                 "Generate a JSON file describing what pip did to install " | ||||
|                 "the provided requirements. " | ||||
|                 "Can be used in combination with --dry-run and --ignore-installed " | ||||
|                 "to 'resolve' the requirements. " | ||||
|                 "When - is used as file name it writes to stdout. " | ||||
|                 "When writing to stdout, please combine with the --quiet option " | ||||
|                 "to avoid mixing pip logging output with JSON output." | ||||
|             ), | ||||
|         ) | ||||
| 
 | ||||
|     @with_cleanup | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         if options.use_user_site and options.target_dir is not None: | ||||
|             raise CommandError("Can not combine '--user' and '--target'") | ||||
| 
 | ||||
|         # Check whether the environment we're installing into is externally | ||||
|         # managed, as specified in PEP 668. Specifying --root, --target, or | ||||
|         # --prefix disables the check, since there's no reliable way to locate | ||||
|         # the EXTERNALLY-MANAGED file for those cases. An exception is also | ||||
|         # made specifically for "--dry-run --report" for convenience. | ||||
|         installing_into_current_environment = ( | ||||
|             not (options.dry_run and options.json_report_file) | ||||
|             and options.root_path is None | ||||
|             and options.target_dir is None | ||||
|             and options.prefix_path is None | ||||
|         ) | ||||
|         if ( | ||||
|             installing_into_current_environment | ||||
|             and not options.override_externally_managed | ||||
|         ): | ||||
|             check_externally_managed() | ||||
| 
 | ||||
|         upgrade_strategy = "to-satisfy-only" | ||||
|         if options.upgrade: | ||||
|             upgrade_strategy = options.upgrade_strategy | ||||
| 
 | ||||
|         cmdoptions.check_dist_restriction(options, check_target=True) | ||||
| 
 | ||||
|         logger.verbose("Using %s", get_pip_version()) | ||||
|         options.use_user_site = decide_user_install( | ||||
|             options.use_user_site, | ||||
|             prefix_path=options.prefix_path, | ||||
|             target_dir=options.target_dir, | ||||
|             root_path=options.root_path, | ||||
|             isolated_mode=options.isolated_mode, | ||||
|         ) | ||||
| 
 | ||||
|         target_temp_dir: Optional[TempDirectory] = None | ||||
|         target_temp_dir_path: Optional[str] = None | ||||
|         if options.target_dir: | ||||
|             options.ignore_installed = True | ||||
|             options.target_dir = os.path.abspath(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." | ||||
|                 ) | ||||
| 
 | ||||
|             # Create a target directory for using with the target option | ||||
|             target_temp_dir = TempDirectory(kind="target") | ||||
|             target_temp_dir_path = target_temp_dir.path | ||||
|             self.enter_context(target_temp_dir) | ||||
| 
 | ||||
|         global_options = options.global_options or [] | ||||
| 
 | ||||
|         session = self.get_default_session(options) | ||||
| 
 | ||||
|         target_python = make_target_python(options) | ||||
|         finder = self._build_package_finder( | ||||
|             options=options, | ||||
|             session=session, | ||||
|             target_python=target_python, | ||||
|             ignore_requires_python=options.ignore_requires_python, | ||||
|         ) | ||||
|         build_tracker = self.enter_context(get_build_tracker()) | ||||
| 
 | ||||
|         directory = TempDirectory( | ||||
|             delete=not options.no_clean, | ||||
|             kind="install", | ||||
|             globally_managed=True, | ||||
|         ) | ||||
| 
 | ||||
|         try: | ||||
|             reqs = self.get_requirements(args, options, finder, session) | ||||
|             check_legacy_setup_py_options(options, reqs) | ||||
| 
 | ||||
|             wheel_cache = WheelCache(options.cache_dir) | ||||
| 
 | ||||
|             # Only when installing is it permitted to use PEP 660. | ||||
|             # In other circumstances (pip wheel, pip download) we generate | ||||
|             # regular (i.e. non editable) metadata and wheels. | ||||
|             for req in reqs: | ||||
|                 req.permit_editable_wheels = True | ||||
| 
 | ||||
|             preparer = self.make_requirement_preparer( | ||||
|                 temp_build_dir=directory, | ||||
|                 options=options, | ||||
|                 build_tracker=build_tracker, | ||||
|                 session=session, | ||||
|                 finder=finder, | ||||
|                 use_user_site=options.use_user_site, | ||||
|                 verbosity=self.verbosity, | ||||
|             ) | ||||
|             resolver = self.make_resolver( | ||||
|                 preparer=preparer, | ||||
|                 finder=finder, | ||||
|                 options=options, | ||||
|                 wheel_cache=wheel_cache, | ||||
|                 use_user_site=options.use_user_site, | ||||
|                 ignore_installed=options.ignore_installed, | ||||
|                 ignore_requires_python=options.ignore_requires_python, | ||||
|                 force_reinstall=options.force_reinstall, | ||||
|                 upgrade_strategy=upgrade_strategy, | ||||
|                 use_pep517=options.use_pep517, | ||||
|                 py_version_info=options.python_version, | ||||
|             ) | ||||
| 
 | ||||
|             self.trace_basic_info(finder) | ||||
| 
 | ||||
|             requirement_set = resolver.resolve( | ||||
|                 reqs, check_supported_wheels=not options.target_dir | ||||
|             ) | ||||
| 
 | ||||
|             if options.json_report_file: | ||||
|                 report = InstallationReport(requirement_set.requirements_to_install) | ||||
|                 if options.json_report_file == "-": | ||||
|                     print_json(data=report.to_dict()) | ||||
|                 else: | ||||
|                     with open(options.json_report_file, "w", encoding="utf-8") as f: | ||||
|                         json.dump(report.to_dict(), f, indent=2, ensure_ascii=False) | ||||
| 
 | ||||
|             if options.dry_run: | ||||
|                 would_install_items = sorted( | ||||
|                     (r.metadata["name"], r.metadata["version"]) | ||||
|                     for r in requirement_set.requirements_to_install | ||||
|                 ) | ||||
|                 if would_install_items: | ||||
|                     write_output( | ||||
|                         "Would install %s", | ||||
|                         " ".join("-".join(item) for item in would_install_items), | ||||
|                     ) | ||||
|                 return SUCCESS | ||||
| 
 | ||||
|             try: | ||||
|                 pip_req = requirement_set.get_requirement("pip") | ||||
|             except KeyError: | ||||
|                 modifying_pip = False | ||||
|             else: | ||||
|                 # 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) | ||||
| 
 | ||||
|             reqs_to_build = [ | ||||
|                 r | ||||
|                 for r in requirement_set.requirements.values() | ||||
|                 if should_build_for_install_command(r) | ||||
|             ] | ||||
| 
 | ||||
|             _, build_failures = build( | ||||
|                 reqs_to_build, | ||||
|                 wheel_cache=wheel_cache, | ||||
|                 verify=True, | ||||
|                 build_options=[], | ||||
|                 global_options=global_options, | ||||
|             ) | ||||
| 
 | ||||
|             if build_failures: | ||||
|                 raise InstallationError( | ||||
|                     "Failed to build installable wheels for some " | ||||
|                     "pyproject.toml based projects ({})".format( | ||||
|                         ", ".join(r.name for r in build_failures)  # type: ignore | ||||
|                     ) | ||||
|                 ) | ||||
| 
 | ||||
|             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 | ||||
|             ) | ||||
|             if should_warn_about_conflicts: | ||||
|                 conflicts = self._determine_conflicts(to_install) | ||||
| 
 | ||||
|             # Don't warn about script install locations if | ||||
|             # --target or --prefix has been specified | ||||
|             warn_script_location = options.warn_script_location | ||||
|             if options.target_dir or options.prefix_path: | ||||
|                 warn_script_location = False | ||||
| 
 | ||||
|             installed = install_given_reqs( | ||||
|                 to_install, | ||||
|                 global_options, | ||||
|                 root=options.root_path, | ||||
|                 home=target_temp_dir_path, | ||||
|                 prefix=options.prefix_path, | ||||
|                 warn_script_location=warn_script_location, | ||||
|                 use_user_site=options.use_user_site, | ||||
|                 pycompile=options.compile, | ||||
|             ) | ||||
| 
 | ||||
|             lib_locations = get_lib_location_guesses( | ||||
|                 user=options.use_user_site, | ||||
|                 home=target_temp_dir_path, | ||||
|                 root=options.root_path, | ||||
|                 prefix=options.prefix_path, | ||||
|                 isolated=options.isolated_mode, | ||||
|             ) | ||||
|             env = get_environment(lib_locations) | ||||
| 
 | ||||
|             # Display a summary of installed packages, with extra care to | ||||
|             # display a package name as it was requested by the user. | ||||
|             installed.sort(key=operator.attrgetter("name")) | ||||
|             summary = [] | ||||
|             installed_versions = {} | ||||
|             for distribution in env.iter_all_distributions(): | ||||
|                 installed_versions[distribution.canonical_name] = distribution.version | ||||
|             for package in installed: | ||||
|                 display_name = package.name | ||||
|                 version = installed_versions.get(canonicalize_name(display_name), None) | ||||
|                 if version: | ||||
|                     text = f"{display_name}-{version}" | ||||
|                 else: | ||||
|                     text = display_name | ||||
|                 summary.append(text) | ||||
| 
 | ||||
|             if conflicts is not None: | ||||
|                 self._warn_about_conflicts( | ||||
|                     conflicts, | ||||
|                     resolver_variant=self.determine_resolver_variant(options), | ||||
|                 ) | ||||
| 
 | ||||
|             installed_desc = " ".join(summary) | ||||
|             if installed_desc: | ||||
|                 write_output( | ||||
|                     "Successfully installed %s", | ||||
|                     installed_desc, | ||||
|                 ) | ||||
|         except OSError as error: | ||||
|             show_traceback = self.verbosity >= 1 | ||||
| 
 | ||||
|             message = create_os_error_message( | ||||
|                 error, | ||||
|                 show_traceback, | ||||
|                 options.use_user_site, | ||||
|             ) | ||||
|             logger.error(message, exc_info=show_traceback) | ||||
| 
 | ||||
|             return ERROR | ||||
| 
 | ||||
|         if options.target_dir: | ||||
|             assert target_temp_dir | ||||
|             self._handle_target_dir( | ||||
|                 options.target_dir, target_temp_dir, options.upgrade | ||||
|             ) | ||||
|         if options.root_user_action == "warn": | ||||
|             warn_if_run_as_root() | ||||
|         return SUCCESS | ||||
| 
 | ||||
|     def _handle_target_dir( | ||||
|         self, target_dir: str, target_temp_dir: TempDirectory, upgrade: bool | ||||
|     ) -> None: | ||||
|         ensure_dir(target_dir) | ||||
| 
 | ||||
|         # Checking both purelib and platlib directories for installed | ||||
|         # packages to be moved to target directory | ||||
|         lib_dir_list = [] | ||||
| 
 | ||||
|         # Checking both purelib and platlib directories for installed | ||||
|         # packages to be moved to target directory | ||||
|         scheme = get_scheme("", home=target_temp_dir.path) | ||||
|         purelib_dir = scheme.purelib | ||||
|         platlib_dir = scheme.platlib | ||||
|         data_dir = scheme.data | ||||
| 
 | ||||
|         if os.path.exists(purelib_dir): | ||||
|             lib_dir_list.append(purelib_dir) | ||||
|         if os.path.exists(platlib_dir) and platlib_dir != purelib_dir: | ||||
|             lib_dir_list.append(platlib_dir) | ||||
|         if os.path.exists(data_dir): | ||||
|             lib_dir_list.append(data_dir) | ||||
| 
 | ||||
|         for lib_dir in lib_dir_list: | ||||
|             for item in os.listdir(lib_dir): | ||||
|                 if lib_dir == data_dir: | ||||
|                     ddir = os.path.join(data_dir, item) | ||||
|                     if any(s.startswith(ddir) for s in lib_dir_list[:-1]): | ||||
|                         continue | ||||
|                 target_item_dir = os.path.join(target_dir, item) | ||||
|                 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, | ||||
|                         ) | ||||
|                         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, | ||||
|                         ) | ||||
|                         continue | ||||
|                     if os.path.isdir(target_item_dir): | ||||
|                         shutil.rmtree(target_item_dir) | ||||
|                     else: | ||||
|                         os.remove(target_item_dir) | ||||
| 
 | ||||
|                 shutil.move(os.path.join(lib_dir, item), target_item_dir) | ||||
| 
 | ||||
|     def _determine_conflicts( | ||||
|         self, to_install: List[InstallRequirement] | ||||
|     ) -> Optional[ConflictDetails]: | ||||
|         try: | ||||
|             return check_install_conflicts(to_install) | ||||
|         except Exception: | ||||
|             logger.exception( | ||||
|                 "Error while checking for conflicts. Please file an issue on " | ||||
|                 "pip's issue tracker: https://github.com/pypa/pip/issues/new" | ||||
|             ) | ||||
|             return None | ||||
| 
 | ||||
|     def _warn_about_conflicts( | ||||
|         self, conflict_details: ConflictDetails, resolver_variant: str | ||||
|     ) -> None: | ||||
|         package_set, (missing, conflicting) = conflict_details | ||||
|         if not missing and not conflicting: | ||||
|             return | ||||
| 
 | ||||
|         parts: List[str] = [] | ||||
|         if resolver_variant == "legacy": | ||||
|             parts.append( | ||||
|                 "pip's legacy dependency resolver does not consider dependency " | ||||
|                 "conflicts when selecting packages. This behaviour is the " | ||||
|                 "source of the following dependency conflicts." | ||||
|             ) | ||||
|         else: | ||||
|             assert resolver_variant == "resolvelib" | ||||
|             parts.append( | ||||
|                 "pip's dependency resolver does not currently take into account " | ||||
|                 "all the packages that are installed. This behaviour is the " | ||||
|                 "source of the following dependency conflicts." | ||||
|             ) | ||||
| 
 | ||||
|         # NOTE: There is some duplication here, with commands/check.py | ||||
|         for project_name in missing: | ||||
|             version = package_set[project_name][0] | ||||
|             for dependency in missing[project_name]: | ||||
|                 message = ( | ||||
|                     f"{project_name} {version} requires {dependency[1]}, " | ||||
|                     "which is not installed." | ||||
|                 ) | ||||
|                 parts.append(message) | ||||
| 
 | ||||
|         for project_name in conflicting: | ||||
|             version = package_set[project_name][0] | ||||
|             for dep_name, dep_version, req in conflicting[project_name]: | ||||
|                 message = ( | ||||
|                     "{name} {version} requires {requirement}, but {you} have " | ||||
|                     "{dep_name} {dep_version} which is incompatible." | ||||
|                 ).format( | ||||
|                     name=project_name, | ||||
|                     version=version, | ||||
|                     requirement=req, | ||||
|                     dep_name=dep_name, | ||||
|                     dep_version=dep_version, | ||||
|                     you=("you" if resolver_variant == "resolvelib" else "you'll"), | ||||
|                 ) | ||||
|                 parts.append(message) | ||||
| 
 | ||||
|         logger.critical("\n".join(parts)) | ||||
| 
 | ||||
| 
 | ||||
| def get_lib_location_guesses( | ||||
|     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, | ||||
|         isolated=isolated, | ||||
|         prefix=prefix, | ||||
|     ) | ||||
|     return [scheme.purelib, scheme.platlib] | ||||
| 
 | ||||
| 
 | ||||
| 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)) | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def decide_user_install( | ||||
|     use_user_site: Optional[bool], | ||||
|     prefix_path: Optional[str] = None, | ||||
|     target_dir: Optional[str] = None, | ||||
|     root_path: Optional[str] = None, | ||||
|     isolated_mode: bool = False, | ||||
| ) -> bool: | ||||
|     """Determine whether to do a user install based on the input options. | ||||
| 
 | ||||
|     If use_user_site is False, no additional checks are done. | ||||
|     If use_user_site is True, it is checked for compatibility with other | ||||
|     options. | ||||
|     If use_user_site is None, the default behaviour depends on the environment, | ||||
|     which is provided by the other arguments. | ||||
|     """ | ||||
|     # In some cases (config from tox), use_user_site can be set to an integer | ||||
|     # rather than a bool, which 'use_user_site is False' wouldn't catch. | ||||
|     if (use_user_site is not None) and (not use_user_site): | ||||
|         logger.debug("Non-user install by explicit request") | ||||
|         return False | ||||
| 
 | ||||
|     if use_user_site: | ||||
|         if prefix_path: | ||||
|             raise CommandError( | ||||
|                 "Can not combine '--user' and '--prefix' as they imply " | ||||
|                 "different installation locations" | ||||
|             ) | ||||
|         if virtualenv_no_global(): | ||||
|             raise InstallationError( | ||||
|                 "Can not perform a '--user' install. User site-packages " | ||||
|                 "are not visible in this virtualenv." | ||||
|             ) | ||||
|         logger.debug("User install by explicit request") | ||||
|         return True | ||||
| 
 | ||||
|     # If we are here, user installs have not been explicitly requested/avoided | ||||
|     assert use_user_site is None | ||||
| 
 | ||||
|     # user install incompatible with --prefix/--target | ||||
|     if prefix_path or target_dir: | ||||
|         logger.debug("Non-user install due to --prefix or --target option") | ||||
|         return False | ||||
| 
 | ||||
|     # If user installs are not enabled, choose a non-user install | ||||
|     if not site.ENABLE_USER_SITE: | ||||
|         logger.debug("Non-user install because user site-packages disabled") | ||||
|         return False | ||||
| 
 | ||||
|     # If we have permission for a non-user install, do that, | ||||
|     # otherwise do a user install. | ||||
|     if site_packages_writable(root=root_path, isolated=isolated_mode): | ||||
|         logger.debug("Non-user install because site-packages writeable") | ||||
|         return False | ||||
| 
 | ||||
|     logger.info( | ||||
|         "Defaulting to user installation because normal site-packages " | ||||
|         "is not writeable" | ||||
|     ) | ||||
|     return True | ||||
| 
 | ||||
| 
 | ||||
| def create_os_error_message( | ||||
|     error: OSError, show_traceback: bool, using_user_site: bool | ||||
| ) -> str: | ||||
|     """Format an error message for an OSError | ||||
| 
 | ||||
|     It may occur anytime during the execution of the install command. | ||||
|     """ | ||||
|     parts = [] | ||||
| 
 | ||||
|     # Mention the error if we are not going to show a traceback | ||||
|     parts.append("Could not install packages due to an OSError") | ||||
|     if not show_traceback: | ||||
|         parts.append(": ") | ||||
|         parts.append(str(error)) | ||||
|     else: | ||||
|         parts.append(".") | ||||
| 
 | ||||
|     # Spilt the error indication from a helper message (if any) | ||||
|     parts[-1] += "\n" | ||||
| 
 | ||||
|     # Suggest useful actions to the user: | ||||
|     #  (1) using user site-packages or (2) verifying the permissions | ||||
|     if error.errno == errno.EACCES: | ||||
|         user_option_part = "Consider using the `--user` option" | ||||
|         permissions_part = "Check the permissions" | ||||
| 
 | ||||
|         if not running_under_virtualenv() and not using_user_site: | ||||
|             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 | ||||
|     ): | ||||
|         parts.append( | ||||
|             "HINT: This error might have occurred since " | ||||
|             "this system does not have Windows Long Path " | ||||
|             "support enabled. You can find information on " | ||||
|             "how to enable this at " | ||||
|             "https://pip.pypa.io/warnings/enable-long-paths\n" | ||||
|         ) | ||||
| 
 | ||||
|     return "".join(parts).strip() + "\n" | ||||
|  | @ -0,0 +1,375 @@ | |||
| import json | ||||
| import logging | ||||
| from optparse import Values | ||||
| from typing import TYPE_CHECKING, Generator, List, Optional, Sequence, Tuple, cast | ||||
| 
 | ||||
| from pip._vendor.packaging.utils import canonicalize_name | ||||
| from pip._vendor.packaging.version import Version | ||||
| 
 | ||||
| from pip._internal.cli import cmdoptions | ||||
| from pip._internal.cli.index_command import IndexGroupCommand | ||||
| from pip._internal.cli.status_codes import SUCCESS | ||||
| from pip._internal.exceptions import CommandError | ||||
| from pip._internal.metadata import BaseDistribution, get_environment | ||||
| from pip._internal.models.selection_prefs import SelectionPreferences | ||||
| from pip._internal.utils.compat import stdlib_pkgs | ||||
| from pip._internal.utils.misc import tabulate, write_output | ||||
| 
 | ||||
| if TYPE_CHECKING: | ||||
|     from pip._internal.index.package_finder import PackageFinder | ||||
|     from pip._internal.network.session import PipSession | ||||
| 
 | ||||
|     class _DistWithLatestInfo(BaseDistribution): | ||||
|         """Give the distribution object a couple of extra fields. | ||||
| 
 | ||||
|         These will be populated during ``get_outdated()``. This is dirty but | ||||
|         makes the rest of the code much cleaner. | ||||
|         """ | ||||
| 
 | ||||
|         latest_version: Version | ||||
|         latest_filetype: str | ||||
| 
 | ||||
|     _ProcessedDists = Sequence[_DistWithLatestInfo] | ||||
| 
 | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class ListCommand(IndexGroupCommand): | ||||
|     """ | ||||
|     List installed packages, including editables. | ||||
| 
 | ||||
|     Packages are listed in a case-insensitive sorted order. | ||||
|     """ | ||||
| 
 | ||||
|     ignore_require_venv = True | ||||
|     usage = """ | ||||
|       %prog [options]""" | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option( | ||||
|             "-o", | ||||
|             "--outdated", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             help="List outdated packages", | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "-u", | ||||
|             "--uptodate", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             help="List uptodate packages", | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "-e", | ||||
|             "--editable", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             help="List editable projects.", | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "-l", | ||||
|             "--local", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             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", | ||||
|             default=False, | ||||
|             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", | ||||
|             default=False, | ||||
|             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", | ||||
|             default="columns", | ||||
|             choices=("columns", "freeze", "json"), | ||||
|             help=( | ||||
|                 "Select the output format among: columns (default), freeze, or json. " | ||||
|                 "The 'freeze' format cannot be used with the --outdated option." | ||||
|             ), | ||||
|         ) | ||||
| 
 | ||||
|         self.cmd_opts.add_option( | ||||
|             "--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.", | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "--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) | ||||
| 
 | ||||
|         self.parser.insert_option_group(0, index_opts) | ||||
|         self.parser.insert_option_group(0, self.cmd_opts) | ||||
| 
 | ||||
|     def handle_pip_version_check(self, options: Values) -> None: | ||||
|         if options.outdated or options.uptodate: | ||||
|             super().handle_pip_version_check(options) | ||||
| 
 | ||||
|     def _build_package_finder( | ||||
|         self, options: Values, session: "PipSession" | ||||
|     ) -> "PackageFinder": | ||||
|         """ | ||||
|         Create a package finder appropriate to this list command. | ||||
|         """ | ||||
|         # Lazy import the heavy index modules as most list invocations won't need 'em. | ||||
|         from pip._internal.index.collector import LinkCollector | ||||
|         from pip._internal.index.package_finder import PackageFinder | ||||
| 
 | ||||
|         link_collector = LinkCollector.create(session, options=options) | ||||
| 
 | ||||
|         # Pass allow_yanked=False to ignore yanked versions. | ||||
|         selection_prefs = SelectionPreferences( | ||||
|             allow_yanked=False, | ||||
|             allow_all_prereleases=options.pre, | ||||
|         ) | ||||
| 
 | ||||
|         return PackageFinder.create( | ||||
|             link_collector=link_collector, | ||||
|             selection_prefs=selection_prefs, | ||||
|         ) | ||||
| 
 | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         if options.outdated and options.uptodate: | ||||
|             raise CommandError("Options --outdated and --uptodate cannot be combined.") | ||||
| 
 | ||||
|         if options.outdated and options.list_format == "freeze": | ||||
|             raise CommandError( | ||||
|                 "List format 'freeze' cannot be used with the --outdated option." | ||||
|             ) | ||||
| 
 | ||||
|         cmdoptions.check_list_path_option(options) | ||||
| 
 | ||||
|         skip = set(stdlib_pkgs) | ||||
|         if options.excludes: | ||||
|             skip.update(canonicalize_name(n) for n in options.excludes) | ||||
| 
 | ||||
|         packages: _ProcessedDists = [ | ||||
|             cast("_DistWithLatestInfo", d) | ||||
|             for d in get_environment(options.path).iter_installed_distributions( | ||||
|                 local_only=options.local, | ||||
|                 user_only=options.user, | ||||
|                 editables_only=options.editable, | ||||
|                 include_editables=options.include_editable, | ||||
|                 skip=skip, | ||||
|             ) | ||||
|         ] | ||||
| 
 | ||||
|         # get_not_required must be called firstly in order to find and | ||||
|         # filter out all dependencies correctly. Otherwise a package | ||||
|         # can't be identified as requirement because some parent packages | ||||
|         # could be filtered out before. | ||||
|         if options.not_required: | ||||
|             packages = self.get_not_required(packages, options) | ||||
| 
 | ||||
|         if options.outdated: | ||||
|             packages = self.get_outdated(packages, options) | ||||
|         elif options.uptodate: | ||||
|             packages = self.get_uptodate(packages, options) | ||||
| 
 | ||||
|         self.output_package_listing(packages, options) | ||||
|         return SUCCESS | ||||
| 
 | ||||
|     def get_outdated( | ||||
|         self, packages: "_ProcessedDists", options: Values | ||||
|     ) -> "_ProcessedDists": | ||||
|         return [ | ||||
|             dist | ||||
|             for dist in self.iter_packages_latest_infos(packages, options) | ||||
|             if dist.latest_version > dist.version | ||||
|         ] | ||||
| 
 | ||||
|     def get_uptodate( | ||||
|         self, packages: "_ProcessedDists", options: Values | ||||
|     ) -> "_ProcessedDists": | ||||
|         return [ | ||||
|             dist | ||||
|             for dist in self.iter_packages_latest_infos(packages, options) | ||||
|             if dist.latest_version == dist.version | ||||
|         ] | ||||
| 
 | ||||
|     def get_not_required( | ||||
|         self, packages: "_ProcessedDists", options: Values | ||||
|     ) -> "_ProcessedDists": | ||||
|         dep_keys = { | ||||
|             canonicalize_name(dep.name) | ||||
|             for dist in packages | ||||
|             for dep in (dist.iter_dependencies() or ()) | ||||
|         } | ||||
| 
 | ||||
|         # Create a set to remove duplicate packages, and cast it to a list | ||||
|         # to keep the return type consistent with get_outdated and | ||||
|         # get_uptodate | ||||
|         return list({pkg for pkg in packages if pkg.canonical_name not in dep_keys}) | ||||
| 
 | ||||
|     def iter_packages_latest_infos( | ||||
|         self, packages: "_ProcessedDists", options: Values | ||||
|     ) -> Generator["_DistWithLatestInfo", None, None]: | ||||
|         with self._build_session(options) as session: | ||||
|             finder = self._build_package_finder(options, session) | ||||
| 
 | ||||
|             def latest_info( | ||||
|                 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 | ||||
|                     ] | ||||
| 
 | ||||
|                 evaluator = finder.make_candidate_evaluator( | ||||
|                     project_name=dist.canonical_name, | ||||
|                 ) | ||||
|                 best_candidate = evaluator.sort_best_candidate(all_candidates) | ||||
|                 if best_candidate is None: | ||||
|                     return None | ||||
| 
 | ||||
|                 remote_version = best_candidate.version | ||||
|                 if best_candidate.link.is_wheel: | ||||
|                     typ = "wheel" | ||||
|                 else: | ||||
|                     typ = "sdist" | ||||
|                 dist.latest_version = remote_version | ||||
|                 dist.latest_filetype = typ | ||||
|                 return dist | ||||
| 
 | ||||
|             for dist in map(latest_info, packages): | ||||
|                 if dist is not None: | ||||
|                     yield dist | ||||
| 
 | ||||
|     def output_package_listing( | ||||
|         self, packages: "_ProcessedDists", options: Values | ||||
|     ) -> None: | ||||
|         packages = sorted( | ||||
|             packages, | ||||
|             key=lambda dist: dist.canonical_name, | ||||
|         ) | ||||
|         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": | ||||
|             for dist in packages: | ||||
|                 if options.verbose >= 1: | ||||
|                     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": | ||||
|             write_output(format_for_json(packages, options)) | ||||
| 
 | ||||
|     def output_package_listing_columns( | ||||
|         self, data: List[List[str]], header: List[str] | ||||
|     ) -> None: | ||||
|         # insert the header first: we need to know the size of column names | ||||
|         if len(data) > 0: | ||||
|             data.insert(0, header) | ||||
| 
 | ||||
|         pkg_strings, sizes = tabulate(data) | ||||
| 
 | ||||
|         # Create and add a separator. | ||||
|         if len(data) > 0: | ||||
|             pkg_strings.insert(1, " ".join("-" * x for x in sizes)) | ||||
| 
 | ||||
|         for val in pkg_strings: | ||||
|             write_output(val) | ||||
| 
 | ||||
| 
 | ||||
| def format_for_columns( | ||||
|     pkgs: "_ProcessedDists", options: Values | ||||
| ) -> Tuple[List[List[str]], List[str]]: | ||||
|     """ | ||||
|     Convert the package data into something usable | ||||
|     by output_package_listing_columns. | ||||
|     """ | ||||
|     header = ["Package", "Version"] | ||||
| 
 | ||||
|     running_outdated = options.outdated | ||||
|     if running_outdated: | ||||
|         header.extend(["Latest", "Type"]) | ||||
| 
 | ||||
|     has_editables = any(x.editable for x in pkgs) | ||||
|     if has_editables: | ||||
|         header.append("Editable project location") | ||||
| 
 | ||||
|     if options.verbose >= 1: | ||||
|         header.append("Location") | ||||
|     if options.verbose >= 1: | ||||
|         header.append("Installer") | ||||
| 
 | ||||
|     data = [] | ||||
|     for proj in pkgs: | ||||
|         # if we're working on the 'outdated' list, separate out the | ||||
|         # latest_version and type | ||||
|         row = [proj.raw_name, proj.raw_version] | ||||
| 
 | ||||
|         if running_outdated: | ||||
|             row.append(str(proj.latest_version)) | ||||
|             row.append(proj.latest_filetype) | ||||
| 
 | ||||
|         if has_editables: | ||||
|             row.append(proj.editable_project_location or "") | ||||
| 
 | ||||
|         if options.verbose >= 1: | ||||
|             row.append(proj.location or "") | ||||
|         if options.verbose >= 1: | ||||
|             row.append(proj.installer) | ||||
| 
 | ||||
|         data.append(row) | ||||
| 
 | ||||
|     return data, header | ||||
| 
 | ||||
| 
 | ||||
| def format_for_json(packages: "_ProcessedDists", options: Values) -> str: | ||||
|     data = [] | ||||
|     for dist in packages: | ||||
|         info = { | ||||
|             "name": dist.raw_name, | ||||
|             "version": str(dist.version), | ||||
|         } | ||||
|         if options.verbose >= 1: | ||||
|             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 | ||||
|         editable_project_location = dist.editable_project_location | ||||
|         if editable_project_location: | ||||
|             info["editable_project_location"] = editable_project_location | ||||
|         data.append(info) | ||||
|     return json.dumps(data) | ||||
|  | @ -0,0 +1,172 @@ | |||
| import logging | ||||
| import shutil | ||||
| import sys | ||||
| import textwrap | ||||
| import xmlrpc.client | ||||
| from collections import OrderedDict | ||||
| from optparse import Values | ||||
| from typing import TYPE_CHECKING, Dict, List, Optional, TypedDict | ||||
| 
 | ||||
| from pip._vendor.packaging.version import parse as parse_version | ||||
| 
 | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.req_command import SessionCommandMixin | ||||
| from pip._internal.cli.status_codes import NO_MATCHES_FOUND, SUCCESS | ||||
| from pip._internal.exceptions import CommandError | ||||
| from pip._internal.metadata import get_default_environment | ||||
| from pip._internal.models.index import PyPI | ||||
| from pip._internal.network.xmlrpc import PipXmlrpcTransport | ||||
| from pip._internal.utils.logging import indent_log | ||||
| from pip._internal.utils.misc import write_output | ||||
| 
 | ||||
| if TYPE_CHECKING: | ||||
| 
 | ||||
|     class TransformedHit(TypedDict): | ||||
|         name: str | ||||
|         summary: str | ||||
|         versions: List[str] | ||||
| 
 | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class SearchCommand(Command, SessionCommandMixin): | ||||
|     """Search for PyPI packages whose name or summary contains <query>.""" | ||||
| 
 | ||||
|     usage = """ | ||||
|       %prog [options] <query>""" | ||||
|     ignore_require_venv = True | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option( | ||||
|             "-i", | ||||
|             "--index", | ||||
|             dest="index", | ||||
|             metavar="URL", | ||||
|             default=PyPI.pypi_url, | ||||
|             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).") | ||||
|         query = args | ||||
|         pypi_hits = self.search(query, options) | ||||
|         hits = transform_hits(pypi_hits) | ||||
| 
 | ||||
|         terminal_width = None | ||||
|         if sys.stdout.isatty(): | ||||
|             terminal_width = shutil.get_terminal_size()[0] | ||||
| 
 | ||||
|         print_results(hits, terminal_width=terminal_width) | ||||
|         if pypi_hits: | ||||
|             return SUCCESS | ||||
|         return NO_MATCHES_FOUND | ||||
| 
 | ||||
|     def search(self, query: List[str], options: Values) -> List[Dict[str, str]]: | ||||
|         index_url = options.index | ||||
| 
 | ||||
|         session = self.get_default_session(options) | ||||
| 
 | ||||
|         transport = PipXmlrpcTransport(index_url, session) | ||||
|         pypi = xmlrpc.client.ServerProxy(index_url, transport) | ||||
|         try: | ||||
|             hits = pypi.search({"name": query, "summary": query}, "or") | ||||
|         except xmlrpc.client.Fault as fault: | ||||
|             message = ( | ||||
|                 f"XMLRPC request failed [code: {fault.faultCode}]\n{fault.faultString}" | ||||
|             ) | ||||
|             raise CommandError(message) | ||||
|         assert isinstance(hits, list) | ||||
|         return hits | ||||
| 
 | ||||
| 
 | ||||
| def transform_hits(hits: List[Dict[str, str]]) -> List["TransformedHit"]: | ||||
|     """ | ||||
|     The list from pypi is really a list of versions. We want a list of | ||||
|     packages with the list of versions stored inline. This converts the | ||||
|     list from pypi into one we can use. | ||||
|     """ | ||||
|     packages: Dict[str, TransformedHit] = OrderedDict() | ||||
|     for hit in hits: | ||||
|         name = hit["name"] | ||||
|         summary = hit["summary"] | ||||
|         version = hit["version"] | ||||
| 
 | ||||
|         if name not in packages.keys(): | ||||
|             packages[name] = { | ||||
|                 "name": name, | ||||
|                 "summary": summary, | ||||
|                 "versions": [version], | ||||
|             } | ||||
|         else: | ||||
|             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 | ||||
| 
 | ||||
|     return list(packages.values()) | ||||
| 
 | ||||
| 
 | ||||
| def print_dist_installation_info(name: str, latest: str) -> None: | ||||
|     env = get_default_environment() | ||||
|     dist = env.get_distribution(name) | ||||
|     if dist is not None: | ||||
|         with indent_log(): | ||||
|             if dist.version == latest: | ||||
|                 write_output("INSTALLED: %s (latest)", dist.version) | ||||
|             else: | ||||
|                 write_output("INSTALLED: %s", dist.version) | ||||
|                 if parse_version(latest).pre: | ||||
|                     write_output( | ||||
|                         "LATEST:    %s (pre-release; install" | ||||
|                         " with `pip install --pre`)", | ||||
|                         latest, | ||||
|                     ) | ||||
|                 else: | ||||
|                     write_output("LATEST:    %s", latest) | ||||
| 
 | ||||
| 
 | ||||
| def print_results( | ||||
|     hits: List["TransformedHit"], | ||||
|     name_column_width: Optional[int] = None, | ||||
|     terminal_width: Optional[int] = None, | ||||
| ) -> None: | ||||
|     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 | ||||
|         ) | ||||
| 
 | ||||
|     for hit in hits: | ||||
|         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) | ||||
| 
 | ||||
|         name_latest = f"{name} ({latest})" | ||||
|         line = f"{name_latest:{name_column_width}} - {summary}" | ||||
|         try: | ||||
|             write_output(line) | ||||
|             print_dist_installation_info(name, latest) | ||||
|         except UnicodeEncodeError: | ||||
|             pass | ||||
| 
 | ||||
| 
 | ||||
| def highest_version(versions: List[str]) -> str: | ||||
|     return max(versions, key=parse_version) | ||||
|  | @ -0,0 +1,224 @@ | |||
| import logging | ||||
| from optparse import Values | ||||
| from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional | ||||
| 
 | ||||
| from pip._vendor.packaging.requirements import InvalidRequirement | ||||
| from pip._vendor.packaging.utils import canonicalize_name | ||||
| 
 | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.status_codes import ERROR, SUCCESS | ||||
| from pip._internal.metadata import BaseDistribution, get_default_environment | ||||
| from pip._internal.utils.misc import write_output | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class ShowCommand(Command): | ||||
|     """ | ||||
|     Show information about one or more installed packages. | ||||
| 
 | ||||
|     The output is in RFC-compliant mail header format. | ||||
|     """ | ||||
| 
 | ||||
|     usage = """ | ||||
|       %prog [options] <package> ...""" | ||||
|     ignore_require_venv = True | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option( | ||||
|             "-f", | ||||
|             "--files", | ||||
|             dest="files", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             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.") | ||||
|             return ERROR | ||||
|         query = args | ||||
| 
 | ||||
|         results = search_packages_info(query) | ||||
|         if not print_results( | ||||
|             results, list_files=options.files, verbose=options.verbose | ||||
|         ): | ||||
|             return ERROR | ||||
|         return SUCCESS | ||||
| 
 | ||||
| 
 | ||||
| class _PackageInfo(NamedTuple): | ||||
|     name: str | ||||
|     version: str | ||||
|     location: str | ||||
|     editable_project_location: Optional[str] | ||||
|     requires: List[str] | ||||
|     required_by: List[str] | ||||
|     installer: str | ||||
|     metadata_version: str | ||||
|     classifiers: List[str] | ||||
|     summary: str | ||||
|     homepage: str | ||||
|     project_urls: List[str] | ||||
|     author: str | ||||
|     author_email: str | ||||
|     license: str | ||||
|     license_expression: str | ||||
|     entry_points: List[str] | ||||
|     files: Optional[List[str]] | ||||
| 
 | ||||
| 
 | ||||
| def search_packages_info(query: List[str]) -> Generator[_PackageInfo, None, None]: | ||||
|     """ | ||||
|     Gather details from installed distributions. Print distribution name, | ||||
|     version, location, and installed files. Installed files requires a | ||||
|     pip generated 'installed-files.txt' in the distributions '.egg-info' | ||||
|     directory. | ||||
|     """ | ||||
|     env = get_default_environment() | ||||
| 
 | ||||
|     installed = {dist.canonical_name: dist for dist in env.iter_all_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)) | ||||
| 
 | ||||
|     def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[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()} | ||||
|         ) | ||||
| 
 | ||||
|     for query_name in query_names: | ||||
|         try: | ||||
|             dist = installed[query_name] | ||||
|         except KeyError: | ||||
|             continue | ||||
| 
 | ||||
|         try: | ||||
|             requires = sorted( | ||||
|                 # Avoid duplicates in requirements (e.g. due to environment markers). | ||||
|                 {req.name for req in dist.iter_dependencies()}, | ||||
|                 key=str.lower, | ||||
|             ) | ||||
|         except InvalidRequirement: | ||||
|             requires = sorted(dist.iter_raw_dependencies(), key=str.lower) | ||||
| 
 | ||||
|         try: | ||||
|             required_by = sorted(_get_requiring_packages(dist), key=str.lower) | ||||
|         except InvalidRequirement: | ||||
|             required_by = ["#N/A"] | ||||
| 
 | ||||
|         try: | ||||
|             entry_points_text = dist.read_text("entry_points.txt") | ||||
|             entry_points = entry_points_text.splitlines(keepends=False) | ||||
|         except FileNotFoundError: | ||||
|             entry_points = [] | ||||
| 
 | ||||
|         files_iter = dist.iter_declared_entries() | ||||
|         if files_iter is None: | ||||
|             files: Optional[List[str]] = None | ||||
|         else: | ||||
|             files = sorted(files_iter) | ||||
| 
 | ||||
|         metadata = dist.metadata | ||||
| 
 | ||||
|         project_urls = metadata.get_all("Project-URL", []) | ||||
|         homepage = metadata.get("Home-page", "") | ||||
|         if not homepage: | ||||
|             # It's common that there is a "homepage" Project-URL, but Home-page | ||||
|             # remains unset (especially as PEP 621 doesn't surface the field). | ||||
|             # | ||||
|             # This logic was taken from PyPI's codebase. | ||||
|             for url in project_urls: | ||||
|                 url_label, url = url.split(",", maxsplit=1) | ||||
|                 normalized_label = ( | ||||
|                     url_label.casefold().replace("-", "").replace("_", "").strip() | ||||
|                 ) | ||||
|                 if normalized_label == "homepage": | ||||
|                     homepage = url.strip() | ||||
|                     break | ||||
| 
 | ||||
|         yield _PackageInfo( | ||||
|             name=dist.raw_name, | ||||
|             version=dist.raw_version, | ||||
|             location=dist.location or "", | ||||
|             editable_project_location=dist.editable_project_location, | ||||
|             requires=requires, | ||||
|             required_by=required_by, | ||||
|             installer=dist.installer, | ||||
|             metadata_version=dist.metadata_version or "", | ||||
|             classifiers=metadata.get_all("Classifier", []), | ||||
|             summary=metadata.get("Summary", ""), | ||||
|             homepage=homepage, | ||||
|             project_urls=project_urls, | ||||
|             author=metadata.get("Author", ""), | ||||
|             author_email=metadata.get("Author-email", ""), | ||||
|             license=metadata.get("License", ""), | ||||
|             license_expression=metadata.get("License-Expression", ""), | ||||
|             entry_points=entry_points, | ||||
|             files=files, | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| def print_results( | ||||
|     distributions: Iterable[_PackageInfo], | ||||
|     list_files: bool, | ||||
|     verbose: bool, | ||||
| ) -> bool: | ||||
|     """ | ||||
|     Print the information from installed distributions found. | ||||
|     """ | ||||
|     results_printed = False | ||||
|     for i, dist in enumerate(distributions): | ||||
|         results_printed = True | ||||
|         if i > 0: | ||||
|             write_output("---") | ||||
| 
 | ||||
|         metadata_version_tuple = tuple(map(int, dist.metadata_version.split("."))) | ||||
| 
 | ||||
|         write_output("Name: %s", dist.name) | ||||
|         write_output("Version: %s", dist.version) | ||||
|         write_output("Summary: %s", dist.summary) | ||||
|         write_output("Home-page: %s", dist.homepage) | ||||
|         write_output("Author: %s", dist.author) | ||||
|         write_output("Author-email: %s", dist.author_email) | ||||
|         if metadata_version_tuple >= (2, 4) and dist.license_expression: | ||||
|             write_output("License-Expression: %s", dist.license_expression) | ||||
|         else: | ||||
|             write_output("License: %s", dist.license) | ||||
|         write_output("Location: %s", dist.location) | ||||
|         if dist.editable_project_location is not None: | ||||
|             write_output( | ||||
|                 "Editable project location: %s", dist.editable_project_location | ||||
|             ) | ||||
|         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) | ||||
|             write_output("Installer: %s", dist.installer) | ||||
|             write_output("Classifiers:") | ||||
|             for classifier in dist.classifiers: | ||||
|                 write_output("  %s", classifier) | ||||
|             write_output("Entry-points:") | ||||
|             for entry in dist.entry_points: | ||||
|                 write_output("  %s", entry.strip()) | ||||
|             write_output("Project-URLs:") | ||||
|             for project_url in dist.project_urls: | ||||
|                 write_output("  %s", project_url) | ||||
|         if list_files: | ||||
|             write_output("Files:") | ||||
|             if dist.files is None: | ||||
|                 write_output("Cannot locate RECORD or installed-files.txt") | ||||
|             else: | ||||
|                 for line in dist.files: | ||||
|                     write_output("  %s", line.strip()) | ||||
|     return results_printed | ||||
|  | @ -0,0 +1,114 @@ | |||
| import logging | ||||
| from optparse import Values | ||||
| from typing import List | ||||
| 
 | ||||
| from pip._vendor.packaging.utils import canonicalize_name | ||||
| 
 | ||||
| from pip._internal.cli import cmdoptions | ||||
| from pip._internal.cli.base_command import Command | ||||
| from pip._internal.cli.index_command import SessionCommandMixin | ||||
| from pip._internal.cli.status_codes import SUCCESS | ||||
| from pip._internal.exceptions import InstallationError | ||||
| from pip._internal.req import parse_requirements | ||||
| from pip._internal.req.constructors import ( | ||||
|     install_req_from_line, | ||||
|     install_req_from_parsed_requirement, | ||||
| ) | ||||
| from pip._internal.utils.misc import ( | ||||
|     check_externally_managed, | ||||
|     protect_pip_from_modification_on_windows, | ||||
|     warn_if_run_as_root, | ||||
| ) | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class UninstallCommand(Command, SessionCommandMixin): | ||||
|     """ | ||||
|     Uninstall packages. | ||||
| 
 | ||||
|     pip is able to uninstall most installed packages. Known exceptions are: | ||||
| 
 | ||||
|     - Pure distutils packages installed with ``python setup.py install``, which | ||||
|       leave behind no metadata to determine what files were installed. | ||||
|     - Script wrappers installed by ``python setup.py develop``. | ||||
|     """ | ||||
| 
 | ||||
|     usage = """ | ||||
|       %prog [options] <package> ... | ||||
|       %prog [options] -r <requirements file> ...""" | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option( | ||||
|             "-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." | ||||
|             ), | ||||
|         ) | ||||
|         self.cmd_opts.add_option( | ||||
|             "-y", | ||||
|             "--yes", | ||||
|             dest="yes", | ||||
|             action="store_true", | ||||
|             help="Don't ask for confirmation of uninstall deletions.", | ||||
|         ) | ||||
|         self.cmd_opts.add_option(cmdoptions.root_user_action()) | ||||
|         self.cmd_opts.add_option(cmdoptions.override_externally_managed()) | ||||
|         self.parser.insert_option_group(0, self.cmd_opts) | ||||
| 
 | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         session = self.get_default_session(options) | ||||
| 
 | ||||
|         reqs_to_uninstall = {} | ||||
|         for name in args: | ||||
|             req = install_req_from_line( | ||||
|                 name, | ||||
|                 isolated=options.isolated_mode, | ||||
|             ) | ||||
|             if req.name: | ||||
|                 reqs_to_uninstall[canonicalize_name(req.name)] = req | ||||
|             else: | ||||
|                 logger.warning( | ||||
|                     "Invalid requirement: %r ignored -" | ||||
|                     " the uninstall command expects named" | ||||
|                     " requirements.", | ||||
|                     name, | ||||
|                 ) | ||||
|         for filename in options.requirements: | ||||
|             for parsed_req in parse_requirements( | ||||
|                 filename, options=options, session=session | ||||
|             ): | ||||
|                 req = install_req_from_parsed_requirement( | ||||
|                     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'"pip help {self.name}")' | ||||
|             ) | ||||
| 
 | ||||
|         if not options.override_externally_managed: | ||||
|             check_externally_managed() | ||||
| 
 | ||||
|         protect_pip_from_modification_on_windows( | ||||
|             modifying_pip="pip" in reqs_to_uninstall | ||||
|         ) | ||||
| 
 | ||||
|         for req in reqs_to_uninstall.values(): | ||||
|             uninstall_pathset = req.uninstall( | ||||
|                 auto_confirm=options.yes, | ||||
|                 verbose=self.verbosity > 0, | ||||
|             ) | ||||
|             if uninstall_pathset: | ||||
|                 uninstall_pathset.commit() | ||||
|         if options.root_user_action == "warn": | ||||
|             warn_if_run_as_root() | ||||
|         return SUCCESS | ||||
|  | @ -0,0 +1,182 @@ | |||
| import logging | ||||
| import os | ||||
| import shutil | ||||
| from optparse import Values | ||||
| from typing import List | ||||
| 
 | ||||
| from pip._internal.cache import WheelCache | ||||
| from pip._internal.cli import cmdoptions | ||||
| from pip._internal.cli.req_command import RequirementCommand, with_cleanup | ||||
| from pip._internal.cli.status_codes import SUCCESS | ||||
| from pip._internal.exceptions import CommandError | ||||
| from pip._internal.operations.build.build_tracker import get_build_tracker | ||||
| from pip._internal.req.req_install import ( | ||||
|     InstallRequirement, | ||||
|     check_legacy_setup_py_options, | ||||
| ) | ||||
| from pip._internal.utils.misc import ensure_dir, normalize_path | ||||
| from pip._internal.utils.temp_dir import TempDirectory | ||||
| from pip._internal.wheel_builder import build, should_build_for_wheel_command | ||||
| 
 | ||||
| logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class WheelCommand(RequirementCommand): | ||||
|     """ | ||||
|     Build Wheel archives for your requirements and dependencies. | ||||
| 
 | ||||
|     Wheel is a built-package format, and offers the advantage of not | ||||
|     recompiling your software during every install. For more details, see the | ||||
|     wheel docs: https://wheel.readthedocs.io/en/latest/ | ||||
| 
 | ||||
|     'pip wheel' uses the build system interface as described here: | ||||
|     https://pip.pypa.io/en/stable/reference/build-system/ | ||||
| 
 | ||||
|     """ | ||||
| 
 | ||||
|     usage = """ | ||||
|       %prog [options] <requirement specifier> ... | ||||
|       %prog [options] -r <requirements file> ... | ||||
|       %prog [options] [-e] <vcs project url> ... | ||||
|       %prog [options] [-e] <local project path> ... | ||||
|       %prog [options] <archive url/path> ...""" | ||||
| 
 | ||||
|     def add_options(self) -> None: | ||||
|         self.cmd_opts.add_option( | ||||
|             "-w", | ||||
|             "--wheel-dir", | ||||
|             dest="wheel_dir", | ||||
|             metavar="dir", | ||||
|             default=os.curdir, | ||||
|             help=( | ||||
|                 "Build wheels into <dir>, where the default is the " | ||||
|                 "current working directory." | ||||
|             ), | ||||
|         ) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_binary()) | ||||
|         self.cmd_opts.add_option(cmdoptions.only_binary()) | ||||
|         self.cmd_opts.add_option(cmdoptions.prefer_binary()) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_build_isolation()) | ||||
|         self.cmd_opts.add_option(cmdoptions.use_pep517()) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_use_pep517()) | ||||
|         self.cmd_opts.add_option(cmdoptions.check_build_deps()) | ||||
|         self.cmd_opts.add_option(cmdoptions.constraints()) | ||||
|         self.cmd_opts.add_option(cmdoptions.editable()) | ||||
|         self.cmd_opts.add_option(cmdoptions.requirements()) | ||||
|         self.cmd_opts.add_option(cmdoptions.src()) | ||||
|         self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) | ||||
|         self.cmd_opts.add_option(cmdoptions.no_deps()) | ||||
|         self.cmd_opts.add_option(cmdoptions.progress_bar()) | ||||
| 
 | ||||
|         self.cmd_opts.add_option( | ||||
|             "--no-verify", | ||||
|             dest="no_verify", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             help="Don't verify if built wheel is valid.", | ||||
|         ) | ||||
| 
 | ||||
|         self.cmd_opts.add_option(cmdoptions.config_settings()) | ||||
|         self.cmd_opts.add_option(cmdoptions.build_options()) | ||||
|         self.cmd_opts.add_option(cmdoptions.global_options()) | ||||
| 
 | ||||
|         self.cmd_opts.add_option( | ||||
|             "--pre", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             help=( | ||||
|                 "Include pre-release and development versions. By default, " | ||||
|                 "pip only finds stable versions." | ||||
|             ), | ||||
|         ) | ||||
| 
 | ||||
|         self.cmd_opts.add_option(cmdoptions.require_hashes()) | ||||
| 
 | ||||
|         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) | ||||
| 
 | ||||
|     @with_cleanup | ||||
|     def run(self, options: Values, args: List[str]) -> int: | ||||
|         session = self.get_default_session(options) | ||||
| 
 | ||||
|         finder = self._build_package_finder(options, session) | ||||
| 
 | ||||
|         options.wheel_dir = normalize_path(options.wheel_dir) | ||||
|         ensure_dir(options.wheel_dir) | ||||
| 
 | ||||
|         build_tracker = self.enter_context(get_build_tracker()) | ||||
| 
 | ||||
|         directory = TempDirectory( | ||||
|             delete=not options.no_clean, | ||||
|             kind="wheel", | ||||
|             globally_managed=True, | ||||
|         ) | ||||
| 
 | ||||
|         reqs = self.get_requirements(args, options, finder, session) | ||||
|         check_legacy_setup_py_options(options, reqs) | ||||
| 
 | ||||
|         wheel_cache = WheelCache(options.cache_dir) | ||||
| 
 | ||||
|         preparer = self.make_requirement_preparer( | ||||
|             temp_build_dir=directory, | ||||
|             options=options, | ||||
|             build_tracker=build_tracker, | ||||
|             session=session, | ||||
|             finder=finder, | ||||
|             download_dir=options.wheel_dir, | ||||
|             use_user_site=False, | ||||
|             verbosity=self.verbosity, | ||||
|         ) | ||||
| 
 | ||||
|         resolver = self.make_resolver( | ||||
|             preparer=preparer, | ||||
|             finder=finder, | ||||
|             options=options, | ||||
|             wheel_cache=wheel_cache, | ||||
|             ignore_requires_python=options.ignore_requires_python, | ||||
|             use_pep517=options.use_pep517, | ||||
|         ) | ||||
| 
 | ||||
|         self.trace_basic_info(finder) | ||||
| 
 | ||||
|         requirement_set = resolver.resolve(reqs, check_supported_wheels=True) | ||||
| 
 | ||||
|         reqs_to_build: List[InstallRequirement] = [] | ||||
|         for req in requirement_set.requirements.values(): | ||||
|             if req.is_wheel: | ||||
|                 preparer.save_linked_requirement(req) | ||||
|             elif should_build_for_wheel_command(req): | ||||
|                 reqs_to_build.append(req) | ||||
| 
 | ||||
|         preparer.prepare_linked_requirements_more(requirement_set.requirements.values()) | ||||
| 
 | ||||
|         # build wheels | ||||
|         build_successes, build_failures = build( | ||||
|             reqs_to_build, | ||||
|             wheel_cache=wheel_cache, | ||||
|             verify=(not options.no_verify), | ||||
|             build_options=options.build_options or [], | ||||
|             global_options=options.global_options or [], | ||||
|         ) | ||||
|         for req in build_successes: | ||||
|             assert req.link and req.link.is_wheel | ||||
|             assert req.local_file_path | ||||
|             # copy from cache to target directory | ||||
|             try: | ||||
|                 shutil.copy(req.local_file_path, options.wheel_dir) | ||||
|             except OSError as e: | ||||
|                 logger.warning( | ||||
|                     "Building wheel for %s failed: %s", | ||||
|                     req.name, | ||||
|                     e, | ||||
|                 ) | ||||
|                 build_failures.append(req) | ||||
|         if len(build_failures) != 0: | ||||
|             raise CommandError("Failed to build one or more wheels") | ||||
| 
 | ||||
|         return SUCCESS | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Tykayn
						Tykayn