Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pip/_vendor/packaging/specifiers.py: 39%

302 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-02-26 06:33 +0000

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 

5import abc 

6import functools 

7import itertools 

8import re 

9import warnings 

10from typing import ( 

11 Callable, 

12 Dict, 

13 Iterable, 

14 Iterator, 

15 List, 

16 Optional, 

17 Pattern, 

18 Set, 

19 Tuple, 

20 TypeVar, 

21 Union, 

22) 

23 

24from .utils import canonicalize_version 

25from .version import LegacyVersion, Version, parse 

26 

27ParsedVersion = Union[Version, LegacyVersion] 

28UnparsedVersion = Union[Version, LegacyVersion, str] 

29VersionTypeVar = TypeVar("VersionTypeVar", bound=UnparsedVersion) 

30CallableOperator = Callable[[ParsedVersion, str], bool] 

31 

32 

33class InvalidSpecifier(ValueError): 

34 """ 

35 An invalid specifier was found, users should refer to PEP 440. 

36 """ 

37 

38 

39class BaseSpecifier(metaclass=abc.ABCMeta): 

40 @abc.abstractmethod 

41 def __str__(self) -> str: 

42 """ 

43 Returns the str representation of this Specifier like object. This 

44 should be representative of the Specifier itself. 

45 """ 

46 

47 @abc.abstractmethod 

48 def __hash__(self) -> int: 

49 """ 

50 Returns a hash value for this Specifier like object. 

51 """ 

52 

53 @abc.abstractmethod 

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

55 """ 

56 Returns a boolean representing whether or not the two Specifier like 

57 objects are equal. 

58 """ 

59 

60 @abc.abstractproperty 

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

62 """ 

63 Returns whether or not pre-releases as a whole are allowed by this 

64 specifier. 

65 """ 

66 

67 @prereleases.setter 

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

69 """ 

70 Sets whether or not pre-releases as a whole are allowed by this 

71 specifier. 

72 """ 

73 

74 @abc.abstractmethod 

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

76 """ 

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

78 """ 

79 

80 @abc.abstractmethod 

81 def filter( 

82 self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None 

83 ) -> Iterable[VersionTypeVar]: 

84 """ 

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

86 are contained within this specifier are allowed in it. 

87 """ 

88 

89 

90class _IndividualSpecifier(BaseSpecifier): 

91 

92 _operators: Dict[str, str] = {} 

93 _regex: Pattern[str] 

94 

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

96 match = self._regex.search(spec) 

97 if not match: 

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

99 

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

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

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

103 ) 

104 

105 # Store whether or not this Specifier should accept prereleases 

106 self._prereleases = prereleases 

107 

108 def __repr__(self) -> str: 

109 pre = ( 

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

111 if self._prereleases is not None 

112 else "" 

113 ) 

114 

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

116 

117 def __str__(self) -> str: 

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

119 

120 @property 

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

122 return self._spec[0], canonicalize_version(self._spec[1]) 

123 

124 def __hash__(self) -> int: 

125 return hash(self._canonical_spec) 

126 

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

128 if isinstance(other, str): 

129 try: 

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

131 except InvalidSpecifier: 

132 return NotImplemented 

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

134 return NotImplemented 

135 

136 return self._canonical_spec == other._canonical_spec 

137 

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

139 operator_callable: CallableOperator = getattr( 

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

141 ) 

142 return operator_callable 

143 

144 def _coerce_version(self, version: UnparsedVersion) -> ParsedVersion: 

145 if not isinstance(version, (LegacyVersion, Version)): 

146 version = parse(version) 

147 return version 

148 

149 @property 

150 def operator(self) -> str: 

151 return self._spec[0] 

152 

153 @property 

154 def version(self) -> str: 

155 return self._spec[1] 

156 

157 @property 

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

159 return self._prereleases 

160 

161 @prereleases.setter 

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

163 self._prereleases = value 

164 

165 def __contains__(self, item: str) -> bool: 

166 return self.contains(item) 

167 

168 def contains( 

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

170 ) -> bool: 

171 

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

173 if prereleases is None: 

174 prereleases = self.prereleases 

175 

176 # Normalize item to a Version or LegacyVersion, this allows us to have 

177 # a shortcut for ``"2.0" in Specifier(">=2") 

178 normalized_item = self._coerce_version(item) 

179 

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

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

182 # logic if this version is a prereleases. 

183 if normalized_item.is_prerelease and not prereleases: 

184 return False 

185 

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

187 # within this Specifier or not. 

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

189 return operator_callable(normalized_item, self.version) 

190 

191 def filter( 

192 self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None 

193 ) -> Iterable[VersionTypeVar]: 

194 

195 yielded = False 

196 found_prereleases = [] 

197 

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

199 

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

201 # them match, yield them. 

202 for version in iterable: 

203 parsed_version = self._coerce_version(version) 

204 

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

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

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

208 # else matches this specifier. 

209 if parsed_version.is_prerelease and not ( 

210 prereleases or self.prereleases 

211 ): 

212 found_prereleases.append(version) 

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

214 # accepting prereleases from the beginning. 

215 else: 

216 yielded = True 

217 yield version 

218 

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

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

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

222 if not yielded and found_prereleases: 

223 for version in found_prereleases: 

224 yield version 

225 

226 

227class LegacySpecifier(_IndividualSpecifier): 

228 

229 _regex_str = r""" 

230 (?P<operator>(==|!=|<=|>=|<|>)) 

231 \s* 

232 (?P<version> 

233 [^,;\s)]* # Since this is a "legacy" specifier, and the version 

234 # string can be just about anything, we match everything 

235 # except for whitespace, a semi-colon for marker support, 

236 # a closing paren since versions can be enclosed in 

237 # them, and a comma since it's a version separator. 

238 ) 

239 """ 

240 

241 _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) 

242 

243 _operators = { 

244 "==": "equal", 

245 "!=": "not_equal", 

246 "<=": "less_than_equal", 

247 ">=": "greater_than_equal", 

248 "<": "less_than", 

249 ">": "greater_than", 

250 } 

251 

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

253 super().__init__(spec, prereleases) 

254 

255 warnings.warn( 

256 "Creating a LegacyVersion has been deprecated and will be " 

257 "removed in the next major release", 

258 DeprecationWarning, 

259 ) 

260 

261 def _coerce_version(self, version: UnparsedVersion) -> LegacyVersion: 

262 if not isinstance(version, LegacyVersion): 

263 version = LegacyVersion(str(version)) 

264 return version 

265 

266 def _compare_equal(self, prospective: LegacyVersion, spec: str) -> bool: 

267 return prospective == self._coerce_version(spec) 

268 

269 def _compare_not_equal(self, prospective: LegacyVersion, spec: str) -> bool: 

270 return prospective != self._coerce_version(spec) 

271 

272 def _compare_less_than_equal(self, prospective: LegacyVersion, spec: str) -> bool: 

273 return prospective <= self._coerce_version(spec) 

274 

275 def _compare_greater_than_equal( 

276 self, prospective: LegacyVersion, spec: str 

277 ) -> bool: 

278 return prospective >= self._coerce_version(spec) 

279 

280 def _compare_less_than(self, prospective: LegacyVersion, spec: str) -> bool: 

281 return prospective < self._coerce_version(spec) 

282 

283 def _compare_greater_than(self, prospective: LegacyVersion, spec: str) -> bool: 

284 return prospective > self._coerce_version(spec) 

285 

286 

287def _require_version_compare( 

288 fn: Callable[["Specifier", ParsedVersion, str], bool] 

289) -> Callable[["Specifier", ParsedVersion, str], bool]: 

290 @functools.wraps(fn) 

291 def wrapped(self: "Specifier", prospective: ParsedVersion, spec: str) -> bool: 

292 if not isinstance(prospective, Version): 

293 return False 

294 return fn(self, prospective, spec) 

295 

296 return wrapped 

297 

298 

299class Specifier(_IndividualSpecifier): 

300 

301 _regex_str = r""" 

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

303 (?P<version> 

304 (?: 

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

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

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

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

309 # but included entirely as an escape hatch. 

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

311 \s* 

312 [^\s]* # We just match everything, except for whitespace 

313 # since we are only testing for strict identity. 

314 ) 

315 | 

316 (?: 

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

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

319 # operators separately to enable that. 

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

321 

322 \s* 

323 v? 

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

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

326 (?: # pre release 

327 [-_\.]? 

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

329 [-_\.]? 

330 [0-9]* 

331 )? 

332 (?: # post release 

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

334 )? 

335 

336 # You cannot use a wild card and a dev or local version 

337 # together so group them with a | and make them optional. 

338 (?: 

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

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

341 | 

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

343 )? 

344 ) 

345 | 

346 (?: 

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

348 # release segment. 

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

350 

351 \s* 

352 v? 

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

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

355 (?: # pre release 

356 [-_\.]? 

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

358 [-_\.]? 

359 [0-9]* 

360 )? 

361 (?: # post release 

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

363 )? 

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

365 ) 

366 | 

367 (?: 

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

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

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

371 # matching wild cards. 

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

373 # operators so we want to make sure they 

374 # don't match here. 

375 

376 \s* 

377 v? 

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

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

380 (?: # pre release 

381 [-_\.]? 

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

383 [-_\.]? 

384 [0-9]* 

385 )? 

386 (?: # post release 

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

388 )? 

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

390 ) 

391 ) 

392 """ 

393 

394 _regex = re.compile(r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) 

395 

396 _operators = { 

397 "~=": "compatible", 

398 "==": "equal", 

399 "!=": "not_equal", 

400 "<=": "less_than_equal", 

401 ">=": "greater_than_equal", 

402 "<": "less_than", 

403 ">": "greater_than", 

404 "===": "arbitrary", 

405 } 

406 

407 @_require_version_compare 

408 def _compare_compatible(self, prospective: ParsedVersion, spec: str) -> bool: 

409 

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

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

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

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

414 # the other specifiers. 

415 

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

417 # ignore suffix segments. 

418 prefix = ".".join( 

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

420 ) 

421 

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

423 prefix += ".*" 

424 

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

426 prospective, prefix 

427 ) 

428 

429 @_require_version_compare 

430 def _compare_equal(self, prospective: ParsedVersion, spec: str) -> bool: 

431 

432 # We need special logic to handle prefix matching 

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

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

435 prospective = Version(prospective.public) 

436 # Split the spec out by dots, and pretend that there is an implicit 

437 # dot in between a release segment and a pre-release segment. 

438 split_spec = _version_split(spec[:-2]) # Remove the trailing .* 

439 

440 # Split the prospective version out by dots, and pretend that there 

441 # is an implicit dot in between a release segment and a pre-release 

442 # segment. 

443 split_prospective = _version_split(str(prospective)) 

444 

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

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

447 # prospective version or not. 

448 shortened_prospective = split_prospective[: len(split_spec)] 

449 

450 # Pad out our two sides with zeros so that they both equal the same 

451 # length. 

452 padded_spec, padded_prospective = _pad_version( 

453 split_spec, shortened_prospective 

454 ) 

455 

456 return padded_prospective == padded_spec 

457 else: 

458 # Convert our spec string into a Version 

459 spec_version = Version(spec) 

460 

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

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

463 # segment. 

464 if not spec_version.local: 

465 prospective = Version(prospective.public) 

466 

467 return prospective == spec_version 

468 

469 @_require_version_compare 

470 def _compare_not_equal(self, prospective: ParsedVersion, spec: str) -> bool: 

471 return not self._compare_equal(prospective, spec) 

472 

473 @_require_version_compare 

474 def _compare_less_than_equal(self, prospective: ParsedVersion, spec: str) -> bool: 

475 

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

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

478 # the prospective version. 

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

480 

481 @_require_version_compare 

482 def _compare_greater_than_equal( 

483 self, prospective: ParsedVersion, spec: str 

484 ) -> bool: 

485 

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

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

488 # the prospective version. 

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

490 

491 @_require_version_compare 

492 def _compare_less_than(self, prospective: ParsedVersion, spec_str: str) -> bool: 

493 

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

495 # it as a version. 

496 spec = Version(spec_str) 

497 

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

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

500 # instead of doing extra unneeded work. 

501 if not prospective < spec: 

502 return False 

503 

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

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

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

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

508 if not spec.is_prerelease and prospective.is_prerelease: 

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

510 return False 

511 

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

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

514 # version in the spec. 

515 return True 

516 

517 @_require_version_compare 

518 def _compare_greater_than(self, prospective: ParsedVersion, spec_str: str) -> bool: 

519 

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

521 # it as a version. 

522 spec = Version(spec_str) 

523 

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

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

526 # instead of doing extra unneeded work. 

527 if not prospective > spec: 

528 return False 

529 

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

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

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

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

534 if not spec.is_postrelease and prospective.is_postrelease: 

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

536 return False 

537 

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

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

540 if prospective.local is not None: 

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

542 return False 

543 

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

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

546 # same version in the spec. 

547 return True 

548 

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

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

551 

552 @property 

553 def prereleases(self) -> bool: 

554 

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

556 # blindly use that. 

557 if self._prereleases is not None: 

558 return self._prereleases 

559 

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

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

562 # prerelease. 

563 operator, version = self._spec 

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

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

566 # want to remove before parsing. 

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

568 version = version[:-2] 

569 

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

571 # specifier allows pre-releases. 

572 if parse(version).is_prerelease: 

573 return True 

574 

575 return False 

576 

577 @prereleases.setter 

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

579 self._prereleases = value 

580 

581 

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

583 

584 

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

586 result: List[str] = [] 

587 for item in version.split("."): 

588 match = _prefix_regex.search(item) 

589 if match: 

590 result.extend(match.groups()) 

591 else: 

592 result.append(item) 

593 return result 

594 

595 

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

597 return not any( 

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

599 ) 

600 

601 

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

603 left_split, right_split = [], [] 

604 

605 # Get the release segment of our versions 

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

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

608 

609 # Get the rest of our versions 

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

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

612 

613 # Insert our padding 

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

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

616 

617 return (list(itertools.chain(*left_split)), list(itertools.chain(*right_split))) 

618 

619 

620class SpecifierSet(BaseSpecifier): 

621 def __init__( 

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

623 ) -> None: 

624 

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

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

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

628 

629 # Parsed each individual specifier, attempting first to make it a 

630 # Specifier and falling back to a LegacySpecifier. 

631 parsed: Set[_IndividualSpecifier] = set() 

632 for specifier in split_specifiers: 

633 try: 

634 parsed.add(Specifier(specifier)) 

635 except InvalidSpecifier: 

636 parsed.add(LegacySpecifier(specifier)) 

637 

638 # Turn our parsed specifiers into a frozen set and save them for later. 

639 self._specs = frozenset(parsed) 

640 

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

642 # we accept prereleases or not. 

643 self._prereleases = prereleases 

644 

645 def __repr__(self) -> str: 

646 pre = ( 

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

648 if self._prereleases is not None 

649 else "" 

650 ) 

651 

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

653 

654 def __str__(self) -> str: 

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

656 

657 def __hash__(self) -> int: 

658 return hash(self._specs) 

659 

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

661 if isinstance(other, str): 

662 other = SpecifierSet(other) 

663 elif not isinstance(other, SpecifierSet): 

664 return NotImplemented 

665 

666 specifier = SpecifierSet() 

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

668 

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

670 specifier._prereleases = other._prereleases 

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

672 specifier._prereleases = self._prereleases 

673 elif self._prereleases == other._prereleases: 

674 specifier._prereleases = self._prereleases 

675 else: 

676 raise ValueError( 

677 "Cannot combine SpecifierSets with True and False prerelease " 

678 "overrides." 

679 ) 

680 

681 return specifier 

682 

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

684 if isinstance(other, (str, _IndividualSpecifier)): 

685 other = SpecifierSet(str(other)) 

686 elif not isinstance(other, SpecifierSet): 

687 return NotImplemented 

688 

689 return self._specs == other._specs 

690 

691 def __len__(self) -> int: 

692 return len(self._specs) 

693 

694 def __iter__(self) -> Iterator[_IndividualSpecifier]: 

695 return iter(self._specs) 

696 

697 @property 

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

699 

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

701 # pass that through here. 

702 if self._prereleases is not None: 

703 return self._prereleases 

704 

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

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

707 # pre-releases or not. 

708 if not self._specs: 

709 return None 

710 

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

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

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

714 

715 @prereleases.setter 

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

717 self._prereleases = value 

718 

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

720 return self.contains(item) 

721 

722 def contains( 

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

724 ) -> bool: 

725 

726 # Ensure that our item is a Version or LegacyVersion instance. 

727 if not isinstance(item, (LegacyVersion, Version)): 

728 item = parse(item) 

729 

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

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

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

733 if prereleases is None: 

734 prereleases = self.prereleases 

735 

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

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

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

739 # short circuit that here. 

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

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

742 if not prereleases and item.is_prerelease: 

743 return False 

744 

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

746 # given version is contained within all of them. 

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

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

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

750 

751 def filter( 

752 self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None 

753 ) -> Iterable[VersionTypeVar]: 

754 

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

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

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

758 if prereleases is None: 

759 prereleases = self.prereleases 

760 

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

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

763 # each specifier. 

764 if self._specs: 

765 for spec in self._specs: 

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

767 return iterable 

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

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

770 # releases, and which will filter out LegacyVersion in general. 

771 else: 

772 filtered: List[VersionTypeVar] = [] 

773 found_prereleases: List[VersionTypeVar] = [] 

774 

775 item: UnparsedVersion 

776 parsed_version: Union[Version, LegacyVersion] 

777 

778 for item in iterable: 

779 # Ensure that we some kind of Version class for this item. 

780 if not isinstance(item, (LegacyVersion, Version)): 

781 parsed_version = parse(item) 

782 else: 

783 parsed_version = item 

784 

785 # Filter out any item which is parsed as a LegacyVersion 

786 if isinstance(parsed_version, LegacyVersion): 

787 continue 

788 

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

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

791 if parsed_version.is_prerelease and not prereleases: 

792 if not filtered: 

793 found_prereleases.append(item) 

794 else: 

795 filtered.append(item) 

796 

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

798 # ahead and use the pre-releases 

799 if not filtered and found_prereleases and prereleases is None: 

800 return found_prereleases 

801 

802 return filtered