Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/setuptools/_distutils/_vendor/packaging/specifiers.py: 13%

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

255 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 

11import abc 

12import itertools 

13import re 

14from typing import Callable, Iterable, Iterator, List, Optional, Tuple, TypeVar, Union 

15 

16from .utils import canonicalize_version 

17from .version import Version 

18 

19UnparsedVersion = Union[Version, str] 

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

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

22 

23 

24def _coerce_version(version: UnparsedVersion) -> Version: 

25 if not isinstance(version, Version): 

26 version = Version(version) 

27 return version 

28 

29 

30class InvalidSpecifier(ValueError): 

31 """ 

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

33 string that is invalid. 

34 

35 >>> Specifier("lolwat") 

36 Traceback (most recent call last): 

37 ... 

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

39 """ 

40 

41 

42class BaseSpecifier(metaclass=abc.ABCMeta): 

43 @abc.abstractmethod 

44 def __str__(self) -> str: 

45 """ 

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

47 should be representative of the Specifier itself. 

48 """ 

49 

50 @abc.abstractmethod 

51 def __hash__(self) -> int: 

52 """ 

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

54 """ 

55 

56 @abc.abstractmethod 

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

58 """ 

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

60 objects are equal. 

61 

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

63 """ 

64 

65 @property 

66 @abc.abstractmethod 

67 def prereleases(self) -> Optional[bool]: 

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

69 

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

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

72 """ 

73 

74 @prereleases.setter 

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

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

77 

78 :param value: The value to set. 

79 """ 

80 

81 @abc.abstractmethod 

82 def contains(self, item: str, prereleases: Optional[bool] = None) -> bool: 

83 """ 

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

85 """ 

86 

87 @abc.abstractmethod 

88 def filter( 

89 self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None 

90 ) -> Iterator[UnparsedVersionVar]: 

91 """ 

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

93 are contained within this specifier are allowed in it. 

94 """ 

95 

96 

97class Specifier(BaseSpecifier): 

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

99 

100 .. tip:: 

101 

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

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

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

105 """ 

106 

107 _operator_regex_str = r""" 

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

109 """ 

110 _version_regex_str = r""" 

111 (?P<version> 

112 (?: 

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

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

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

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

117 # but included entirely as an escape hatch. 

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

119 \s* 

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

121 # we match everything except for whitespace, a 

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

123 # since versions can be enclosed in them. 

124 ) 

125 | 

126 (?: 

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

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

129 # operators separately to enable that. 

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

131 

132 \s* 

133 v? 

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

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

136 

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

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

139 (?: 

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

141 | 

142 (?: # pre release 

143 [-_\.]? 

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

145 [-_\.]? 

146 [0-9]* 

147 )? 

148 (?: # post release 

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

150 )? 

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

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

153 )? 

154 ) 

155 | 

156 (?: 

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

158 # release segment. 

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

160 

161 \s* 

162 v? 

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

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

165 (?: # pre release 

166 [-_\.]? 

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

168 [-_\.]? 

169 [0-9]* 

170 )? 

171 (?: # post release 

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

173 )? 

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

175 ) 

176 | 

177 (?: 

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

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

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

181 # matching wild cards. 

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

183 # operators so we want to make sure they 

184 # don't match here. 

185 

186 \s* 

187 v? 

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

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

190 (?: # pre release 

191 [-_\.]? 

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

193 [-_\.]? 

194 [0-9]* 

195 )? 

196 (?: # post release 

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

198 )? 

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

200 ) 

201 ) 

202 """ 

203 

204 _regex = re.compile( 

205 r"^\s*" + _operator_regex_str + _version_regex_str + r"\s*$", 

206 re.VERBOSE | re.IGNORECASE, 

207 ) 

208 

209 _operators = { 

210 "~=": "compatible", 

211 "==": "equal", 

212 "!=": "not_equal", 

213 "<=": "less_than_equal", 

214 ">=": "greater_than_equal", 

215 "<": "less_than", 

216 ">": "greater_than", 

217 "===": "arbitrary", 

218 } 

219 

220 def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: 

221 """Initialize a Specifier instance. 

222 

223 :param spec: 

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

225 normalized before use. 

226 :param prereleases: 

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

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

229 given specifiers. 

230 :raises InvalidSpecifier: 

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

232 """ 

233 match = self._regex.search(spec) 

234 if not match: 

235 raise InvalidSpecifier(f"Invalid specifier: '{spec}'") 

236 

237 self._spec: Tuple[str, str] = ( 

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

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

240 ) 

241 

242 # Store whether or not this Specifier should accept prereleases 

243 self._prereleases = prereleases 

244 

245 # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515 

246 @property # type: ignore[override] 

247 def prereleases(self) -> bool: 

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

249 # blindly use that. 

250 if self._prereleases is not None: 

251 return self._prereleases 

252 

253 # Look at all of our specifiers and determine if they are inclusive 

254 # operators, and if they are if they are including an explicit 

255 # prerelease. 

256 operator, version = self._spec 

257 if operator in ["==", ">=", "<=", "~=", "==="]: 

258 # The == specifier can include a trailing .*, if it does we 

259 # want to remove before parsing. 

260 if operator == "==" and version.endswith(".*"): 

261 version = version[:-2] 

262 

263 # Parse the version, and if it is a pre-release than this 

264 # specifier allows pre-releases. 

265 if Version(version).is_prerelease: 

266 return True 

267 

268 return False 

269 

270 @prereleases.setter 

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

272 self._prereleases = value 

273 

274 @property 

275 def operator(self) -> str: 

276 """The operator of this specifier. 

277 

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

279 '==' 

280 """ 

281 return self._spec[0] 

282 

283 @property 

284 def version(self) -> str: 

285 """The version of this specifier. 

286 

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

288 '1.2.3' 

289 """ 

290 return self._spec[1] 

291 

292 def __repr__(self) -> str: 

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

294 

295 >>> Specifier('>=1.0.0') 

296 <Specifier('>=1.0.0')> 

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

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

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

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

301 """ 

302 pre = ( 

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

304 if self._prereleases is not None 

305 else "" 

306 ) 

307 

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

309 

310 def __str__(self) -> str: 

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

312 

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

314 '>=1.0.0' 

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

316 '>=1.0.0' 

317 """ 

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

319 

320 @property 

321 def _canonical_spec(self) -> Tuple[str, str]: 

322 canonical_version = canonicalize_version( 

323 self._spec[1], 

324 strip_trailing_zero=(self._spec[0] != "~="), 

325 ) 

326 return self._spec[0], canonical_version 

327 

328 def __hash__(self) -> int: 

329 return hash(self._canonical_spec) 

330 

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

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

333 

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

335 

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

337 

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

339 True 

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

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

342 True 

343 >>> Specifier("==1.2.3") == "==1.2.3" 

344 True 

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

346 False 

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

348 False 

349 """ 

350 if isinstance(other, str): 

351 try: 

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

353 except InvalidSpecifier: 

354 return NotImplemented 

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

356 return NotImplemented 

357 

358 return self._canonical_spec == other._canonical_spec 

359 

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

361 operator_callable: CallableOperator = getattr( 

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

363 ) 

364 return operator_callable 

365 

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

367 

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

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

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

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

372 # the other specifiers. 

373 

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

375 # ignore suffix segments. 

376 prefix = _version_join( 

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

378 ) 

379 

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

381 prefix += ".*" 

382 

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

384 prospective, prefix 

385 ) 

386 

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

388 

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 

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

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

435 # the prospective version. 

436 return Version(prospective.public) <= Version(spec) 

437 

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

439 

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

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

442 # the prospective version. 

443 return Version(prospective.public) >= Version(spec) 

444 

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

446 

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

448 # it as a version. 

449 spec = Version(spec_str) 

450 

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

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

453 # instead of doing extra unneeded work. 

454 if not prospective < spec: 

455 return False 

456 

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

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

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

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

461 if not spec.is_prerelease and prospective.is_prerelease: 

462 if Version(prospective.base_version) == Version(spec.base_version): 

463 return False 

464 

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

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

467 # version in the spec. 

468 return True 

469 

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

471 

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

473 # it as a version. 

474 spec = Version(spec_str) 

475 

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

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

478 # instead of doing extra unneeded work. 

479 if not prospective > spec: 

480 return False 

481 

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

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

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

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

486 if not spec.is_postrelease and prospective.is_postrelease: 

487 if Version(prospective.base_version) == Version(spec.base_version): 

488 return False 

489 

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

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

492 if prospective.local is not None: 

493 if Version(prospective.base_version) == Version(spec.base_version): 

494 return False 

495 

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

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

498 # same version in the spec. 

499 return True 

500 

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

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

503 

504 def __contains__(self, item: Union[str, Version]) -> bool: 

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

506 

507 :param item: The item to check for. 

508 

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

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

511 

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

513 True 

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

515 True 

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

517 False 

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

519 False 

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

521 True 

522 """ 

523 return self.contains(item) 

524 

525 def contains( 

526 self, item: UnparsedVersion, prereleases: Optional[bool] = None 

527 ) -> bool: 

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

529 

530 :param item: 

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

532 :class:`Version` instance. 

533 :param prereleases: 

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

535 ``None`` (the default), it uses :attr:`prereleases` to determine 

536 whether or not prereleases are allowed. 

537 

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

539 True 

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

541 True 

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

543 False 

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

545 False 

546 >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1") 

547 True 

548 >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True) 

549 True 

550 """ 

551 

552 # Determine if prereleases are to be allowed or not. 

553 if prereleases is None: 

554 prereleases = self.prereleases 

555 

556 # Normalize item to a Version, this allows us to have a shortcut for 

557 # "2.0" in Specifier(">=2") 

558 normalized_item = _coerce_version(item) 

559 

560 # Determine if we should be supporting prereleases in this specifier 

561 # or not, if we do not support prereleases than we can short circuit 

562 # logic if this version is a prereleases. 

563 if normalized_item.is_prerelease and not prereleases: 

564 return False 

565 

566 # Actually do the comparison to determine if this item is contained 

567 # within this Specifier or not. 

568 operator_callable: CallableOperator = self._get_operator(self.operator) 

569 return operator_callable(normalized_item, self.version) 

570 

571 def filter( 

572 self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None 

573 ) -> Iterator[UnparsedVersionVar]: 

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

575 

576 :param iterable: 

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

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

579 :param prereleases: 

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

581 ``None`` (the default), it will be intelligently decide whether to allow 

582 prereleases or not (based on the :attr:`prereleases` attribute, and 

583 whether the only versions matching are prereleases). 

584 

585 This method is smarter than just ``filter(Specifier().contains, [...])`` 

586 because it implements the rule from :pep:`440` that a prerelease item 

587 SHOULD be accepted if no other versions match the given specifier. 

588 

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

590 ['1.3'] 

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

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

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

594 ['1.5a1'] 

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

596 ['1.3', '1.5a1'] 

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

598 ['1.3', '1.5a1'] 

599 """ 

600 

601 yielded = False 

602 found_prereleases = [] 

603 

604 kw = {"prereleases": prereleases if prereleases is not None else True} 

605 

606 # Attempt to iterate over all the values in the iterable and if any of 

607 # them match, yield them. 

608 for version in iterable: 

609 parsed_version = _coerce_version(version) 

610 

611 if self.contains(parsed_version, **kw): 

612 # If our version is a prerelease, and we were not set to allow 

613 # prereleases, then we'll store it for later in case nothing 

614 # else matches this specifier. 

615 if parsed_version.is_prerelease and not ( 

616 prereleases or self.prereleases 

617 ): 

618 found_prereleases.append(version) 

619 # Either this is not a prerelease, or we should have been 

620 # accepting prereleases from the beginning. 

621 else: 

622 yielded = True 

623 yield version 

624 

625 # Now that we've iterated over everything, determine if we've yielded 

626 # any values, and if we have not and we have any prereleases stored up 

627 # then we will go ahead and yield the prereleases. 

628 if not yielded and found_prereleases: 

629 for version in found_prereleases: 

630 yield version 

631 

632 

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

634 

635 

636def _version_split(version: str) -> List[str]: 

637 """Split version into components. 

638 

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

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

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

642 version string. 

643 """ 

644 result: List[str] = [] 

645 

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

647 result.append(epoch or "0") 

648 

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

650 match = _prefix_regex.search(item) 

651 if match: 

652 result.extend(match.groups()) 

653 else: 

654 result.append(item) 

655 return result 

656 

657 

658def _version_join(components: List[str]) -> str: 

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

660 

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

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

663 components numeric. 

664 """ 

665 epoch, *rest = components 

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

667 

668 

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

670 return not any( 

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

672 ) 

673 

674 

675def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]: 

676 left_split, right_split = [], [] 

677 

678 # Get the release segment of our versions 

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

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

681 

682 # Get the rest of our versions 

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

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

685 

686 # Insert our padding 

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

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

689 

690 return ( 

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

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

693 ) 

694 

695 

696class SpecifierSet(BaseSpecifier): 

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

698 

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

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

701 """ 

702 

703 def __init__( 

704 self, specifiers: str = "", prereleases: Optional[bool] = None 

705 ) -> None: 

706 """Initialize a SpecifierSet instance. 

707 

708 :param specifiers: 

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

710 specifiers which will be parsed and normalized before use. 

711 :param prereleases: 

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

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

714 given specifiers. 

715 

716 :raises InvalidSpecifier: 

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

718 raised. 

719 """ 

720 

721 # Split on `,` to break each individual specifier into it's own item, and 

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

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

724 

725 # Make each individual specifier a Specifier and save in a frozen set for later. 

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

727 

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

729 # we accept prereleases or not. 

730 self._prereleases = prereleases 

731 

732 @property 

733 def prereleases(self) -> Optional[bool]: 

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

735 # pass that through here. 

736 if self._prereleases is not None: 

737 return self._prereleases 

738 

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

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

741 # pre-releases or not. 

742 if not self._specs: 

743 return None 

744 

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

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

747 return any(s.prereleases for s in self._specs) 

748 

749 @prereleases.setter 

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

751 self._prereleases = value 

752 

753 def __repr__(self) -> str: 

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

755 

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

757 match the input string. 

758 

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

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

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

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

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

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

765 """ 

766 pre = ( 

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

768 if self._prereleases is not None 

769 else "" 

770 ) 

771 

772 return f"<SpecifierSet({str(self)!r}{pre})>" 

773 

774 def __str__(self) -> str: 

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

776 

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

778 match the input string. 

779 

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

781 '!=1.0.1,>=1.0.0' 

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

783 '!=1.0.1,>=1.0.0' 

784 """ 

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

786 

787 def __hash__(self) -> int: 

788 return hash(self._specs) 

789 

790 def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet": 

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

792 

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

794 

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

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

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

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

799 """ 

800 if isinstance(other, str): 

801 other = SpecifierSet(other) 

802 elif not isinstance(other, SpecifierSet): 

803 return NotImplemented 

804 

805 specifier = SpecifierSet() 

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

807 

808 if self._prereleases is None and other._prereleases is not None: 

809 specifier._prereleases = other._prereleases 

810 elif self._prereleases is not None and other._prereleases is None: 

811 specifier._prereleases = self._prereleases 

812 elif self._prereleases == other._prereleases: 

813 specifier._prereleases = self._prereleases 

814 else: 

815 raise ValueError( 

816 "Cannot combine SpecifierSets with True and False prerelease " 

817 "overrides." 

818 ) 

819 

820 return specifier 

821 

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

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

824 

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

826 

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

828 

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

830 True 

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

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

833 True 

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

835 True 

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

837 False 

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

839 False 

840 """ 

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

842 other = SpecifierSet(str(other)) 

843 elif not isinstance(other, SpecifierSet): 

844 return NotImplemented 

845 

846 return self._specs == other._specs 

847 

848 def __len__(self) -> int: 

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

850 return len(self._specs) 

851 

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

853 """ 

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

855 in this specifier set. 

856 

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

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

859 """ 

860 return iter(self._specs) 

861 

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

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

864 

865 :param item: The item to check for. 

866 

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

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

869 

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

871 True 

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

873 True 

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

875 False 

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

877 False 

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

879 True 

880 """ 

881 return self.contains(item) 

882 

883 def contains( 

884 self, 

885 item: UnparsedVersion, 

886 prereleases: Optional[bool] = None, 

887 installed: Optional[bool] = None, 

888 ) -> bool: 

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

890 

891 :param item: 

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

893 :class:`Version` instance. 

894 :param prereleases: 

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

896 ``None`` (the default), it uses :attr:`prereleases` to determine 

897 whether or not prereleases are allowed. 

898 

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

900 True 

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

902 True 

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

904 False 

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

906 False 

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

908 True 

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

910 True 

911 """ 

912 # Ensure that our item is a Version instance. 

913 if not isinstance(item, Version): 

914 item = Version(item) 

915 

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

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

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

919 if prereleases is None: 

920 prereleases = self.prereleases 

921 

922 # We can determine if we're going to allow pre-releases by looking to 

923 # see if any of the underlying items supports them. If none of them do 

924 # and this item is a pre-release then we do not allow it and we can 

925 # short circuit that here. 

926 # Note: This means that 1.0.dev1 would not be contained in something 

927 # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 

928 if not prereleases and item.is_prerelease: 

929 return False 

930 

931 if installed and item.is_prerelease: 

932 item = Version(item.base_version) 

933 

934 # We simply dispatch to the underlying specs here to make sure that the 

935 # given version is contained within all of them. 

936 # Note: This use of all() here means that an empty set of specifiers 

937 # will always return True, this is an explicit design decision. 

938 return all(s.contains(item, prereleases=prereleases) for s in self._specs) 

939 

940 def filter( 

941 self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None 

942 ) -> Iterator[UnparsedVersionVar]: 

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

944 

945 :param iterable: 

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

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

948 :param prereleases: 

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

950 ``None`` (the default), it will be intelligently decide whether to allow 

951 prereleases or not (based on the :attr:`prereleases` attribute, and 

952 whether the only versions matching are prereleases). 

953 

954 This method is smarter than just ``filter(SpecifierSet(...).contains, [...])`` 

955 because it implements the rule from :pep:`440` that a prerelease item 

956 SHOULD be accepted if no other versions match the given specifier. 

957 

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

959 ['1.3'] 

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

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

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

963 [] 

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

965 ['1.3', '1.5a1'] 

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

967 ['1.3', '1.5a1'] 

968 

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

970 versions in the set. 

971 

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

973 ['1.3'] 

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

975 ['1.5a1'] 

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

977 ['1.3', '1.5a1'] 

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

979 ['1.3', '1.5a1'] 

980 """ 

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

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

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

984 if prereleases is None: 

985 prereleases = self.prereleases 

986 

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

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

989 # each specifier. 

990 if self._specs: 

991 for spec in self._specs: 

992 iterable = spec.filter(iterable, prereleases=bool(prereleases)) 

993 return iter(iterable) 

994 # If we do not have any specifiers, then we need to have a rough filter 

995 # which will filter out any pre-releases, unless there are no final 

996 # releases. 

997 else: 

998 filtered: List[UnparsedVersionVar] = [] 

999 found_prereleases: List[UnparsedVersionVar] = [] 

1000 

1001 for item in iterable: 

1002 parsed_version = _coerce_version(item) 

1003 

1004 # Store any item which is a pre-release for later unless we've 

1005 # already found a final version or we are accepting prereleases 

1006 if parsed_version.is_prerelease and not prereleases: 

1007 if not filtered: 

1008 found_prereleases.append(item) 

1009 else: 

1010 filtered.append(item) 

1011 

1012 # If we've found no items except for pre-releases, then we'll go 

1013 # ahead and use the pre-releases 

1014 if not filtered and found_prereleases and prereleases is None: 

1015 return iter(found_prereleases) 

1016 

1017 return iter(filtered)