comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/site-packages/packaging/specifiers.py @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
comparison
equal deleted inserted replaced
67:0e9998148a16 68:5028fdace37b
1 # This file is dual licensed under the terms of the Apache License, Version
2 # 2.0, and the BSD License. See the LICENSE file in the root of this repository
3 # for complete details.
4 """
5 .. testsetup::
6
7 from packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier
8 from packaging.version import Version
9 """
10
11 from __future__ import annotations
12
13 import abc
14 import itertools
15 import re
16 from typing import Callable, Iterable, Iterator, TypeVar, Union
17
18 from .utils import canonicalize_version
19 from .version import Version
20
21 UnparsedVersion = Union[Version, str]
22 UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
23 CallableOperator = Callable[[Version, str], bool]
24
25
26 def _coerce_version(version: UnparsedVersion) -> Version:
27 if not isinstance(version, Version):
28 version = Version(version)
29 return version
30
31
32 class InvalidSpecifier(ValueError):
33 """
34 Raised when attempting to create a :class:`Specifier` with a specifier
35 string that is invalid.
36
37 >>> Specifier("lolwat")
38 Traceback (most recent call last):
39 ...
40 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
41 """
42
43
44 class BaseSpecifier(metaclass=abc.ABCMeta):
45 @abc.abstractmethod
46 def __str__(self) -> str:
47 """
48 Returns the str representation of this Specifier-like object. This
49 should be representative of the Specifier itself.
50 """
51
52 @abc.abstractmethod
53 def __hash__(self) -> int:
54 """
55 Returns a hash value for this Specifier-like object.
56 """
57
58 @abc.abstractmethod
59 def __eq__(self, other: object) -> bool:
60 """
61 Returns a boolean representing whether or not the two Specifier-like
62 objects are equal.
63
64 :param other: The other object to check against.
65 """
66
67 @property
68 @abc.abstractmethod
69 def prereleases(self) -> bool | None:
70 """Whether or not pre-releases as a whole are allowed.
71
72 This can be set to either ``True`` or ``False`` to explicitly enable or disable
73 prereleases or it can be set to ``None`` (the default) to use default semantics.
74 """
75
76 @prereleases.setter
77 def prereleases(self, value: bool) -> None:
78 """Setter for :attr:`prereleases`.
79
80 :param value: The value to set.
81 """
82
83 @abc.abstractmethod
84 def contains(self, item: str, prereleases: bool | None = None) -> bool:
85 """
86 Determines if the given item is contained within this specifier.
87 """
88
89 @abc.abstractmethod
90 def filter(
91 self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
92 ) -> Iterator[UnparsedVersionVar]:
93 """
94 Takes an iterable of items and filters them so that only items which
95 are contained within this specifier are allowed in it.
96 """
97
98
99 class Specifier(BaseSpecifier):
100 """This class abstracts handling of version specifiers.
101
102 .. tip::
103
104 It is generally not required to instantiate this manually. You should instead
105 prefer to work with :class:`SpecifierSet` instead, which can parse
106 comma-separated version specifiers (which is what package metadata contains).
107 """
108
109 _operator_regex_str = r"""
110 (?P<operator>(~=|==|!=|<=|>=|<|>|===))
111 """
112 _version_regex_str = r"""
113 (?P<version>
114 (?:
115 # The identity operators allow for an escape hatch that will
116 # do an exact string match of the version you wish to install.
117 # This will not be parsed by PEP 440 and we cannot determine
118 # any semantic meaning from it. This operator is discouraged
119 # but included entirely as an escape hatch.
120 (?<====) # Only match for the identity operator
121 \s*
122 [^\s;)]* # The arbitrary version can be just about anything,
123 # we match everything except for whitespace, a
124 # semi-colon for marker support, and a closing paren
125 # since versions can be enclosed in them.
126 )
127 |
128 (?:
129 # The (non)equality operators allow for wild card and local
130 # versions to be specified so we have to define these two
131 # operators separately to enable that.
132 (?<===|!=) # Only match for equals and not equals
133
134 \s*
135 v?
136 (?:[0-9]+!)? # epoch
137 [0-9]+(?:\.[0-9]+)* # release
138
139 # You cannot use a wild card and a pre-release, post-release, a dev or
140 # local version together so group them with a | and make them optional.
141 (?:
142 \.\* # Wild card syntax of .*
143 |
144 (?: # pre release
145 [-_\.]?
146 (alpha|beta|preview|pre|a|b|c|rc)
147 [-_\.]?
148 [0-9]*
149 )?
150 (?: # post release
151 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
152 )?
153 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
154 (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
155 )?
156 )
157 |
158 (?:
159 # The compatible operator requires at least two digits in the
160 # release segment.
161 (?<=~=) # Only match for the compatible operator
162
163 \s*
164 v?
165 (?:[0-9]+!)? # epoch
166 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
167 (?: # pre release
168 [-_\.]?
169 (alpha|beta|preview|pre|a|b|c|rc)
170 [-_\.]?
171 [0-9]*
172 )?
173 (?: # post release
174 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
175 )?
176 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
177 )
178 |
179 (?:
180 # All other operators only allow a sub set of what the
181 # (non)equality operators do. Specifically they do not allow
182 # local versions to be specified nor do they allow the prefix
183 # matching wild cards.
184 (?<!==|!=|~=) # We have special cases for these
185 # operators so we want to make sure they
186 # don't match here.
187
188 \s*
189 v?
190 (?:[0-9]+!)? # epoch
191 [0-9]+(?:\.[0-9]+)* # release
192 (?: # pre release
193 [-_\.]?
194 (alpha|beta|preview|pre|a|b|c|rc)
195 [-_\.]?
196 [0-9]*
197 )?
198 (?: # post release
199 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
200 )?
201 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
202 )
203 )
204 """
205
206 _regex = re.compile(
207 r"^\s*" + _operator_regex_str + _version_regex_str + r"\s*$",
208 re.VERBOSE | re.IGNORECASE,
209 )
210
211 _operators = {
212 "~=": "compatible",
213 "==": "equal",
214 "!=": "not_equal",
215 "<=": "less_than_equal",
216 ">=": "greater_than_equal",
217 "<": "less_than",
218 ">": "greater_than",
219 "===": "arbitrary",
220 }
221
222 def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
223 """Initialize a Specifier instance.
224
225 :param spec:
226 The string representation of a specifier which will be parsed and
227 normalized before use.
228 :param prereleases:
229 This tells the specifier if it should accept prerelease versions if
230 applicable or not. The default of ``None`` will autodetect it from the
231 given specifiers.
232 :raises InvalidSpecifier:
233 If the given specifier is invalid (i.e. bad syntax).
234 """
235 match = self._regex.search(spec)
236 if not match:
237 raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
238
239 self._spec: tuple[str, str] = (
240 match.group("operator").strip(),
241 match.group("version").strip(),
242 )
243
244 # Store whether or not this Specifier should accept prereleases
245 self._prereleases = prereleases
246
247 # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515
248 @property # type: ignore[override]
249 def prereleases(self) -> bool:
250 # If there is an explicit prereleases set for this, then we'll just
251 # blindly use that.
252 if self._prereleases is not None:
253 return self._prereleases
254
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:
268 return True
269
270 return False
271
272 @prereleases.setter
273 def prereleases(self, value: bool) -> None:
274 self._prereleases = value
275
276 @property
277 def operator(self) -> str:
278 """The operator of this specifier.
279
280 >>> Specifier("==1.2.3").operator
281 '=='
282 """
283 return self._spec[0]
284
285 @property
286 def version(self) -> str:
287 """The version of this specifier.
288
289 >>> Specifier("==1.2.3").version
290 '1.2.3'
291 """
292 return self._spec[1]
293
294 def __repr__(self) -> str:
295 """A representation of the Specifier that shows all internal state.
296
297 >>> Specifier('>=1.0.0')
298 <Specifier('>=1.0.0')>
299 >>> Specifier('>=1.0.0', prereleases=False)
300 <Specifier('>=1.0.0', prereleases=False)>
301 >>> Specifier('>=1.0.0', prereleases=True)
302 <Specifier('>=1.0.0', prereleases=True)>
303 """
304 pre = (
305 f", prereleases={self.prereleases!r}"
306 if self._prereleases is not None
307 else ""
308 )
309
310 return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
311
312 def __str__(self) -> str:
313 """A string representation of the Specifier that can be round-tripped.
314
315 >>> str(Specifier('>=1.0.0'))
316 '>=1.0.0'
317 >>> str(Specifier('>=1.0.0', prereleases=False))
318 '>=1.0.0'
319 """
320 return "{}{}".format(*self._spec)
321
322 @property
323 def _canonical_spec(self) -> tuple[str, str]:
324 canonical_version = canonicalize_version(
325 self._spec[1],
326 strip_trailing_zero=(self._spec[0] != "~="),
327 )
328 return self._spec[0], canonical_version
329
330 def __hash__(self) -> int:
331 return hash(self._canonical_spec)
332
333 def __eq__(self, other: object) -> bool:
334 """Whether or not the two Specifier-like objects are equal.
335
336 :param other: The other object to check against.
337
338 The value of :attr:`prereleases` is ignored.
339
340 >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
341 True
342 >>> (Specifier("==1.2.3", prereleases=False) ==
343 ... Specifier("==1.2.3", prereleases=True))
344 True
345 >>> Specifier("==1.2.3") == "==1.2.3"
346 True
347 >>> Specifier("==1.2.3") == Specifier("==1.2.4")
348 False
349 >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
350 False
351 """
352 if isinstance(other, str):
353 try:
354 other = self.__class__(str(other))
355 except InvalidSpecifier:
356 return NotImplemented
357 elif not isinstance(other, self.__class__):
358 return NotImplemented
359
360 return self._canonical_spec == other._canonical_spec
361
362 def _get_operator(self, op: str) -> CallableOperator:
363 operator_callable: CallableOperator = getattr(
364 self, f"_compare_{self._operators[op]}"
365 )
366 return operator_callable
367
368 def _compare_compatible(self, prospective: Version, spec: str) -> bool:
369 # Compatible releases have an equivalent combination of >= and ==. That
370 # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
371 # implement this in terms of the other specifiers instead of
372 # implementing it ourselves. The only thing we need to do is construct
373 # the other specifiers.
374
375 # We want everything but the last item in the version, but we want to
376 # ignore suffix segments.
377 prefix = _version_join(
378 list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
379 )
380
381 # Add the prefix notation to the end of our string
382 prefix += ".*"
383
384 return self._get_operator(">=")(prospective, spec) and self._get_operator("==")(
385 prospective, prefix
386 )
387
388 def _compare_equal(self, prospective: Version, spec: str) -> bool:
389 # We need special logic to handle prefix matching
390 if spec.endswith(".*"):
391 # In the case of prefix matching we want to ignore local segment.
392 normalized_prospective = canonicalize_version(
393 prospective.public, strip_trailing_zero=False
394 )
395 # Get the normalized version string ignoring the trailing .*
396 normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)
397 # Split the spec out by bangs and dots, and pretend that there is
398 # an implicit dot in between a release segment and a pre-release segment.
399 split_spec = _version_split(normalized_spec)
400
401 # Split the prospective version out by bangs and dots, and pretend
402 # that there is an implicit dot in between a release segment and
403 # a pre-release segment.
404 split_prospective = _version_split(normalized_prospective)
405
406 # 0-pad the prospective version before shortening it to get the correct
407 # shortened version.
408 padded_prospective, _ = _pad_version(split_prospective, split_spec)
409
410 # Shorten the prospective version to be the same length as the spec
411 # so that we can determine if the specifier is a prefix of the
412 # prospective version or not.
413 shortened_prospective = padded_prospective[: len(split_spec)]
414
415 return shortened_prospective == split_spec
416 else:
417 # Convert our spec string into a Version
418 spec_version = Version(spec)
419
420 # If the specifier does not have a local segment, then we want to
421 # act as if the prospective version also does not have a local
422 # segment.
423 if not spec_version.local:
424 prospective = Version(prospective.public)
425
426 return prospective == spec_version
427
428 def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
429 return not self._compare_equal(prospective, spec)
430
431 def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
432 # NB: Local version identifiers are NOT permitted in the version
433 # specifier, so local version labels can be universally removed from
434 # the prospective version.
435 return Version(prospective.public) <= Version(spec)
436
437 def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
438 # NB: Local version identifiers are NOT permitted in the version
439 # specifier, so local version labels can be universally removed from
440 # the prospective version.
441 return Version(prospective.public) >= Version(spec)
442
443 def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
444 # Convert our spec to a Version instance, since we'll want to work with
445 # it as a version.
446 spec = Version(spec_str)
447
448 # Check to see if the prospective version is less than the spec
449 # version. If it's not we can short circuit and just return False now
450 # instead of doing extra unneeded work.
451 if not prospective < spec:
452 return False
453
454 # This special case is here so that, unless the specifier itself
455 # includes is a pre-release version, that we do not accept pre-release
456 # versions for the version mentioned in the specifier (e.g. <3.1 should
457 # 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
461
462 # If we've gotten to here, it means that prospective version is both
463 # less than the spec version *and* it's not a pre-release of the same
464 # version in the spec.
465 return True
466
467 def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
468 # Convert our spec to a Version instance, since we'll want to work with
469 # it as a version.
470 spec = Version(spec_str)
471
472 # Check to see if the prospective version is greater than the spec
473 # version. If it's not we can short circuit and just return False now
474 # instead of doing extra unneeded work.
475 if not prospective > spec:
476 return False
477
478 # This special case is here so that, unless the specifier itself
479 # includes is a post-release version, that we do not accept
480 # post-release versions for the version mentioned in the specifier
481 # (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
485
486 # Ensure that we do not allow a local version of the version mentioned
487 # 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
491
492 # If we've gotten to here, it means that prospective version is both
493 # greater than the spec version *and* it's not a pre-release of the
494 # same version in the spec.
495 return True
496
497 def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
498 return str(prospective).lower() == str(spec).lower()
499
500 def __contains__(self, item: str | Version) -> bool:
501 """Return whether or not the item is contained in this specifier.
502
503 :param item: The item to check for.
504
505 This is used for the ``in`` operator and behaves the same as
506 :meth:`contains` with no ``prereleases`` argument passed.
507
508 >>> "1.2.3" in Specifier(">=1.2.3")
509 True
510 >>> Version("1.2.3") in Specifier(">=1.2.3")
511 True
512 >>> "1.0.0" in Specifier(">=1.2.3")
513 False
514 >>> "1.3.0a1" in Specifier(">=1.2.3")
515 False
516 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
517 True
518 """
519 return self.contains(item)
520
521 def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
522 """Return whether or not the item is contained in this specifier.
523
524 :param item:
525 The item to check for, which can be a version string or a
526 :class:`Version` instance.
527 :param prereleases:
528 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.
531
532 >>> Specifier(">=1.2.3").contains("1.2.3")
533 True
534 >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
535 True
536 >>> Specifier(">=1.2.3").contains("1.0.0")
537 False
538 >>> Specifier(">=1.2.3").contains("1.3.0a1")
539 False
540 >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1")
541 True
542 >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True)
543 True
544 """
545
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)
564
565 def filter(
566 self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
567 ) -> Iterator[UnparsedVersionVar]:
568 """Filter items in the given iterable, that match the specifier.
569
570 :param iterable:
571 An iterable that can contain version strings and :class:`Version` instances.
572 The items in the iterable will be filtered according to the specifier.
573 :param prereleases:
574 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.
582
583 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
584 ['1.3']
585 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
586 ['1.2.3', '1.3', <Version('1.4')>]
587 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
588 ['1.5a1']
589 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
590 ['1.3', '1.5a1']
591 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
592 ['1.3', '1.5a1']
593 """
594
595 yielded = False
596 found_prereleases = []
597
598 kw = {"prereleases": prereleases if prereleases is not None else True}
599
600 # Attempt to iterate over all the values in the iterable and if any of
601 # them match, yield them.
602 for version in iterable:
603 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
611 ):
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 yield version
618
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
625
626
627 _prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
628
629
630 def _version_split(version: str) -> list[str]:
631 """Split version into components.
632
633 The split components are intended for version comparison. The logic does
634 not attempt to retain the original version string, so joining the
635 components back with :func:`_version_join` may not produce the original
636 version string.
637 """
638 result: list[str] = []
639
640 epoch, _, rest = version.rpartition("!")
641 result.append(epoch or "0")
642
643 for item in rest.split("."):
644 match = _prefix_regex.search(item)
645 if match:
646 result.extend(match.groups())
647 else:
648 result.append(item)
649 return result
650
651
652 def _version_join(components: list[str]) -> str:
653 """Join split version components into a version string.
654
655 This function assumes the input came from :func:`_version_split`, where the
656 first component must be the epoch (either empty or numeric), and all other
657 components numeric.
658 """
659 epoch, *rest = components
660 return f"{epoch}!{'.'.join(rest)}"
661
662
663 def _is_not_suffix(segment: str) -> bool:
664 return not any(
665 segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
666 )
667
668
669 def _pad_version(left: list[str], right: list[str]) -> tuple[list[str], list[str]]:
670 left_split, right_split = [], []
671
672 # Get the release segment of our versions
673 left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
674 right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
675
676 # Get the rest of our versions
677 left_split.append(left[len(left_split[0]) :])
678 right_split.append(right[len(right_split[0]) :])
679
680 # Insert our padding
681 left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0])))
682 right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0])))
683
684 return (
685 list(itertools.chain.from_iterable(left_split)),
686 list(itertools.chain.from_iterable(right_split)),
687 )
688
689
690 class SpecifierSet(BaseSpecifier):
691 """This class abstracts handling of a set of version specifiers.
692
693 It can be passed a single specifier (``>=3.0``), a comma-separated list of
694 specifiers (``>=3.0,!=3.1``), or no specifier at all.
695 """
696
697 def __init__(
698 self,
699 specifiers: str | Iterable[Specifier] = "",
700 prereleases: bool | None = None,
701 ) -> None:
702 """Initialize a SpecifierSet instance.
703
704 :param specifiers:
705 The string representation of a specifier or a comma-separated list of
706 specifiers which will be parsed and normalized before use.
707 May also be an iterable of ``Specifier`` instances, which will be used
708 as is.
709 :param prereleases:
710 This tells the SpecifierSet if it should accept prerelease versions if
711 applicable or not. The default of ``None`` will autodetect it from the
712 given specifiers.
713
714 :raises InvalidSpecifier:
715 If the given ``specifiers`` are not parseable than this exception will be
716 raised.
717 """
718
719 if isinstance(specifiers, str):
720 # Split on `,` to break each individual specifier into its own item, and
721 # strip each item to remove leading/trailing whitespace.
722 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
723
724 # Make each individual specifier a Specifier and save in a frozen set
725 # for later.
726 self._specs = frozenset(map(Specifier, split_specifiers))
727 else:
728 # Save the supplied specifiers in a frozen set.
729 self._specs = frozenset(specifiers)
730
731 # Store our prereleases value so we can use it later to determine if
732 # we accept prereleases or not.
733 self._prereleases = prereleases
734
735 @property
736 def prereleases(self) -> bool | None:
737 # If we have been given an explicit prerelease modifier, then we'll
738 # pass that through here.
739 if self._prereleases is not None:
740 return self._prereleases
741
742 # If we don't have any specifiers, and we don't have a forced value,
743 # then we'll just return None since we don't know if this should have
744 # pre-releases or not.
745 if not self._specs:
746 return None
747
748 # Otherwise we'll see if any of the given specifiers accept
749 # prereleases, if any of them do we'll return True, otherwise False.
750 return any(s.prereleases for s in self._specs)
751
752 @prereleases.setter
753 def prereleases(self, value: bool) -> None:
754 self._prereleases = value
755
756 def __repr__(self) -> str:
757 """A representation of the specifier set that shows all internal state.
758
759 Note that the ordering of the individual specifiers within the set may not
760 match the input string.
761
762 >>> SpecifierSet('>=1.0.0,!=2.0.0')
763 <SpecifierSet('!=2.0.0,>=1.0.0')>
764 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
765 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
766 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
767 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
768 """
769 pre = (
770 f", prereleases={self.prereleases!r}"
771 if self._prereleases is not None
772 else ""
773 )
774
775 return f"<SpecifierSet({str(self)!r}{pre})>"
776
777 def __str__(self) -> str:
778 """A string representation of the specifier set that can be round-tripped.
779
780 Note that the ordering of the individual specifiers within the set may not
781 match the input string.
782
783 >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
784 '!=1.0.1,>=1.0.0'
785 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
786 '!=1.0.1,>=1.0.0'
787 """
788 return ",".join(sorted(str(s) for s in self._specs))
789
790 def __hash__(self) -> int:
791 return hash(self._specs)
792
793 def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
794 """Return a SpecifierSet which is a combination of the two sets.
795
796 :param other: The other object to combine with.
797
798 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
799 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
800 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
801 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
802 """
803 if isinstance(other, str):
804 other = SpecifierSet(other)
805 elif not isinstance(other, SpecifierSet):
806 return NotImplemented
807
808 specifier = SpecifierSet()
809 specifier._specs = frozenset(self._specs | other._specs)
810
811 if self._prereleases is None and other._prereleases is not None:
812 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:
816 specifier._prereleases = self._prereleases
817 else:
818 raise ValueError(
819 "Cannot combine SpecifierSets with True and False prerelease "
820 "overrides."
821 )
822
823 return specifier
824
825 def __eq__(self, other: object) -> bool:
826 """Whether or not the two SpecifierSet-like objects are equal.
827
828 :param other: The other object to check against.
829
830 The value of :attr:`prereleases` is ignored.
831
832 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
833 True
834 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
835 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
836 True
837 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
838 True
839 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
840 False
841 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
842 False
843 """
844 if isinstance(other, (str, Specifier)):
845 other = SpecifierSet(str(other))
846 elif not isinstance(other, SpecifierSet):
847 return NotImplemented
848
849 return self._specs == other._specs
850
851 def __len__(self) -> int:
852 """Returns the number of specifiers in this specifier set."""
853 return len(self._specs)
854
855 def __iter__(self) -> Iterator[Specifier]:
856 """
857 Returns an iterator over all the underlying :class:`Specifier` instances
858 in this specifier set.
859
860 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
861 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
862 """
863 return iter(self._specs)
864
865 def __contains__(self, item: UnparsedVersion) -> bool:
866 """Return whether or not the item is contained in this specifier.
867
868 :param item: The item to check for.
869
870 This is used for the ``in`` operator and behaves the same as
871 :meth:`contains` with no ``prereleases`` argument passed.
872
873 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
874 True
875 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
876 True
877 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
878 False
879 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
880 False
881 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
882 True
883 """
884 return self.contains(item)
885
886 def contains(
887 self,
888 item: UnparsedVersion,
889 prereleases: bool | None = None,
890 installed: bool | None = None,
891 ) -> bool:
892 """Return whether or not the item is contained in this SpecifierSet.
893
894 :param item:
895 The item to check for, which can be a version string or a
896 :class:`Version` instance.
897 :param prereleases:
898 Whether or not to match prereleases with this SpecifierSet. If set to
899 ``None`` (the default), it uses :attr:`prereleases` to determine
900 whether or not prereleases are allowed.
901
902 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
903 True
904 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
905 True
906 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
907 False
908 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
909 False
910 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True).contains("1.3.0a1")
911 True
912 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
913 True
914 """
915 # Ensure that our item is a Version instance.
916 if not isinstance(item, Version):
917 item = Version(item)
918
919 # Determine if we're forcing a prerelease or not, if we're not forcing
920 # one for this particular filter call, then we'll use whatever the
921 # SpecifierSet thinks for whether or not we should support prereleases.
922 if prereleases is None:
923 prereleases = self.prereleases
924
925 # We can determine if we're going to allow pre-releases by looking to
926 # see if any of the underlying items supports them. If none of them do
927 # and this item is a pre-release then we do not allow it and we can
928 # short circuit that here.
929 # Note: This means that 1.0.dev1 would not be contained in something
930 # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0
931 if not prereleases and item.is_prerelease:
932 return False
933
934 if installed and item.is_prerelease:
935 item = Version(item.base_version)
936
937 # We simply dispatch to the underlying specs here to make sure that the
938 # given version is contained within all of them.
939 # Note: This use of all() here means that an empty set of specifiers
940 # will always return True, this is an explicit design decision.
941 return all(s.contains(item, prereleases=prereleases) for s in self._specs)
942
943 def filter(
944 self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None
945 ) -> Iterator[UnparsedVersionVar]:
946 """Filter items in the given iterable, that match the specifiers in this set.
947
948 :param iterable:
949 An iterable that can contain version strings and :class:`Version` instances.
950 The items in the iterable will be filtered according to the specifier.
951 :param prereleases:
952 Whether or not to allow prereleases in the returned iterator. If set to
953 ``None`` (the default), it will be intelligently decide whether to allow
954 prereleases or not (based on the :attr:`prereleases` attribute, and
955 whether the only versions matching are prereleases).
956
957 This method is smarter than just ``filter(SpecifierSet(...).contains, [...])``
958 because it implements the rule from :pep:`440` that a prerelease item
959 SHOULD be accepted if no other versions match the given specifier.
960
961 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
962 ['1.3']
963 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
964 ['1.3', <Version('1.4')>]
965 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
966 []
967 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
968 ['1.3', '1.5a1']
969 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
970 ['1.3', '1.5a1']
971
972 An "empty" SpecifierSet will filter items based on the presence of prerelease
973 versions in the set.
974
975 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
976 ['1.3']
977 >>> list(SpecifierSet("").filter(["1.5a1"]))
978 ['1.5a1']
979 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
980 ['1.3', '1.5a1']
981 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
982 ['1.3', '1.5a1']
983 """
984 # Determine if we're forcing a prerelease or not, if we're not forcing
985 # one for this particular filter call, then we'll use whatever the
986 # SpecifierSet thinks for whether or not we should support prereleases.
987 if prereleases is None:
988 prereleases = self.prereleases
989
990 # If we have any specifiers, then we want to wrap our iterable in the
991 # filter method for each one, this will act as a logical AND amongst
992 # each specifier.
993 if self._specs:
994 for spec in self._specs:
995 iterable = spec.filter(iterable, prereleases=bool(prereleases))
996 return iter(iterable)
997 # If we do not have any specifiers, then we need to have a rough filter
998 # which will filter out any pre-releases, unless there are no final
999 # releases.
1000 else:
1001 filtered: list[UnparsedVersionVar] = []
1002 found_prereleases: list[UnparsedVersionVar] = []
1003
1004 for item in iterable:
1005 parsed_version = _coerce_version(item)
1006
1007 # Store any item which is a pre-release for later unless we've
1008 # already found a final version or we are accepting prereleases
1009 if parsed_version.is_prerelease and not prereleases:
1010 if not filtered:
1011 found_prereleases.append(item)
1012 else:
1013 filtered.append(item)
1014
1015 # If we've found no items except for pre-releases, then we'll go
1016 # ahead and use the pre-releases
1017 if not filtered and found_prereleases and prereleases is None:
1018 return iter(found_prereleases)
1019
1020 return iter(filtered)