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

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

259 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 

16from typing import Callable, Final, Iterable, Iterator, TypeVar, Union 

17 

18from .utils import canonicalize_version 

19from .version import Version 

20 

21UnparsedVersion = Union[Version, str] 

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

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

24 

25 

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

27 if not isinstance(version, Version): 

28 version = Version(version) 

29 return version 

30 

31 

32class 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 

44class 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 # noqa: B027 

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 

99class 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: Final = { 

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 @property 

248 def prereleases(self) -> bool: 

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

250 # blindly use that. 

251 if self._prereleases is not None: 

252 return self._prereleases 

253 

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

255 # the version in the specifier is a prerelease. 

256 operator, version = self._spec 

257 if operator != "!=": 

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 # Compatible releases have an equivalent combination of >= and ==. That 

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

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

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

371 # the other specifiers. 

372 

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

374 # ignore suffix segments. 

375 prefix = _version_join( 

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

377 ) 

378 

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

380 prefix += ".*" 

381 

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

383 prospective, prefix 

384 ) 

385 

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

387 # We need special logic to handle prefix matching 

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

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

390 normalized_prospective = canonicalize_version( 

391 prospective.public, strip_trailing_zero=False 

392 ) 

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

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

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

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

397 split_spec = _version_split(normalized_spec) 

398 

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

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

401 # a pre-release segment. 

402 split_prospective = _version_split(normalized_prospective) 

403 

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

405 # shortened version. 

406 padded_prospective, _ = _pad_version(split_prospective, split_spec) 

407 

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

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

410 # prospective version or not. 

411 shortened_prospective = padded_prospective[: len(split_spec)] 

412 

413 return shortened_prospective == split_spec 

414 else: 

415 # Convert our spec string into a Version 

416 spec_version = Version(spec) 

417 

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

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

420 # segment. 

421 if not spec_version.local: 

422 prospective = Version(prospective.public) 

423 

424 return prospective == spec_version 

425 

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

427 return not self._compare_equal(prospective, spec) 

428 

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

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

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

432 # the prospective version. 

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

434 

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

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

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

438 # the prospective version. 

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

440 

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

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

443 # it as a version. 

444 spec = Version(spec_str) 

445 

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

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

448 # instead of doing extra unneeded work. 

449 if not prospective < spec: 

450 return False 

451 

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

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

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

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

456 if not spec.is_prerelease and prospective.is_prerelease: 

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

458 return False 

459 

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

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

462 # version in the spec. 

463 return True 

464 

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

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

467 # it as a version. 

468 spec = Version(spec_str) 

469 

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

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

472 # instead of doing extra unneeded work. 

473 if not prospective > spec: 

474 return False 

475 

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

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

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

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

480 if not spec.is_postrelease and prospective.is_postrelease: 

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

482 return False 

483 

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

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

486 if prospective.local is not None: 

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

488 return False 

489 

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

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

492 # same version in the spec. 

493 return True 

494 

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

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

497 

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

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

500 

501 :param item: The item to check for. 

502 

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

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

505 

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

507 True 

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

509 True 

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

511 False 

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

513 False 

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

515 True 

516 """ 

517 return self.contains(item) 

518 

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

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

521 

522 :param item: 

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

524 :class:`Version` instance. 

525 :param prereleases: 

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

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

528 whether or not prereleases are allowed. 

529 

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

531 True 

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

533 True 

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

535 False 

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

537 False 

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

539 True 

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

541 True 

542 """ 

543 

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

545 if prereleases is None: 

546 prereleases = self.prereleases 

547 

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

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

550 normalized_item = _coerce_version(item) 

551 

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

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

554 # logic if this version is a prereleases. 

555 if normalized_item.is_prerelease and not prereleases: 

556 return False 

557 

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

559 # within this Specifier or not. 

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

561 return operator_callable(normalized_item, self.version) 

562 

563 def filter( 

564 self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None 

565 ) -> Iterator[UnparsedVersionVar]: 

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

567 

568 :param iterable: 

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

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

571 :param prereleases: 

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

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

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

575 whether the only versions matching are prereleases). 

576 

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

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

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

580 

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

582 ['1.3'] 

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

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

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

586 ['1.5a1'] 

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

588 ['1.3', '1.5a1'] 

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

590 ['1.3', '1.5a1'] 

591 """ 

592 

593 yielded = False 

594 found_prereleases = [] 

595 

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

597 

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

599 # them match, yield them. 

600 for version in iterable: 

601 parsed_version = _coerce_version(version) 

602 

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

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

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

606 # else matches this specifier. 

607 if parsed_version.is_prerelease and not ( 

608 prereleases or self.prereleases 

609 ): 

610 found_prereleases.append(version) 

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

612 # accepting prereleases from the beginning. 

613 else: 

614 yielded = True 

615 yield version 

616 

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

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

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

620 if not yielded and found_prereleases: 

621 for version in found_prereleases: 

622 yield version 

623 

624 

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

626 

627 

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

629 """Split version into components. 

630 

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

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

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

634 version string. 

635 """ 

636 result: list[str] = [] 

637 

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

639 result.append(epoch or "0") 

640 

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

642 match = _prefix_regex.search(item) 

643 if match: 

644 result.extend(match.groups()) 

645 else: 

646 result.append(item) 

647 return result 

648 

649 

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

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

652 

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

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

655 components numeric. 

656 """ 

657 epoch, *rest = components 

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

659 

660 

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

662 return not any( 

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

664 ) 

665 

666 

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

668 left_split, right_split = [], [] 

669 

670 # Get the release segment of our versions 

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

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

673 

674 # Get the rest of our versions 

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

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

677 

678 # Insert our padding 

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

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

681 

682 return ( 

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

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

685 ) 

686 

687 

688class SpecifierSet(BaseSpecifier): 

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

690 

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

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

693 """ 

694 

695 def __init__( 

696 self, 

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

698 prereleases: bool | None = None, 

699 ) -> None: 

700 """Initialize a SpecifierSet instance. 

701 

702 :param specifiers: 

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

704 specifiers which will be parsed and normalized before use. 

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

706 as is. 

707 :param prereleases: 

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

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

710 given specifiers. 

711 

712 :raises InvalidSpecifier: 

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

714 raised. 

715 """ 

716 

717 if isinstance(specifiers, str): 

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

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

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

721 

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

723 # for later. 

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

725 else: 

726 # Save the supplied specifiers in a frozen set. 

727 self._specs = frozenset(specifiers) 

728 

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

730 # we accept prereleases or not. 

731 self._prereleases = prereleases 

732 

733 @property 

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

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

736 # pass that through here. 

737 if self._prereleases is not None: 

738 return self._prereleases 

739 

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

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

742 # pre-releases or not. 

743 if not self._specs: 

744 return None 

745 

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

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

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

749 

750 @prereleases.setter 

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

752 self._prereleases = value 

753 

754 def __repr__(self) -> str: 

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

756 

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

758 match the input string. 

759 

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

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

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

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

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

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

766 """ 

767 pre = ( 

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

769 if self._prereleases is not None 

770 else "" 

771 ) 

772 

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

774 

775 def __str__(self) -> str: 

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

777 

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

779 match the input string. 

780 

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

782 '!=1.0.1,>=1.0.0' 

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

784 '!=1.0.1,>=1.0.0' 

785 """ 

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

787 

788 def __hash__(self) -> int: 

789 return hash(self._specs) 

790 

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

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

793 

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

795 

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

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

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

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

800 """ 

801 if isinstance(other, str): 

802 other = SpecifierSet(other) 

803 elif not isinstance(other, SpecifierSet): 

804 return NotImplemented 

805 

806 specifier = SpecifierSet() 

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

808 

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

810 specifier._prereleases = other._prereleases 

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

812 specifier._prereleases = self._prereleases 

813 elif self._prereleases == other._prereleases: 

814 specifier._prereleases = self._prereleases 

815 else: 

816 raise ValueError( 

817 "Cannot combine SpecifierSets with True and False prerelease 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: bool | None = None, 

887 installed: bool | None = 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: bool | None = 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)