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

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

260 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 InvalidVersion, Version 

20 

21UnparsedVersion = Union[Version, str] 

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

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

24 

25 

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

27 if not isinstance(version, Version): 

28 try: 

29 version = Version(version) 

30 except InvalidVersion: 

31 return None 

32 return version 

33 

34 

35class InvalidSpecifier(ValueError): 

36 """ 

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

38 string that is invalid. 

39 

40 >>> Specifier("lolwat") 

41 Traceback (most recent call last): 

42 ... 

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

44 """ 

45 

46 

47class BaseSpecifier(metaclass=abc.ABCMeta): 

48 @abc.abstractmethod 

49 def __str__(self) -> str: 

50 """ 

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

52 should be representative of the Specifier itself. 

53 """ 

54 

55 @abc.abstractmethod 

56 def __hash__(self) -> int: 

57 """ 

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

59 """ 

60 

61 @abc.abstractmethod 

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

63 """ 

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

65 objects are equal. 

66 

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

68 """ 

69 

70 @property 

71 @abc.abstractmethod 

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

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

74 

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

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

77 """ 

78 

79 @prereleases.setter # noqa: B027 

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

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

82 

83 :param value: The value to set. 

84 """ 

85 

86 @abc.abstractmethod 

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

88 """ 

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

90 """ 

91 

92 @abc.abstractmethod 

93 def filter( 

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

95 ) -> Iterator[UnparsedVersionVar]: 

96 """ 

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

98 are contained within this specifier are allowed in it. 

99 """ 

100 

101 

102class Specifier(BaseSpecifier): 

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

104 

105 .. tip:: 

106 

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

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

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

110 """ 

111 

112 _operator_regex_str = r""" 

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

114 """ 

115 _version_regex_str = r""" 

116 (?P<version> 

117 (?: 

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

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

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

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

122 # but included entirely as an escape hatch. 

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

124 \s* 

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

126 # we match everything except for whitespace, a 

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

128 # since versions can be enclosed in them. 

129 ) 

130 | 

131 (?: 

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

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

134 # operators separately to enable that. 

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

136 

137 \s* 

138 v? 

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

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

141 

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

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

144 (?: 

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

146 | 

147 (?: # pre release 

148 [-_\.]? 

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

150 [-_\.]? 

151 [0-9]* 

152 )? 

153 (?: # post release 

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

155 )? 

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

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

158 )? 

159 ) 

160 | 

161 (?: 

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

163 # release segment. 

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

165 

166 \s* 

167 v? 

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

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

170 (?: # pre release 

171 [-_\.]? 

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

173 [-_\.]? 

174 [0-9]* 

175 )? 

176 (?: # post release 

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

178 )? 

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

180 ) 

181 | 

182 (?: 

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

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

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

186 # matching wild cards. 

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

188 # operators so we want to make sure they 

189 # don't match here. 

190 

191 \s* 

192 v? 

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

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

195 (?: # pre release 

196 [-_\.]? 

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

198 [-_\.]? 

199 [0-9]* 

200 )? 

201 (?: # post release 

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

203 )? 

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

205 ) 

206 ) 

207 """ 

208 

209 _regex = re.compile( 

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

211 re.VERBOSE | re.IGNORECASE, 

212 ) 

213 

214 _operators: Final = { 

215 "~=": "compatible", 

216 "==": "equal", 

217 "!=": "not_equal", 

218 "<=": "less_than_equal", 

219 ">=": "greater_than_equal", 

220 "<": "less_than", 

221 ">": "greater_than", 

222 "===": "arbitrary", 

223 } 

224 

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

226 """Initialize a Specifier instance. 

227 

228 :param spec: 

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

230 normalized before use. 

231 :param prereleases: 

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

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

234 given specifiers. 

235 :raises InvalidSpecifier: 

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

237 """ 

238 match = self._regex.search(spec) 

239 if not match: 

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

241 

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

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

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

245 ) 

246 

247 # Store whether or not this Specifier should accept prereleases 

248 self._prereleases = prereleases 

249 

250 @property 

251 def prereleases(self) -> bool: 

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

253 # blindly use that. 

254 if self._prereleases is not None: 

255 return self._prereleases 

256 

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

258 # the version in the specifier is a prerelease. 

259 operator, version = self._spec 

260 if operator != "!=": 

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

262 # want to remove before parsing. 

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

264 version = version[:-2] 

265 

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

267 # specifier allows pre-releases. 

268 if Version(version).is_prerelease: 

269 return True 

270 

271 return False 

272 

273 @prereleases.setter 

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

275 self._prereleases = value 

276 

277 @property 

278 def operator(self) -> str: 

279 """The operator of this specifier. 

280 

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

282 '==' 

283 """ 

284 return self._spec[0] 

285 

286 @property 

287 def version(self) -> str: 

288 """The version of this specifier. 

289 

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

291 '1.2.3' 

292 """ 

293 return self._spec[1] 

294 

295 def __repr__(self) -> str: 

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

297 

298 >>> Specifier('>=1.0.0') 

299 <Specifier('>=1.0.0')> 

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

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

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

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

304 """ 

305 pre = ( 

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

307 if self._prereleases is not None 

308 else "" 

309 ) 

310 

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

312 

313 def __str__(self) -> str: 

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

315 

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

317 '>=1.0.0' 

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

319 '>=1.0.0' 

320 """ 

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

322 

323 @property 

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

325 operator, version = self._spec 

326 if operator == "===": 

327 return operator, version 

328 

329 canonical_version = canonicalize_version( 

330 version, 

331 strip_trailing_zero=(operator != "~="), 

332 ) 

333 

334 return operator, canonical_version 

335 

336 def __hash__(self) -> int: 

337 return hash(self._canonical_spec) 

338 

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

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

341 

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

343 

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

345 

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

347 True 

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

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

350 True 

351 >>> Specifier("==1.2.3") == "==1.2.3" 

352 True 

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

354 False 

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

356 False 

357 """ 

358 if isinstance(other, str): 

359 try: 

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

361 except InvalidSpecifier: 

362 return NotImplemented 

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

364 return NotImplemented 

365 

366 return self._canonical_spec == other._canonical_spec 

367 

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

369 operator_callable: CallableOperator = getattr( 

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

371 ) 

372 return operator_callable 

373 

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

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

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

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

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

379 # the other specifiers. 

380 

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

382 # ignore suffix segments. 

383 prefix = _version_join( 

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

385 ) 

386 

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

388 prefix += ".*" 

389 

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

391 prospective, prefix 

392 ) 

393 

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

395 # We need special logic to handle prefix matching 

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

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

398 normalized_prospective = canonicalize_version( 

399 prospective.public, strip_trailing_zero=False 

400 ) 

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

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

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

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

405 split_spec = _version_split(normalized_spec) 

406 

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

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

409 # a pre-release segment. 

410 split_prospective = _version_split(normalized_prospective) 

411 

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

413 # shortened version. 

414 padded_prospective, _ = _pad_version(split_prospective, split_spec) 

415 

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

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

418 # prospective version or not. 

419 shortened_prospective = padded_prospective[: len(split_spec)] 

420 

421 return shortened_prospective == split_spec 

422 else: 

423 # Convert our spec string into a Version 

424 spec_version = Version(spec) 

425 

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

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

428 # segment. 

429 if not spec_version.local: 

430 prospective = Version(prospective.public) 

431 

432 return prospective == spec_version 

433 

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

435 return not self._compare_equal(prospective, spec) 

436 

437 def _compare_less_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_greater_than_equal(self, prospective: Version, spec: str) -> bool: 

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

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

446 # the prospective version. 

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

448 

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

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

451 # it as a version. 

452 spec = Version(spec_str) 

453 

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

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

456 # instead of doing extra unneeded work. 

457 if not prospective < spec: 

458 return False 

459 

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

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

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

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

464 if ( 

465 not spec.is_prerelease 

466 and prospective.is_prerelease 

467 and Version(prospective.base_version) == Version(spec.base_version) 

468 ): 

469 return False 

470 

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

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

473 # version in the spec. 

474 return True 

475 

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

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

478 # it as a version. 

479 spec = Version(spec_str) 

480 

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

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

483 # instead of doing extra unneeded work. 

484 if not prospective > spec: 

485 return False 

486 

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

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

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

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

491 if ( 

492 not spec.is_postrelease 

493 and prospective.is_postrelease 

494 and Version(prospective.base_version) == Version(spec.base_version) 

495 ): 

496 return False 

497 

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

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

500 if prospective.local is not None and Version( 

501 prospective.base_version 

502 ) == Version(spec.base_version): 

503 return False 

504 

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

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

507 # same version in the spec. 

508 return True 

509 

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

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

512 

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

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

515 

516 :param item: The item to check for. 

517 

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

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

520 

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

522 True 

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

524 True 

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

526 False 

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

528 True 

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

530 True 

531 """ 

532 return self.contains(item) 

533 

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

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

536 

537 :param item: 

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

539 :class:`Version` instance. 

540 :param prereleases: 

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

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

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

544 

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

546 True 

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

548 True 

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

550 False 

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

552 True 

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

554 False 

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

556 True 

557 """ 

558 

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

560 

561 def filter( 

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

563 ) -> Iterator[UnparsedVersionVar]: 

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

565 

566 :param iterable: 

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

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

569 :param prereleases: 

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

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

572 and match prereleases if there are no other versions. 

573 

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

575 ['1.3'] 

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

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

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

579 ['1.5a1'] 

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

581 ['1.3', '1.5a1'] 

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

583 ['1.3', '1.5a1'] 

584 """ 

585 prereleases_versions = [] 

586 found_non_prereleases = False 

587 

588 # Determine if to include prereleases by default 

589 include_prereleases = ( 

590 prereleases if prereleases is not None else self.prereleases 

591 ) 

592 

593 # Get the matching operator 

594 operator_callable = self._get_operator(self.operator) 

595 

596 # Filter versions 

597 for version in iterable: 

598 parsed_version = _coerce_version(version) 

599 if parsed_version is None: 

600 continue 

601 

602 if operator_callable(parsed_version, self.version): 

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

604 if not parsed_version.is_prerelease or include_prereleases: 

605 found_non_prereleases = True 

606 yield version 

607 # Otherwise collect prereleases for potential later use 

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

609 prereleases_versions.append(version) 

610 

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

612 # explicitly forbidden, yield the collected prereleases 

613 if ( 

614 not found_non_prereleases 

615 and prereleases is None 

616 and self._prereleases is not False 

617 ): 

618 yield from prereleases_versions 

619 

620 

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

622 

623 

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

625 """Split version into components. 

626 

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

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

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

630 version string. 

631 """ 

632 result: list[str] = [] 

633 

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

635 result.append(epoch or "0") 

636 

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

638 match = _prefix_regex.search(item) 

639 if match: 

640 result.extend(match.groups()) 

641 else: 

642 result.append(item) 

643 return result 

644 

645 

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

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

648 

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

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

651 components numeric. 

652 """ 

653 epoch, *rest = components 

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

655 

656 

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

658 return not any( 

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

660 ) 

661 

662 

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

664 left_split, right_split = [], [] 

665 

666 # Get the release segment of our versions 

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

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

669 

670 # Get the rest of our versions 

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

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

673 

674 # Insert our padding 

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

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

677 

678 return ( 

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

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

681 ) 

682 

683 

684class SpecifierSet(BaseSpecifier): 

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

686 

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

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

689 """ 

690 

691 def __init__( 

692 self, 

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

694 prereleases: bool | None = None, 

695 ) -> None: 

696 """Initialize a SpecifierSet instance. 

697 

698 :param specifiers: 

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

700 specifiers which will be parsed and normalized before use. 

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

702 as is. 

703 :param prereleases: 

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

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

706 given specifiers. 

707 

708 :raises InvalidSpecifier: 

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

710 raised. 

711 """ 

712 

713 if isinstance(specifiers, str): 

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

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

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

717 

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

719 # for later. 

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

721 else: 

722 # Save the supplied specifiers in a frozen set. 

723 self._specs = frozenset(specifiers) 

724 

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

726 # we accept prereleases or not. 

727 self._prereleases = prereleases 

728 

729 @property 

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

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

732 # pass that through here. 

733 if self._prereleases is not None: 

734 return self._prereleases 

735 

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

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

738 # pre-releases or not. 

739 if not self._specs: 

740 return None 

741 

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

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

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

745 return True 

746 

747 return None 

748 

749 @prereleases.setter 

750 def prereleases(self, value: bool | None) -> 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: 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 ( 

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

812 ) or self._prereleases == other._prereleases: 

813 specifier._prereleases = self._prereleases 

814 else: 

815 raise ValueError( 

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

817 ) 

818 

819 return specifier 

820 

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

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

823 

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

825 

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

827 

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

829 True 

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

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

832 True 

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

834 True 

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

836 False 

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

838 False 

839 """ 

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

841 other = SpecifierSet(str(other)) 

842 elif not isinstance(other, SpecifierSet): 

843 return NotImplemented 

844 

845 return self._specs == other._specs 

846 

847 def __len__(self) -> int: 

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

849 return len(self._specs) 

850 

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

852 """ 

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

854 in this specifier set. 

855 

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

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

858 """ 

859 return iter(self._specs) 

860 

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

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

863 

864 :param item: The item to check for. 

865 

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

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

868 

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

870 True 

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

872 True 

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

874 False 

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

876 True 

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

878 True 

879 """ 

880 return self.contains(item) 

881 

882 def contains( 

883 self, 

884 item: UnparsedVersion, 

885 prereleases: bool | None = None, 

886 installed: bool | None = None, 

887 ) -> bool: 

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

889 

890 :param item: 

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

892 :class:`Version` instance. 

893 :param prereleases: 

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

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

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

897 :param installed: 

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

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

900 

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

902 True 

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

904 True 

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

906 False 

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

908 True 

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

910 False 

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

912 True 

913 """ 

914 version = _coerce_version(item) 

915 if version is None: 

916 return False 

917 

918 if installed and version.is_prerelease: 

919 prereleases = True 

920 

921 return bool(list(self.filter([version], prereleases=prereleases))) 

922 

923 def filter( 

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

925 ) -> Iterator[UnparsedVersionVar]: 

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

927 

928 :param iterable: 

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

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

931 :param prereleases: 

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

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

934 and match prereleases if there are no other versions. 

935 

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

937 ['1.3'] 

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

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

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

941 ['1.5a1'] 

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

943 ['1.3', '1.5a1'] 

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

945 ['1.3', '1.5a1'] 

946 

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

948 versions in the set. 

949 

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

951 ['1.3'] 

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

953 ['1.5a1'] 

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

955 ['1.3', '1.5a1'] 

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

957 ['1.3', '1.5a1'] 

958 """ 

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

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

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

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

963 prereleases = self.prereleases 

964 

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

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

967 # each specifier. 

968 if self._specs: 

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

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

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

972 for spec in self._specs: 

973 iterable = spec.filter( 

974 iterable, prereleases=True if prereleases is None else prereleases 

975 ) 

976 

977 if prereleases is not None: 

978 # If we have a forced prereleases value, 

979 # we can immediately return the iterator. 

980 return iter(iterable) 

981 else: 

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

983 if prereleases is True: 

984 return iter(iterable) 

985 

986 if prereleases is False: 

987 return ( 

988 item 

989 for item in iterable 

990 if (version := _coerce_version(item)) is not None 

991 and not version.is_prerelease 

992 ) 

993 

994 # Finally if prereleases is None, apply PEP 440 logic: 

995 # exclude prereleases unless there are no final releases that matched. 

996 filtered: list[UnparsedVersionVar] = [] 

997 found_prereleases: list[UnparsedVersionVar] = [] 

998 

999 for item in iterable: 

1000 parsed_version = _coerce_version(item) 

1001 if parsed_version is None: 

1002 continue 

1003 if parsed_version.is_prerelease: 

1004 found_prereleases.append(item) 

1005 else: 

1006 filtered.append(item) 

1007 

1008 return iter(filtered if filtered else found_prereleases)