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
@@ -13,22 +13,33 @@ from __future__ import annotations
13
13
  import abc
14
14
  import itertools
15
15
  import re
16
- from typing import Callable, Iterable, Iterator, TypeVar, Union
16
+ from typing import Callable, Final, Iterable, Iterator, TypeVar, Union
17
17
 
18
18
  from .utils import canonicalize_version
19
- from .version import Version
19
+ from .version import InvalidVersion, Version
20
20
 
21
21
  UnparsedVersion = Union[Version, str]
22
22
  UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
23
23
  CallableOperator = Callable[[Version, str], bool]
24
24
 
25
25
 
26
- def _coerce_version(version: UnparsedVersion) -> Version:
26
+ def _coerce_version(version: UnparsedVersion) -> Version | None:
27
27
  if not isinstance(version, Version):
28
- version = Version(version)
28
+ try:
29
+ version = Version(version)
30
+ except InvalidVersion:
31
+ return None
29
32
  return version
30
33
 
31
34
 
35
+ def _public_version(version: Version) -> Version:
36
+ return version.__replace__(local=None)
37
+
38
+
39
+ def _base_version(version: Version) -> Version:
40
+ return version.__replace__(pre=None, post=None, dev=None, local=None)
41
+
42
+
32
43
  class InvalidSpecifier(ValueError):
33
44
  """
34
45
  Raised when attempting to create a :class:`Specifier` with a specifier
@@ -42,6 +53,14 @@ class InvalidSpecifier(ValueError):
42
53
 
43
54
 
44
55
  class BaseSpecifier(metaclass=abc.ABCMeta):
56
+ __slots__ = ()
57
+ __match_args__ = ("_str",)
58
+
59
+ @property
60
+ def _str(self) -> str:
61
+ """Internal property for match_args"""
62
+ return str(self)
63
+
45
64
  @abc.abstractmethod
46
65
  def __str__(self) -> str:
47
66
  """
@@ -73,7 +92,7 @@ class BaseSpecifier(metaclass=abc.ABCMeta):
73
92
  prereleases or it can be set to ``None`` (the default) to use default semantics.
74
93
  """
75
94
 
76
- @prereleases.setter
95
+ @prereleases.setter # noqa: B027
77
96
  def prereleases(self, value: bool) -> None:
78
97
  """Setter for :attr:`prereleases`.
79
98
 
@@ -106,6 +125,8 @@ class Specifier(BaseSpecifier):
106
125
  comma-separated version specifiers (which is what package metadata contains).
107
126
  """
108
127
 
128
+ __slots__ = ("_prereleases", "_spec", "_spec_version")
129
+
109
130
  _operator_regex_str = r"""
110
131
  (?P<operator>(~=|==|!=|<=|>=|<|>|===))
111
132
  """
@@ -204,11 +225,11 @@ class Specifier(BaseSpecifier):
204
225
  """
205
226
 
206
227
  _regex = re.compile(
207
- r"^\s*" + _operator_regex_str + _version_regex_str + r"\s*$",
228
+ r"\s*" + _operator_regex_str + _version_regex_str + r"\s*",
208
229
  re.VERBOSE | re.IGNORECASE,
209
230
  )
210
231
 
211
- _operators = {
232
+ _operators: Final = {
212
233
  "~=": "compatible",
213
234
  "==": "equal",
214
235
  "!=": "not_equal",
@@ -232,7 +253,7 @@ class Specifier(BaseSpecifier):
232
253
  :raises InvalidSpecifier:
233
254
  If the given specifier is invalid (i.e. bad syntax).
234
255
  """
235
- match = self._regex.search(spec)
256
+ match = self._regex.fullmatch(spec)
236
257
  if not match:
237
258
  raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
238
259
 
@@ -244,33 +265,62 @@ class Specifier(BaseSpecifier):
244
265
  # Store whether or not this Specifier should accept prereleases
245
266
  self._prereleases = prereleases
246
267
 
247
- # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515
248
- @property # type: ignore[override]
249
- def prereleases(self) -> bool:
268
+ # Specifier version cache
269
+ self._spec_version: tuple[str, Version] | None = None
270
+
271
+ def _get_spec_version(self, version: str) -> Version | None:
272
+ """One element cache, as only one spec Version is needed per Specifier."""
273
+ if self._spec_version is not None and self._spec_version[0] == version:
274
+ return self._spec_version[1]
275
+
276
+ version_specifier = _coerce_version(version)
277
+ if version_specifier is None:
278
+ return None
279
+
280
+ self._spec_version = (version, version_specifier)
281
+ return version_specifier
282
+
283
+ def _require_spec_version(self, version: str) -> Version:
284
+ """Get spec version, asserting it's valid (not for === operator).
285
+
286
+ This method should only be called for operators where version
287
+ strings are guaranteed to be valid PEP 440 versions (not ===).
288
+ """
289
+ spec_version = self._get_spec_version(version)
290
+ assert spec_version is not None
291
+ return spec_version
292
+
293
+ @property
294
+ def prereleases(self) -> bool | None:
250
295
  # If there is an explicit prereleases set for this, then we'll just
251
296
  # blindly use that.
252
297
  if self._prereleases is not None:
253
298
  return self._prereleases
254
299
 
255
- # Look at all of our specifiers and determine if they are inclusive
256
- # operators, and if they are if they are including an explicit
257
- # prerelease.
258
- operator, version = self._spec
259
- if operator in ["==", ">=", "<=", "~=", "===", ">", "<"]:
260
- # The == specifier can include a trailing .*, if it does we
261
- # want to remove before parsing.
262
- if operator == "==" and version.endswith(".*"):
263
- version = version[:-2]
264
-
265
- # Parse the version, and if it is a pre-release than this
266
- # specifier allows pre-releases.
267
- if Version(version).is_prerelease:
300
+ # Only the "!=" operator does not imply prereleases when
301
+ # the version in the specifier is a prerelease.
302
+ operator, version_str = self._spec
303
+ if operator != "!=":
304
+ # The == specifier with trailing .* cannot include prereleases
305
+ # e.g. "==1.0a1.*" is not valid.
306
+ if operator == "==" and version_str.endswith(".*"):
307
+ return False
308
+
309
+ # "===" can have arbitrary string versions, so we cannot parse
310
+ # those, we take prereleases as unknown (None) for those.
311
+ version = self._get_spec_version(version_str)
312
+ if version is None:
313
+ return None
314
+
315
+ # For all other operators, use the check if spec Version
316
+ # object implies pre-releases.
317
+ if version.is_prerelease:
268
318
  return True
269
319
 
270
320
  return False
271
321
 
272
322
  @prereleases.setter
273
- def prereleases(self, value: bool) -> None:
323
+ def prereleases(self, value: bool | None) -> None:
274
324
  self._prereleases = value
275
325
 
276
326
  @property
@@ -321,11 +371,17 @@ class Specifier(BaseSpecifier):
321
371
 
322
372
  @property
323
373
  def _canonical_spec(self) -> tuple[str, str]:
374
+ operator, version = self._spec
375
+ if operator == "===" or version.endswith(".*"):
376
+ return operator, version
377
+
378
+ spec_version = self._require_spec_version(version)
379
+
324
380
  canonical_version = canonicalize_version(
325
- self._spec[1],
326
- strip_trailing_zero=(self._spec[0] != "~="),
381
+ spec_version, strip_trailing_zero=(operator != "~=")
327
382
  )
328
- return self._spec[0], canonical_version
383
+
384
+ return operator, canonical_version
329
385
 
330
386
  def __hash__(self) -> int:
331
387
  return hash(self._canonical_spec)
@@ -390,7 +446,7 @@ class Specifier(BaseSpecifier):
390
446
  if spec.endswith(".*"):
391
447
  # In the case of prefix matching we want to ignore local segment.
392
448
  normalized_prospective = canonicalize_version(
393
- prospective.public, strip_trailing_zero=False
449
+ _public_version(prospective), strip_trailing_zero=False
394
450
  )
395
451
  # Get the normalized version string ignoring the trailing .*
396
452
  normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)
@@ -415,13 +471,13 @@ class Specifier(BaseSpecifier):
415
471
  return shortened_prospective == split_spec
416
472
  else:
417
473
  # Convert our spec string into a Version
418
- spec_version = Version(spec)
474
+ spec_version = self._require_spec_version(spec)
419
475
 
420
476
  # If the specifier does not have a local segment, then we want to
421
477
  # act as if the prospective version also does not have a local
422
478
  # segment.
423
479
  if not spec_version.local:
424
- prospective = Version(prospective.public)
480
+ prospective = _public_version(prospective)
425
481
 
426
482
  return prospective == spec_version
427
483
 
@@ -432,18 +488,18 @@ class Specifier(BaseSpecifier):
432
488
  # NB: Local version identifiers are NOT permitted in the version
433
489
  # specifier, so local version labels can be universally removed from
434
490
  # the prospective version.
435
- return Version(prospective.public) <= Version(spec)
491
+ return _public_version(prospective) <= self._require_spec_version(spec)
436
492
 
437
493
  def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
438
494
  # NB: Local version identifiers are NOT permitted in the version
439
495
  # specifier, so local version labels can be universally removed from
440
496
  # the prospective version.
441
- return Version(prospective.public) >= Version(spec)
497
+ return _public_version(prospective) >= self._require_spec_version(spec)
442
498
 
443
499
  def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
444
500
  # Convert our spec to a Version instance, since we'll want to work with
445
501
  # it as a version.
446
- spec = Version(spec_str)
502
+ spec = self._require_spec_version(spec_str)
447
503
 
448
504
  # Check to see if the prospective version is less than the spec
449
505
  # version. If it's not we can short circuit and just return False now
@@ -455,9 +511,12 @@ class Specifier(BaseSpecifier):
455
511
  # includes is a pre-release version, that we do not accept pre-release
456
512
  # versions for the version mentioned in the specifier (e.g. <3.1 should
457
513
  # not match 3.1.dev0, but should match 3.0.dev0).
458
- if not spec.is_prerelease and prospective.is_prerelease:
459
- if Version(prospective.base_version) == Version(spec.base_version):
460
- return False
514
+ if (
515
+ not spec.is_prerelease
516
+ and prospective.is_prerelease
517
+ and _base_version(prospective) == _base_version(spec)
518
+ ):
519
+ return False
461
520
 
462
521
  # If we've gotten to here, it means that prospective version is both
463
522
  # less than the spec version *and* it's not a pre-release of the same
@@ -467,7 +526,7 @@ class Specifier(BaseSpecifier):
467
526
  def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
468
527
  # Convert our spec to a Version instance, since we'll want to work with
469
528
  # it as a version.
470
- spec = Version(spec_str)
529
+ spec = self._require_spec_version(spec_str)
471
530
 
472
531
  # Check to see if the prospective version is greater than the spec
473
532
  # version. If it's not we can short circuit and just return False now
@@ -479,22 +538,26 @@ class Specifier(BaseSpecifier):
479
538
  # includes is a post-release version, that we do not accept
480
539
  # post-release versions for the version mentioned in the specifier
481
540
  # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
482
- if not spec.is_postrelease and prospective.is_postrelease:
483
- if Version(prospective.base_version) == Version(spec.base_version):
484
- return False
541
+ if (
542
+ not spec.is_postrelease
543
+ and prospective.is_postrelease
544
+ and _base_version(prospective) == _base_version(spec)
545
+ ):
546
+ return False
485
547
 
486
548
  # Ensure that we do not allow a local version of the version mentioned
487
549
  # in the specifier, which is technically greater than, to match.
488
- if prospective.local is not None:
489
- if Version(prospective.base_version) == Version(spec.base_version):
490
- return False
550
+ if prospective.local is not None and _base_version(
551
+ prospective
552
+ ) == _base_version(spec):
553
+ return False
491
554
 
492
555
  # If we've gotten to here, it means that prospective version is both
493
556
  # greater than the spec version *and* it's not a pre-release of the
494
557
  # same version in the spec.
495
558
  return True
496
559
 
497
- def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
560
+ def _compare_arbitrary(self, prospective: Version | str, spec: str) -> bool:
498
561
  return str(prospective).lower() == str(spec).lower()
499
562
 
500
563
  def __contains__(self, item: str | Version) -> bool:
@@ -512,7 +575,7 @@ class Specifier(BaseSpecifier):
512
575
  >>> "1.0.0" in Specifier(">=1.2.3")
513
576
  False
514
577
  >>> "1.3.0a1" in Specifier(">=1.2.3")
515
- False
578
+ True
516
579
  >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
517
580
  True
518
581
  """
@@ -526,8 +589,8 @@ class Specifier(BaseSpecifier):
526
589
  :class:`Version` instance.
527
590
  :param prereleases:
528
591
  Whether or not to match prereleases with this Specifier. If set to
529
- ``None`` (the default), it uses :attr:`prereleases` to determine
530
- whether or not prereleases are allowed.
592
+ ``None`` (the default), it will follow the recommendation from
593
+ :pep:`440` and match prereleases, as there are no other versions.
531
594
 
532
595
  >>> Specifier(">=1.2.3").contains("1.2.3")
533
596
  True
@@ -536,31 +599,14 @@ class Specifier(BaseSpecifier):
536
599
  >>> Specifier(">=1.2.3").contains("1.0.0")
537
600
  False
538
601
  >>> Specifier(">=1.2.3").contains("1.3.0a1")
539
- False
540
- >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1")
541
602
  True
542
- >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True)
603
+ >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1")
604
+ False
605
+ >>> Specifier(">=1.2.3").contains("1.3.0a1")
543
606
  True
544
607
  """
545
608
 
546
- # Determine if prereleases are to be allowed or not.
547
- if prereleases is None:
548
- prereleases = self.prereleases
549
-
550
- # Normalize item to a Version, this allows us to have a shortcut for
551
- # "2.0" in Specifier(">=2")
552
- normalized_item = _coerce_version(item)
553
-
554
- # Determine if we should be supporting prereleases in this specifier
555
- # or not, if we do not support prereleases than we can short circuit
556
- # logic if this version is a prereleases.
557
- if normalized_item.is_prerelease and not prereleases:
558
- return False
559
-
560
- # Actually do the comparison to determine if this item is contained
561
- # within this Specifier or not.
562
- operator_callable: CallableOperator = self._get_operator(self.operator)
563
- return operator_callable(normalized_item, self.version)
609
+ return bool(list(self.filter([item], prereleases=prereleases)))
564
610
 
565
611
  def filter(
566
612
  self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
@@ -572,13 +618,8 @@ class Specifier(BaseSpecifier):
572
618
  The items in the iterable will be filtered according to the specifier.
573
619
  :param prereleases:
574
620
  Whether or not to allow prereleases in the returned iterator. If set to
575
- ``None`` (the default), it will be intelligently decide whether to allow
576
- prereleases or not (based on the :attr:`prereleases` attribute, and
577
- whether the only versions matching are prereleases).
578
-
579
- This method is smarter than just ``filter(Specifier().contains, [...])``
580
- because it implements the rule from :pep:`440` that a prerelease item
581
- SHOULD be accepted if no other versions match the given specifier.
621
+ ``None`` (the default), it will follow the recommendation from :pep:`440`
622
+ and match prereleases if there are no other versions.
582
623
 
583
624
  >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
584
625
  ['1.3']
@@ -591,40 +632,46 @@ class Specifier(BaseSpecifier):
591
632
  >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
592
633
  ['1.3', '1.5a1']
593
634
  """
635
+ prereleases_versions = []
636
+ found_non_prereleases = False
594
637
 
595
- yielded = False
596
- found_prereleases = []
638
+ # Determine if to include prereleases by default
639
+ include_prereleases = (
640
+ prereleases if prereleases is not None else self.prereleases
641
+ )
597
642
 
598
- kw = {"prereleases": prereleases if prereleases is not None else True}
643
+ # Get the matching operator
644
+ operator_callable = self._get_operator(self.operator)
599
645
 
600
- # Attempt to iterate over all the values in the iterable and if any of
601
- # them match, yield them.
646
+ # Filter versions
602
647
  for version in iterable:
603
648
  parsed_version = _coerce_version(version)
604
-
605
- if self.contains(parsed_version, **kw):
606
- # If our version is a prerelease, and we were not set to allow
607
- # prereleases, then we'll store it for later in case nothing
608
- # else matches this specifier.
609
- if parsed_version.is_prerelease and not (
610
- prereleases or self.prereleases
649
+ if parsed_version is None:
650
+ # === operator can match arbitrary (non-version) strings
651
+ if self.operator == "===" and self._compare_arbitrary(
652
+ version, self.version
611
653
  ):
612
- found_prereleases.append(version)
613
- # Either this is not a prerelease, or we should have been
614
- # accepting prereleases from the beginning.
615
- else:
616
- yielded = True
617
654
  yield version
655
+ elif operator_callable(parsed_version, self.version):
656
+ # If it's not a prerelease or prereleases are allowed, yield it directly
657
+ if not parsed_version.is_prerelease or include_prereleases:
658
+ found_non_prereleases = True
659
+ yield version
660
+ # Otherwise collect prereleases for potential later use
661
+ elif prereleases is None and self._prereleases is not False:
662
+ prereleases_versions.append(version)
618
663
 
619
- # Now that we've iterated over everything, determine if we've yielded
620
- # any values, and if we have not and we have any prereleases stored up
621
- # then we will go ahead and yield the prereleases.
622
- if not yielded and found_prereleases:
623
- for version in found_prereleases:
624
- yield version
664
+ # If no non-prereleases were found and prereleases weren't
665
+ # explicitly forbidden, yield the collected prereleases
666
+ if (
667
+ not found_non_prereleases
668
+ and prereleases is None
669
+ and self._prereleases is not False
670
+ ):
671
+ yield from prereleases_versions
625
672
 
626
673
 
627
- _prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
674
+ _prefix_regex = re.compile(r"([0-9]+)((?:a|b|c|rc)[0-9]+)")
628
675
 
629
676
 
630
677
  def _version_split(version: str) -> list[str]:
@@ -641,7 +688,7 @@ def _version_split(version: str) -> list[str]:
641
688
  result.append(epoch or "0")
642
689
 
643
690
  for item in rest.split("."):
644
- match = _prefix_regex.search(item)
691
+ match = _prefix_regex.fullmatch(item)
645
692
  if match:
646
693
  result.extend(match.groups())
647
694
  else:
@@ -694,6 +741,8 @@ class SpecifierSet(BaseSpecifier):
694
741
  specifiers (``>=3.0,!=3.1``), or no specifier at all.
695
742
  """
696
743
 
744
+ __slots__ = ("_prereleases", "_specs")
745
+
697
746
  def __init__(
698
747
  self,
699
748
  specifiers: str | Iterable[Specifier] = "",
@@ -747,10 +796,13 @@ class SpecifierSet(BaseSpecifier):
747
796
 
748
797
  # Otherwise we'll see if any of the given specifiers accept
749
798
  # prereleases, if any of them do we'll return True, otherwise False.
750
- return any(s.prereleases for s in self._specs)
799
+ if any(s.prereleases for s in self._specs):
800
+ return True
801
+
802
+ return None
751
803
 
752
804
  @prereleases.setter
753
- def prereleases(self, value: bool) -> None:
805
+ def prereleases(self, value: bool | None) -> None:
754
806
  self._prereleases = value
755
807
 
756
808
  def __repr__(self) -> str:
@@ -810,9 +862,9 @@ class SpecifierSet(BaseSpecifier):
810
862
 
811
863
  if self._prereleases is None and other._prereleases is not None:
812
864
  specifier._prereleases = other._prereleases
813
- elif self._prereleases is not None and other._prereleases is None:
814
- specifier._prereleases = self._prereleases
815
- elif self._prereleases == other._prereleases:
865
+ elif (
866
+ self._prereleases is not None and other._prereleases is None
867
+ ) or self._prereleases == other._prereleases:
816
868
  specifier._prereleases = self._prereleases
817
869
  else:
818
870
  raise ValueError(
@@ -876,7 +928,7 @@ class SpecifierSet(BaseSpecifier):
876
928
  >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
877
929
  False
878
930
  >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
879
- False
931
+ True
880
932
  >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
881
933
  True
882
934
  """
@@ -895,8 +947,11 @@ class SpecifierSet(BaseSpecifier):
895
947
  :class:`Version` instance.
896
948
  :param prereleases:
897
949
  Whether or not to match prereleases with this SpecifierSet. If set to
898
- ``None`` (the default), it uses :attr:`prereleases` to determine
899
- whether or not prereleases are allowed.
950
+ ``None`` (the default), it will follow the recommendation from :pep:`440`
951
+ and match prereleases, as there are no other versions.
952
+ :param installed:
953
+ Whether or not the item is installed. If set to ``True``, it will
954
+ accept prerelease versions even if the specifier does not allow them.
900
955
 
901
956
  >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
902
957
  True
@@ -905,39 +960,19 @@ class SpecifierSet(BaseSpecifier):
905
960
  >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
906
961
  False
907
962
  >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
908
- False
909
- >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True).contains("1.3.0a1")
910
963
  True
964
+ >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")
965
+ False
911
966
  >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
912
967
  True
913
968
  """
914
- # Ensure that our item is a Version instance.
915
- if not isinstance(item, Version):
916
- item = Version(item)
917
-
918
- # Determine if we're forcing a prerelease or not, if we're not forcing
919
- # one for this particular filter call, then we'll use whatever the
920
- # SpecifierSet thinks for whether or not we should support prereleases.
921
- if prereleases is None:
922
- prereleases = self.prereleases
969
+ version = _coerce_version(item)
923
970
 
924
- # We can determine if we're going to allow pre-releases by looking to
925
- # see if any of the underlying items supports them. If none of them do
926
- # and this item is a pre-release then we do not allow it and we can
927
- # short circuit that here.
928
- # Note: This means that 1.0.dev1 would not be contained in something
929
- # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0
930
- if not prereleases and item.is_prerelease:
931
- return False
932
-
933
- if installed and item.is_prerelease:
934
- item = Version(item.base_version)
971
+ if version is not None and installed and version.is_prerelease:
972
+ prereleases = True
935
973
 
936
- # We simply dispatch to the underlying specs here to make sure that the
937
- # given version is contained within all of them.
938
- # Note: This use of all() here means that an empty set of specifiers
939
- # will always return True, this is an explicit design decision.
940
- return all(s.contains(item, prereleases=prereleases) for s in self._specs)
974
+ check_item = item if version is None else version
975
+ return bool(list(self.filter([check_item], prereleases=prereleases)))
941
976
 
942
977
  def filter(
943
978
  self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
@@ -949,20 +984,15 @@ class SpecifierSet(BaseSpecifier):
949
984
  The items in the iterable will be filtered according to the specifier.
950
985
  :param prereleases:
951
986
  Whether or not to allow prereleases in the returned iterator. If set to
952
- ``None`` (the default), it will be intelligently decide whether to allow
953
- prereleases or not (based on the :attr:`prereleases` attribute, and
954
- whether the only versions matching are prereleases).
955
-
956
- This method is smarter than just ``filter(SpecifierSet(...).contains, [...])``
957
- because it implements the rule from :pep:`440` that a prerelease item
958
- SHOULD be accepted if no other versions match the given specifier.
987
+ ``None`` (the default), it will follow the recommendation from :pep:`440`
988
+ and match prereleases if there are no other versions.
959
989
 
960
990
  >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
961
991
  ['1.3']
962
992
  >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
963
993
  ['1.3', <Version('1.4')>]
964
994
  >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
965
- []
995
+ ['1.5a1']
966
996
  >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
967
997
  ['1.3', '1.5a1']
968
998
  >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
@@ -983,37 +1013,56 @@ class SpecifierSet(BaseSpecifier):
983
1013
  # Determine if we're forcing a prerelease or not, if we're not forcing
984
1014
  # one for this particular filter call, then we'll use whatever the
985
1015
  # SpecifierSet thinks for whether or not we should support prereleases.
986
- if prereleases is None:
1016
+ if prereleases is None and self.prereleases is not None:
987
1017
  prereleases = self.prereleases
988
1018
 
989
1019
  # If we have any specifiers, then we want to wrap our iterable in the
990
1020
  # filter method for each one, this will act as a logical AND amongst
991
1021
  # each specifier.
992
1022
  if self._specs:
1023
+ # When prereleases is None, we need to let all versions through
1024
+ # the individual filters, then decide about prereleases at the end
1025
+ # based on whether any non-prereleases matched ALL specs.
993
1026
  for spec in self._specs:
994
- iterable = spec.filter(iterable, prereleases=bool(prereleases))
995
- return iter(iterable)
996
- # If we do not have any specifiers, then we need to have a rough filter
997
- # which will filter out any pre-releases, unless there are no final
998
- # releases.
1027
+ iterable = spec.filter(
1028
+ iterable, prereleases=True if prereleases is None else prereleases
1029
+ )
1030
+
1031
+ if prereleases is not None:
1032
+ # If we have a forced prereleases value,
1033
+ # we can immediately return the iterator.
1034
+ return iter(iterable)
999
1035
  else:
1000
- filtered: list[UnparsedVersionVar] = []
1001
- found_prereleases: list[UnparsedVersionVar] = []
1002
-
1003
- for item in iterable:
1004
- parsed_version = _coerce_version(item)
1005
-
1006
- # Store any item which is a pre-release for later unless we've
1007
- # already found a final version or we are accepting prereleases
1008
- if parsed_version.is_prerelease and not prereleases:
1009
- if not filtered:
1010
- found_prereleases.append(item)
1011
- else:
1012
- filtered.append(item)
1013
-
1014
- # If we've found no items except for pre-releases, then we'll go
1015
- # ahead and use the pre-releases
1016
- if not filtered and found_prereleases and prereleases is None:
1017
- return iter(found_prereleases)
1018
-
1019
- return iter(filtered)
1036
+ # Handle empty SpecifierSet cases where prereleases is not None.
1037
+ if prereleases is True:
1038
+ return iter(iterable)
1039
+
1040
+ if prereleases is False:
1041
+ return (
1042
+ item
1043
+ for item in iterable
1044
+ if (version := _coerce_version(item)) is None
1045
+ or not version.is_prerelease
1046
+ )
1047
+
1048
+ # Finally if prereleases is None, apply PEP 440 logic:
1049
+ # exclude prereleases unless there are no final releases that matched.
1050
+ filtered_items: list[UnparsedVersionVar] = []
1051
+ found_prereleases: list[UnparsedVersionVar] = []
1052
+ found_final_release = False
1053
+
1054
+ for item in iterable:
1055
+ parsed_version = _coerce_version(item)
1056
+ # Arbitrary strings are always included as it is not
1057
+ # possible to determine if they are prereleases,
1058
+ # and they have already passed all specifiers.
1059
+ if parsed_version is None:
1060
+ filtered_items.append(item)
1061
+ found_prereleases.append(item)
1062
+ elif parsed_version.is_prerelease:
1063
+ found_prereleases.append(item)
1064
+ else:
1065
+ filtered_items.append(item)
1066
+ found_final_release = True
1067
+
1068
+ return iter(filtered_items if found_final_release else found_prereleases)