pip 25.3__py3-none-any.whl → 26.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. pip/__init__.py +1 -1
  2. pip/_internal/build_env.py +194 -5
  3. pip/_internal/cli/base_command.py +11 -0
  4. pip/_internal/cli/cmdoptions.py +157 -0
  5. pip/_internal/cli/index_command.py +20 -0
  6. pip/_internal/cli/main.py +11 -6
  7. pip/_internal/cli/main_parser.py +3 -1
  8. pip/_internal/cli/parser.py +93 -33
  9. pip/_internal/cli/progress_bars.py +4 -2
  10. pip/_internal/cli/req_command.py +99 -23
  11. pip/_internal/commands/cache.py +24 -0
  12. pip/_internal/commands/completion.py +2 -1
  13. pip/_internal/commands/download.py +8 -4
  14. pip/_internal/commands/index.py +13 -6
  15. pip/_internal/commands/install.py +36 -29
  16. pip/_internal/commands/list.py +14 -16
  17. pip/_internal/commands/lock.py +16 -8
  18. pip/_internal/commands/wheel.py +8 -13
  19. pip/_internal/exceptions.py +76 -3
  20. pip/_internal/index/collector.py +2 -3
  21. pip/_internal/index/package_finder.py +84 -18
  22. pip/_internal/locations/__init__.py +1 -2
  23. pip/_internal/locations/_sysconfig.py +4 -1
  24. pip/_internal/models/link.py +18 -14
  25. pip/_internal/models/release_control.py +92 -0
  26. pip/_internal/models/selection_prefs.py +6 -3
  27. pip/_internal/network/auth.py +6 -2
  28. pip/_internal/network/download.py +4 -5
  29. pip/_internal/network/session.py +14 -10
  30. pip/_internal/operations/install/wheel.py +1 -2
  31. pip/_internal/operations/prepare.py +2 -3
  32. pip/_internal/req/constructors.py +3 -1
  33. pip/_internal/req/pep723.py +41 -0
  34. pip/_internal/req/req_file.py +10 -1
  35. pip/_internal/resolution/resolvelib/factory.py +12 -1
  36. pip/_internal/resolution/resolvelib/requirements.py +7 -3
  37. pip/_internal/self_outdated_check.py +6 -13
  38. pip/_internal/utils/datetime.py +18 -0
  39. pip/_internal/utils/filesystem.py +40 -1
  40. pip/_internal/utils/logging.py +34 -2
  41. pip/_internal/utils/misc.py +18 -12
  42. pip/_internal/utils/pylock.py +116 -0
  43. pip/_internal/utils/unpacking.py +1 -1
  44. pip/_internal/vcs/versioncontrol.py +3 -1
  45. pip/_vendor/cachecontrol/__init__.py +6 -3
  46. pip/_vendor/cachecontrol/adapter.py +0 -1
  47. pip/_vendor/cachecontrol/controller.py +1 -1
  48. pip/_vendor/cachecontrol/filewrapper.py +3 -1
  49. pip/_vendor/certifi/__init__.py +1 -1
  50. pip/_vendor/certifi/cacert.pem +0 -332
  51. pip/_vendor/idna/LICENSE.md +1 -1
  52. pip/_vendor/idna/codec.py +1 -1
  53. pip/_vendor/idna/core.py +1 -1
  54. pip/_vendor/idna/idnadata.py +72 -6
  55. pip/_vendor/idna/package_data.py +1 -1
  56. pip/_vendor/idna/uts46data.py +891 -731
  57. pip/_vendor/packaging/__init__.py +1 -1
  58. pip/_vendor/packaging/_elffile.py +0 -1
  59. pip/_vendor/packaging/_manylinux.py +36 -36
  60. pip/_vendor/packaging/_musllinux.py +1 -1
  61. pip/_vendor/packaging/_parser.py +22 -10
  62. pip/_vendor/packaging/_structures.py +8 -0
  63. pip/_vendor/packaging/_tokenizer.py +23 -25
  64. pip/_vendor/packaging/licenses/__init__.py +13 -11
  65. pip/_vendor/packaging/licenses/_spdx.py +41 -1
  66. pip/_vendor/packaging/markers.py +64 -38
  67. pip/_vendor/packaging/metadata.py +143 -27
  68. pip/_vendor/packaging/pylock.py +635 -0
  69. pip/_vendor/packaging/requirements.py +5 -10
  70. pip/_vendor/packaging/specifiers.py +219 -170
  71. pip/_vendor/packaging/tags.py +15 -20
  72. pip/_vendor/packaging/utils.py +19 -24
  73. pip/_vendor/packaging/version.py +315 -105
  74. pip/_vendor/platformdirs/version.py +2 -2
  75. pip/_vendor/platformdirs/windows.py +7 -1
  76. pip/_vendor/vendor.txt +5 -5
  77. {pip-25.3.dist-info → pip-26.0.dist-info}/METADATA +2 -2
  78. {pip-25.3.dist-info → pip-26.0.dist-info}/RECORD +103 -100
  79. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/AUTHORS.txt +18 -0
  80. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/idna/LICENSE.md +1 -1
  81. pip/_internal/models/pylock.py +0 -188
  82. {pip-25.3.dist-info → pip-26.0.dist-info}/WHEEL +0 -0
  83. {pip-25.3.dist-info → pip-26.0.dist-info}/entry_points.txt +0 -0
  84. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/LICENSE.txt +0 -0
  85. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +0 -0
  86. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/certifi/LICENSE +0 -0
  87. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/dependency_groups/LICENSE.txt +0 -0
  88. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distlib/LICENSE.txt +0 -0
  89. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distro/LICENSE +0 -0
  90. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/msgpack/COPYING +0 -0
  91. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE +0 -0
  92. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +0 -0
  93. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.BSD +0 -0
  94. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pkg_resources/LICENSE +0 -0
  95. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/platformdirs/LICENSE +0 -0
  96. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pygments/LICENSE +0 -0
  97. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +0 -0
  98. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/requests/LICENSE +0 -0
  99. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/resolvelib/LICENSE +0 -0
  100. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/rich/LICENSE +0 -0
  101. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli/LICENSE +0 -0
  102. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli_w/LICENSE +0 -0
  103. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/truststore/LICENSE +0 -0
  104. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/urllib3/LICENSE.txt +0 -0
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import logging
6
6
  import optparse
7
+ import os
8
+ import re
7
9
  import shutil
8
10
  import sys
9
11
  import textwrap
@@ -11,8 +13,12 @@ from collections.abc import Generator
11
13
  from contextlib import suppress
12
14
  from typing import Any, NoReturn
13
15
 
16
+ from pip._vendor.rich.markup import escape
17
+ from pip._vendor.rich.theme import Theme
18
+
14
19
  from pip._internal.cli.status_codes import UNKNOWN_ERROR
15
20
  from pip._internal.configuration import Configuration, ConfigurationError
21
+ from pip._internal.utils.logging import PipConsole
16
22
  from pip._internal.utils.misc import redact_auth_from_url, strtobool
17
23
 
18
24
  logger = logging.getLogger(__name__)
@@ -21,6 +27,17 @@ logger = logging.getLogger(__name__)
21
27
  class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
22
28
  """A prettier/less verbose help formatter for optparse."""
23
29
 
30
+ styles = {
31
+ "optparse.shortargs": "green",
32
+ "optparse.longargs": "cyan",
33
+ "optparse.groups": "bold blue",
34
+ "optparse.metavar": "yellow",
35
+ }
36
+ highlights = {
37
+ r"\s(-{1}[\w]+[\w-]*)": "shortargs", # highlight -letter as short args
38
+ r"\s(-{2}[\w]+[\w-]*)": "longargs", # highlight --words as long args
39
+ }
40
+
24
41
  def __init__(self, *args: Any, **kwargs: Any) -> None:
25
42
  # help position must be aligned with __init__.parseopts.description
26
43
  kwargs["max_help_position"] = 30
@@ -29,61 +46,82 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
29
46
  super().__init__(*args, **kwargs)
30
47
 
31
48
  def format_option_strings(self, option: optparse.Option) -> str:
32
- return self._format_option_strings(option)
33
-
34
- def _format_option_strings(
35
- self, option: optparse.Option, mvarfmt: str = " <{}>", optsep: str = ", "
36
- ) -> str:
37
- """
38
- Return a comma-separated list of option strings and metavars.
39
-
40
- :param option: tuple of (short opt, long opt), e.g: ('-f', '--format')
41
- :param mvarfmt: metavar format string
42
- :param optsep: separator
43
- """
49
+ """Return a comma-separated list of option strings and metavars."""
44
50
  opts = []
45
51
 
46
52
  if option._short_opts:
47
- opts.append(option._short_opts[0])
53
+ opts.append(f"[optparse.shortargs]{option._short_opts[0]}[/]")
48
54
  if option._long_opts:
49
- opts.append(option._long_opts[0])
55
+ opts.append(f"[optparse.longargs]{option._long_opts[0]}[/]")
50
56
  if len(opts) > 1:
51
- opts.insert(1, optsep)
57
+ opts.insert(1, ", ")
52
58
 
53
59
  if option.takes_value():
54
60
  assert option.dest is not None
55
61
  metavar = option.metavar or option.dest.lower()
56
- opts.append(mvarfmt.format(metavar.lower()))
62
+ opts.append(f" [optparse.metavar]<{escape(metavar.lower())}>[/]")
57
63
 
58
64
  return "".join(opts)
59
65
 
66
+ def format_option(self, option: optparse.Option) -> str:
67
+ """Overridden method with Rich support."""
68
+ # fmt: off
69
+ result = []
70
+ opts = self.option_strings[option]
71
+ opt_width = self.help_position - self.current_indent - 2
72
+ # Remove the rich style tags before calculating width during
73
+ # text wrap calculations. Also store the length removed to adjust
74
+ # the padding in the else branch.
75
+ stripped = re.sub(r"(\[[a-z.]+\])|(\[\/\])", "", opts)
76
+ style_tag_length = len(opts) - len(stripped)
77
+ if len(stripped) > opt_width:
78
+ opts = "%*s%s\n" % (self.current_indent, "", opts) # noqa: UP031
79
+ indent_first = self.help_position
80
+ else: # start help on same line as opts
81
+ opts = "%*s%-*s " % (self.current_indent, "", # noqa: UP031
82
+ opt_width + style_tag_length, opts)
83
+ indent_first = 0
84
+ result.append(opts)
85
+ if option.help:
86
+ help_text = self.expand_default(option)
87
+ help_lines = textwrap.wrap(help_text, self.help_width)
88
+ result.append("%*s%s\n" % (indent_first, "", help_lines[0])) # noqa: UP031
89
+ result.extend(["%*s%s\n" % (self.help_position, "", line) # noqa: UP031
90
+ for line in help_lines[1:]])
91
+ elif opts[-1] != "\n":
92
+ result.append("\n")
93
+ return "".join(result)
94
+ # fmt: on
95
+
60
96
  def format_heading(self, heading: str) -> str:
61
97
  if heading == "Options":
62
98
  return ""
63
- return heading + ":\n"
99
+ return "[optparse.groups]" + escape(heading) + ":[/]\n"
64
100
 
65
101
  def format_usage(self, usage: str) -> str:
66
102
  """
67
103
  Ensure there is only one newline between usage and the first heading
68
104
  if there is no description.
69
105
  """
70
- msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " "))
106
+ contents = self.indent_lines(textwrap.dedent(usage), " ")
107
+ msg = f"\n[optparse.groups]Usage:[/] {escape(contents)}\n"
71
108
  return msg
72
109
 
73
110
  def format_description(self, description: str | None) -> str:
74
111
  # leave full control over description to us
75
112
  if description:
76
113
  if hasattr(self.parser, "main"):
77
- label = "Commands"
114
+ label = "[optparse.groups]Commands:[/]"
78
115
  else:
79
- label = "Description"
116
+ label = "[optparse.groups]Description:[/]"
117
+
80
118
  # some doc strings have initial newlines, some don't
81
119
  description = description.lstrip("\n")
82
120
  # some doc strings have final newlines and spaces, some don't
83
121
  description = description.rstrip()
84
122
  # dedent, then reindent
85
123
  description = self.indent_lines(textwrap.dedent(description), " ")
86
- description = f"{label}:\n{description}\n"
124
+ description = f"{label}\n{description}\n"
87
125
  return description
88
126
  else:
89
127
  return ""
@@ -91,10 +129,17 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
91
129
  def format_epilog(self, epilog: str | None) -> str:
92
130
  # leave full control over epilog to us
93
131
  if epilog:
94
- return epilog
132
+ return escape(epilog)
95
133
  else:
96
134
  return ""
97
135
 
136
+ def expand_default(self, option: optparse.Option) -> str:
137
+ """Overridden HelpFormatter.expand_default() which colorizes flags."""
138
+ help = escape(super().expand_default(option))
139
+ for regex, style in self.highlights.items():
140
+ help = re.sub(regex, rf"[optparse.{style}] \1[/]", help)
141
+ return help
142
+
98
143
  def indent_lines(self, text: str, indent: str) -> str:
99
144
  new_lines = [indent + line for line in text.split("\n")]
100
145
  return "\n".join(new_lines)
@@ -185,23 +230,25 @@ class ConfigOptionParser(CustomOptionParser):
185
230
  override_order = ["global", self.name, ":env:"]
186
231
 
187
232
  # Pool the options into different groups
188
- section_items: dict[str, list[tuple[str, Any]]] = {
189
- name: [] for name in override_order
233
+ # Use a dict because we need to implement the fallthrough logic after PR 12201
234
+ # was merged which removed the fallthrough logic for options
235
+ section_items_dict: dict[str, dict[str, Any]] = {
236
+ name: {} for name in override_order
190
237
  }
191
238
 
192
- for _, value in self.config.items(): # noqa: PERF102
239
+ for _, value in self.config.items():
193
240
  for section_key, val in value.items():
194
- # ignore empty values
195
- if not val:
196
- logger.debug(
197
- "Ignoring configuration key '%s' as its value is empty.",
198
- section_key,
199
- )
200
- continue
201
241
 
202
242
  section, key = section_key.split(".", 1)
203
243
  if section in override_order:
204
- section_items[section].append((key, val))
244
+ section_items_dict[section][key] = val
245
+
246
+ # Now that we a dict of items per section, convert to list of tuples
247
+ # Make sure we completely remove empty values again
248
+ section_items = {
249
+ name: [(k, v) for k, v in section_items_dict[name].items() if v]
250
+ for name in override_order
251
+ }
205
252
 
206
253
  # Yield each group in their override order
207
254
  for section in override_order:
@@ -296,3 +343,16 @@ class ConfigOptionParser(CustomOptionParser):
296
343
  def error(self, msg: str) -> NoReturn:
297
344
  self.print_usage(sys.stderr)
298
345
  self.exit(UNKNOWN_ERROR, f"{msg}\n")
346
+
347
+ def print_help(self, file: Any = None) -> None:
348
+ # This is unfortunate but necessary since arguments may have not been
349
+ # parsed yet at this point, so detect --no-color manually.
350
+ no_color = (
351
+ "--no-color" in sys.argv
352
+ or bool(strtobool(os.environ.get("PIP_NO_COLOR", "no") or "no"))
353
+ or "NO_COLOR" in os.environ
354
+ )
355
+ console = PipConsole(
356
+ theme=Theme(PrettyHelpFormatter.styles), no_color=no_color, file=file
357
+ )
358
+ console.print(self.format_help().rstrip(), highlight=False)
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import functools
4
4
  import sys
5
5
  from collections.abc import Generator, Iterable, Iterator
6
- from typing import Callable, Literal, TypeVar
6
+ from typing import TYPE_CHECKING, Callable, Literal, TypeVar
7
7
 
8
8
  from pip._vendor.rich.progress import (
9
9
  BarColumn,
@@ -20,9 +20,11 @@ from pip._vendor.rich.progress import (
20
20
  )
21
21
 
22
22
  from pip._internal.cli.spinners import RateLimiter
23
- from pip._internal.req.req_install import InstallRequirement
24
23
  from pip._internal.utils.logging import get_console, get_indentation
25
24
 
25
+ if TYPE_CHECKING:
26
+ from pip._internal.req.req_install import InstallRequirement
27
+
26
28
  T = TypeVar("T")
27
29
  ProgressRenderer = Callable[[Iterable[T]], Iterator[T]]
28
30
  BarType = Literal["on", "off", "raw"]
@@ -13,12 +13,21 @@ from functools import partial
13
13
  from optparse import Values
14
14
  from typing import Any, Callable, TypeVar
15
15
 
16
- from pip._internal.build_env import SubprocessBuildEnvironmentInstaller
16
+ from pip._internal.build_env import (
17
+ BuildEnvironmentInstaller,
18
+ InprocessBuildEnvironmentInstaller,
19
+ SubprocessBuildEnvironmentInstaller,
20
+ )
17
21
  from pip._internal.cache import WheelCache
18
22
  from pip._internal.cli import cmdoptions
23
+ from pip._internal.cli.cmdoptions import make_target_python
19
24
  from pip._internal.cli.index_command import IndexGroupCommand
20
25
  from pip._internal.cli.index_command import SessionCommandMixin as SessionCommandMixin
21
- from pip._internal.exceptions import CommandError, PreviousBuildDirError
26
+ from pip._internal.exceptions import (
27
+ CommandError,
28
+ PreviousBuildDirError,
29
+ UnsupportedPythonVersion,
30
+ )
22
31
  from pip._internal.index.collector import LinkCollector
23
32
  from pip._internal.index.package_finder import PackageFinder
24
33
  from pip._internal.models.selection_prefs import SelectionPreferences
@@ -32,10 +41,12 @@ from pip._internal.req.constructors import (
32
41
  install_req_from_parsed_requirement,
33
42
  install_req_from_req_string,
34
43
  )
44
+ from pip._internal.req.pep723 import PEP723Exception, pep723_metadata
35
45
  from pip._internal.req.req_dependency_group import parse_dependency_groups
36
46
  from pip._internal.req.req_file import parse_requirements
37
47
  from pip._internal.req.req_install import InstallRequirement
38
48
  from pip._internal.resolution.base import BaseResolver
49
+ from pip._internal.utils.packaging import check_requires_python
39
50
  from pip._internal.utils.temp_dir import (
40
51
  TempDirectory,
41
52
  TempDirectoryTypeRegistry,
@@ -93,6 +104,31 @@ def with_cleanup(
93
104
  return wrapper
94
105
 
95
106
 
107
+ def parse_constraint_files(
108
+ constraint_files: list[str],
109
+ finder: PackageFinder,
110
+ options: Values,
111
+ session: PipSession,
112
+ ) -> list[InstallRequirement]:
113
+ requirements = []
114
+ for filename in constraint_files:
115
+ for parsed_req in parse_requirements(
116
+ filename,
117
+ constraint=True,
118
+ finder=finder,
119
+ options=options,
120
+ session=session,
121
+ ):
122
+ req_to_add = install_req_from_parsed_requirement(
123
+ parsed_req,
124
+ isolated=options.isolated_mode,
125
+ user_supplied=False,
126
+ )
127
+ requirements.append(req_to_add)
128
+
129
+ return requirements
130
+
131
+
96
132
  class RequirementCommand(IndexGroupCommand):
97
133
  def __init__(self, *args: Any, **kw: Any) -> None:
98
134
  super().__init__(*args, **kw)
@@ -152,16 +188,31 @@ class RequirementCommand(IndexGroupCommand):
152
188
  "build-constraint" in options.features_enabled
153
189
  )
154
190
 
191
+ env_installer: BuildEnvironmentInstaller
192
+ if "inprocess-build-deps" in options.features_enabled:
193
+ build_constraint_reqs = parse_constraint_files(
194
+ build_constraints, finder, options, session
195
+ )
196
+ env_installer = InprocessBuildEnvironmentInstaller(
197
+ finder=finder,
198
+ build_tracker=build_tracker,
199
+ build_constraints=build_constraint_reqs,
200
+ verbosity=verbosity,
201
+ wheel_cache=WheelCache(options.cache_dir),
202
+ )
203
+ else:
204
+ env_installer = SubprocessBuildEnvironmentInstaller(
205
+ finder,
206
+ build_constraints=build_constraints,
207
+ build_constraint_feature_enabled=build_constraint_feature_enabled,
208
+ )
209
+
155
210
  return RequirementPreparer(
156
211
  build_dir=temp_build_dir_path,
157
212
  src_dir=options.src_dir,
158
213
  download_dir=download_dir,
159
214
  build_isolation=options.build_isolation,
160
- build_isolation_installer=SubprocessBuildEnvironmentInstaller(
161
- finder,
162
- build_constraints=build_constraints,
163
- build_constraint_feature_enabled=build_constraint_feature_enabled,
164
- ),
215
+ build_isolation_installer=env_installer,
165
216
  check_build_deps=options.check_build_deps,
166
217
  build_tracker=build_tracker,
167
218
  session=session,
@@ -172,7 +223,6 @@ class RequirementCommand(IndexGroupCommand):
172
223
  lazy_wheel=lazy_wheel,
173
224
  verbosity=verbosity,
174
225
  legacy_resolver=legacy_resolver,
175
- resume_retries=options.resume_retries,
176
226
  )
177
227
 
178
228
  @classmethod
@@ -245,22 +295,14 @@ class RequirementCommand(IndexGroupCommand):
245
295
  requirements: list[InstallRequirement] = []
246
296
 
247
297
  if not should_ignore_regular_constraints(options):
248
- for filename in options.constraints:
249
- for parsed_req in parse_requirements(
250
- filename,
251
- constraint=True,
252
- finder=finder,
253
- options=options,
254
- session=session,
255
- ):
256
- req_to_add = install_req_from_parsed_requirement(
257
- parsed_req,
258
- isolated=options.isolated_mode,
259
- user_supplied=False,
260
- )
261
- requirements.append(req_to_add)
298
+ constraints = parse_constraint_files(
299
+ options.constraints, finder, options, session
300
+ )
301
+ requirements.extend(constraints)
262
302
 
263
303
  for req in args:
304
+ if not req.strip():
305
+ continue
264
306
  req_to_add = install_req_from_line(
265
307
  req,
266
308
  comes_from=None,
@@ -305,6 +347,38 @@ class RequirementCommand(IndexGroupCommand):
305
347
  )
306
348
  requirements.append(req_to_add)
307
349
 
350
+ if options.requirements_from_scripts:
351
+ if len(options.requirements_from_scripts) > 1:
352
+ raise CommandError("--requirements-from-script can only be given once")
353
+
354
+ script = options.requirements_from_scripts[0]
355
+ try:
356
+ script_metadata = pep723_metadata(script)
357
+ except PEP723Exception as exc:
358
+ raise CommandError(exc.msg)
359
+
360
+ script_requires_python = script_metadata.get("requires-python", "")
361
+
362
+ if script_requires_python and not options.ignore_requires_python:
363
+ target_python = make_target_python(options)
364
+
365
+ if not check_requires_python(
366
+ requires_python=script_requires_python,
367
+ version_info=target_python.py_version_info,
368
+ ):
369
+ raise UnsupportedPythonVersion(
370
+ f"Script {script!r} requires a different Python: "
371
+ f"{target_python.py_version} not in {script_requires_python!r}"
372
+ )
373
+
374
+ for req in script_metadata.get("dependencies", []):
375
+ req_to_add = install_req_from_req_string(
376
+ req,
377
+ isolated=options.isolated_mode,
378
+ user_supplied=True,
379
+ )
380
+ requirements.append(req_to_add)
381
+
308
382
  # If any requirement has hash options, enable hash checking.
309
383
  if any(req.has_hash_options for req in requirements):
310
384
  options.require_hashes = True
@@ -314,6 +388,7 @@ class RequirementCommand(IndexGroupCommand):
314
388
  or options.editables
315
389
  or options.requirements
316
390
  or options.dependency_groups
391
+ or options.requirements_from_scripts
317
392
  ):
318
393
  opts = {"name": self.name}
319
394
  if options.find_links:
@@ -359,7 +434,7 @@ class RequirementCommand(IndexGroupCommand):
359
434
  selection_prefs = SelectionPreferences(
360
435
  allow_yanked=True,
361
436
  format_control=options.format_control,
362
- allow_all_prereleases=options.pre,
437
+ release_control=options.release_control,
363
438
  prefer_binary=options.prefer_binary,
364
439
  ignore_requires_python=ignore_requires_python,
365
440
  )
@@ -368,4 +443,5 @@ class RequirementCommand(IndexGroupCommand):
368
443
  link_collector=link_collector,
369
444
  selection_prefs=selection_prefs,
370
445
  target_python=target_python,
446
+ uploaded_prior_to=options.uploaded_prior_to,
371
447
  )
@@ -189,7 +189,31 @@ class CacheCommand(Command):
189
189
  bytes_removed += os.stat(filename).st_size
190
190
  os.unlink(filename)
191
191
  logger.verbose("Removed %s", filename)
192
+
193
+ http_dirs = filesystem.subdirs_without_files(self._cache_dir(options, "http"))
194
+ wheel_dirs = filesystem.subdirs_without_wheels(
195
+ self._cache_dir(options, "wheels")
196
+ )
197
+ dirs = [*http_dirs, *wheel_dirs]
198
+
199
+ for subdir in dirs:
200
+ try:
201
+ for file in subdir.iterdir():
202
+ file.unlink(missing_ok=True)
203
+ subdir.rmdir()
204
+ except FileNotFoundError:
205
+ # If the directory is already gone, that's fine.
206
+ pass
207
+ logger.verbose("Removed %s", subdir)
208
+
209
+ # selfcheck.json is no longer used by pip.
210
+ selfcheck_json = self._cache_dir(options, "selfcheck.json")
211
+ if os.path.isfile(selfcheck_json):
212
+ os.remove(selfcheck_json)
213
+ logger.verbose("Removed legacy selfcheck.json file")
214
+
192
215
  logger.info("Files removed: %s (%s)", len(files), format_size(bytes_removed))
216
+ logger.info("Directories removed: %s", len(dirs))
193
217
 
194
218
  def purge_cache(self, options: Values, args: list[str]) -> None:
195
219
  if args:
@@ -14,9 +14,10 @@ COMPLETION_SCRIPTS = {
14
14
  "bash": """
15
15
  _pip_completion()
16
16
  {{
17
+ local IFS=$' \\t\\n'
17
18
  COMPREPLY=( $( COMP_WORDS="${{COMP_WORDS[*]}}" \\
18
19
  COMP_CWORD=$COMP_CWORD \\
19
- PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) )
20
+ PIP_AUTO_COMPLETE=1 "$1" 2>/dev/null ) )
20
21
  }}
21
22
  complete -o default -F _pip_completion {prog}
22
23
  """,
@@ -37,12 +37,9 @@ class DownloadCommand(RequirementCommand):
37
37
  self.cmd_opts.add_option(cmdoptions.constraints())
38
38
  self.cmd_opts.add_option(cmdoptions.build_constraints())
39
39
  self.cmd_opts.add_option(cmdoptions.requirements())
40
+ self.cmd_opts.add_option(cmdoptions.requirements_from_scripts())
40
41
  self.cmd_opts.add_option(cmdoptions.no_deps())
41
- self.cmd_opts.add_option(cmdoptions.no_binary())
42
- self.cmd_opts.add_option(cmdoptions.only_binary())
43
- self.cmd_opts.add_option(cmdoptions.prefer_binary())
44
42
  self.cmd_opts.add_option(cmdoptions.src())
45
- self.cmd_opts.add_option(cmdoptions.pre())
46
43
  self.cmd_opts.add_option(cmdoptions.require_hashes())
47
44
  self.cmd_opts.add_option(cmdoptions.progress_bar())
48
45
  self.cmd_opts.add_option(cmdoptions.no_build_isolation())
@@ -68,7 +65,13 @@ class DownloadCommand(RequirementCommand):
68
65
  self.parser,
69
66
  )
70
67
 
68
+ selection_opts = cmdoptions.make_option_group(
69
+ cmdoptions.package_selection_group,
70
+ self.parser,
71
+ )
72
+
71
73
  self.parser.insert_option_group(0, index_opts)
74
+ self.parser.insert_option_group(0, selection_opts)
72
75
  self.parser.insert_option_group(0, self.cmd_opts)
73
76
 
74
77
  @with_cleanup
@@ -80,6 +83,7 @@ class DownloadCommand(RequirementCommand):
80
83
 
81
84
  cmdoptions.check_dist_restriction(options)
82
85
  cmdoptions.check_build_constraints(options)
86
+ cmdoptions.check_release_control_exclusive(options)
83
87
 
84
88
  options.download_dir = normalize_path(options.download_dir)
85
89
  ensure_dir(options.download_dir)
@@ -6,6 +6,7 @@ from collections.abc import Iterable
6
6
  from optparse import Values
7
7
  from typing import Any, Callable
8
8
 
9
+ from pip._vendor.packaging.utils import canonicalize_name
9
10
  from pip._vendor.packaging.version import Version
10
11
 
11
12
  from pip._internal.cli import cmdoptions
@@ -40,17 +41,20 @@ class IndexCommand(IndexGroupCommand):
40
41
  cmdoptions.add_target_python_options(self.cmd_opts)
41
42
 
42
43
  self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
43
- self.cmd_opts.add_option(cmdoptions.pre())
44
44
  self.cmd_opts.add_option(cmdoptions.json())
45
- self.cmd_opts.add_option(cmdoptions.no_binary())
46
- self.cmd_opts.add_option(cmdoptions.only_binary())
47
45
 
48
46
  index_opts = cmdoptions.make_option_group(
49
47
  cmdoptions.index_group,
50
48
  self.parser,
51
49
  )
52
50
 
51
+ selection_opts = cmdoptions.make_option_group(
52
+ cmdoptions.package_selection_group,
53
+ self.parser,
54
+ )
55
+
53
56
  self.parser.insert_option_group(0, index_opts)
57
+ self.parser.insert_option_group(0, selection_opts)
54
58
  self.parser.insert_option_group(0, self.cmd_opts)
55
59
 
56
60
  def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
@@ -59,6 +63,8 @@ class IndexCommand(IndexGroupCommand):
59
63
  }
60
64
 
61
65
  def run(self, options: Values, args: list[str]) -> int:
66
+ cmdoptions.check_release_control_exclusive(options)
67
+
62
68
  handler_map = self.handler_map()
63
69
 
64
70
  # Determine action
@@ -95,7 +101,8 @@ class IndexCommand(IndexGroupCommand):
95
101
  # Pass allow_yanked=False to ignore yanked versions.
96
102
  selection_prefs = SelectionPreferences(
97
103
  allow_yanked=False,
98
- allow_all_prereleases=options.pre,
104
+ release_control=options.release_control,
105
+ format_control=options.format_control,
99
106
  ignore_requires_python=ignore_requires_python,
100
107
  )
101
108
 
@@ -103,6 +110,7 @@ class IndexCommand(IndexGroupCommand):
103
110
  link_collector=link_collector,
104
111
  selection_prefs=selection_prefs,
105
112
  target_python=target_python,
113
+ uploaded_prior_to=options.uploaded_prior_to,
106
114
  )
107
115
 
108
116
  def get_available_package_versions(self, options: Values, args: list[Any]) -> None:
@@ -124,8 +132,7 @@ class IndexCommand(IndexGroupCommand):
124
132
  candidate.version for candidate in finder.find_all_candidates(query)
125
133
  )
126
134
 
127
- if not options.pre:
128
- # Remove prereleases
135
+ if self.should_exclude_prerelease(options, canonicalize_name(query)):
129
136
  versions = (
130
137
  version for version in versions if not version.is_prerelease
131
138
  )