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

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

258 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, 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 

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 = { 

212 "~=": "compatible", 

213 "==": "equal", 

214 "!=": "not_equal", 

215 "<=": "less_than_equal", 

216 ">=": "greater_than_equal", 

217 "<": "less_than", 

218 ">": "greater_than", 

219 "===": "arbitrary", 

220 } 

221 

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

223 """Initialize a Specifier instance. 

224 

225 :param spec: 

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

227 normalized before use. 

228 :param prereleases: 

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

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

231 given specifiers. 

232 :raises InvalidSpecifier: 

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

234 """ 

235 match = self._regex.search(spec) 

236 if not match: 

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

238 

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

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

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

242 ) 

243 

244 # Store whether or not this Specifier should accept prereleases 

245 self._prereleases = prereleases 

246 

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

248 @property # type: ignore[override] 

249 def prereleases(self) -> bool: 

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

251 # blindly use that. 

252 if self._prereleases is not None: 

253 return self._prereleases 

254 

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

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

257 # prerelease. 

258 operator, version = self._spec 

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

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

261 # want to remove before parsing. 

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

263 version = version[:-2] 

264 

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

266 # specifier allows pre-releases. 

267 if Version(version).is_prerelease: 

268 return True 

269 

270 return False 

271 

272 @prereleases.setter 

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

274 self._prereleases = value 

275 

276 @property 

277 def operator(self) -> str: 

278 """The operator of this specifier. 

279 

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

281 '==' 

282 """ 

283 return self._spec[0] 

284 

285 @property 

286 def version(self) -> str: 

287 """The version of this specifier. 

288 

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

290 '1.2.3' 

291 """ 

292 return self._spec[1] 

293 

294 def __repr__(self) -> str: 

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

296 

297 >>> Specifier('>=1.0.0') 

298 <Specifier('>=1.0.0')> 

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

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

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

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

303 """ 

304 pre = ( 

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

306 if self._prereleases is not None 

307 else "" 

308 ) 

309 

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

311 

312 def __str__(self) -> str: 

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

314 

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

316 '>=1.0.0' 

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

318 '>=1.0.0' 

319 """ 

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

321 

322 @property 

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

324 canonical_version = canonicalize_version( 

325 self._spec[1], 

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

327 ) 

328 return self._spec[0], canonical_version 

329 

330 def __hash__(self) -> int: 

331 return hash(self._canonical_spec) 

332 

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

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

335 

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

337 

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

339 

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

341 True 

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

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

344 True 

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

346 True 

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

348 False 

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

350 False 

351 """ 

352 if isinstance(other, str): 

353 try: 

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

355 except InvalidSpecifier: 

356 return NotImplemented 

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

358 return NotImplemented 

359 

360 return self._canonical_spec == other._canonical_spec 

361 

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

363 operator_callable: CallableOperator = getattr( 

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

365 ) 

366 return operator_callable 

367 

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

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

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

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

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

373 # the other specifiers. 

374 

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

376 # ignore suffix segments. 

377 prefix = _version_join( 

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

379 ) 

380 

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

382 prefix += ".*" 

383 

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

385 prospective, prefix 

386 ) 

387 

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

389 # We need special logic to handle prefix matching 

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

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

392 normalized_prospective = canonicalize_version( 

393 prospective.public, strip_trailing_zero=False 

394 ) 

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

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

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

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

399 split_spec = _version_split(normalized_spec) 

400 

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

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

403 # a pre-release segment. 

404 split_prospective = _version_split(normalized_prospective) 

405 

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

407 # shortened version. 

408 padded_prospective, _ = _pad_version(split_prospective, split_spec) 

409 

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

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

412 # prospective version or not. 

413 shortened_prospective = padded_prospective[: len(split_spec)] 

414 

415 return shortened_prospective == split_spec 

416 else: 

417 # Convert our spec string into a Version 

418 spec_version = Version(spec) 

419 

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

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

422 # segment. 

423 if not spec_version.local: 

424 prospective = Version(prospective.public) 

425 

426 return prospective == spec_version 

427 

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

429 return not self._compare_equal(prospective, spec) 

430 

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

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

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

434 # the prospective version. 

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

436 

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

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

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

440 # the prospective version. 

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

442 

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

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

445 # it as a version. 

446 spec = Version(spec_str) 

447 

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

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

450 # instead of doing extra unneeded work. 

451 if not prospective < spec: 

452 return False 

453 

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

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

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

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

458 if not spec.is_prerelease and prospective.is_prerelease: 

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

460 return False 

461 

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

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

464 # version in the spec. 

465 return True 

466 

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

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

469 # it as a version. 

470 spec = Version(spec_str) 

471 

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

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

474 # instead of doing extra unneeded work. 

475 if not prospective > spec: 

476 return False 

477 

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

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

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

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

482 if not spec.is_postrelease and prospective.is_postrelease: 

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

484 return False 

485 

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

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

488 if prospective.local is not None: 

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

490 return False 

491 

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

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

494 # same version in the spec. 

495 return True 

496 

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

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

499 

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

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

502 

503 :param item: The item to check for. 

504 

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

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

507 

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

509 True 

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

511 True 

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

513 False 

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

515 False 

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

517 True 

518 """ 

519 return self.contains(item) 

520 

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

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

523 

524 :param item: 

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

526 :class:`Version` instance. 

527 :param prereleases: 

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

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

530 whether or not prereleases are allowed. 

531 

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

533 True 

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

535 True 

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

537 False 

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

539 False 

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

541 True 

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

543 True 

544 """ 

545 

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

547 if prereleases is None: 

548 prereleases = self.prereleases 

549 

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

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

552 normalized_item = _coerce_version(item) 

553 

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

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

556 # logic if this version is a prereleases. 

557 if normalized_item.is_prerelease and not prereleases: 

558 return False 

559 

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

561 # within this Specifier or not. 

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

563 return operator_callable(normalized_item, self.version) 

564 

565 def filter( 

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

567 ) -> Iterator[UnparsedVersionVar]: 

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

569 

570 :param iterable: 

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

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

573 :param prereleases: 

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

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

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

577 whether the only versions matching are prereleases). 

578 

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

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

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

582 

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

584 ['1.3'] 

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

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

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

588 ['1.5a1'] 

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

590 ['1.3', '1.5a1'] 

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

592 ['1.3', '1.5a1'] 

593 """ 

594 

595 yielded = False 

596 found_prereleases = [] 

597 

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

599 

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

601 # them match, yield them. 

602 for version in iterable: 

603 parsed_version = _coerce_version(version) 

604 

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

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

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

608 # else matches this specifier. 

609 if parsed_version.is_prerelease and not ( 

610 prereleases or self.prereleases 

611 ): 

612 found_prereleases.append(version) 

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

614 # accepting prereleases from the beginning. 

615 else: 

616 yielded = True 

617 yield version 

618 

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

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

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

622 if not yielded and found_prereleases: 

623 for version in found_prereleases: 

624 yield version 

625 

626 

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

628 

629 

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

631 """Split version into components. 

632 

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

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

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

636 version string. 

637 """ 

638 result: list[str] = [] 

639 

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

641 result.append(epoch or "0") 

642 

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

644 match = _prefix_regex.search(item) 

645 if match: 

646 result.extend(match.groups()) 

647 else: 

648 result.append(item) 

649 return result 

650 

651 

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

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

654 

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

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

657 components numeric. 

658 """ 

659 epoch, *rest = components 

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

661 

662 

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

664 return not any( 

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

666 ) 

667 

668 

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

670 left_split, right_split = [], [] 

671 

672 # Get the release segment of our versions 

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

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

675 

676 # Get the rest of our versions 

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

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

679 

680 # Insert our padding 

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

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

683 

684 return ( 

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

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

687 ) 

688 

689 

690class SpecifierSet(BaseSpecifier): 

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

692 

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

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

695 """ 

696 

697 def __init__( 

698 self, 

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

700 prereleases: bool | None = None, 

701 ) -> None: 

702 """Initialize a SpecifierSet instance. 

703 

704 :param specifiers: 

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

706 specifiers which will be parsed and normalized before use. 

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

708 as is. 

709 :param prereleases: 

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

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

712 given specifiers. 

713 

714 :raises InvalidSpecifier: 

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

716 raised. 

717 """ 

718 

719 if isinstance(specifiers, str): 

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

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

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

723 

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

725 # for later. 

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

727 else: 

728 # Save the supplied specifiers in a frozen set. 

729 self._specs = frozenset(specifiers) 

730 

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

732 # we accept prereleases or not. 

733 self._prereleases = prereleases 

734 

735 @property 

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

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

738 # pass that through here. 

739 if self._prereleases is not None: 

740 return self._prereleases 

741 

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

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

744 # pre-releases or not. 

745 if not self._specs: 

746 return None 

747 

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

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

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

751 

752 @prereleases.setter 

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

754 self._prereleases = value 

755 

756 def __repr__(self) -> str: 

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

758 

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

760 match the input string. 

761 

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

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

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

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

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

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

768 """ 

769 pre = ( 

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

771 if self._prereleases is not None 

772 else "" 

773 ) 

774 

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

776 

777 def __str__(self) -> str: 

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

779 

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

781 match the input string. 

782 

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

784 '!=1.0.1,>=1.0.0' 

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

786 '!=1.0.1,>=1.0.0' 

787 """ 

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

789 

790 def __hash__(self) -> int: 

791 return hash(self._specs) 

792 

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

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

795 

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

797 

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

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

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

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

802 """ 

803 if isinstance(other, str): 

804 other = SpecifierSet(other) 

805 elif not isinstance(other, SpecifierSet): 

806 return NotImplemented 

807 

808 specifier = SpecifierSet() 

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

810 

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

812 specifier._prereleases = other._prereleases 

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

814 specifier._prereleases = self._prereleases 

815 elif self._prereleases == other._prereleases: 

816 specifier._prereleases = self._prereleases 

817 else: 

818 raise ValueError( 

819 "Cannot combine SpecifierSets with True and False prerelease " 

820 "overrides." 

821 ) 

822 

823 return specifier 

824 

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

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

827 

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

829 

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

831 

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

833 True 

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

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

836 True 

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

838 True 

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

840 False 

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

842 False 

843 """ 

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

845 other = SpecifierSet(str(other)) 

846 elif not isinstance(other, SpecifierSet): 

847 return NotImplemented 

848 

849 return self._specs == other._specs 

850 

851 def __len__(self) -> int: 

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

853 return len(self._specs) 

854 

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

856 """ 

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

858 in this specifier set. 

859 

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

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

862 """ 

863 return iter(self._specs) 

864 

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

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

867 

868 :param item: The item to check for. 

869 

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

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

872 

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

874 True 

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

876 True 

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

878 False 

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

880 False 

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

882 True 

883 """ 

884 return self.contains(item) 

885 

886 def contains( 

887 self, 

888 item: UnparsedVersion, 

889 prereleases: bool | None = None, 

890 installed: bool | None = None, 

891 ) -> bool: 

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

893 

894 :param item: 

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

896 :class:`Version` instance. 

897 :param prereleases: 

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

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

900 whether or not prereleases are allowed. 

901 

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

903 True 

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

905 True 

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

907 False 

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

909 False 

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

911 True 

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

913 True 

914 """ 

915 # Ensure that our item is a Version instance. 

916 if not isinstance(item, Version): 

917 item = Version(item) 

918 

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

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

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

922 if prereleases is None: 

923 prereleases = self.prereleases 

924 

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

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

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

928 # short circuit that here. 

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

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

931 if not prereleases and item.is_prerelease: 

932 return False 

933 

934 if installed and item.is_prerelease: 

935 item = Version(item.base_version) 

936 

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

938 # given version is contained within all of them. 

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

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

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

942 

943 def filter( 

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

945 ) -> Iterator[UnparsedVersionVar]: 

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

947 

948 :param iterable: 

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

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

951 :param prereleases: 

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

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

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

955 whether the only versions matching are prereleases). 

956 

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

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

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

960 

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

962 ['1.3'] 

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

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

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

966 [] 

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

968 ['1.3', '1.5a1'] 

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

970 ['1.3', '1.5a1'] 

971 

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

973 versions in the set. 

974 

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

976 ['1.3'] 

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

978 ['1.5a1'] 

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

980 ['1.3', '1.5a1'] 

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

982 ['1.3', '1.5a1'] 

983 """ 

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

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

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

987 if prereleases is None: 

988 prereleases = self.prereleases 

989 

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

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

992 # each specifier. 

993 if self._specs: 

994 for spec in self._specs: 

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

996 return iter(iterable) 

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

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

999 # releases. 

1000 else: 

1001 filtered: list[UnparsedVersionVar] = [] 

1002 found_prereleases: list[UnparsedVersionVar] = [] 

1003 

1004 for item in iterable: 

1005 parsed_version = _coerce_version(item) 

1006 

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

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

1009 if parsed_version.is_prerelease and not prereleases: 

1010 if not filtered: 

1011 found_prereleases.append(item) 

1012 else: 

1013 filtered.append(item) 

1014 

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

1016 # ahead and use the pre-releases 

1017 if not filtered and found_prereleases and prereleases is None: 

1018 return iter(found_prereleases) 

1019 

1020 return iter(filtered)