Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

View file

@ -2,11 +2,9 @@
Package containing all pip commands
"""
from __future__ import annotations
import importlib
from collections import namedtuple
from typing import Any
from typing import Any, Dict, Optional
from pip._internal.cli.base_command import Command
@ -19,17 +17,12 @@ CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")
# 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] = {
commands_dict: Dict[str, CommandInfo] = {
"install": CommandInfo(
"pip._internal.commands.install",
"InstallCommand",
"Install packages.",
),
"lock": CommandInfo(
"pip._internal.commands.lock",
"LockCommand",
"Generate a lock file.",
),
"download": CommandInfo(
"pip._internal.commands.download",
"DownloadCommand",
@ -125,7 +118,7 @@ def create_command(name: str, **kwargs: Any) -> Command:
return command
def get_similar_commands(name: str) -> str | None:
def get_similar_commands(name: str) -> Optional[str]:
"""Command name auto-correct."""
from difflib import get_close_matches

View file

@ -1,14 +1,13 @@
import os
import textwrap
from optparse import Values
from typing import Callable
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__)
@ -49,8 +48,8 @@ class CacheCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
return {
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,
@ -58,18 +57,15 @@ class CacheCommand(Command):
"purge": self.purge_cache,
}
def run(self, options: Values, args: list[str]) -> int:
handler_map = self.handler_map()
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 handler_map:
if not args or args[0] not in handlers:
logger.error(
"Need an action (%s) to perform.",
", ".join(sorted(handler_map)),
", ".join(sorted(handlers)),
)
return ERROR
@ -77,20 +73,20 @@ class CacheCommand(Command):
# Error handling happens here, not in the action-handlers.
try:
handler_map[action](options, args[1:])
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[str]) -> None:
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[str]) -> None:
def get_cache_info(self, options: Values, args: List[Any]) -> None:
if args:
raise CommandError("Too many arguments")
@ -132,7 +128,7 @@ class CacheCommand(Command):
logger.info(message)
def list_cache_items(self, options: Values, args: list[str]) -> None:
def list_cache_items(self, options: Values, args: List[Any]) -> None:
if len(args) > 1:
raise CommandError("Too many arguments")
@ -147,7 +143,7 @@ class CacheCommand(Command):
else:
self.format_for_abspath(files)
def format_for_human(self, files: list[str]) -> None:
def format_for_human(self, files: List[str]) -> None:
if not files:
logger.info("No locally built wheels cached.")
return
@ -160,11 +156,11 @@ class CacheCommand(Command):
logger.info("Cache contents:\n")
logger.info("\n".join(sorted(results)))
def format_for_abspath(self, files: list[str]) -> None:
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[str]) -> None:
def remove_cache_items(self, options: Values, args: List[Any]) -> None:
if len(args) > 1:
raise CommandError("Too many arguments")
@ -184,14 +180,12 @@ class CacheCommand(Command):
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))
logger.info("Files removed: %s", len(files))
def purge_cache(self, options: Values, args: list[str]) -> None:
def purge_cache(self, options: Values, args: List[Any]) -> None:
if args:
raise CommandError("Too many arguments")
@ -200,14 +194,14 @@ class CacheCommand(Command):
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]:
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]:
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:

View file

@ -1,15 +1,14 @@
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,
warn_legacy_versions_and_specifiers,
)
from pip._internal.utils.compatibility_tags import get_supported
from pip._internal.utils.misc import write_output
logger = logging.getLogger(__name__)
@ -18,19 +17,13 @@ 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:
def run(self, options: Values, args: List[str]) -> int:
package_set, parsing_probs = create_package_set_from_installed()
warn_legacy_versions_and_specifiers(package_set)
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
@ -53,13 +46,8 @@ class CheckCommand(Command):
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:
if missing or conflicting or parsing_probs:
return ERROR
else:
write_output("No broken requirements found.")

View file

@ -1,6 +1,7 @@
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
@ -37,18 +38,12 @@ COMPLETION_SCRIPTS = {
""",
"fish": """
function __fish_complete_pip
set -lx COMP_WORDS \\
(commandline --current-process --tokenize --cut-at-cursor) \\
(commandline --current-token --cut-at-cursor)
set -lx COMP_CWORD (math (count $COMP_WORDS) - 1)
set -lx COMP_WORDS (commandline -o) ""
set -lx COMP_CWORD ( \\
math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
)
set -lx PIP_AUTO_COMPLETE 1
set -l completions
if string match -q '2.*' $version
set completions (eval $COMP_WORDS[1])
else
set completions ($COMP_WORDS[1])
end
string split \\ -- $completions
string split \\ -- (eval $COMP_WORDS[1])
end
complete -fa "(__fish_complete_pip)" -c {prog}
""",
@ -118,7 +113,7 @@ class CompletionCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: list[str]) -> int:
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)]

View file

@ -1,10 +1,8 @@
from __future__ import annotations
import logging
import os
import subprocess
from optparse import Values
from typing import Any, Callable
from typing import Any, List, Optional
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import ERROR, SUCCESS
@ -95,8 +93,8 @@ class ConfigurationCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
return {
def run(self, options: Values, args: List[str]) -> int:
handlers = {
"list": self.list_values,
"edit": self.open_in_editor,
"get": self.get_name,
@ -105,14 +103,11 @@ class ConfigurationCommand(Command):
"debug": self.list_config_values,
}
def run(self, options: Values, args: list[str]) -> int:
handler_map = self.handler_map()
# Determine action
if not args or args[0] not in handler_map:
if not args or args[0] not in handlers:
logger.error(
"Need an action (%s) to perform.",
", ".join(sorted(handler_map)),
", ".join(sorted(handlers)),
)
return ERROR
@ -136,14 +131,14 @@ class ConfigurationCommand(Command):
# Error handling happens here, not in the action-handlers.
try:
handler_map[action](options, args[1:])
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) -> Kind | None:
def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]:
file_options = [
key
for key, value in (
@ -173,32 +168,31 @@ class ConfigurationCommand(Command):
"(--user, --site, --global) to perform."
)
def list_values(self, options: Values, args: list[str]) -> None:
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()):
for key, value in sorted(value.items()):
write_output("%s=%r", key, value)
write_output("%s=%r", key, value)
def get_name(self, options: Values, args: list[str]) -> None:
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:
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:
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:
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)
@ -212,15 +206,13 @@ class ConfigurationCommand(Command):
file_exists = os.path.exists(fname)
write_output("%s, exists: %r", fname, file_exists)
if file_exists:
self.print_config_file_values(variant, fname)
self.print_config_file_values(variant)
def print_config_file_values(self, variant: Kind, fname: str) -> None:
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():
if name == fname:
for confname, confvalue in value.items():
write_output("%s: %s", confname, confvalue)
write_output("%s: %s", name, value)
def print_env_var_values(self) -> None:
"""Get key-values pairs present as environment variables"""
@ -230,7 +222,7 @@ class ConfigurationCommand(Command):
env_var = f"PIP_{key.upper()}"
write_output("%s=%r", env_var, value)
def open_in_editor(self, options: Values, args: list[str]) -> None:
def open_in_editor(self, options: Values, args: List[str]) -> None:
editor = self._determine_editor(options)
fname = self.configuration.get_file_to_edit()
@ -252,7 +244,7 @@ class ConfigurationCommand(Command):
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:
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 = (

View file

@ -1,12 +1,11 @@
from __future__ import annotations
import importlib.resources
import locale
import logging
import os
import sys
from optparse import Values
from types import ModuleType
from typing import Any
from typing import Any, Dict, List, Optional
import pip._vendor
from pip._vendor.certifi import where
@ -18,7 +17,6 @@ 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
@ -36,8 +34,8 @@ def show_sys_implementation() -> None:
show_value("name", implementation_name)
def create_vendor_txt_map() -> dict[str, str]:
with open_text_resource("pip._vendor", "vendor.txt") as f:
def create_vendor_txt_map() -> Dict[str, str]:
with importlib.resources.open_text("pip._vendor", "vendor.txt") as f:
# Purge non version specifying lines.
# Also, remove any space prefix or suffixes (including comments).
lines = [
@ -48,7 +46,7 @@ def create_vendor_txt_map() -> dict[str, str]:
return dict(line.split("==", 1) for line in lines)
def get_module_from_module_name(module_name: str) -> ModuleType | None:
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.
@ -66,7 +64,7 @@ def get_module_from_module_name(module_name: str) -> ModuleType | None:
raise
def get_vendor_version_from_module(module_name: str) -> str | None:
def get_vendor_version_from_module(module_name: str) -> Optional[str]:
module = get_module_from_module_name(module_name)
version = getattr(module, "__version__", None)
@ -81,7 +79,7 @@ def get_vendor_version_from_module(module_name: str) -> str | None:
return version
def show_actual_vendor_versions(vendor_txt_versions: dict[str, str]) -> None:
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.
"""
@ -171,7 +169,7 @@ class DebugCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
self.parser.config.load()
def run(self, options: Values, args: list[str]) -> int:
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 "

View file

@ -1,12 +1,14 @@
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
@ -35,9 +37,9 @@ class DownloadCommand(RequirementCommand):
def add_options(self) -> None:
self.cmd_opts.add_option(cmdoptions.constraints())
self.cmd_opts.add_option(cmdoptions.build_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())
@ -47,6 +49,7 @@ class DownloadCommand(RequirementCommand):
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())
@ -72,14 +75,13 @@ class DownloadCommand(RequirementCommand):
self.parser.insert_option_group(0, self.cmd_opts)
@with_cleanup
def run(self, options: Values, args: list[str]) -> int:
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)
cmdoptions.check_build_constraints(options)
options.download_dir = normalize_path(options.download_dir)
ensure_dir(options.download_dir)
@ -103,6 +105,7 @@ class DownloadCommand(RequirementCommand):
)
reqs = self.get_requirements(args, options, finder, session)
check_legacy_setup_py_options(options, reqs)
preparer = self.make_requirement_preparer(
temp_build_dir=directory,
@ -120,6 +123,7 @@ class DownloadCommand(RequirementCommand):
finder=finder,
options=options,
ignore_requires_python=options.ignore_requires_python,
use_pep517=options.use_pep517,
py_version_info=options.python_version,
)
@ -127,15 +131,16 @@ class DownloadCommand(RequirementCommand):
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
downloaded: list[str] = []
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())
requirement_set.warn_legacy_versions_and_specifiers()
if downloaded:
write_output("Successfully downloaded %s", " ".join(downloaded))

View file

@ -1,5 +1,6 @@
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
@ -12,11 +13,12 @@ def _should_suppress_build_backends() -> bool:
return sys.version_info < (3, 12)
def _dev_pkgs() -> set[str]:
def _dev_pkgs() -> AbstractSet[str]:
pkgs = {"pip"}
if _should_suppress_build_backends():
pkgs |= {"setuptools", "distribute", "wheel"}
pkgs |= {"setuptools", "distribute", "wheel", "pkg-resources"}
return pkgs
@ -28,9 +30,9 @@ class FreezeCommand(Command):
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(
@ -84,7 +86,7 @@ class FreezeCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: list[str]) -> int:
def run(self, options: Values, args: List[str]) -> int:
skip = set(stdlib_pkgs)
if not options.freeze_all:
skip.update(_dev_pkgs())

View file

@ -2,6 +2,7 @@ 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
@ -36,7 +37,7 @@ class HashCommand(Command):
)
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: list[str]) -> int:
def run(self, options: Values, args: List[str]) -> int:
if not args:
self.parser.print_usage(sys.stderr)
return ERROR

View file

@ -1,4 +1,5 @@
from optparse import Values
from typing import List
from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import SUCCESS
@ -12,7 +13,7 @@ class HelpCommand(Command):
%prog <command>"""
ignore_require_venv = True
def run(self, options: Values, args: list[str]) -> int:
def run(self, options: Values, args: List[str]) -> int:
from pip._internal.commands import (
commands_dict,
create_command,

View file

@ -1,20 +1,13 @@
from __future__ import annotations
import json
import logging
from collections.abc import Iterable
from optparse import Values
from typing import Any, Callable
from typing import Any, Iterable, List, Optional, Union
from pip._vendor.packaging.version import Version
from pip._vendor.packaging.version import LegacyVersion, 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 (
get_installed_distribution,
print_dist_installation_info,
)
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
@ -41,7 +34,6 @@ class IndexCommand(IndexGroupCommand):
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
self.cmd_opts.add_option(cmdoptions.pre())
self.cmd_opts.add_option(cmdoptions.json())
self.cmd_opts.add_option(cmdoptions.no_binary())
self.cmd_opts.add_option(cmdoptions.only_binary())
@ -53,19 +45,22 @@ class IndexCommand(IndexGroupCommand):
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, self.cmd_opts)
def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
return {
def run(self, options: Values, args: List[str]) -> int:
handlers = {
"versions": self.get_available_package_versions,
}
def run(self, options: Values, args: list[str]) -> int:
handler_map = self.handler_map()
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 handler_map:
if not args or args[0] not in handlers:
logger.error(
"Need an action (%s) to perform.",
", ".join(sorted(handler_map)),
", ".join(sorted(handlers)),
)
return ERROR
@ -73,7 +68,7 @@ class IndexCommand(IndexGroupCommand):
# Error handling happens here, not in the action-handlers.
try:
handler_map[action](options, args[1:])
handlers[action](options, args[1:])
except PipError as e:
logger.error(e.args[0])
return ERROR
@ -84,8 +79,8 @@ class IndexCommand(IndexGroupCommand):
self,
options: Values,
session: PipSession,
target_python: TargetPython | None = None,
ignore_requires_python: bool | None = None,
target_python: Optional[TargetPython] = None,
ignore_requires_python: Optional[bool] = None,
) -> PackageFinder:
"""
Create a package finder appropriate to the index command.
@ -105,7 +100,7 @@ class IndexCommand(IndexGroupCommand):
target_python=target_python,
)
def get_available_package_versions(self, options: Values, args: list[Any]) -> None:
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")
@ -120,7 +115,7 @@ class IndexCommand(IndexGroupCommand):
ignore_requires_python=options.ignore_requires_python,
)
versions: Iterable[Version] = (
versions: Iterable[Union[LegacyVersion, Version]] = (
candidate.version for candidate in finder.find_all_candidates(query)
)
@ -139,21 +134,6 @@ class IndexCommand(IndexGroupCommand):
formatted_versions = [str(ver) for ver in sorted(versions, reverse=True)]
latest = formatted_versions[0]
dist = get_installed_distribution(query)
if options.json:
structured_output = {
"name": query,
"versions": formatted_versions,
"latest": latest,
}
if dist is not None:
structured_output["installed_version"] = str(dist.version)
write_output(json.dumps(structured_output))
else:
write_output(f"{query} ({latest})")
write_output("Available versions: {}".format(", ".join(formatted_versions)))
print_dist_installation_info(latest, dist)
write_output(f"{query} ({latest})")
write_output("Available versions: {}".format(", ".join(formatted_versions)))
print_dist_installation_info(query, latest)

View file

@ -1,13 +1,13 @@
import logging
from optparse import Values
from typing import Any
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.req_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
@ -45,7 +45,7 @@ class InspectCommand(Command):
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:
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,
@ -62,8 +62,8 @@ class InspectCommand(Command):
print_json(data=output)
return SUCCESS
def _dist_to_dict(self, dist: BaseDistribution) -> dict[str, Any]:
res: dict[str, Any] = {
def _dist_to_dict(self, dist: BaseDistribution) -> Dict[str, Any]:
res: Dict[str, Any] = {
"metadata": dist.metadata_dict,
"metadata_location": dist.info_location,
}

View file

@ -1,5 +1,3 @@
from __future__ import annotations
import errno
import json
import operator
@ -7,32 +5,20 @@ import os
import shutil
import site
from optparse import SUPPRESS_HELP, Values
from pathlib import Path
from typing import List, Optional
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.requests.exceptions import InvalidProxyURL
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,
warn_if_run_as_root,
with_cleanup,
)
from pip._internal.cli.status_codes import ERROR, SUCCESS
from pip._internal.exceptions import (
CommandError,
InstallationError,
InstallWheelBuildError,
)
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
@ -41,6 +27,7 @@ from pip._internal.operations.check import ConflictDetails, check_install_confli
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
@ -50,7 +37,6 @@ from pip._internal.utils.misc import (
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
@ -58,7 +44,7 @@ from pip._internal.utils.virtualenv import (
running_under_virtualenv,
virtualenv_no_global,
)
from pip._internal.wheel_builder import build
from pip._internal.wheel_builder import build, should_build_for_install_command
logger = getLogger(__name__)
@ -86,7 +72,6 @@ class InstallCommand(RequirementCommand):
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.build_constraints())
self.cmd_opts.add_option(cmdoptions.no_deps())
self.cmd_opts.add_option(cmdoptions.pre())
@ -210,10 +195,12 @@ class InstallCommand(RequirementCommand):
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",
@ -276,7 +263,7 @@ class InstallCommand(RequirementCommand):
)
@with_cleanup
def run(self, options: Values, args: list[str]) -> int:
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'")
@ -301,7 +288,6 @@ class InstallCommand(RequirementCommand):
if options.upgrade:
upgrade_strategy = options.upgrade_strategy
cmdoptions.check_build_constraints(options)
cmdoptions.check_dist_restriction(options, check_target=True)
logger.verbose("Using %s", get_pip_version())
@ -313,8 +299,8 @@ class InstallCommand(RequirementCommand):
isolated_mode=options.isolated_mode,
)
target_temp_dir: TempDirectory | None = None
target_temp_dir_path: str | None = None
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)
@ -333,6 +319,8 @@ class InstallCommand(RequirementCommand):
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)
@ -352,6 +340,7 @@ class InstallCommand(RequirementCommand):
try:
reqs = self.get_requirements(args, options, finder, session)
check_legacy_setup_py_options(options, reqs)
wheel_cache = WheelCache(options.cache_dir)
@ -380,7 +369,7 @@ class InstallCommand(RequirementCommand):
ignore_requires_python=options.ignore_requires_python,
force_reinstall=options.force_reinstall,
upgrade_strategy=upgrade_strategy,
py_version_info=options.python_version,
use_pep517=options.use_pep517,
)
self.trace_basic_info(finder)
@ -398,6 +387,9 @@ class InstallCommand(RequirementCommand):
json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
if options.dry_run:
# In non dry-run mode, the legacy versions and specifiers check
# will be done as part of conflict detection.
requirement_set.warn_legacy_versions_and_specifiers()
would_install_items = sorted(
(r.metadata["name"], r.metadata["version"])
for r in requirement_set.requirements_to_install
@ -409,13 +401,6 @@ class InstallCommand(RequirementCommand):
)
return SUCCESS
# If there is any more preparation to do for the actual installation, do
# so now. This includes actually downloading the files in the case that
# we have been using PEP-658 metadata so far.
preparer.prepare_linked_requirements_more(
requirement_set.requirements.values()
)
try:
pip_req = requirement_set.get_requirement("pip")
except KeyError:
@ -427,22 +412,31 @@ class InstallCommand(RequirementCommand):
protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
reqs_to_build = [
r for r in requirement_set.requirements_to_install if not r.is_wheel
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 InstallWheelBuildError(build_failures)
raise InstallationError(
"Could not build wheels for {}, which is required to "
"install 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: ConflictDetails | None = None
conflicts: Optional[ConflictDetails] = None
should_warn_about_conflicts = (
not options.ignore_dependencies and options.warn_about_conflicts
)
@ -457,13 +451,13 @@ class InstallCommand(RequirementCommand):
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,
progress_bar=options.progress_bar,
)
lib_locations = get_lib_location_guesses(
@ -475,21 +469,17 @@ class InstallCommand(RequirementCommand):
)
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)
items = []
for result in installed:
item = result.name
try:
installed_dist = env.get_distribution(item)
if installed_dist is not None:
item = f"{item}-{installed_dist.version}"
except Exception:
pass
items.append(item)
if conflicts is not None:
self._warn_about_conflicts(
@ -497,7 +487,7 @@ class InstallCommand(RequirementCommand):
resolver_variant=self.determine_resolver_variant(options),
)
installed_desc = " ".join(summary)
installed_desc = " ".join(items)
if installed_desc:
write_output(
"Successfully installed %s",
@ -579,8 +569,8 @@ class InstallCommand(RequirementCommand):
shutil.move(os.path.join(lib_dir, item), target_item_dir)
def _determine_conflicts(
self, to_install: list[InstallRequirement]
) -> ConflictDetails | None:
self, to_install: List[InstallRequirement]
) -> Optional[ConflictDetails]:
try:
return check_install_conflicts(to_install)
except Exception:
@ -597,7 +587,7 @@ class InstallCommand(RequirementCommand):
if not missing and not conflicting:
return
parts: list[str] = []
parts: List[str] = []
if resolver_variant == "legacy":
parts.append(
"pip's legacy dependency resolver does not consider dependency "
@ -643,11 +633,11 @@ class InstallCommand(RequirementCommand):
def get_lib_location_guesses(
user: bool = False,
home: str | None = None,
root: str | None = None,
home: Optional[str] = None,
root: Optional[str] = None,
isolated: bool = False,
prefix: str | None = None,
) -> list[str]:
prefix: Optional[str] = None,
) -> List[str]:
scheme = get_scheme(
"",
user=user,
@ -659,7 +649,7 @@ def get_lib_location_guesses(
return [scheme.purelib, scheme.platlib]
def site_packages_writable(root: str | None, isolated: bool) -> bool:
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))
@ -667,10 +657,10 @@ def site_packages_writable(root: str | None, isolated: bool) -> bool:
def decide_user_install(
use_user_site: bool | None,
prefix_path: str | None = None,
target_dir: str | None = None,
root_path: str | None = None,
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.
@ -687,7 +677,6 @@ def decide_user_install(
logger.debug("Non-user install by explicit request")
return False
# If we have been asked for a user install explicitly, check compatibility.
if use_user_site:
if prefix_path:
raise CommandError(
@ -699,13 +688,6 @@ def decide_user_install(
"Can not perform a '--user' install. User site-packages "
"are not visible in this virtualenv."
)
# Catch all remaining cases which honour the site.ENABLE_USER_SITE
# value, such as a plain Python installation (e.g. no virtualenv).
if not site.ENABLE_USER_SITE:
raise InstallationError(
"Can not perform a '--user' install. User site-packages "
"are disabled for this Python."
)
logger.debug("User install by explicit request")
return True
@ -773,31 +755,20 @@ def create_os_error_message(
parts.append(permissions_part)
parts.append(".\n")
# Suggest to check "pip config debug" in case of invalid proxy
if type(error) is InvalidProxyURL:
# 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(
'Consider checking your local proxy configuration with "pip config debug"'
"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"
)
parts.append(".\n")
# On Windows, errors like EINVAL or ENOENT may occur
# if a file or folder name exceeds 255 characters,
# or if the full path exceeds 260 characters and long path support isn't enabled.
# This condition checks for such cases and adds a hint to the error output.
if WINDOWS and error.errno in (errno.EINVAL, errno.ENOENT) and error.filename:
if any(len(part) > 255 for part in Path(error.filename).parts):
parts.append(
"HINT: This error might be caused by a file or folder name exceeding "
"255 characters, which is a Windows limitation even if long paths "
"are enabled.\n "
)
if 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"

View file

@ -1,27 +1,24 @@
from __future__ import annotations
import json
import logging
from collections.abc import Generator, Sequence
from email.parser import Parser
from optparse import Values
from typing import TYPE_CHECKING, cast
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 InvalidVersion, Version
from pip._internal.cli import cmdoptions
from pip._internal.cli.index_command import IndexGroupCommand
from pip._internal.cli.req_command import IndexGroupCommand
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.exceptions import CommandError
from pip._internal.index.collector import LinkCollector
from pip._internal.index.package_finder import PackageFinder
from pip._internal.metadata import BaseDistribution, get_environment
from pip._internal.models.selection_prefs import SelectionPreferences
from pip._internal.network.session import PipSession
from pip._internal.utils.compat import stdlib_pkgs
from pip._internal.utils.misc import tabulate, write_output
if TYPE_CHECKING:
from pip._internal.index.package_finder import PackageFinder
from pip._internal.network.session import PipSession
from pip._internal.metadata.base import DistributionVersion
class _DistWithLatestInfo(BaseDistribution):
"""Give the distribution object a couple of extra fields.
@ -30,12 +27,14 @@ if TYPE_CHECKING:
makes the rest of the code much cleaner.
"""
latest_version: Version
latest_version: DistributionVersion
latest_filetype: str
_ProcessedDists = Sequence[_DistWithLatestInfo]
from pip._vendor.packaging.version import parse
logger = logging.getLogger(__name__)
@ -129,7 +128,7 @@ class ListCommand(IndexGroupCommand):
"--include-editable",
action="store_true",
dest="include_editable",
help="Include editable package in output.",
help="Include editable package from output.",
default=True,
)
self.cmd_opts.add_option(cmdoptions.list_exclude())
@ -138,20 +137,12 @@ class ListCommand(IndexGroupCommand):
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.
@ -165,7 +156,7 @@ class ListCommand(IndexGroupCommand):
selection_prefs=selection_prefs,
)
def run(self, options: Values, args: list[str]) -> int:
def run(self, options: Values, args: List[str]) -> int:
if options.outdated and options.uptodate:
raise CommandError("Options --outdated and --uptodate cannot be combined.")
@ -180,7 +171,7 @@ class ListCommand(IndexGroupCommand):
if options.excludes:
skip.update(canonicalize_name(n) for n in options.excludes)
packages: _ProcessedDists = [
packages: "_ProcessedDists" = [
cast("_DistWithLatestInfo", d)
for d in get_environment(options.path).iter_installed_distributions(
local_only=options.local,
@ -207,26 +198,26 @@ class ListCommand(IndexGroupCommand):
return SUCCESS
def get_outdated(
self, packages: _ProcessedDists, options: Values
) -> _ProcessedDists:
self, packages: "_ProcessedDists", options: Values
) -> "_ProcessedDists":
return [
dist
for dist in self.iter_packages_latest_infos(packages, options)
if dist.latest_version > dist.version
if parse(str(dist.latest_version)) > parse(str(dist.version))
]
def get_uptodate(
self, packages: _ProcessedDists, options: Values
) -> _ProcessedDists:
self, packages: "_ProcessedDists", options: Values
) -> "_ProcessedDists":
return [
dist
for dist in self.iter_packages_latest_infos(packages, options)
if dist.latest_version == dist.version
if parse(str(dist.latest_version)) == parse(str(dist.version))
]
def get_not_required(
self, packages: _ProcessedDists, options: Values
) -> _ProcessedDists:
self, packages: "_ProcessedDists", options: Values
) -> "_ProcessedDists":
dep_keys = {
canonicalize_name(dep.name)
for dist in packages
@ -239,14 +230,14 @@ class ListCommand(IndexGroupCommand):
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]:
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,
) -> _DistWithLatestInfo | None:
dist: "_DistWithLatestInfo",
) -> Optional["_DistWithLatestInfo"]:
all_candidates = finder.find_all_candidates(dist.canonical_name)
if not options.pre:
# Remove prereleases
@ -277,7 +268,7 @@ class ListCommand(IndexGroupCommand):
yield dist
def output_package_listing(
self, packages: _ProcessedDists, options: Values
self, packages: "_ProcessedDists", options: Values
) -> None:
packages = sorted(
packages,
@ -288,19 +279,17 @@ class ListCommand(IndexGroupCommand):
self.output_package_listing_columns(data, header)
elif options.list_format == "freeze":
for dist in packages:
try:
req_string = f"{dist.raw_name}=={dist.version}"
except InvalidVersion:
req_string = f"{dist.raw_name}==={dist.raw_version}"
if options.verbose >= 1:
write_output("%s (%s)", req_string, dist.location)
write_output(
"%s==%s (%s)", dist.raw_name, dist.version, dist.location
)
else:
write_output(req_string)
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]
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:
@ -317,8 +306,8 @@ class ListCommand(IndexGroupCommand):
def format_for_columns(
pkgs: _ProcessedDists, options: Values
) -> tuple[list[list[str]], list[str]]:
pkgs: "_ProcessedDists", options: Values
) -> Tuple[List[List[str]], List[str]]:
"""
Convert the package data into something usable
by output_package_listing_columns.
@ -329,40 +318,25 @@ def format_for_columns(
if running_outdated:
header.extend(["Latest", "Type"])
def wheel_build_tag(dist: BaseDistribution) -> str | None:
try:
wheel_file = dist.read_text("WHEEL")
except FileNotFoundError:
return None
return Parser().parsestr(wheel_file).get("Build")
build_tags = [wheel_build_tag(p) for p in pkgs]
has_build_tags = any(build_tags)
if has_build_tags:
header.append("Build")
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")
has_editables = any(x.editable for x in pkgs)
if has_editables:
header.append("Editable project location")
data = []
for i, proj in enumerate(pkgs):
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]
row = [proj.raw_name, str(proj.version)]
if running_outdated:
row.append(str(proj.latest_version))
row.append(proj.latest_filetype)
if has_build_tags:
row.append(build_tags[i] or "")
if has_editables:
row.append(proj.editable_project_location or "")
@ -376,16 +350,12 @@ def format_for_columns(
return data, header
def format_for_json(packages: _ProcessedDists, options: Values) -> str:
def format_for_json(packages: "_ProcessedDists", options: Values) -> str:
data = []
for dist in packages:
try:
version = str(dist.version)
except InvalidVersion:
version = dist.raw_version
info = {
"name": dist.raw_name,
"version": version,
"version": str(dist.version),
}
if options.verbose >= 1:
info["location"] = dist.location or ""

View file

@ -1,167 +0,0 @@
import sys
from optparse import Values
from pathlib import Path
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.models.pylock import Pylock, is_valid_pylock_file_name
from pip._internal.operations.build.build_tracker import get_build_tracker
from pip._internal.utils.logging import getLogger
from pip._internal.utils.misc import (
get_pip_version,
)
from pip._internal.utils.temp_dir import TempDirectory
logger = getLogger(__name__)
class LockCommand(RequirementCommand):
"""
EXPERIMENTAL - Lock packages and their dependencies from:
- PyPI (and other indexes) using requirement specifiers.
- VCS project urls.
- Local project directories.
- Local or remote source archives.
pip also supports locking from "requirements files", which provide an easy
way to specify a whole environment to be installed.
The generated lock file is only guaranteed to be valid for the current
python version and platform.
"""
usage = """
%prog [options] [-e] <local project path> ...
%prog [options] <requirement specifier> [package-index-options] ...
%prog [options] -r <requirements file> [package-index-options] ...
%prog [options] <archive url/path> ..."""
def add_options(self) -> None:
self.cmd_opts.add_option(
cmdoptions.PipOption(
"--output",
"-o",
dest="output_file",
metavar="path",
type="path",
default="pylock.toml",
help="Lock file name (default=pylock.toml). Use - for stdout.",
)
)
self.cmd_opts.add_option(cmdoptions.requirements())
self.cmd_opts.add_option(cmdoptions.constraints())
self.cmd_opts.add_option(cmdoptions.build_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(cmdoptions.src())
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.check_build_deps())
self.cmd_opts.add_option(cmdoptions.config_settings())
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())
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:
logger.verbose("Using %s", get_pip_version())
logger.warning(
"pip lock is currently an experimental command. "
"It may be removed/changed in a future release "
"without prior warning."
)
cmdoptions.check_build_constraints(options)
session = self.get_default_session(options)
finder = self._build_package_finder(
options=options,
session=session,
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,
)
reqs = self.get_requirements(args, options, finder, session)
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=False,
verbosity=self.verbosity,
)
resolver = self.make_resolver(
preparer=preparer,
finder=finder,
options=options,
wheel_cache=wheel_cache,
use_user_site=False,
ignore_installed=True,
ignore_requires_python=options.ignore_requires_python,
upgrade_strategy="to-satisfy-only",
)
self.trace_basic_info(finder)
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
if options.output_file == "-":
base_dir = Path.cwd()
else:
output_file_path = Path(options.output_file)
if not is_valid_pylock_file_name(output_file_path):
logger.warning(
"%s is not a valid lock file name.",
output_file_path,
)
base_dir = output_file_path.parent
pylock_toml = Pylock.from_install_requirements(
requirement_set.requirements.values(), base_dir=base_dir
).as_toml()
if options.output_file == "-":
sys.stdout.write(pylock_toml)
else:
output_file_path.write_text(pylock_toml, encoding="utf-8")
return SUCCESS

View file

@ -1,5 +1,3 @@
from __future__ import annotations
import logging
import shutil
import sys
@ -7,7 +5,7 @@ import textwrap
import xmlrpc.client
from collections import OrderedDict
from optparse import Values
from typing import TypedDict
from typing import TYPE_CHECKING, Dict, List, Optional
from pip._vendor.packaging.version import parse as parse_version
@ -16,17 +14,18 @@ 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.metadata.base import BaseDistribution
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:
from typing import TypedDict
class TransformedHit(TypedDict):
name: str
summary: str
versions: list[str]
class TransformedHit(TypedDict):
name: str
summary: str
versions: List[str]
logger = logging.getLogger(__name__)
@ -51,7 +50,7 @@ class SearchCommand(Command, SessionCommandMixin):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: list[str]) -> int:
def run(self, options: Values, args: List[str]) -> int:
if not args:
raise CommandError("Missing required argument (search query).")
query = args
@ -67,7 +66,7 @@ class SearchCommand(Command, SessionCommandMixin):
return SUCCESS
return NO_MATCHES_FOUND
def search(self, query: list[str], options: Values) -> list[dict[str, str]]:
def search(self, query: List[str], options: Values) -> List[Dict[str, str]]:
index_url = options.index
session = self.get_default_session(options)
@ -77,21 +76,22 @@ class SearchCommand(Command, SessionCommandMixin):
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}"
message = "XMLRPC request failed [code: {code}]\n{string}".format(
code=fault.faultCode,
string=fault.faultString,
)
raise CommandError(message)
assert isinstance(hits, list)
return hits
def transform_hits(hits: list[dict[str, str]]) -> list[TransformedHit]:
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()
packages: Dict[str, "TransformedHit"] = OrderedDict()
for hit in hits:
name = hit["name"]
summary = hit["summary"]
@ -113,7 +113,9 @@ def transform_hits(hits: list[dict[str, str]]) -> list[TransformedHit]:
return list(packages.values())
def print_dist_installation_info(latest: str, dist: BaseDistribution | None) -> None:
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:
@ -130,15 +132,10 @@ def print_dist_installation_info(latest: str, dist: BaseDistribution | None) ->
write_output("LATEST: %s", latest)
def get_installed_distribution(name: str) -> BaseDistribution | None:
env = get_default_environment()
return env.get_distribution(name)
def print_results(
hits: list[TransformedHit],
name_column_width: int | None = None,
terminal_width: int | None = None,
hits: List["TransformedHit"],
name_column_width: Optional[int] = None,
terminal_width: Optional[int] = None,
) -> None:
if not hits:
return
@ -168,11 +165,10 @@ def print_results(
line = f"{name_latest:{name_column_width}} - {summary}"
try:
write_output(line)
dist = get_installed_distribution(name)
print_dist_installation_info(latest, dist)
print_dist_installation_info(name, latest)
except UnicodeEncodeError:
pass
def highest_version(versions: list[str]) -> str:
def highest_version(versions: List[str]) -> str:
return max(versions, key=parse_version)

View file

@ -1,12 +1,7 @@
from __future__ import annotations
import logging
import string
from collections.abc import Generator, Iterable, Iterator
from optparse import Values
from typing import NamedTuple
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
@ -17,13 +12,6 @@ from pip._internal.utils.misc import write_output
logger = logging.getLogger(__name__)
def normalize_project_url_label(label: str) -> str:
# This logic is from PEP 753 (Well-known Project URLs in Metadata).
chars_to_remove = string.punctuation + string.whitespace
removal_map = str.maketrans("", "", chars_to_remove)
return label.translate(removal_map).lower()
class ShowCommand(Command):
"""
Show information about one or more installed packages.
@ -47,7 +35,7 @@ class ShowCommand(Command):
self.parser.insert_option_group(0, self.cmd_opts)
def run(self, options: Values, args: list[str]) -> int:
def run(self, options: Values, args: List[str]) -> int:
if not args:
logger.warning("ERROR: Please provide a package name or names.")
return ERROR
@ -65,24 +53,23 @@ class _PackageInfo(NamedTuple):
name: str
version: str
location: str
editable_project_location: str | None
requires: list[str]
required_by: list[str]
editable_project_location: Optional[str]
requires: List[str]
required_by: List[str]
installer: str
metadata_version: str
classifiers: list[str]
classifiers: List[str]
summary: str
homepage: str
project_urls: list[str]
project_urls: List[str]
author: str
author_email: str
license: str
license_expression: str
entry_points: list[str]
files: list[str] | None
entry_points: List[str]
files: Optional[List[str]]
def search_packages_info(query: list[str]) -> Generator[_PackageInfo, None, None]:
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
@ -113,19 +100,8 @@ def search_packages_info(query: list[str]) -> Generator[_PackageInfo, None, None
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"]
requires = sorted((req.name for req in dist.iter_dependencies()), key=str.lower)
required_by = sorted(_get_requiring_packages(dist), key=str.lower)
try:
entry_points_text = dist.read_text("entry_points.txt")
@ -135,27 +111,15 @@ def search_packages_info(query: list[str]) -> Generator[_PackageInfo, None, None
files_iter = dist.iter_declared_entries()
if files_iter is None:
files: list[str] | None = 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).
for url in project_urls:
url_label, url = url.split(",", maxsplit=1)
normalized_label = normalize_project_url_label(url_label)
if normalized_label == "homepage":
homepage = url.strip()
break
yield _PackageInfo(
name=dist.raw_name,
version=dist.raw_version,
version=str(dist.version),
location=dist.location or "",
editable_project_location=dist.editable_project_location,
requires=requires,
@ -164,12 +128,11 @@ def search_packages_info(query: list[str]) -> Generator[_PackageInfo, None, None
metadata_version=dist.metadata_version or "",
classifiers=metadata.get_all("Classifier", []),
summary=metadata.get("Summary", ""),
homepage=homepage,
project_urls=project_urls,
homepage=metadata.get("Home-page", ""),
project_urls=metadata.get_all("Project-URL", []),
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,
)
@ -189,18 +152,13 @@ def print_results(
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("License: %s", dist.license)
write_output("Location: %s", dist.location)
if dist.editable_project_location is not None:
write_output(

View file

@ -1,11 +1,12 @@
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.req_command import SessionCommandMixin, warn_if_run_as_root
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.exceptions import InstallationError
from pip._internal.req import parse_requirements
@ -16,7 +17,6 @@ from pip._internal.req.constructors import (
from pip._internal.utils.misc import (
check_externally_managed,
protect_pip_from_modification_on_windows,
warn_if_run_as_root,
)
logger = logging.getLogger(__name__)
@ -61,7 +61,7 @@ class UninstallCommand(Command, SessionCommandMixin):
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:
def run(self, options: Values, args: List[str]) -> int:
session = self.get_default_session(options)
reqs_to_uninstall = {}

View file

@ -2,6 +2,7 @@ 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
@ -11,10 +12,11 @@ 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
from pip._internal.wheel_builder import build, should_build_for_wheel_command
logger = logging.getLogger(__name__)
@ -56,9 +58,9 @@ class WheelCommand(RequirementCommand):
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.build_constraints())
self.cmd_opts.add_option(cmdoptions.editable())
self.cmd_opts.add_option(cmdoptions.requirements())
self.cmd_opts.add_option(cmdoptions.src())
@ -75,6 +77,8 @@ class WheelCommand(RequirementCommand):
)
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",
@ -97,9 +101,7 @@ class WheelCommand(RequirementCommand):
self.parser.insert_option_group(0, self.cmd_opts)
@with_cleanup
def run(self, options: Values, args: list[str]) -> int:
cmdoptions.check_build_constraints(options)
def run(self, options: Values, args: List[str]) -> int:
session = self.get_default_session(options)
finder = self._build_package_finder(options, session)
@ -116,6 +118,7 @@ class WheelCommand(RequirementCommand):
)
reqs = self.get_requirements(args, options, finder, session)
check_legacy_setup_py_options(options, reqs)
wheel_cache = WheelCache(options.cache_dir)
@ -136,26 +139,30 @@ class WheelCommand(RequirementCommand):
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)
preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
reqs_to_build: list[InstallRequirement] = []
reqs_to_build: List[InstallRequirement] = []
for req in requirement_set.requirements.values():
if req.is_wheel:
preparer.save_linked_requirement(req)
else:
elif should_build_for_wheel_command(req):
reqs_to_build.append(req)
preparer.prepare_linked_requirements_more(requirement_set.requirements.values())
requirement_set.warn_legacy_versions_and_specifiers()
# 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