Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/packaging/specifiers.py: 25%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

321 statements  

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 

11from __future__ import annotations 

12 

13import abc 

14import itertools 

15import re 

16import typing 

17from typing import Any, Callable, Final, Iterable, Iterator, TypeVar, Union 

18 

19from .utils import canonicalize_version 

20from .version import InvalidVersion, Version 

21 

22__all__ = [ 

23 "BaseSpecifier", 

24 "InvalidSpecifier", 

25 "Specifier", 

26 "SpecifierSet", 

27] 

28 

29 

30def __dir__() -> list[str]: 

31 return __all__ 

32 

33 

34T = TypeVar("T") 

35UnparsedVersion = Union[Version, str] 

36UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion) 

37CallableOperator = Callable[[Version, str], bool] 

38 

39 

40def _coerce_version(version: UnparsedVersion) -> Version | None: 

41 if not isinstance(version, Version): 

42 try: 

43 version = Version(version) 

44 except InvalidVersion: 

45 return None 

46 return version 

47 

48 

49def _public_version(version: Version) -> Version: 

50 if version.local is None: 

51 return version 

52 return version.__replace__(local=None) 

53 

54 

55def _base_version(version: Version) -> Version: 

56 if ( 

57 version.pre is None 

58 and version.post is None 

59 and version.dev is None 

60 and version.local is None 

61 ): 

62 return version 

63 return version.__replace__(pre=None, post=None, dev=None, local=None) 

64 

65 

66class InvalidSpecifier(ValueError): 

67 """ 

68 Raised when attempting to create a :class:`Specifier` with a specifier 

69 string that is invalid. 

70 

71 >>> Specifier("lolwat") 

72 Traceback (most recent call last): 

73 ... 

74 packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat' 

75 """ 

76 

77 

78class BaseSpecifier(metaclass=abc.ABCMeta): 

79 __slots__ = () 

80 __match_args__ = ("_str",) 

81 

82 @property 

83 def _str(self) -> str: 

84 """Internal property for match_args""" 

85 return str(self) 

86 

87 @abc.abstractmethod 

88 def __str__(self) -> str: 

89 """ 

90 Returns the str representation of this Specifier-like object. This 

91 should be representative of the Specifier itself. 

92 """ 

93 

94 @abc.abstractmethod 

95 def __hash__(self) -> int: 

96 """ 

97 Returns a hash value for this Specifier-like object. 

98 """ 

99 

100 @abc.abstractmethod 

101 def __eq__(self, other: object) -> bool: 

102 """ 

103 Returns a boolean representing whether or not the two Specifier-like 

104 objects are equal. 

105 

106 :param other: The other object to check against. 

107 """ 

108 

109 @property 

110 @abc.abstractmethod 

111 def prereleases(self) -> bool | None: 

112 """Whether or not pre-releases as a whole are allowed. 

113 

114 This can be set to either ``True`` or ``False`` to explicitly enable or disable 

115 prereleases or it can be set to ``None`` (the default) to use default semantics. 

116 """ 

117 

118 @prereleases.setter # noqa: B027 

119 def prereleases(self, value: bool) -> None: 

120 """Setter for :attr:`prereleases`. 

121 

122 :param value: The value to set. 

123 """ 

124 

125 @abc.abstractmethod 

126 def contains(self, item: str, prereleases: bool | None = None) -> bool: 

127 """ 

128 Determines if the given item is contained within this specifier. 

129 """ 

130 

131 @typing.overload 

132 def filter( 

133 self, 

134 iterable: Iterable[UnparsedVersionVar], 

135 prereleases: bool | None = None, 

136 key: None = ..., 

137 ) -> Iterator[UnparsedVersionVar]: ... 

138 

139 @typing.overload 

140 def filter( 

141 self, 

142 iterable: Iterable[T], 

143 prereleases: bool | None = None, 

144 key: Callable[[T], UnparsedVersion] = ..., 

145 ) -> Iterator[T]: ... 

146 

147 @abc.abstractmethod 

148 def filter( 

149 self, 

150 iterable: Iterable[Any], 

151 prereleases: bool | None = None, 

152 key: Callable[[Any], UnparsedVersion] | None = None, 

153 ) -> Iterator[Any]: 

154 """ 

155 Takes an iterable of items and filters them so that only items which 

156 are contained within this specifier are allowed in it. 

157 """ 

158 

159 

160class Specifier(BaseSpecifier): 

161 """This class abstracts handling of version specifiers. 

162 

163 .. tip:: 

164 

165 It is generally not required to instantiate this manually. You should instead 

166 prefer to work with :class:`SpecifierSet` instead, which can parse 

167 comma-separated version specifiers (which is what package metadata contains). 

168 """ 

169 

170 __slots__ = ("_prereleases", "_spec", "_spec_version") 

171 

172 _operator_regex_str = r""" 

173 (?P<operator>(~=|==|!=|<=|>=|<|>|===)) 

174 """ 

175 _version_regex_str = r""" 

176 (?P<version> 

177 (?: 

178 # The identity operators allow for an escape hatch that will 

179 # do an exact string match of the version you wish to install. 

180 # This will not be parsed by PEP 440 and we cannot determine 

181 # any semantic meaning from it. This operator is discouraged 

182 # but included entirely as an escape hatch. 

183 (?<====) # Only match for the identity operator 

184 \s* 

185 [^\s;)]* # The arbitrary version can be just about anything, 

186 # we match everything except for whitespace, a 

187 # semi-colon for marker support, and a closing paren 

188 # since versions can be enclosed in them. 

189 ) 

190 | 

191 (?: 

192 # The (non)equality operators allow for wild card and local 

193 # versions to be specified so we have to define these two 

194 # operators separately to enable that. 

195 (?<===|!=) # Only match for equals and not equals 

196 

197 \s* 

198 v? 

199 (?:[0-9]+!)? # epoch 

200 [0-9]+(?:\.[0-9]+)* # release 

201 

202 # You cannot use a wild card and a pre-release, post-release, a dev or 

203 # local version together so group them with a | and make them optional. 

204 (?: 

205 \.\* # Wild card syntax of .* 

206 | 

207 (?: # pre release 

208 [-_\.]? 

209 (alpha|beta|preview|pre|a|b|c|rc) 

210 [-_\.]? 

211 [0-9]* 

212 )? 

213 (?: # post release 

214 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) 

215 )? 

216 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release 

217 (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local 

218 )? 

219 ) 

220 | 

221 (?: 

222 # The compatible operator requires at least two digits in the 

223 # release segment. 

224 (?<=~=) # Only match for the compatible operator 

225 

226 \s* 

227 v? 

228 (?:[0-9]+!)? # epoch 

229 [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) 

230 (?: # pre release 

231 [-_\.]? 

232 (alpha|beta|preview|pre|a|b|c|rc) 

233 [-_\.]? 

234 [0-9]* 

235 )? 

236 (?: # post release 

237 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) 

238 )? 

239 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release 

240 ) 

241 | 

242 (?: 

243 # All other operators only allow a sub set of what the 

244 # (non)equality operators do. Specifically they do not allow 

245 # local versions to be specified nor do they allow the prefix 

246 # matching wild cards. 

247 (?<!==|!=|~=) # We have special cases for these 

248 # operators so we want to make sure they 

249 # don't match here. 

250 

251 \s* 

252 v? 

253 (?:[0-9]+!)? # epoch 

254 [0-9]+(?:\.[0-9]+)* # release 

255 (?: # pre release 

256 [-_\.]? 

257 (alpha|beta|preview|pre|a|b|c|rc) 

258 [-_\.]? 

259 [0-9]* 

260 )? 

261 (?: # post release 

262 (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) 

263 )? 

264 (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release 

265 ) 

266 ) 

267 """ 

268 

269 _regex = re.compile( 

270 r"\s*" + _operator_regex_str + _version_regex_str + r"\s*", 

271 re.VERBOSE | re.IGNORECASE, 

272 ) 

273 

274 _operators: Final = { 

275 "~=": "compatible", 

276 "==": "equal", 

277 "!=": "not_equal", 

278 "<=": "less_than_equal", 

279 ">=": "greater_than_equal", 

280 "<": "less_than", 

281 ">": "greater_than", 

282 "===": "arbitrary", 

283 } 

284 

285 def __init__(self, spec: str = "", prereleases: bool | None = None) -> None: 

286 """Initialize a Specifier instance. 

287 

288 :param spec: 

289 The string representation of a specifier which will be parsed and 

290 normalized before use. 

291 :param prereleases: 

292 This tells the specifier if it should accept prerelease versions if 

293 applicable or not. The default of ``None`` will autodetect it from the 

294 given specifiers. 

295 :raises InvalidSpecifier: 

296 If the given specifier is invalid (i.e. bad syntax). 

297 """ 

298 match = self._regex.fullmatch(spec) 

299 if not match: 

300 raise InvalidSpecifier(f"Invalid specifier: {spec!r}") 

301 

302 self._spec: tuple[str, str] = ( 

303 match.group("operator").strip(), 

304 match.group("version").strip(), 

305 ) 

306 

307 # Store whether or not this Specifier should accept prereleases 

308 self._prereleases = prereleases 

309 

310 # Specifier version cache 

311 self._spec_version: tuple[str, Version] | None = None 

312 

313 def _get_spec_version(self, version: str) -> Version | None: 

314 """One element cache, as only one spec Version is needed per Specifier.""" 

315 if self._spec_version is not None and self._spec_version[0] == version: 

316 return self._spec_version[1] 

317 

318 version_specifier = _coerce_version(version) 

319 if version_specifier is None: 

320 return None 

321 

322 self._spec_version = (version, version_specifier) 

323 return version_specifier 

324 

325 def _require_spec_version(self, version: str) -> Version: 

326 """Get spec version, asserting it's valid (not for === operator). 

327 

328 This method should only be called for operators where version 

329 strings are guaranteed to be valid PEP 440 versions (not ===). 

330 """ 

331 spec_version = self._get_spec_version(version) 

332 assert spec_version is not None 

333 return spec_version 

334 

335 @property 

336 def prereleases(self) -> bool | None: 

337 # If there is an explicit prereleases set for this, then we'll just 

338 # blindly use that. 

339 if self._prereleases is not None: 

340 return self._prereleases 

341 

342 # Only the "!=" operator does not imply prereleases when 

343 # the version in the specifier is a prerelease. 

344 operator, version_str = self._spec 

345 if operator == "!=": 

346 return False 

347 

348 # The == specifier with trailing .* cannot include prereleases 

349 # e.g. "==1.0a1.*" is not valid. 

350 if operator == "==" and version_str.endswith(".*"): 

351 return False 

352 

353 # "===" can have arbitrary string versions, so we cannot parse 

354 # those, we take prereleases as unknown (None) for those. 

355 version = self._get_spec_version(version_str) 

356 if version is None: 

357 return None 

358 

359 # For all other operators, use the check if spec Version 

360 # object implies pre-releases. 

361 return version.is_prerelease 

362 

363 @prereleases.setter 

364 def prereleases(self, value: bool | None) -> None: 

365 self._prereleases = value 

366 

367 @property 

368 def operator(self) -> str: 

369 """The operator of this specifier. 

370 

371 >>> Specifier("==1.2.3").operator 

372 '==' 

373 """ 

374 return self._spec[0] 

375 

376 @property 

377 def version(self) -> str: 

378 """The version of this specifier. 

379 

380 >>> Specifier("==1.2.3").version 

381 '1.2.3' 

382 """ 

383 return self._spec[1] 

384 

385 def __repr__(self) -> str: 

386 """A representation of the Specifier that shows all internal state. 

387 

388 >>> Specifier('>=1.0.0') 

389 <Specifier('>=1.0.0')> 

390 >>> Specifier('>=1.0.0', prereleases=False) 

391 <Specifier('>=1.0.0', prereleases=False)> 

392 >>> Specifier('>=1.0.0', prereleases=True) 

393 <Specifier('>=1.0.0', prereleases=True)> 

394 """ 

395 pre = ( 

396 f", prereleases={self.prereleases!r}" 

397 if self._prereleases is not None 

398 else "" 

399 ) 

400 

401 return f"<{self.__class__.__name__}({str(self)!r}{pre})>" 

402 

403 def __str__(self) -> str: 

404 """A string representation of the Specifier that can be round-tripped. 

405 

406 >>> str(Specifier('>=1.0.0')) 

407 '>=1.0.0' 

408 >>> str(Specifier('>=1.0.0', prereleases=False)) 

409 '>=1.0.0' 

410 """ 

411 return "{}{}".format(*self._spec) 

412 

413 @property 

414 def _canonical_spec(self) -> tuple[str, str]: 

415 operator, version = self._spec 

416 if operator == "===" or version.endswith(".*"): 

417 return operator, version 

418 

419 spec_version = self._require_spec_version(version) 

420 

421 canonical_version = canonicalize_version( 

422 spec_version, strip_trailing_zero=(operator != "~=") 

423 ) 

424 

425 return operator, canonical_version 

426 

427 def __hash__(self) -> int: 

428 return hash(self._canonical_spec) 

429 

430 def __eq__(self, other: object) -> bool: 

431 """Whether or not the two Specifier-like objects are equal. 

432 

433 :param other: The other object to check against. 

434 

435 The value of :attr:`prereleases` is ignored. 

436 

437 >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0") 

438 True 

439 >>> (Specifier("==1.2.3", prereleases=False) == 

440 ... Specifier("==1.2.3", prereleases=True)) 

441 True 

442 >>> Specifier("==1.2.3") == "==1.2.3" 

443 True 

444 >>> Specifier("==1.2.3") == Specifier("==1.2.4") 

445 False 

446 >>> Specifier("==1.2.3") == Specifier("~=1.2.3") 

447 False 

448 """ 

449 if isinstance(other, str): 

450 try: 

451 other = self.__class__(str(other)) 

452 except InvalidSpecifier: 

453 return NotImplemented 

454 elif not isinstance(other, self.__class__): 

455 return NotImplemented 

456 

457 return self._canonical_spec == other._canonical_spec 

458 

459 def _get_operator(self, op: str) -> CallableOperator: 

460 operator_callable: CallableOperator = getattr( 

461 self, f"_compare_{self._operators[op]}" 

462 ) 

463 return operator_callable 

464 

465 def _compare_compatible(self, prospective: Version, spec: str) -> bool: 

466 # Compatible releases have an equivalent combination of >= and ==. That 

467 # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to 

468 # implement this in terms of the other specifiers instead of 

469 # implementing it ourselves. The only thing we need to do is construct 

470 # the other specifiers. 

471 

472 # We want everything but the last item in the version, but we want to 

473 # ignore suffix segments. 

474 prefix = _version_join( 

475 list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] 

476 ) 

477 

478 # Add the prefix notation to the end of our string 

479 prefix += ".*" 

480 

481 return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( 

482 prospective, prefix 

483 ) 

484 

485 def _compare_equal(self, prospective: Version, spec: str) -> bool: 

486 # We need special logic to handle prefix matching 

487 if spec.endswith(".*"): 

488 # In the case of prefix matching we want to ignore local segment. 

489 normalized_prospective = canonicalize_version( 

490 _public_version(prospective), strip_trailing_zero=False 

491 ) 

492 # Get the normalized version string ignoring the trailing .* 

493 normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False) 

494 # Split the spec out by bangs and dots, and pretend that there is 

495 # an implicit dot in between a release segment and a pre-release segment. 

496 split_spec = _version_split(normalized_spec) 

497 

498 # Split the prospective version out by bangs and dots, and pretend 

499 # that there is an implicit dot in between a release segment and 

500 # a pre-release segment. 

501 split_prospective = _version_split(normalized_prospective) 

502 

503 # 0-pad the prospective version before shortening it to get the correct 

504 # shortened version. 

505 padded_prospective, _ = _pad_version(split_prospective, split_spec) 

506 

507 # Shorten the prospective version to be the same length as the spec 

508 # so that we can determine if the specifier is a prefix of the 

509 # prospective version or not. 

510 shortened_prospective = padded_prospective[: len(split_spec)] 

511 

512 return shortened_prospective == split_spec 

513 else: 

514 # Convert our spec string into a Version 

515 spec_version = self._require_spec_version(spec) 

516 

517 # If the specifier does not have a local segment, then we want to 

518 # act as if the prospective version also does not have a local 

519 # segment. 

520 if not spec_version.local: 

521 prospective = _public_version(prospective) 

522 

523 return prospective == spec_version 

524 

525 def _compare_not_equal(self, prospective: Version, spec: str) -> bool: 

526 return not self._compare_equal(prospective, spec) 

527 

528 def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool: 

529 # NB: Local version identifiers are NOT permitted in the version 

530 # specifier, so local version labels can be universally removed from 

531 # the prospective version. 

532 return _public_version(prospective) <= self._require_spec_version(spec) 

533 

534 def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool: 

535 # NB: Local version identifiers are NOT permitted in the version 

536 # specifier, so local version labels can be universally removed from 

537 # the prospective version. 

538 return _public_version(prospective) >= self._require_spec_version(spec) 

539 

540 def _compare_less_than(self, prospective: Version, spec_str: str) -> bool: 

541 # Convert our spec to a Version instance, since we'll want to work with 

542 # it as a version. 

543 spec = self._require_spec_version(spec_str) 

544 

545 # Check to see if the prospective version is less than the spec 

546 # version. If it's not we can short circuit and just return False now 

547 # instead of doing extra unneeded work. 

548 if not prospective < spec: 

549 return False 

550 

551 # This special case is here so that, unless the specifier itself 

552 # includes is a pre-release version, that we do not accept pre-release 

553 # versions for the version mentioned in the specifier (e.g. <3.1 should 

554 # not match 3.1.dev0, but should match 3.0.dev0). 

555 if ( 

556 not spec.is_prerelease 

557 and prospective.is_prerelease 

558 and _base_version(prospective) == _base_version(spec) 

559 ): 

560 return False 

561 

562 # If we've gotten to here, it means that prospective version is both 

563 # less than the spec version *and* it's not a pre-release of the same 

564 # version in the spec. 

565 return True 

566 

567 def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool: 

568 # Convert our spec to a Version instance, since we'll want to work with 

569 # it as a version. 

570 spec = self._require_spec_version(spec_str) 

571 

572 # Check to see if the prospective version is greater than the spec 

573 # version. If it's not we can short circuit and just return False now 

574 # instead of doing extra unneeded work. 

575 if not prospective > spec: 

576 return False 

577 

578 # This special case is here so that, unless the specifier itself 

579 # includes is a post-release version, that we do not accept 

580 # post-release versions for the version mentioned in the specifier 

581 # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). 

582 if ( 

583 not spec.is_postrelease 

584 and prospective.is_postrelease 

585 and _base_version(prospective) == _base_version(spec) 

586 ): 

587 return False 

588 

589 # Ensure that we do not allow a local version of the version mentioned 

590 # in the specifier, which is technically greater than, to match. 

591 if prospective.local is not None and _base_version( 

592 prospective 

593 ) == _base_version(spec): 

594 return False 

595 

596 # If we've gotten to here, it means that prospective version is both 

597 # greater than the spec version *and* it's not a pre-release of the 

598 # same version in the spec. 

599 return True 

600 

601 def _compare_arbitrary(self, prospective: Version | str, spec: str) -> bool: 

602 return str(prospective).lower() == str(spec).lower() 

603 

604 def __contains__(self, item: str | Version) -> bool: 

605 """Return whether or not the item is contained in this specifier. 

606 

607 :param item: The item to check for. 

608 

609 This is used for the ``in`` operator and behaves the same as 

610 :meth:`contains` with no ``prereleases`` argument passed. 

611 

612 >>> "1.2.3" in Specifier(">=1.2.3") 

613 True 

614 >>> Version("1.2.3") in Specifier(">=1.2.3") 

615 True 

616 >>> "1.0.0" in Specifier(">=1.2.3") 

617 False 

618 >>> "1.3.0a1" in Specifier(">=1.2.3") 

619 True 

620 >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True) 

621 True 

622 """ 

623 return self.contains(item) 

624 

625 def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool: 

626 """Return whether or not the item is contained in this specifier. 

627 

628 :param item: 

629 The item to check for, which can be a version string or a 

630 :class:`Version` instance. 

631 :param prereleases: 

632 Whether or not to match prereleases with this Specifier. If set to 

633 ``None`` (the default), it will follow the recommendation from 

634 :pep:`440` and match prereleases, as there are no other versions. 

635 

636 >>> Specifier(">=1.2.3").contains("1.2.3") 

637 True 

638 >>> Specifier(">=1.2.3").contains(Version("1.2.3")) 

639 True 

640 >>> Specifier(">=1.2.3").contains("1.0.0") 

641 False 

642 >>> Specifier(">=1.2.3").contains("1.3.0a1") 

643 True 

644 >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1") 

645 False 

646 >>> Specifier(">=1.2.3").contains("1.3.0a1") 

647 True 

648 """ 

649 

650 return bool(list(self.filter([item], prereleases=prereleases))) 

651 

652 @typing.overload 

653 def filter( 

654 self, 

655 iterable: Iterable[UnparsedVersionVar], 

656 prereleases: bool | None = None, 

657 key: None = ..., 

658 ) -> Iterator[UnparsedVersionVar]: ... 

659 

660 @typing.overload 

661 def filter( 

662 self, 

663 iterable: Iterable[T], 

664 prereleases: bool | None = None, 

665 key: Callable[[T], UnparsedVersion] = ..., 

666 ) -> Iterator[T]: ... 

667 

668 def filter( 

669 self, 

670 iterable: Iterable[Any], 

671 prereleases: bool | None = None, 

672 key: Callable[[Any], UnparsedVersion] | None = None, 

673 ) -> Iterator[Any]: 

674 """Filter items in the given iterable, that match the specifier. 

675 

676 :param iterable: 

677 An iterable that can contain version strings and :class:`Version` instances. 

678 The items in the iterable will be filtered according to the specifier. 

679 :param prereleases: 

680 Whether or not to allow prereleases in the returned iterator. If set to 

681 ``None`` (the default), it will follow the recommendation from :pep:`440` 

682 and match prereleases if there are no other versions. 

683 :param key: 

684 A callable that takes a single argument (an item from the iterable) and 

685 returns a version string or :class:`Version` instance to be used for 

686 filtering. 

687 

688 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) 

689 ['1.3'] 

690 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")])) 

691 ['1.2.3', '1.3', <Version('1.4')>] 

692 >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"])) 

693 ['1.5a1'] 

694 >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) 

695 ['1.3', '1.5a1'] 

696 >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) 

697 ['1.3', '1.5a1'] 

698 >>> list(Specifier(">=1.2.3").filter( 

699 ... [{"ver": "1.2"}, {"ver": "1.3"}], 

700 ... key=lambda x: x["ver"])) 

701 [{'ver': '1.3'}] 

702 """ 

703 prereleases_versions = [] 

704 found_non_prereleases = False 

705 

706 # Determine if to include prereleases by default 

707 include_prereleases = ( 

708 prereleases if prereleases is not None else self.prereleases 

709 ) 

710 

711 # Get the matching operator 

712 operator_callable = self._get_operator(self.operator) 

713 

714 # Filter versions 

715 for version in iterable: 

716 parsed_version = _coerce_version(version if key is None else key(version)) 

717 if parsed_version is None: 

718 # === operator can match arbitrary (non-version) strings 

719 if self.operator == "===" and self._compare_arbitrary( 

720 version, self.version 

721 ): 

722 yield version 

723 elif operator_callable(parsed_version, self.version): 

724 # If it's not a prerelease or prereleases are allowed, yield it directly 

725 if not parsed_version.is_prerelease or include_prereleases: 

726 found_non_prereleases = True 

727 yield version 

728 # Otherwise collect prereleases for potential later use 

729 elif prereleases is None and self._prereleases is not False: 

730 prereleases_versions.append(version) 

731 

732 # If no non-prereleases were found and prereleases weren't 

733 # explicitly forbidden, yield the collected prereleases 

734 if ( 

735 not found_non_prereleases 

736 and prereleases is None 

737 and self._prereleases is not False 

738 ): 

739 yield from prereleases_versions 

740 

741 

742_prefix_regex = re.compile(r"([0-9]+)((?:a|b|c|rc)[0-9]+)") 

743 

744 

745def _pep440_filter_prereleases( 

746 iterable: Iterable[Any], key: Callable[[Any], UnparsedVersion] | None 

747) -> Iterator[Any]: 

748 """Filter per PEP 440: exclude prereleases unless no finals exist.""" 

749 # Two lists used: 

750 # * all_nonfinal to preserve order if no finals exist 

751 # * arbitrary_strings for streaming when first final found 

752 all_nonfinal: list[Any] = [] 

753 arbitrary_strings: list[Any] = [] 

754 

755 found_final = False 

756 for item in iterable: 

757 parsed = _coerce_version(item if key is None else key(item)) 

758 

759 if parsed is None: 

760 # Arbitrary strings are always included as it is not 

761 # possible to determine if they are prereleases, 

762 # and they have already passed all specifiers. 

763 if found_final: 

764 yield item 

765 else: 

766 arbitrary_strings.append(item) 

767 all_nonfinal.append(item) 

768 continue 

769 

770 if not parsed.is_prerelease: 

771 # Final release found - flush arbitrary strings, then yield 

772 if not found_final: 

773 yield from arbitrary_strings 

774 found_final = True 

775 yield item 

776 continue 

777 

778 # Prerelease - buffer if no finals yet, otherwise skip 

779 if not found_final: 

780 all_nonfinal.append(item) 

781 

782 # No finals found - yield all buffered items 

783 if not found_final: 

784 yield from all_nonfinal 

785 

786 

787def _version_split(version: str) -> list[str]: 

788 """Split version into components. 

789 

790 The split components are intended for version comparison. The logic does 

791 not attempt to retain the original version string, so joining the 

792 components back with :func:`_version_join` may not produce the original 

793 version string. 

794 """ 

795 result: list[str] = [] 

796 

797 epoch, _, rest = version.rpartition("!") 

798 result.append(epoch or "0") 

799 

800 for item in rest.split("."): 

801 match = _prefix_regex.fullmatch(item) 

802 if match: 

803 result.extend(match.groups()) 

804 else: 

805 result.append(item) 

806 return result 

807 

808 

809def _version_join(components: list[str]) -> str: 

810 """Join split version components into a version string. 

811 

812 This function assumes the input came from :func:`_version_split`, where the 

813 first component must be the epoch (either empty or numeric), and all other 

814 components numeric. 

815 """ 

816 epoch, *rest = components 

817 return f"{epoch}!{'.'.join(rest)}" 

818 

819 

820def _is_not_suffix(segment: str) -> bool: 

821 return not any( 

822 segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") 

823 ) 

824 

825 

826def _pad_version(left: list[str], right: list[str]) -> tuple[list[str], list[str]]: 

827 left_split, right_split = [], [] 

828 

829 # Get the release segment of our versions 

830 left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) 

831 right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) 

832 

833 # Get the rest of our versions 

834 left_split.append(left[len(left_split[0]) :]) 

835 right_split.append(right[len(right_split[0]) :]) 

836 

837 # Insert our padding 

838 left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) 

839 right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) 

840 

841 return ( 

842 list(itertools.chain.from_iterable(left_split)), 

843 list(itertools.chain.from_iterable(right_split)), 

844 ) 

845 

846 

847class SpecifierSet(BaseSpecifier): 

848 """This class abstracts handling of a set of version specifiers. 

849 

850 It can be passed a single specifier (``>=3.0``), a comma-separated list of 

851 specifiers (``>=3.0,!=3.1``), or no specifier at all. 

852 """ 

853 

854 __slots__ = ("_prereleases", "_specs") 

855 

856 def __init__( 

857 self, 

858 specifiers: str | Iterable[Specifier] = "", 

859 prereleases: bool | None = None, 

860 ) -> None: 

861 """Initialize a SpecifierSet instance. 

862 

863 :param specifiers: 

864 The string representation of a specifier or a comma-separated list of 

865 specifiers which will be parsed and normalized before use. 

866 May also be an iterable of ``Specifier`` instances, which will be used 

867 as is. 

868 :param prereleases: 

869 This tells the SpecifierSet if it should accept prerelease versions if 

870 applicable or not. The default of ``None`` will autodetect it from the 

871 given specifiers. 

872 

873 :raises InvalidSpecifier: 

874 If the given ``specifiers`` are not parseable than this exception will be 

875 raised. 

876 """ 

877 

878 if isinstance(specifiers, str): 

879 # Split on `,` to break each individual specifier into its own item, and 

880 # strip each item to remove leading/trailing whitespace. 

881 split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] 

882 

883 # Make each individual specifier a Specifier and save in a frozen set 

884 # for later. 

885 self._specs = frozenset(map(Specifier, split_specifiers)) 

886 else: 

887 # Save the supplied specifiers in a frozen set. 

888 self._specs = frozenset(specifiers) 

889 

890 # Store our prereleases value so we can use it later to determine if 

891 # we accept prereleases or not. 

892 self._prereleases = prereleases 

893 

894 @property 

895 def prereleases(self) -> bool | None: 

896 # If we have been given an explicit prerelease modifier, then we'll 

897 # pass that through here. 

898 if self._prereleases is not None: 

899 return self._prereleases 

900 

901 # If we don't have any specifiers, and we don't have a forced value, 

902 # then we'll just return None since we don't know if this should have 

903 # pre-releases or not. 

904 if not self._specs: 

905 return None 

906 

907 # Otherwise we'll see if any of the given specifiers accept 

908 # prereleases, if any of them do we'll return True, otherwise False. 

909 if any(s.prereleases for s in self._specs): 

910 return True 

911 

912 return None 

913 

914 @prereleases.setter 

915 def prereleases(self, value: bool | None) -> None: 

916 self._prereleases = value 

917 

918 def __repr__(self) -> str: 

919 """A representation of the specifier set that shows all internal state. 

920 

921 Note that the ordering of the individual specifiers within the set may not 

922 match the input string. 

923 

924 >>> SpecifierSet('>=1.0.0,!=2.0.0') 

925 <SpecifierSet('!=2.0.0,>=1.0.0')> 

926 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False) 

927 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)> 

928 >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True) 

929 <SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)> 

930 """ 

931 pre = ( 

932 f", prereleases={self.prereleases!r}" 

933 if self._prereleases is not None 

934 else "" 

935 ) 

936 

937 return f"<{self.__class__.__name__}({str(self)!r}{pre})>" 

938 

939 def __str__(self) -> str: 

940 """A string representation of the specifier set that can be round-tripped. 

941 

942 Note that the ordering of the individual specifiers within the set may not 

943 match the input string. 

944 

945 >>> str(SpecifierSet(">=1.0.0,!=1.0.1")) 

946 '!=1.0.1,>=1.0.0' 

947 >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False)) 

948 '!=1.0.1,>=1.0.0' 

949 """ 

950 return ",".join(sorted(str(s) for s in self._specs)) 

951 

952 def __hash__(self) -> int: 

953 return hash(self._specs) 

954 

955 def __and__(self, other: SpecifierSet | str) -> SpecifierSet: 

956 """Return a SpecifierSet which is a combination of the two sets. 

957 

958 :param other: The other object to combine with. 

959 

960 >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1' 

961 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')> 

962 >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1') 

963 <SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')> 

964 """ 

965 if isinstance(other, str): 

966 other = SpecifierSet(other) 

967 elif not isinstance(other, SpecifierSet): 

968 return NotImplemented 

969 

970 specifier = SpecifierSet() 

971 specifier._specs = frozenset(self._specs | other._specs) 

972 

973 # Combine prerelease settings: use common or non-None value 

974 if self._prereleases is None or self._prereleases == other._prereleases: 

975 specifier._prereleases = other._prereleases 

976 elif other._prereleases is None: 

977 specifier._prereleases = self._prereleases 

978 else: 

979 raise ValueError( 

980 "Cannot combine SpecifierSets with True and False prerelease overrides." 

981 ) 

982 

983 return specifier 

984 

985 def __eq__(self, other: object) -> bool: 

986 """Whether or not the two SpecifierSet-like objects are equal. 

987 

988 :param other: The other object to check against. 

989 

990 The value of :attr:`prereleases` is ignored. 

991 

992 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1") 

993 True 

994 >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) == 

995 ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)) 

996 True 

997 >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1" 

998 True 

999 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0") 

1000 False 

1001 >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2") 

1002 False 

1003 """ 

1004 if isinstance(other, (str, Specifier)): 

1005 other = SpecifierSet(str(other)) 

1006 elif not isinstance(other, SpecifierSet): 

1007 return NotImplemented 

1008 

1009 return self._specs == other._specs 

1010 

1011 def __len__(self) -> int: 

1012 """Returns the number of specifiers in this specifier set.""" 

1013 return len(self._specs) 

1014 

1015 def __iter__(self) -> Iterator[Specifier]: 

1016 """ 

1017 Returns an iterator over all the underlying :class:`Specifier` instances 

1018 in this specifier set. 

1019 

1020 >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str) 

1021 [<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>] 

1022 """ 

1023 return iter(self._specs) 

1024 

1025 def __contains__(self, item: UnparsedVersion) -> bool: 

1026 """Return whether or not the item is contained in this specifier. 

1027 

1028 :param item: The item to check for. 

1029 

1030 This is used for the ``in`` operator and behaves the same as 

1031 :meth:`contains` with no ``prereleases`` argument passed. 

1032 

1033 >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1") 

1034 True 

1035 >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1") 

1036 True 

1037 >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1") 

1038 False 

1039 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1") 

1040 True 

1041 >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True) 

1042 True 

1043 """ 

1044 return self.contains(item) 

1045 

1046 def contains( 

1047 self, 

1048 item: UnparsedVersion, 

1049 prereleases: bool | None = None, 

1050 installed: bool | None = None, 

1051 ) -> bool: 

1052 """Return whether or not the item is contained in this SpecifierSet. 

1053 

1054 :param item: 

1055 The item to check for, which can be a version string or a 

1056 :class:`Version` instance. 

1057 :param prereleases: 

1058 Whether or not to match prereleases with this SpecifierSet. If set to 

1059 ``None`` (the default), it will follow the recommendation from :pep:`440` 

1060 and match prereleases, as there are no other versions. 

1061 :param installed: 

1062 Whether or not the item is installed. If set to ``True``, it will 

1063 accept prerelease versions even if the specifier does not allow them. 

1064 

1065 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3") 

1066 True 

1067 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3")) 

1068 True 

1069 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1") 

1070 False 

1071 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1") 

1072 True 

1073 >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1") 

1074 False 

1075 >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True) 

1076 True 

1077 """ 

1078 version = _coerce_version(item) 

1079 

1080 if version is not None and installed and version.is_prerelease: 

1081 prereleases = True 

1082 

1083 check_item = item if version is None else version 

1084 return bool(list(self.filter([check_item], prereleases=prereleases))) 

1085 

1086 @typing.overload 

1087 def filter( 

1088 self, 

1089 iterable: Iterable[UnparsedVersionVar], 

1090 prereleases: bool | None = None, 

1091 key: None = ..., 

1092 ) -> Iterator[UnparsedVersionVar]: ... 

1093 

1094 @typing.overload 

1095 def filter( 

1096 self, 

1097 iterable: Iterable[T], 

1098 prereleases: bool | None = None, 

1099 key: Callable[[T], UnparsedVersion] = ..., 

1100 ) -> Iterator[T]: ... 

1101 

1102 def filter( 

1103 self, 

1104 iterable: Iterable[Any], 

1105 prereleases: bool | None = None, 

1106 key: Callable[[Any], UnparsedVersion] | None = None, 

1107 ) -> Iterator[Any]: 

1108 """Filter items in the given iterable, that match the specifiers in this set. 

1109 

1110 :param iterable: 

1111 An iterable that can contain version strings and :class:`Version` instances. 

1112 The items in the iterable will be filtered according to the specifier. 

1113 :param prereleases: 

1114 Whether or not to allow prereleases in the returned iterator. If set to 

1115 ``None`` (the default), it will follow the recommendation from :pep:`440` 

1116 and match prereleases if there are no other versions. 

1117 :param key: 

1118 A callable that takes a single argument (an item from the iterable) and 

1119 returns a version string or :class:`Version` instance to be used for 

1120 filtering. 

1121 

1122 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) 

1123 ['1.3'] 

1124 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")])) 

1125 ['1.3', <Version('1.4')>] 

1126 >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"])) 

1127 ['1.5a1'] 

1128 >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) 

1129 ['1.3', '1.5a1'] 

1130 >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) 

1131 ['1.3', '1.5a1'] 

1132 >>> list(SpecifierSet(">=1.2.3").filter( 

1133 ... [{"ver": "1.2"}, {"ver": "1.3"}], 

1134 ... key=lambda x: x["ver"])) 

1135 [{'ver': '1.3'}] 

1136 

1137 An "empty" SpecifierSet will filter items based on the presence of prerelease 

1138 versions in the set. 

1139 

1140 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"])) 

1141 ['1.3'] 

1142 >>> list(SpecifierSet("").filter(["1.5a1"])) 

1143 ['1.5a1'] 

1144 >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"])) 

1145 ['1.3', '1.5a1'] 

1146 >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True)) 

1147 ['1.3', '1.5a1'] 

1148 """ 

1149 # Determine if we're forcing a prerelease or not, if we're not forcing 

1150 # one for this particular filter call, then we'll use whatever the 

1151 # SpecifierSet thinks for whether or not we should support prereleases. 

1152 if prereleases is None and self.prereleases is not None: 

1153 prereleases = self.prereleases 

1154 

1155 # If we have any specifiers, then we want to wrap our iterable in the 

1156 # filter method for each one, this will act as a logical AND amongst 

1157 # each specifier. 

1158 if self._specs: 

1159 # When prereleases is None, we need to let all versions through 

1160 # the individual filters, then decide about prereleases at the end 

1161 # based on whether any non-prereleases matched ALL specs. 

1162 for spec in self._specs: 

1163 iterable = spec.filter( 

1164 iterable, 

1165 prereleases=True if prereleases is None else prereleases, 

1166 key=key, 

1167 ) 

1168 

1169 if prereleases is not None: 

1170 # If we have a forced prereleases value, 

1171 # we can immediately return the iterator. 

1172 return iter(iterable) 

1173 else: 

1174 # Handle empty SpecifierSet cases where prereleases is not None. 

1175 if prereleases is True: 

1176 return iter(iterable) 

1177 

1178 if prereleases is False: 

1179 return ( 

1180 item 

1181 for item in iterable 

1182 if (version := _coerce_version(item)) is None 

1183 or not version.is_prerelease 

1184 ) 

1185 

1186 # PEP 440: exclude prereleases unless no final releases matched 

1187 return _pep440_filter_prereleases(iterable, key)