Coverage for pyTooling/Versioning/__init__.py: 84%

920 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-12 20:40 +0000

1# ==================================================================================================================== # 

2# _____ _ _ __ __ _ _ # 

3# _ __ _ |_ _|__ ___ | (_)_ __ __ \ \ / /__ _ __ ___(_) ___ _ __ (_)_ __ __ _ # 

4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` \ \ / / _ \ '__/ __| |/ _ \| '_ \| | '_ \ / _` | # 

5# | |_) | |_| || | (_) | (_) | | | | | | (_| |\ V / __/ | \__ \ | (_) | | | | | | | | (_| | # 

6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)_/ \___|_| |___/_|\___/|_| |_|_|_| |_|\__, | # 

7# |_| |___/ |___/ |___/ # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2020-2025 Patrick Lehmann - Bötzingen, Germany # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31""" 

32Implementation of semantic and date versioning version-numbers. 

33 

34.. hint:: See :ref:`high-level help <VERSIONING>` for explanations and usage examples. 

35""" 

36from collections.abc import Iterable as abc_Iterable 

37from enum import Flag, Enum 

38from re import compile as re_compile 

39from sys import version_info # needed for versions before Python 3.11 

40from typing import Optional as Nullable, Union, Callable, Any, Generic, TypeVar, Tuple, Iterable, Iterator, List 

41 

42try: 

43 from pyTooling.Decorators import export, readonly 

44 from pyTooling.MetaClasses import ExtendedType, abstractmethod, mustoverride 

45 from pyTooling.Exceptions import ToolingException 

46 from pyTooling.Common import getFullyQualifiedName 

47except (ImportError, ModuleNotFoundError): # pragma: no cover 

48 print("[pyTooling.Versioning] Could not import from 'pyTooling.*'!") 

49 

50 try: 

51 from Decorators import export, readonly 

52 from MetaClasses import ExtendedType, abstractmethod, mustoverride 

53 from Exceptions import ToolingException 

54 from Common import getFullyQualifiedName 

55 except (ImportError, ModuleNotFoundError) as ex: # pragma: no cover 

56 print("[pyTooling.Versioning] Could not import directly!") 

57 raise ex 

58 

59 

60@export 

61class Parts(Flag): 

62 """Enumeration describing parts of a version number that can be present.""" 

63 Unknown = 0 #: Undocumented 

64 Major = 1 #: Major number is present. (e.g. X in ``vX.0.0``). 

65 Year = 1 #: Year is present. (e.g. X in ``XXXX.10``). 

66 Minor = 2 #: Minor number is present. (e.g. Y in ``v0.Y.0``). 

67 Month = 2 #: Month is present. (e.g. X in ``2024.YY``). 

68 Week = 2 #: Week is present. (e.g. X in ``2024.YY``). 

69 Micro = 4 #: Patch number is present. (e.g. Z in ``v0.0.Z``). 

70 Patch = 4 #: Patch number is present. (e.g. Z in ``v0.0.Z``). 

71 Day = 4 #: Day is present. (e.g. X in ``2024.10.ZZ``). 

72 Level = 8 #: Release level is present. 

73 Dev = 16 #: Development part is present. 

74 Build = 32 #: Build number is present. (e.g. bbbb in ``v0.0.0.bbbb``) 

75 Post = 64 #: Post-release number is present. 

76 Prefix = 128 #: Prefix is present. 

77 Postfix = 256 #: Postfix is present. 

78 Hash = 512 #: Hash is present. 

79# AHead = 256 

80 

81 

82@export 

83class ReleaseLevel(Enum): 

84 """Enumeration describing the version's maturity level.""" 

85 Final = 0 #: 

86 ReleaseCandidate = -10 #: 

87 Development = -20 #: 

88 Gamma = -30 #: 

89 Beta = -40 #: 

90 Alpha = -50 #: 

91 

92 def __eq__(self, other: Any): 

93 """ 

94 Compare two release levels if the level is equal to the second operand. 

95 

96 :param other: Operand to compare against. 

97 :returns: ``True``, if release level is equal the second operand's release level. 

98 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`. 

99 """ 

100 if isinstance(other, str): 100 ↛ 101line 100 didn't jump to line 101 because the condition on line 100 was never true

101 other = ReleaseLevel(other) 

102 

103 if not isinstance(other, ReleaseLevel): 103 ↛ 104line 103 didn't jump to line 104 because the condition on line 103 was never true

104 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by == operator.") 

105 if version_info >= (3, 11): # pragma: no cover 

106 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.") 

107 raise ex 

108 

109 return self is other 

110 

111 def __ne__(self, other: Any): 

112 """ 

113 Compare two release levels if the level is unequal to the second operand. 

114 

115 :param other: Operand to compare against. 

116 :returns: ``True``, if release level is unequal the second operand's release level. 

117 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`. 

118 """ 

119 if isinstance(other, str): 

120 other = ReleaseLevel(other) 

121 

122 if not isinstance(other, ReleaseLevel): 

123 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by != operator.") 

124 if version_info >= (3, 11): # pragma: no cover 

125 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.") 

126 raise ex 

127 

128 return self is not other 

129 

130 def __lt__(self, other: Any): 

131 """ 

132 Compare two release levels if the level is less than the second operand. 

133 

134 :param other: Operand to compare against. 

135 :returns: ``True``, if release level is less than the second operand. 

136 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`. 

137 """ 

138 if isinstance(other, str): 138 ↛ 139line 138 didn't jump to line 139 because the condition on line 138 was never true

139 other = ReleaseLevel(other) 

140 

141 if not isinstance(other, ReleaseLevel): 141 ↛ 142line 141 didn't jump to line 142 because the condition on line 141 was never true

142 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by < operator.") 

143 if version_info >= (3, 11): # pragma: no cover 

144 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.") 

145 raise ex 

146 

147 return self.value < other.value 

148 

149 def __le__(self, other: Any): 

150 """ 

151 Compare two release levels if the level is less than or equal the second operand. 

152 

153 :param other: Operand to compare against. 

154 :returns: ``True``, if release level is less than or equal the second operand. 

155 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`. 

156 """ 

157 if isinstance(other, str): 

158 other = ReleaseLevel(other) 

159 

160 if not isinstance(other, ReleaseLevel): 

161 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by <=>= operator.") 

162 if version_info >= (3, 11): # pragma: no cover 

163 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.") 

164 raise ex 

165 

166 return self.value <= other.value 

167 

168 def __gt__(self, other: Any): 

169 """ 

170 Compare two release levels if the level is greater than the second operand. 

171 

172 :param other: Operand to compare against. 

173 :returns: ``True``, if release level is greater than the second operand. 

174 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`. 

175 """ 

176 if isinstance(other, str): 176 ↛ 177line 176 didn't jump to line 177 because the condition on line 176 was never true

177 other = ReleaseLevel(other) 

178 

179 if not isinstance(other, ReleaseLevel): 179 ↛ 180line 179 didn't jump to line 180 because the condition on line 179 was never true

180 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by > operator.") 

181 if version_info >= (3, 11): # pragma: no cover 

182 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.") 

183 raise ex 

184 

185 return self.value > other.value 

186 

187 def __ge__(self, other: Any): 

188 """ 

189 Compare two release levels if the level is greater than or equal the second operand. 

190 

191 :param other: Operand to compare against. 

192 :returns: ``True``, if release level is greater than or equal the second operand. 

193 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`. 

194 """ 

195 if isinstance(other, str): 

196 other = ReleaseLevel(other) 

197 

198 if not isinstance(other, ReleaseLevel): 

199 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by >= operator.") 

200 if version_info >= (3, 11): # pragma: no cover 

201 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.") 

202 raise ex 

203 

204 return self.value >= other.value 

205 

206 def __hash__(self) -> int: 

207 return hash(self.value) 

208 

209 def __str__(self) -> str: 

210 """ 

211 Returns the release level's string equivalent. 

212 

213 :returns: The string equivalent of the release level. 

214 """ 

215 if self is ReleaseLevel.Final: 

216 return "final" 

217 elif self is ReleaseLevel.ReleaseCandidate: 

218 return "rc" 

219 elif self is ReleaseLevel.Development: 219 ↛ 220line 219 didn't jump to line 220 because the condition on line 219 was never true

220 return "dev" 

221 elif self is ReleaseLevel.Beta: 221 ↛ 222line 221 didn't jump to line 222 because the condition on line 221 was never true

222 return "beta" 

223 elif self is ReleaseLevel.Alpha: 223 ↛ 226line 223 didn't jump to line 226 because the condition on line 223 was always true

224 return "alpha" 

225 

226 raise ToolingException(f"Unknown ReleaseLevel '{self.name}'.") 

227 

228 

229@export 

230class Flags(Flag): 

231 """State enumeration, if a (tagged) version is build from a clean or dirty working directory.""" 

232 NoVCS = 0 #: No Version Control System VCS 

233 Clean = 1 #: A versioned build was created from a *clean* working directory. 

234 Dirty = 2 #: A versioned build was created from a *dirty* working directory. 

235 

236 CVS = 16 #: Concurrent Versions System (CVS) 

237 SVN = 32 #: Subversion (SVN) 

238 Git = 64 #: Git 

239 Hg = 128 #: Mercurial (Hg) 

240 

241 

242@export 

243def WordSizeValidator( 

244 bits: Nullable[int] = None, 

245 majorBits: Nullable[int] = None, 

246 minorBits: Nullable[int] = None, 

247 microBits: Nullable[int] = None, 

248 buildBits: Nullable[int] = None 

249): 

250 """ 

251 A factory function to return a validator for Version instances for a positive integer range based on word-sizes in bits. 

252 

253 :param bits: Number of bits to encode any positive version number part. 

254 :param majorBits: Number of bits to encode a positive major number in a version. 

255 :param minorBits: Number of bits to encode a positive minor number in a version. 

256 :param microBits: Number of bits to encode a positive micro number in a version. 

257 :param buildBits: Number of bits to encode a positive build number in a version. 

258 :return: A validation function for Version instances. 

259 """ 

260 majorMax = minorMax = microMax = buildMax = -1 

261 if bits is not None: 

262 majorMax = minorMax = microMax = buildMax = 2**bits - 1 

263 

264 if majorBits is not None: 

265 majorMax = 2**majorBits - 1 

266 if minorBits is not None: 

267 minorMax = 2**minorBits - 1 

268 if microBits is not None: 

269 microMax = 2 ** microBits - 1 

270 if buildBits is not None: 270 ↛ 271line 270 didn't jump to line 271 because the condition on line 270 was never true

271 buildMax = 2**buildBits - 1 

272 

273 def validator(version: SemanticVersion) -> bool: 

274 if Parts.Major in version._parts and version._major > majorMax: 

275 raise ValueError(f"Field 'Version.Major' > {majorMax}.") 

276 

277 if Parts.Minor in version._parts and version._minor > minorMax: 

278 raise ValueError(f"Field 'Version.Minor' > {minorMax}.") 

279 

280 if Parts.Micro in version._parts and version._micro > microMax: 

281 raise ValueError(f"Field 'Version.Micro' > {microMax}.") 

282 

283 if Parts.Build in version._parts and version._build > buildMax: 283 ↛ 284line 283 didn't jump to line 284 because the condition on line 283 was never true

284 raise ValueError(f"Field 'Version.Build' > {buildMax}.") 

285 

286 return True 

287 

288 return validator 

289 

290 

291@export 

292def MaxValueValidator( 

293 max: Nullable[int] = None, 

294 majorMax: Nullable[int] = None, 

295 minorMax: Nullable[int] = None, 

296 microMax: Nullable[int] = None, 

297 buildMax: Nullable[int] = None 

298): 

299 """ 

300 A factory function to return a validator for Version instances checking for a positive integer range [0..max]. 

301 

302 :param max: The upper bound for any positive version number part. 

303 :param majorMax: The upper bound for the positive major number. 

304 :param minorMax: The upper bound for the positive minor number. 

305 :param microMax: The upper bound for the positive micro number. 

306 :param buildMax: The upper bound for the positive build number. 

307 :return: A validation function for Version instances. 

308 """ 

309 if max is not None: 309 ↛ 312line 309 didn't jump to line 312 because the condition on line 309 was always true

310 majorMax = minorMax = microMax = buildMax = max 

311 

312 def validator(version: SemanticVersion) -> bool: 

313 if Parts.Major in version._parts and version._major > majorMax: 

314 raise ValueError(f"Field 'Version.Major' > {majorMax}.") 

315 

316 if Parts.Minor in version._parts and version._minor > minorMax: 

317 raise ValueError(f"Field 'Version.Minor' > {minorMax}.") 

318 

319 if Parts.Micro in version._parts and version._micro > microMax: 

320 raise ValueError(f"Field 'Version.Micro' > {microMax}.") 

321 

322 if Parts.Build in version._parts and version._build > buildMax: 322 ↛ 323line 322 didn't jump to line 323 because the condition on line 322 was never true

323 raise ValueError(f"Field 'Version.Build' > {buildMax}.") 

324 

325 return True 

326 

327 return validator 

328 

329 

330@export 

331class Version(metaclass=ExtendedType, slots=True): 

332 """Base-class for a version representation.""" 

333 

334 __hash: Nullable[int] #: once computed hash of the object 

335 

336 _parts: Parts #: Integer flag enumeration of present parts in a version number. 

337 _prefix: str #: Prefix string 

338 _major: int #: Major number part of the version number. 

339 _minor: int #: Minor number part of the version number. 

340 _micro: int #: Micro number part of the version number. 

341 _releaseLevel: ReleaseLevel #: Release level (alpha, beta, rc, final, ...). 

342 _releaseNumber: int #: Release number (Python calls this a serial). 

343 _post: int #: Post-release version number part. 

344 _dev: int #: Development number 

345 _build: int #: Build number part of the version number. 

346 _postfix: str #: Postfix string 

347 _hash: str #: Hash from version control system. 

348 _flags: Flags #: State if the version in a working directory is clean or dirty compared to a tagged version. 

349 

350 def __init__( 

351 self, 

352 major: int, 

353 minor: Nullable[int] = None, 

354 micro: Nullable[int] = None, 

355 level: Nullable[ReleaseLevel] = ReleaseLevel.Final, 

356 number: Nullable[int] = None, 

357 post: Nullable[int] = None, 

358 dev: Nullable[int] = None, 

359 *, 

360 build: Nullable[int] = None, 

361 postfix: Nullable[str] = None, 

362 prefix: Nullable[str] = None, 

363 hash: Nullable[str] = None, 

364 flags: Flags = Flags.NoVCS 

365 ) -> None: 

366 """ 

367 Initializes a version number representation. 

368 

369 :param major: Major number part of the version number. 

370 :param minor: Minor number part of the version number. 

371 :param micro: Micro (patch) number part of the version number. 

372 :param level: Release level (alpha, beta, release candidate, final, ...) of the version number. 

373 :param number: Release number part (in combination with release level) of the version number. 

374 :param post: Post number part of the version number. 

375 :param dev: Development number part of the version number. 

376 :param build: Build number part of the version number. 

377 :param postfix: The version number's postfix. 

378 :param prefix: The version number's prefix. 

379 :param hash: Postfix string. 

380 :param flags: The version number's flags. 

381 :raises TypeError: If parameter 'major' is not of type int. 

382 :raises ValueError: If parameter 'major' is a negative number. 

383 :raises TypeError: If parameter 'minor' is not of type int. 

384 :raises ValueError: If parameter 'minor' is a negative number. 

385 :raises TypeError: If parameter 'micro' is not of type int. 

386 :raises ValueError: If parameter 'micro' is a negative number. 

387 :raises TypeError: If parameter 'build' is not of type int. 

388 :raises ValueError: If parameter 'build' is a negative number. 

389 :raises TypeError: If parameter 'prefix' is not of type str. 

390 :raises TypeError: If parameter 'postfix' is not of type str. 

391 """ 

392 self.__hash = None 

393 

394 if not isinstance(major, int): 

395 raise TypeError("Parameter 'major' is not of type 'int'.") 

396 elif major < 0: 

397 raise ValueError("Parameter 'major' is negative.") 

398 

399 self._parts = Parts.Major 

400 self._major = major 

401 

402 if minor is not None: 

403 if not isinstance(minor, int): 

404 raise TypeError("Parameter 'minor' is not of type 'int'.") 

405 elif minor < 0: 

406 raise ValueError("Parameter 'minor' is negative.") 

407 

408 self._parts |= Parts.Minor 

409 self._minor = minor 

410 else: 

411 self._minor = 0 

412 

413 if micro is not None: 

414 if not isinstance(micro, int): 

415 raise TypeError("Parameter 'micro' is not of type 'int'.") 

416 elif micro < 0: 

417 raise ValueError("Parameter 'micro' is negative.") 

418 

419 self._parts |= Parts.Micro 

420 self._micro = micro 

421 else: 

422 self._micro = 0 

423 

424 if level is None: 

425 raise ValueError("Parameter 'level' is None.") 

426 elif not isinstance(level, ReleaseLevel): 

427 raise TypeError("Parameter 'level' is not of type 'ReleaseLevel'.") 

428 elif level is ReleaseLevel.Final: 

429 if number is not None: 

430 raise ValueError("Parameter 'number' must be None, if parameter 'level' is 'Final'.") 

431 

432 self._parts |= Parts.Level 

433 self._releaseLevel = level 

434 self._releaseNumber = 0 

435 else: 

436 self._parts |= Parts.Level 

437 self._releaseLevel = level 

438 

439 if number is not None: 

440 if not isinstance(number, int): 

441 raise TypeError("Parameter 'number' is not of type 'int'.") 

442 elif number < 0: 

443 raise ValueError("Parameter 'number' is negative.") 

444 

445 self._releaseNumber = number 

446 else: 

447 self._releaseNumber = 0 

448 

449 if dev is not None: 

450 if not isinstance(dev, int): 

451 raise TypeError("Parameter 'dev' is not of type 'int'.") 

452 elif dev < 0: 

453 raise ValueError("Parameter 'dev' is negative.") 

454 

455 self._parts |= Parts.Dev 

456 self._dev = dev 

457 else: 

458 self._dev = 0 

459 

460 if post is not None: 

461 if not isinstance(post, int): 

462 raise TypeError("Parameter 'post' is not of type 'int'.") 

463 elif post < 0: 

464 raise ValueError("Parameter 'post' is negative.") 

465 

466 self._parts |= Parts.Post 

467 self._post = post 

468 else: 

469 self._post = 0 

470 

471 if build is not None: 

472 if not isinstance(build, int): 

473 raise TypeError("Parameter 'build' is not of type 'int'.") 

474 elif build < 0: 

475 raise ValueError("Parameter 'build' is negative.") 

476 

477 self._build = build 

478 self._parts |= Parts.Build 

479 else: 

480 self._build = 0 

481 

482 if postfix is not None: 

483 if not isinstance(postfix, str): 

484 raise TypeError("Parameter 'postfix' is not of type 'str'.") 

485 

486 self._parts |= Parts.Postfix 

487 self._postfix = postfix 

488 else: 

489 self._postfix = "" 

490 

491 if prefix is not None: 

492 if not isinstance(prefix, str): 

493 raise TypeError("Parameter 'prefix' is not of type 'str'.") 

494 

495 self._parts |= Parts.Prefix 

496 self._prefix = prefix 

497 else: 

498 self._prefix = "" 

499 

500 if hash is not None: 

501 if not isinstance(hash, str): 

502 raise TypeError("Parameter 'hash' is not of type 'str'.") 

503 

504 self._parts |= Parts.Hash 

505 self._hash = hash 

506 else: 

507 self._hash = "" 

508 

509 if flags is None: 

510 raise ValueError("Parameter 'flags' is None.") 

511 elif not isinstance(flags, Flags): 

512 raise TypeError("Parameter 'flags' is not of type 'Flags'.") 

513 

514 self._flags = flags 

515 

516 @classmethod 

517 @abstractmethod 

518 def Parse(cls, versionString: Nullable[str], validator: Nullable[Callable[["SemanticVersion"], bool]] = None) -> "Version": 

519 """Parse a version string and return a Version instance.""" 

520 

521 @readonly 

522 def Parts(self) -> Parts: 

523 """ 

524 Read-only property to access the used parts of this version number. 

525 

526 :return: A flag enumeration of used version number parts. 

527 """ 

528 return self._parts 

529 

530 @readonly 

531 def Prefix(self) -> str: 

532 """ 

533 Read-only property to access the version number's prefix. 

534 

535 :return: The prefix of the version number. 

536 """ 

537 return self._prefix 

538 

539 @readonly 

540 def Major(self) -> int: 

541 """ 

542 Read-only property to access the major number. 

543 

544 :return: The major number. 

545 """ 

546 return self._major 

547 

548 @readonly 

549 def Minor(self) -> int: 

550 """ 

551 Read-only property to access the minor number. 

552 

553 :return: The minor number. 

554 """ 

555 return self._minor 

556 

557 @readonly 

558 def Micro(self) -> int: 

559 """ 

560 Read-only property to access the micro number. 

561 

562 :return: The micro number. 

563 """ 

564 return self._micro 

565 

566 @readonly 

567 def ReleaseLevel(self) -> ReleaseLevel: 

568 """ 

569 Read-only property to access the release level. 

570 

571 :return: The release level. 

572 """ 

573 return self._releaseLevel 

574 

575 @readonly 

576 def ReleaseNumber(self) -> int: 

577 """ 

578 Read-only property to access the release number. 

579 

580 :return: The release number. 

581 """ 

582 return self._releaseNumber 

583 

584 @readonly 

585 def Post(self) -> int: 

586 """ 

587 Read-only property to access the post number. 

588 

589 :return: The post number. 

590 """ 

591 return self._post 

592 

593 @readonly 

594 def Dev(self) -> int: 

595 """ 

596 Read-only property to access the development number. 

597 

598 :return: The development number. 

599 """ 

600 return self._dev 

601 

602 @readonly 

603 def Build(self) -> int: 

604 """ 

605 Read-only property to access the build number. 

606 

607 :return: The build number. 

608 """ 

609 return self._build 

610 

611 @readonly 

612 def Postfix(self) -> str: 

613 """ 

614 Read-only property to access the version number's postfix. 

615 

616 :return: The postfix of the version number. 

617 """ 

618 return self._postfix 

619 

620 @readonly 

621 def Hash(self) -> str: 

622 """ 

623 Read-only property to access the version number's hash. 

624 

625 :return: The hash. 

626 """ 

627 return self._hash 

628 

629 @readonly 

630 def Flags(self) -> Flags: 

631 """ 

632 Read-only property to access the version number's flags. 

633 

634 :return: The flags of the version number. 

635 """ 

636 return self._flags 

637 

638 def _equal(self, left: "Version", right: "Version") -> Nullable[bool]: 

639 """ 

640 Private helper method to compute the equality of two :class:`Version` instances. 

641 

642 :param left: Left operand. 

643 :param right: Right operand. 

644 :returns: ``True``, if ``left`` is equal to ``right``, otherwise it's ``False``. 

645 """ 

646 return ( 

647 (left._major == right._major) and 

648 (left._minor == right._minor) and 

649 (left._micro == right._micro) and 

650 (left._releaseLevel == right._releaseLevel) and 

651 (left._releaseNumber == right._releaseNumber) and 

652 (left._post == right._post) and 

653 (left._dev == right._dev) and 

654 (left._build == right._build) and 

655 (left._postfix == right._postfix) 

656 ) 

657 

658 def _compare(self, left: "Version", right: "Version") -> Nullable[bool]: 

659 """ 

660 Private helper method to compute the comparison of two :class:`Version` instances. 

661 

662 :param left: Left operand. 

663 :param right: Right operand. 

664 :returns: ``True``, if ``left`` is smaller than ``right``. |br| 

665 False if ``left`` is greater than ``right``. |br| 

666 Otherwise it's None (both operands are equal). 

667 """ 

668 if left._major < right._major: 

669 return True 

670 elif left._major > right._major: 

671 return False 

672 

673 if left._minor < right._minor: 

674 return True 

675 elif left._minor > right._minor: 

676 return False 

677 

678 if left._micro < right._micro: 

679 return True 

680 elif left._micro > right._micro: 

681 return False 

682 

683 if left._releaseLevel < right._releaseLevel: 683 ↛ 684line 683 didn't jump to line 684 because the condition on line 683 was never true

684 return True 

685 elif left._releaseLevel > right._releaseLevel: 685 ↛ 686line 685 didn't jump to line 686 because the condition on line 685 was never true

686 return False 

687 

688 if left._releaseNumber < right._releaseNumber: 688 ↛ 689line 688 didn't jump to line 689 because the condition on line 688 was never true

689 return True 

690 elif left._releaseNumber > right._releaseNumber: 690 ↛ 691line 690 didn't jump to line 691 because the condition on line 690 was never true

691 return False 

692 

693 if left._post < right._post: 693 ↛ 694line 693 didn't jump to line 694 because the condition on line 693 was never true

694 return True 

695 elif left._post > right._post: 695 ↛ 696line 695 didn't jump to line 696 because the condition on line 695 was never true

696 return False 

697 

698 if left._dev < right._dev: 698 ↛ 699line 698 didn't jump to line 699 because the condition on line 698 was never true

699 return True 

700 elif left._dev > right._dev: 700 ↛ 701line 700 didn't jump to line 701 because the condition on line 700 was never true

701 return False 

702 

703 if left._build < right._build: 703 ↛ 704line 703 didn't jump to line 704 because the condition on line 703 was never true

704 return True 

705 elif left._build > right._build: 705 ↛ 706line 705 didn't jump to line 706 because the condition on line 705 was never true

706 return False 

707 

708 return None 

709 

710 def _minimum(self, actual: "Version", expected: "Version") -> Nullable[bool]: 

711 exactMajor = Parts.Minor in expected._parts 

712 exactMinor = Parts.Micro in expected._parts 

713 

714 if exactMajor and actual._major != expected._major: 714 ↛ 715line 714 didn't jump to line 715 because the condition on line 714 was never true

715 return False 

716 elif not exactMajor and actual._major < expected._major: 

717 return False 

718 

719 if exactMinor and actual._minor != expected._minor: 719 ↛ 720line 719 didn't jump to line 720 because the condition on line 719 was never true

720 return False 

721 elif not exactMinor and actual._minor < expected._minor: 

722 return False 

723 

724 if Parts.Micro in expected._parts: 

725 return actual._micro >= expected._micro 

726 

727 return True 

728 

729 def _format(self, formatSpec: str) -> str: 

730 """ 

731 Return a string representation of this version number according to the format specification. 

732 

733 .. topic:: Format Specifiers 

734 

735 * ``%p`` - prefix 

736 * ``%M`` - major number 

737 * ``%m`` - minor number 

738 * ``%u`` - micro number 

739 * ``%b`` - build number 

740 

741 :param formatSpec: The format specification. 

742 :return: Formatted version number. 

743 """ 

744 if formatSpec == "": 

745 return self.__str__() 

746 

747 result = formatSpec 

748 result = result.replace("%p", str(self._prefix)) 

749 result = result.replace("%M", str(self._major)) 

750 result = result.replace("%m", str(self._minor)) 

751 result = result.replace("%u", str(self._micro)) 

752 result = result.replace("%b", str(self._build)) 

753 result = result.replace("%r", str(self._releaseLevel)[0]) 

754 result = result.replace("%R", str(self._releaseLevel)) 

755 result = result.replace("%n", str(self._releaseNumber)) 

756 result = result.replace("%d", str(self._dev)) 

757 result = result.replace("%P", str(self._postfix)) 

758 

759 return result 

760 

761 @mustoverride 

762 def __eq__(self, other: Union["Version", str, int, None]) -> bool: 

763 """ 

764 Compare two version numbers for equality. 

765 

766 The second operand should be an instance of :class:`Version`, but ``str`` and ``int`` are accepted, too. |br| 

767 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major 

768 number is assumed (all other parts are zero). 

769 

770 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

771 number. 

772 

773 :param other: Operand to compare against. 

774 :returns: ``True``, if both version numbers are equal. 

775 :raises ValueError: If parameter ``other`` is None. 

776 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`str` or :class:`ìnt`. 

777 """ 

778 if other is None: 

779 raise ValueError(f"Second operand is None.") 

780 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)): 

781 pass 

782 elif isinstance(other, str): 

783 other = self.__class__.Parse(other) 

784 elif isinstance(other, int): 

785 other = self.__class__(major=other) 

786 else: 

787 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by == operator.") 

788 if version_info >= (3, 11): # pragma: no cover 

789 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, str, int") 

790 raise ex 

791 

792 return self._equal(self, other) 

793 

794 @mustoverride 

795 def __ne__(self, other: Union["Version", str, int, None]) -> bool: 

796 """ 

797 Compare two version numbers for inequality. 

798 

799 The second operand should be an instance of :class:`Version`, but ``str`` and ``int`` are accepted, too. |br| 

800 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major 

801 number is assumed (all other parts are zero). 

802 

803 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

804 number. 

805 

806 :param other: Operand to compare against. 

807 :returns: ``True``, if both version numbers are not equal. 

808 :raises ValueError: If parameter ``other`` is None. 

809 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`str` or :class:`ìnt`. 

810 """ 

811 if other is None: 

812 raise ValueError(f"Second operand is None.") 

813 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)): 

814 pass 

815 elif isinstance(other, str): 

816 other = self.__class__.Parse(other) 

817 elif isinstance(other, int): 

818 other = self.__class__(major=other) 

819 else: 

820 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by == operator.") 

821 if version_info >= (3, 11): # pragma: no cover 

822 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, str, int") 

823 raise ex 

824 

825 return not self._equal(self, other) 

826 

827 @mustoverride 

828 def __lt__(self, other: Union["Version", str, int, None]) -> bool: 

829 """ 

830 Compare two version numbers if the version is less than the second operand. 

831 

832 The second operand should be an instance of :class:`Version`, but :class:`VersionRange`, :class:`VersionSet`, 

833 ``str`` and ``int`` are accepted, too. |br| 

834 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major 

835 number is assumed (all other parts are zero). 

836 

837 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

838 number. 

839 

840 :param other: Operand to compare against. 

841 :returns: ``True``, if version is less than the second operand. 

842 :raises ValueError: If parameter ``other`` is None. 

843 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`. 

844 """ 

845 if other is None: 

846 raise ValueError(f"Second operand is None.") 

847 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)): 

848 pass 

849 elif isinstance(other, VersionRange): 

850 other = other._lowerBound 

851 elif isinstance(other, VersionSet): 

852 other = other._items[0] 

853 elif isinstance(other, str): 

854 other = self.__class__.Parse(other) 

855 elif isinstance(other, int): 

856 other = self.__class__(major=other) 

857 else: 

858 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by < operator.") 

859 if version_info >= (3, 11): # pragma: no cover 

860 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int") 

861 raise ex 

862 

863 return self._compare(self, other) is True 

864 

865 @mustoverride 

866 def __le__(self, other: Union["Version", str, int, None]) -> bool: 

867 """ 

868 Compare two version numbers if the version is less than or equal the second operand. 

869 

870 The second operand should be an instance of :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, but 

871 ``str`` and ``int`` are accepted, too. |br| 

872 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major 

873 number is assumed (all other parts are zero). 

874 

875 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

876 number. 

877 

878 :param other: Operand to compare against. 

879 :returns: ``True``, if version is less than or equal the second operand. 

880 :raises ValueError: If parameter ``other`` is None. 

881 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`. 

882 """ 

883 equalValue = True 

884 if other is None: 

885 raise ValueError(f"Second operand is None.") 

886 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)): 

887 pass 

888 elif isinstance(other, VersionRange): 

889 equalValue = RangeBoundHandling.LowerBoundExclusive not in other._boundHandling 

890 other = other._lowerBound 

891 elif isinstance(other, VersionSet): 

892 other = other._items[0] 

893 elif isinstance(other, str): 

894 other = self.__class__.Parse(other) 

895 elif isinstance(other, int): 

896 other = self.__class__(major=other) 

897 else: 

898 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by <= operator.") 

899 if version_info >= (3, 11): # pragma: no cover 

900 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int") 

901 raise ex 

902 

903 result = self._compare(self, other) 

904 return result if result is not None else equalValue 

905 

906 @mustoverride 

907 def __gt__(self, other: Union["Version", str, int, None]) -> bool: 

908 """ 

909 Compare two version numbers if the version is greater than the second operand. 

910 

911 The second operand should be an instance of :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, but 

912 ``str`` and ``int`` are accepted, too. |br| 

913 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major 

914 number is assumed (all other parts are zero). 

915 

916 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

917 number. 

918 

919 :param other: Operand to compare against. 

920 :returns: ``True``, if version is greater than the second operand. 

921 :raises ValueError: If parameter ``other`` is None. 

922 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`. 

923 """ 

924 if other is None: 

925 raise ValueError(f"Second operand is None.") 

926 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)): 

927 pass 

928 elif isinstance(other, VersionRange): 

929 other = other._upperBound 

930 elif isinstance(other, VersionSet): 

931 other = other._items[-1] 

932 elif isinstance(other, str): 

933 other = self.__class__.Parse(other) 

934 elif isinstance(other, int): 

935 other = self.__class__(major=other) 

936 else: 

937 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by > operator.") 

938 if version_info >= (3, 11): # pragma: no cover 

939 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int") 

940 raise ex 

941 

942 return self._compare(self, other) is False 

943 

944 @mustoverride 

945 def __ge__(self, other: Union["Version", str, int, None]) -> bool: 

946 """ 

947 Compare two version numbers if the version is greater than or equal the second operand. 

948 

949 The second operand should be an instance of :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, but 

950 ``str`` and ``int`` are accepted, too. |br| 

951 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major 

952 number is assumed (all other parts are zero). 

953 

954 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

955 number. 

956 

957 :param other: Operand to compare against. 

958 :returns: ``True``, if version is greater than or equal the second operand. 

959 :raises ValueError: If parameter ``other`` is None. 

960 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`. 

961 """ 

962 equalValue = True 

963 if other is None: 

964 raise ValueError(f"Second operand is None.") 

965 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)): 

966 pass 

967 elif isinstance(other, VersionRange): 

968 equalValue = RangeBoundHandling.UpperBoundExclusive not in other._boundHandling 

969 other = other._upperBound 

970 elif isinstance(other, VersionSet): 

971 other = other._items[-1] 

972 elif isinstance(other, str): 

973 other = self.__class__.Parse(other) 

974 elif isinstance(other, int): 

975 other = self.__class__(major=other) 

976 else: 

977 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by >= operator.") 

978 if version_info >= (3, 11): # pragma: no cover 

979 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int") 

980 raise ex 

981 

982 result = self._compare(self, other) 

983 return not result if result is not None else equalValue 

984 

985 def __rshift__(self, other: Union["Version", str, int, None]) -> bool: 

986 if other is None: 

987 raise ValueError(f"Second operand is None.") 

988 elif isinstance(other, self.__class__): 

989 pass 

990 elif isinstance(other, str): 

991 other = self.__class__.Parse(other) 

992 elif isinstance(other, int): 

993 other = self.__class__(major=other) 

994 else: 

995 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by %= operator.") 

996 if version_info >= (3, 11): # pragma: no cover 

997 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, str, int") 

998 raise ex 

999 

1000 return self._minimum(self, other) 

1001 

1002 def __hash__(self) -> int: 

1003 if self.__hash is None: 1003 ↛ 1018line 1003 didn't jump to line 1018 because the condition on line 1003 was always true

1004 self.__hash = hash(( 

1005 self._prefix, 

1006 self._major, 

1007 self._minor, 

1008 self._micro, 

1009 self._releaseLevel, 

1010 self._releaseNumber, 

1011 self._post, 

1012 self._dev, 

1013 self._build, 

1014 self._postfix, 

1015 self._hash, 

1016 self._flags 

1017 )) 

1018 return self.__hash 

1019 

1020 

1021@export 

1022class SemanticVersion(Version): 

1023 """Representation of a semantic version number like ``3.7.12``.""" 

1024 

1025 _PATTERN = re_compile( 

1026 r"^" 

1027 r"(?P<prefix>[a-zA-Z]*)" 

1028 r"(?P<major>\d+)" 

1029 r"(?:\.(?P<minor>\d+))?" 

1030 r"(?:\.(?P<micro>\d+))?" 

1031 r"(?:" 

1032 r"(?:\.(?P<build>\d+))" 

1033 r"|" 

1034 r"(?:[-](?P<release>dev|final))" 

1035 r"|" 

1036 r"(?:(?P<delim1>[\.\-]?)(?P<level>alpha|beta|gamma|a|b|c|rc|pl)(?P<number>\d+))" 

1037 r")?" 

1038 r"(?:(?P<delim2>[\.\-]post)(?P<post>\d+))?" 

1039 r"(?:(?P<delim3>[\.\-]dev)(?P<dev>\d+))?" 

1040 r"(?:(?P<delim4>[\.\-\+])(?P<postfix>\w+))?" 

1041 r"$" 

1042 ) 

1043# QUESTION: was this how many commits a version is ahead of the last tagged version? 

1044# ahead: int = 0 

1045 

1046 def __init__( 

1047 self, 

1048 major: int, 

1049 minor: Nullable[int] = None, 

1050 micro: Nullable[int] = None, 

1051 level: Nullable[ReleaseLevel] = ReleaseLevel.Final, 

1052 number: Nullable[int] = None, 

1053 post: Nullable[int] = None, 

1054 dev: Nullable[int] = None, 

1055 *, 

1056 build: Nullable[int] = None, 

1057 postfix: Nullable[str] = None, 

1058 prefix: Nullable[str] = None, 

1059 hash: Nullable[str] = None, 

1060 flags: Flags = Flags.NoVCS 

1061 ) -> None: 

1062 """ 

1063 Initializes a semantic version number representation. 

1064 

1065 :param major: Major number part of the version number. 

1066 :param minor: Minor number part of the version number. 

1067 :param micro: Micro (patch) number part of the version number. 

1068 :param build: Build number part of the version number. 

1069 :param level: tbd 

1070 :param number: tbd 

1071 :param post: Post number part of the version number. 

1072 :param dev: Development number part of the version number. 

1073 :param prefix: The version number's prefix. 

1074 :param postfix: The version number's postfix. 

1075 :param flags: The version number's flags. 

1076 :param hash: tbd 

1077 :raises TypeError: If parameter 'major' is not of type int. 

1078 :raises ValueError: If parameter 'major' is a negative number. 

1079 :raises TypeError: If parameter 'minor' is not of type int. 

1080 :raises ValueError: If parameter 'minor' is a negative number. 

1081 :raises TypeError: If parameter 'micro' is not of type int. 

1082 :raises ValueError: If parameter 'micro' is a negative number. 

1083 :raises TypeError: If parameter 'build' is not of type int. 

1084 :raises ValueError: If parameter 'build' is a negative number. 

1085 :raises TypeError: If parameter 'post' is not of type int. 

1086 :raises ValueError: If parameter 'post' is a negative number. 

1087 :raises TypeError: If parameter 'dev' is not of type int. 

1088 :raises ValueError: If parameter 'dev' is a negative number. 

1089 :raises TypeError: If parameter 'prefix' is not of type str. 

1090 :raises TypeError: If parameter 'postfix' is not of type str. 

1091 """ 

1092 super().__init__(major, minor, micro, level, number, post, dev, build=build, postfix=postfix, prefix=prefix, hash=hash, flags=flags) 

1093 

1094 @classmethod 

1095 def Parse(cls, versionString: Nullable[str], validator: Nullable[Callable[["SemanticVersion"], bool]] = None) -> "SemanticVersion": 

1096 """ 

1097 Parse a version string and return a :class:`SemanticVersion` instance. 

1098 

1099 Allowed prefix characters: 

1100 

1101 * ``v|V`` - version, public version, public release 

1102 * ``i|I`` - internal version, internal release 

1103 * ``r|R`` - release, revision 

1104 * ``rev|REV`` - revision 

1105 

1106 :param versionString: The version string to parse. 

1107 :returns: An object representing a semantic version. 

1108 :raises TypeError: If parameter ``other`` is not a string. 

1109 :raises ValueError: If parameter ``other`` is None. 

1110 :raises ValueError: If parameter ``other`` is empty. 

1111 """ 

1112 if versionString is None: 

1113 raise ValueError("Parameter 'versionString' is None.") 

1114 elif not isinstance(versionString, str): 

1115 ex = TypeError(f"Parameter 'versionString' is not of type 'str'.") 

1116 if version_info >= (3, 11): # pragma: no cover 

1117 ex.add_note(f"Got type '{getFullyQualifiedName(versionString)}'.") 

1118 raise ex 

1119 

1120 versionString = versionString.strip() 

1121 if versionString == "": 

1122 raise ValueError("Parameter 'versionString' is empty.") 

1123 

1124 match = cls._PATTERN.match(versionString) 

1125 if match is None: 

1126 raise ValueError(f"Syntax error in parameter 'versionString': '{versionString}'") 

1127 

1128 def toInt(value: Nullable[str]) -> Nullable[int]: 

1129 if value is None or value == "": 

1130 return None 

1131 try: 

1132 return int(value) 

1133 except ValueError as ex: # pragma: no cover 

1134 raise ValueError(f"Invalid part '{value}' in version number '{versionString}'.") from ex 

1135 

1136 release = match["release"] 

1137 if release is not None: 

1138 if release == "dev": 1138 ↛ 1140line 1138 didn't jump to line 1140 because the condition on line 1138 was always true

1139 releaseLevel = ReleaseLevel.Development 

1140 elif release == "final": 

1141 releaseLevel = ReleaseLevel.Final 

1142 else: # pragma: no cover 

1143 raise ValueError(f"Unknown release level '{release}' in version number '{versionString}'.") 

1144 else: 

1145 level = match["level"] 

1146 if level is not None: 

1147 level = level.lower() 

1148 if level == "a" or level == "alpha": 

1149 releaseLevel = ReleaseLevel.Alpha 

1150 elif level == "b" or level == "beta": 

1151 releaseLevel = ReleaseLevel.Beta 

1152 elif level == "c" or level == "gamma": 

1153 releaseLevel = ReleaseLevel.Gamma 

1154 elif level == "rc": 

1155 releaseLevel = ReleaseLevel.ReleaseCandidate 

1156 else: # pragma: no cover 

1157 raise ValueError(f"Unknown release level '{level}' in version number '{versionString}'.") 

1158 else: 

1159 releaseLevel = ReleaseLevel.Final 

1160 

1161 version = cls( 

1162 major=toInt(match["major"]), 

1163 minor=toInt(match["minor"]), 

1164 micro=toInt(match["micro"]), 

1165 level=releaseLevel, 

1166 number=toInt(match["number"]), 

1167 post=toInt(match["post"]), 

1168 dev=toInt(match["dev"]), 

1169 build=toInt(match["build"]), 

1170 postfix=match["postfix"], 

1171 prefix=match["prefix"], 

1172 # hash=match["hash"], 

1173 flags=Flags.Clean 

1174 ) 

1175 if validator is not None and not validator(version): 

1176 raise ValueError(f"Failed to validate version string '{versionString}'.") # pragma: no cover 

1177 

1178 return version 

1179 

1180 @readonly 

1181 def Patch(self) -> int: 

1182 """ 

1183 Read-only property to access the patch number. 

1184 

1185 The patch number is identical to the micro number. 

1186 

1187 :return: The patch number. 

1188 """ 

1189 return self._micro 

1190 

1191 def _equal(self, left: "SemanticVersion", right: "SemanticVersion") -> Nullable[bool]: 

1192 """ 

1193 Private helper method to compute the equality of two :class:`SemanticVersion` instances. 

1194 

1195 :param left: Left operand. 

1196 :param right: Right operand. 

1197 :returns: ``True``, if ``left`` is equal to ``right``, otherwise it's ``False``. 

1198 """ 

1199 return super()._equal(left, right) 

1200 

1201 def _compare(self, left: "SemanticVersion", right: "SemanticVersion") -> Nullable[bool]: 

1202 """ 

1203 Private helper method to compute the comparison of two :class:`SemanticVersion` instances. 

1204 

1205 :param left: Left operand. 

1206 :param right: Right operand. 

1207 :returns: ``True``, if ``left`` is smaller than ``right``. |br| 

1208 False if ``left`` is greater than ``right``. |br| 

1209 Otherwise it's None (both operands are equal). 

1210 """ 

1211 return super()._compare(left, right) 

1212 

1213 def __eq__(self, other: Union["SemanticVersion", str, int, None]) -> bool: 

1214 """ 

1215 Compare two version numbers for equality. 

1216 

1217 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1218 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major 

1219 number is assumed (all other parts are zero). 

1220 

1221 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1222 number. 

1223 

1224 :param other: Operand to compare against. 

1225 :returns: ``True``, if both version numbers are equal. 

1226 :raises ValueError: If parameter ``other`` is None. 

1227 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`. 

1228 """ 

1229 return super().__eq__(other) 

1230 

1231 def __ne__(self, other: Union["SemanticVersion", str, int, None]) -> bool: 

1232 """ 

1233 Compare two version numbers for inequality. 

1234 

1235 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1236 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major 

1237 number is assumed (all other parts are zero). 

1238 

1239 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1240 number. 

1241 

1242 :param other: Operand to compare against. 

1243 :returns: ``True``, if both version numbers are not equal. 

1244 :raises ValueError: If parameter ``other`` is None. 

1245 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`. 

1246 """ 

1247 return super().__ne__(other) 

1248 

1249 def __lt__(self, other: Union["SemanticVersion", str, int, None]) -> bool: 

1250 """ 

1251 Compare two version numbers if the version is less than the second operand. 

1252 

1253 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1254 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major 

1255 number is assumed (all other parts are zero). 

1256 

1257 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1258 number. 

1259 

1260 :param other: Operand to compare against. 

1261 :returns: ``True``, if version is less than the second operand. 

1262 :raises ValueError: If parameter ``other`` is None. 

1263 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`. 

1264 """ 

1265 return super().__lt__(other) 

1266 

1267 def __le__(self, other: Union["SemanticVersion", str, int, None]) -> bool: 

1268 """ 

1269 Compare two version numbers if the version is less than or equal the second operand. 

1270 

1271 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1272 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major 

1273 number is assumed (all other parts are zero). 

1274 

1275 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1276 number. 

1277 

1278 :param other: Operand to compare against. 

1279 :returns: ``True``, if version is less than or equal the second operand. 

1280 :raises ValueError: If parameter ``other`` is None. 

1281 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`. 

1282 """ 

1283 return super().__le__(other) 

1284 

1285 def __gt__(self, other: Union["SemanticVersion", str, int, None]) -> bool: 

1286 """ 

1287 Compare two version numbers if the version is greater than the second operand. 

1288 

1289 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1290 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major 

1291 number is assumed (all other parts are zero). 

1292 

1293 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1294 number. 

1295 

1296 :param other: Operand to compare against. 

1297 :returns: ``True``, if version is greater than the second operand. 

1298 :raises ValueError: If parameter ``other`` is None. 

1299 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`. 

1300 """ 

1301 return super().__gt__(other) 

1302 

1303 def __ge__(self, other: Union["SemanticVersion", str, int, None]) -> bool: 

1304 """ 

1305 Compare two version numbers if the version is greater than or equal the second operand. 

1306 

1307 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1308 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major 

1309 number is assumed (all other parts are zero). 

1310 

1311 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1312 number. 

1313 

1314 :param other: Operand to compare against. 

1315 :returns: ``True``, if version is greater than or equal the second operand. 

1316 :raises ValueError: If parameter ``other`` is None. 

1317 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`. 

1318 """ 

1319 return super().__ge__(other) 

1320 

1321 def __rshift__(self, other: Union["SemanticVersion", str, int, None]) -> bool: 

1322 return super().__rshift__(other) 

1323 

1324 def __hash__(self) -> int: 

1325 return super().__hash__() 

1326 

1327 def __format__(self, formatSpec: str) -> str: 

1328 result = self._format(formatSpec) 

1329 

1330 if (pos := result.find("%")) != -1 and result[pos + 1] != "%": # pragma: no cover 

1331 raise ValueError(f"Unknown format specifier '%{result[pos + 1]}' in '{formatSpec}'.") 

1332 

1333 return result.replace("%%", "%") 

1334 

1335 def __repr__(self) -> str: 

1336 """ 

1337 Return a string representation of this version number without prefix ``v``. 

1338 

1339 :returns: Raw version number representation without a prefix. 

1340 """ 

1341 return f"{self._prefix if Parts.Prefix in self._parts else ''}{self._major}.{self._minor}.{self._micro}" 

1342 

1343 def __str__(self) -> str: 

1344 """ 

1345 Return a string representation of this version number. 

1346 

1347 :returns: Version number representation. 

1348 """ 

1349 result = self._prefix if Parts.Prefix in self._parts else "" 

1350 result += f"{self._major}" # major is always present 

1351 result += f".{self._minor}" if Parts.Minor in self._parts else "" 

1352 result += f".{self._micro}" if Parts.Micro in self._parts else "" 

1353 result += f".{self._build}" if Parts.Build in self._parts else "" 

1354 if self._releaseLevel is ReleaseLevel.Development: 

1355 result += "-dev" 

1356 elif self._releaseLevel is ReleaseLevel.Alpha: 

1357 result += f".alpha{self._releaseNumber}" 

1358 elif self._releaseLevel is ReleaseLevel.Beta: 

1359 result += f".beta{self._releaseNumber}" 

1360 elif self._releaseLevel is ReleaseLevel.Gamma: 1360 ↛ 1361line 1360 didn't jump to line 1361 because the condition on line 1360 was never true

1361 result += f".gamma{self._releaseNumber}" 

1362 elif self._releaseLevel is ReleaseLevel.ReleaseCandidate: 

1363 result += f".rc{self._releaseNumber}" 

1364 result += f".post{self._post}" if Parts.Post in self._parts else "" 

1365 result += f".dev{self._dev}" if Parts.Dev in self._parts else "" 

1366 result += f"+{self._postfix}" if Parts.Postfix in self._parts else "" 

1367 

1368 return result 

1369 

1370 

1371@export 

1372class PythonVersion(SemanticVersion): 

1373 """ 

1374 Represents a Python version. 

1375 """ 

1376 

1377 @classmethod 

1378 def FromSysVersionInfo(cls) -> "PythonVersion": 

1379 """ 

1380 Create a Python version from :data:`sys.version_info`. 

1381 

1382 :returns: A PythonVersion instance of the current Python interpreter's version. 

1383 """ 

1384 from sys import version_info 

1385 

1386 if version_info.releaselevel == "final": 

1387 rl = ReleaseLevel.Final 

1388 number = None 

1389 else: # pragma: no cover 

1390 number = version_info.serial 

1391 

1392 if version_info.releaselevel == "alpha": 

1393 rl = ReleaseLevel.Alpha 

1394 elif version_info.releaselevel == "beta": 

1395 rl = ReleaseLevel.Beta 

1396 elif version_info.releaselevel == "candidate": 

1397 rl = ReleaseLevel.ReleaseCandidate 

1398 else: # pragma: no cover 

1399 raise ToolingException(f"Unsupported release level '{version_info.releaselevel}'.") 

1400 

1401 return cls(version_info.major, version_info.minor, version_info.micro, level=rl, number=number) 

1402 

1403 def __hash__(self) -> int: 

1404 return super().__hash__() 

1405 

1406 def __str__(self) -> str: 

1407 """ 

1408 Return a string representation of this version number. 

1409 

1410 :returns: Version number representation. 

1411 """ 

1412 result = self._prefix if Parts.Prefix in self._parts else "" 

1413 result += f"{self._major}" # major is always present 

1414 result += f".{self._minor}" if Parts.Minor in self._parts else "" 

1415 result += f".{self._micro}" if Parts.Micro in self._parts else "" 

1416 if self._releaseLevel is ReleaseLevel.Alpha: 

1417 result += f"a{self._releaseNumber}" 

1418 elif self._releaseLevel is ReleaseLevel.Beta: 

1419 result += f"b{self._releaseNumber}" 

1420 elif self._releaseLevel is ReleaseLevel.Gamma: 

1421 result += f"c{self._releaseNumber}" 

1422 elif self._releaseLevel is ReleaseLevel.ReleaseCandidate: 

1423 result += f"rc{self._releaseNumber}" 

1424 result += f".post{self._post}" if Parts.Post in self._parts else "" 

1425 result += f".dev{self._dev}" if Parts.Dev in self._parts else "" 

1426 result += f"+{self._postfix}" if Parts.Postfix in self._parts else "" 

1427 

1428 return result 

1429 

1430 

1431@export 

1432class CalendarVersion(Version): 

1433 """Representation of a calendar version number like ``2021.10``.""" 

1434 

1435 def __init__( 

1436 self, 

1437 major: int, 

1438 minor: Nullable[int] = None, 

1439 micro: Nullable[int] = None, 

1440 build: Nullable[int] = None, 

1441 flags: Flags = Flags.Clean, 

1442 prefix: Nullable[str] = None, 

1443 postfix: Nullable[str] = None 

1444 ) -> None: 

1445 """ 

1446 Initializes a calendar version number representation. 

1447 

1448 :param major: Major number part of the version number. 

1449 :param minor: Minor number part of the version number. 

1450 :param micro: Micro (patch) number part of the version number. 

1451 :param build: Build number part of the version number. 

1452 :param flags: The version number's flags. 

1453 :param prefix: The version number's prefix. 

1454 :param postfix: The version number's postfix. 

1455 :raises TypeError: If parameter 'major' is not of type int. 

1456 :raises ValueError: If parameter 'major' is a negative number. 

1457 :raises TypeError: If parameter 'minor' is not of type int. 

1458 :raises ValueError: If parameter 'minor' is a negative number. 

1459 :raises TypeError: If parameter 'micro' is not of type int. 

1460 :raises ValueError: If parameter 'micro' is a negative number. 

1461 :raises TypeError: If parameter 'build' is not of type int. 

1462 :raises ValueError: If parameter 'build' is a negative number. 

1463 :raises TypeError: If parameter 'prefix' is not of type str. 

1464 :raises TypeError: If parameter 'postfix' is not of type str. 

1465 """ 

1466 super().__init__(major, minor, micro, build=build, postfix=postfix, prefix=prefix, flags=flags) 

1467 

1468 @classmethod 

1469 def Parse(cls, versionString: Nullable[str], validator: Nullable[Callable[["CalendarVersion"], bool]] = None) -> "CalendarVersion": 

1470 """ 

1471 Parse a version string and return a :class:`CalendarVersion` instance. 

1472 

1473 :param versionString: The version string to parse. 

1474 :returns: An object representing a calendar version. 

1475 :raises TypeError: If parameter ``other`` is not a string. 

1476 :raises ValueError: If parameter ``other`` is None. 

1477 :raises ValueError: If parameter ``other`` is empty. 

1478 """ 

1479 parts = Parts.Unknown 

1480 

1481 if versionString is None: 

1482 raise ValueError("Parameter 'versionString' is None.") 

1483 elif not isinstance(versionString, str): 

1484 ex = TypeError(f"Parameter 'versionString' is not of type 'str'.") 

1485 if version_info >= (3, 11): # pragma: no cover 

1486 ex.add_note(f"Got type '{getFullyQualifiedName(versionString)}'.") 

1487 raise ex 

1488 elif versionString == "": 

1489 raise ValueError("Parameter 'versionString' is empty.") 

1490 

1491 split = versionString.split(".") 

1492 length = len(split) 

1493 major = int(split[0]) 

1494 minor = 0 

1495 parts |= Parts.Major 

1496 

1497 if length >= 2: 

1498 minor = int(split[1]) 

1499 parts |= Parts.Minor 

1500 

1501 flags = Flags.Clean 

1502 

1503 version = cls(major, minor, flags=flags) 

1504 if validator is not None and not validator(version): 

1505 raise ValueError(f"Failed to validate version string '{versionString}'.") # pragma: no cover 

1506 

1507 return version 

1508 

1509 @property 

1510 def Year(self) -> int: 

1511 """ 

1512 Read-only property to access the year part. 

1513 

1514 :return: The year part. 

1515 """ 

1516 return self._major 

1517 

1518 def _equal(self, left: "CalendarVersion", right: "CalendarVersion") -> Nullable[bool]: 

1519 """ 

1520 Private helper method to compute the equality of two :class:`CalendarVersion` instances. 

1521 

1522 :param left: Left parameter. 

1523 :param right: Right parameter. 

1524 :returns: ``True``, if ``left`` is equal to ``right``, otherwise it's ``False``. 

1525 """ 

1526 return (left._major == right._major) and (left._minor == right._minor) and (left._micro == right._micro) 

1527 

1528 def _compare(self, left: "CalendarVersion", right: "CalendarVersion") -> Nullable[bool]: 

1529 """ 

1530 Private helper method to compute the comparison of two :class:`CalendarVersion` instances. 

1531 

1532 :param left: Left parameter. 

1533 :param right: Right parameter. 

1534 :returns: ``True``, if ``left`` is smaller than ``right``. |br| 

1535 False if ``left`` is greater than ``right``. |br| 

1536 Otherwise it's None (both parameters are equal). 

1537 """ 

1538 if left._major < right._major: 

1539 return True 

1540 elif left._major > right._major: 

1541 return False 

1542 

1543 if left._minor < right._minor: 

1544 return True 

1545 elif left._minor > right._minor: 

1546 return False 

1547 

1548 if left._micro < right._micro: 1548 ↛ 1549line 1548 didn't jump to line 1549 because the condition on line 1548 was never true

1549 return True 

1550 elif left._micro > right._micro: 1550 ↛ 1551line 1550 didn't jump to line 1551 because the condition on line 1550 was never true

1551 return False 

1552 

1553 return None 

1554 

1555 def __eq__(self, other: Union["CalendarVersion", str, int, None]) -> bool: 

1556 """ 

1557 Compare two version numbers for equality. 

1558 

1559 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1560 In case of ``str``, it's tried to parse the string as a calendar version number. In case of ``int``, a single major 

1561 number is assumed (all other parts are zero). 

1562 

1563 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1564 number. 

1565 

1566 :param other: Parameter to compare against. 

1567 :returns: ``True``, if both version numbers are equal. 

1568 :raises ValueError: If parameter ``other`` is None. 

1569 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`. 

1570 """ 

1571 return super().__eq__(other) 

1572 

1573 def __ne__(self, other: Union["CalendarVersion", str, int, None]) -> bool: 

1574 """ 

1575 Compare two version numbers for inequality. 

1576 

1577 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1578 In case of ``str``, it's tried to parse the string as a calendar version number. In case of ``int``, a single major 

1579 number is assumed (all other parts are zero). 

1580 

1581 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1582 number. 

1583 

1584 :param other: Parameter to compare against. 

1585 :returns: ``True``, if both version numbers are not equal. 

1586 :raises ValueError: If parameter ``other`` is None. 

1587 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`. 

1588 """ 

1589 return super().__ne__(other) 

1590 

1591 def __lt__(self, other: Union["CalendarVersion", str, int, None]) -> bool: 

1592 """ 

1593 Compare two version numbers if the version is less than the second operand. 

1594 

1595 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1596 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major 

1597 number is assumed (all other parts are zero). 

1598 

1599 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1600 number. 

1601 

1602 :param other: Parameter to compare against. 

1603 :returns: ``True``, if version is less than the second operand. 

1604 :raises ValueError: If parameter ``other`` is None. 

1605 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`. 

1606 """ 

1607 return super().__lt__(other) 

1608 

1609 def __le__(self, other: Union["CalendarVersion", str, int, None]) -> bool: 

1610 """ 

1611 Compare two version numbers if the version is less than or equal the second operand. 

1612 

1613 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1614 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major 

1615 number is assumed (all other parts are zero). 

1616 

1617 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1618 number. 

1619 

1620 :param other: Parameter to compare against. 

1621 :returns: ``True``, if version is less than or equal the second operand. 

1622 :raises ValueError: If parameter ``other`` is None. 

1623 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`. 

1624 """ 

1625 return super().__le__(other) 

1626 

1627 def __gt__(self, other: Union["CalendarVersion", str, int, None]) -> bool: 

1628 """ 

1629 Compare two version numbers if the version is greater than the second operand. 

1630 

1631 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1632 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major 

1633 number is assumed (all other parts are zero). 

1634 

1635 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1636 number. 

1637 

1638 :param other: Parameter to compare against. 

1639 :returns: ``True``, if version is greater than the second operand. 

1640 :raises ValueError: If parameter ``other`` is None. 

1641 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`. 

1642 """ 

1643 return super().__gt__(other) 

1644 

1645 def __ge__(self, other: Union["CalendarVersion", str, int, None]) -> bool: 

1646 """ 

1647 Compare two version numbers if the version is greater than or equal the second operand. 

1648 

1649 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br| 

1650 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major 

1651 number is assumed (all other parts are zero). 

1652 

1653 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor 

1654 number. 

1655 

1656 :param other: Parameter to compare against. 

1657 :returns: ``True``, if version is greater than or equal the second operand. 

1658 :raises ValueError: If parameter ``other`` is None. 

1659 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`. 

1660 """ 

1661 return super().__ge__(other) 

1662 

1663 def __hash__(self) -> int: 

1664 return super().__hash__() 

1665 

1666 def __format__(self, formatSpec: str) -> str: 

1667 """ 

1668 Return a string representation of this version number according to the format specification. 

1669 

1670 .. topic:: Format Specifiers 

1671 

1672 * ``%M`` - major number (year) 

1673 * ``%m`` - minor number (month/week) 

1674 

1675 :param formatSpec: The format specification. 

1676 :return: Formatted version number. 

1677 """ 

1678 if formatSpec == "": 

1679 return self.__str__() 

1680 

1681 result = formatSpec 

1682 # result = result.replace("%P", str(self._prefix)) 

1683 result = result.replace("%M", str(self._major)) 

1684 result = result.replace("%m", str(self._minor)) 

1685 # result = result.replace("%p", str(self._pre)) 

1686 

1687 return result.replace("%%", "%") 

1688 

1689 def __repr__(self) -> str: 

1690 """ 

1691 Return a string representation of this version number without prefix ``v``. 

1692 

1693 :returns: Raw version number representation without a prefix. 

1694 """ 

1695 return f"{self._major}.{self._minor}" 

1696 

1697 def __str__(self) -> str: 

1698 """ 

1699 Return a string representation of this version number with prefix ``v``. 

1700 

1701 :returns: Version number representation including a prefix. 

1702 """ 

1703 result = f"{self._major}" 

1704 result += f".{self._minor}" if Parts.Minor in self._parts else "" 

1705 

1706 return result 

1707 

1708 

1709@export 

1710class YearMonthVersion(CalendarVersion): 

1711 """Representation of a calendar version number made of year and month like ``2021.10``.""" 

1712 

1713 def __init__( 

1714 self, 

1715 year: int, 

1716 month: Nullable[int] = None, 

1717 build: Nullable[int] = None, 

1718 flags: Flags = Flags.Clean, 

1719 prefix: Nullable[str] = None, 

1720 postfix: Nullable[str] = None 

1721 ) -> None: 

1722 """ 

1723 Initializes a year-month version number representation. 

1724 

1725 :param year: Year part of the version number. 

1726 :param month: Month part of the version number. 

1727 :param build: Build number part of the version number. 

1728 :param flags: The version number's flags. 

1729 :param prefix: The version number's prefix. 

1730 :param postfix: The version number's postfix. 

1731 :raises TypeError: If parameter 'major' is not of type int. 

1732 :raises ValueError: If parameter 'major' is a negative number. 

1733 :raises TypeError: If parameter 'minor' is not of type int. 

1734 :raises ValueError: If parameter 'minor' is a negative number. 

1735 :raises TypeError: If parameter 'micro' is not of type int. 

1736 :raises ValueError: If parameter 'micro' is a negative number. 

1737 :raises TypeError: If parameter 'build' is not of type int. 

1738 :raises ValueError: If parameter 'build' is a negative number. 

1739 :raises TypeError: If parameter 'prefix' is not of type str. 

1740 :raises TypeError: If parameter 'postfix' is not of type str. 

1741 """ 

1742 super().__init__(year, month, 0, build, flags, prefix, postfix) 

1743 

1744 @property 

1745 def Month(self) -> int: 

1746 """ 

1747 Read-only property to access the month part. 

1748 

1749 :return: The month part. 

1750 """ 

1751 return self._minor 

1752 

1753 def __hash__(self) -> int: 

1754 return super().__hash__() 

1755 

1756 

1757@export 

1758class YearWeekVersion(CalendarVersion): 

1759 """Representation of a calendar version number made of year and week like ``2021.47``.""" 

1760 

1761 def __init__( 

1762 self, 

1763 year: int, 

1764 week: Nullable[int] = None, 

1765 build: Nullable[int] = None, 

1766 flags: Flags = Flags.Clean, 

1767 prefix: Nullable[str] = None, 

1768 postfix: Nullable[str] = None 

1769 ) -> None: 

1770 """ 

1771 Initializes a year-week version number representation. 

1772 

1773 :param year: Year part of the version number. 

1774 :param week: Week part of the version number. 

1775 :param build: Build number part of the version number. 

1776 :param flags: The version number's flags. 

1777 :param prefix: The version number's prefix. 

1778 :param postfix: The version number's postfix. 

1779 :raises TypeError: If parameter 'major' is not of type int. 

1780 :raises ValueError: If parameter 'major' is a negative number. 

1781 :raises TypeError: If parameter 'minor' is not of type int. 

1782 :raises ValueError: If parameter 'minor' is a negative number. 

1783 :raises TypeError: If parameter 'micro' is not of type int. 

1784 :raises ValueError: If parameter 'micro' is a negative number. 

1785 :raises TypeError: If parameter 'build' is not of type int. 

1786 :raises ValueError: If parameter 'build' is a negative number. 

1787 :raises TypeError: If parameter 'prefix' is not of type str. 

1788 :raises TypeError: If parameter 'postfix' is not of type str. 

1789 """ 

1790 super().__init__(year, week, 0, build, flags, prefix, postfix) 

1791 

1792 @property 

1793 def Week(self) -> int: 

1794 """ 

1795 Read-only property to access the week part. 

1796 

1797 :return: The week part. 

1798 """ 

1799 return self._minor 

1800 

1801 def __hash__(self) -> int: 

1802 return super().__hash__() 

1803 

1804 

1805@export 

1806class YearReleaseVersion(CalendarVersion): 

1807 """Representation of a calendar version number made of year and release per year like ``2021.2``.""" 

1808 

1809 def __init__( 

1810 self, 

1811 year: int, 

1812 release: Nullable[int] = None, 

1813 build: Nullable[int] = None, 

1814 flags: Flags = Flags.Clean, 

1815 prefix: Nullable[str] = None, 

1816 postfix: Nullable[str] = None 

1817 ) -> None: 

1818 """ 

1819 Initializes a year-release version number representation. 

1820 

1821 :param year: Year part of the version number. 

1822 :param release: Release number of the version number. 

1823 :param build: Build number part of the version number. 

1824 :param flags: The version number's flags. 

1825 :param prefix: The version number's prefix. 

1826 :param postfix: The version number's postfix. 

1827 :raises TypeError: If parameter 'major' is not of type int. 

1828 :raises ValueError: If parameter 'major' is a negative number. 

1829 :raises TypeError: If parameter 'minor' is not of type int. 

1830 :raises ValueError: If parameter 'minor' is a negative number. 

1831 :raises TypeError: If parameter 'micro' is not of type int. 

1832 :raises ValueError: If parameter 'micro' is a negative number. 

1833 :raises TypeError: If parameter 'build' is not of type int. 

1834 :raises ValueError: If parameter 'build' is a negative number. 

1835 :raises TypeError: If parameter 'prefix' is not of type str. 

1836 :raises TypeError: If parameter 'postfix' is not of type str. 

1837 """ 

1838 super().__init__(year, release, 0, build, flags, prefix, postfix) 

1839 

1840 @property 

1841 def Release(self) -> int: 

1842 """ 

1843 Read-only property to access the release number. 

1844 

1845 :return: The release number. 

1846 """ 

1847 return self._minor 

1848 

1849 def __hash__(self) -> int: 

1850 return super().__hash__() 

1851 

1852 

1853@export 

1854class YearMonthDayVersion(CalendarVersion): 

1855 """Representation of a calendar version number made of year, month and day like ``2021.10.15``.""" 

1856 

1857 def __init__( 

1858 self, 

1859 year: int, 

1860 month: Nullable[int] = None, 

1861 day: Nullable[int] = None, 

1862 build: Nullable[int] = None, 

1863 flags: Flags = Flags.Clean, 

1864 prefix: Nullable[str] = None, 

1865 postfix: Nullable[str] = None 

1866 ) -> None: 

1867 """ 

1868 Initializes a year-month-day version number representation. 

1869 

1870 :param year: Year part of the version number. 

1871 :param month: Month part of the version number. 

1872 :param day: Day part of the version number. 

1873 :param build: Build number part of the version number. 

1874 :param flags: The version number's flags. 

1875 :param prefix: The version number's prefix. 

1876 :param postfix: The version number's postfix. 

1877 :raises TypeError: If parameter 'major' is not of type int. 

1878 :raises ValueError: If parameter 'major' is a negative number. 

1879 :raises TypeError: If parameter 'minor' is not of type int. 

1880 :raises ValueError: If parameter 'minor' is a negative number. 

1881 :raises TypeError: If parameter 'micro' is not of type int. 

1882 :raises ValueError: If parameter 'micro' is a negative number. 

1883 :raises TypeError: If parameter 'build' is not of type int. 

1884 :raises ValueError: If parameter 'build' is a negative number. 

1885 :raises TypeError: If parameter 'prefix' is not of type str. 

1886 :raises TypeError: If parameter 'postfix' is not of type str. 

1887 """ 

1888 super().__init__(year, month, day, build, flags, prefix, postfix) 

1889 

1890 @property 

1891 def Month(self) -> int: 

1892 """ 

1893 Read-only property to access the month part. 

1894 

1895 :return: The month part. 

1896 """ 

1897 return self._minor 

1898 

1899 @property 

1900 def Day(self) -> int: 

1901 """ 

1902 Read-only property to access the day part. 

1903 

1904 :return: The day part. 

1905 """ 

1906 return self._micro 

1907 

1908 def __hash__(self) -> int: 

1909 return super().__hash__() 

1910 

1911 

1912V = TypeVar("V", bound=Version) 

1913 

1914@export 

1915class RangeBoundHandling(Flag): 

1916 """ 

1917 A flag defining how to handle bounds in a range. 

1918 

1919 If a bound is inclusive, the bound's value is within the range. If a bound is exclusive, the bound's value is the 

1920 first value outside the range. Inclusive and exclusive behavior can be mixed for lower and upper bounds. 

1921 """ 

1922 BothBoundsInclusive = 0 #: Lower and upper bound are inclusive. 

1923 LowerBoundInclusive = 0 #: Lower bound is inclusive. 

1924 UpperBoundInclusive = 0 #: Upper bound is inclusive. 

1925 LowerBoundExclusive = 1 #: Lower bound is exclusive. 

1926 UpperBoundExclusive = 2 #: Upper bound is exclusive. 

1927 BothBoundsExclusive = 3 #: Lower and upper bound are exclusive. 

1928 

1929 

1930@export 

1931class VersionRange(Generic[V], metaclass=ExtendedType, slots=True): 

1932 """ 

1933 Representation of a version range described by a lower bound and upper bound version. 

1934 

1935 This version range works with :class:`SemanticVersion` and :class:`CalendarVersion` and its derived classes. 

1936 """ 

1937 _lowerBound: V 

1938 _upperBound: V 

1939 _boundHandling: RangeBoundHandling 

1940 

1941 def __init__(self, lowerBound: V, upperBound: V, boundHandling: RangeBoundHandling = RangeBoundHandling.BothBoundsInclusive) -> None: 

1942 """ 

1943 Initializes a version range described by a lower and upper bound. 

1944 

1945 :param lowerBound: lowest version (inclusive). 

1946 :param upperBound: hightest version (inclusive). 

1947 :raises TypeError: If parameter ``lowerBound`` is not of type :class:`Version`. 

1948 :raises TypeError: If parameter ``upperBound`` is not of type :class:`Version`. 

1949 :raises TypeError: If parameter ``lowerBound`` and ``upperBound`` are unrelated types. 

1950 :raises ValueError: If parameter ``lowerBound`` isn't less than or equal to ``upperBound``. 

1951 """ 

1952 if not isinstance(lowerBound, Version): 

1953 ex = TypeError(f"Parameter 'lowerBound' is not of type 'Version'.") 

1954 if version_info >= (3, 11): # pragma: no cover 

1955 ex.add_note(f"Got type '{getFullyQualifiedName(lowerBound)}'.") 

1956 raise ex 

1957 

1958 if not isinstance(upperBound, Version): 

1959 ex = TypeError(f"Parameter 'upperBound' is not of type 'Version'.") 

1960 if version_info >= (3, 11): # pragma: no cover 

1961 ex.add_note(f"Got type '{getFullyQualifiedName(upperBound)}'.") 

1962 raise ex 

1963 

1964 if not ((lBC := lowerBound.__class__) is (uBC := upperBound.__class__) or issubclass(lBC, uBC) or issubclass(uBC, lBC)): 

1965 ex = TypeError(f"Parameters 'lowerBound' and 'upperBound' are not compatible with each other.") 

1966 if version_info >= (3, 11): # pragma: no cover 

1967 ex.add_note(f"Got type '{getFullyQualifiedName(lowerBound)}' for lowerBound and type '{getFullyQualifiedName(upperBound)}' for upperBound.") 

1968 raise ex 

1969 

1970 if not (lowerBound <= upperBound): 

1971 ex = ValueError(f"Parameter 'lowerBound' isn't less than parameter 'upperBound'.") 

1972 if version_info >= (3, 11): # pragma: no cover 

1973 ex.add_note(f"Got '{lowerBound}' for lowerBound and '{upperBound}' for upperBound.") 

1974 raise ex 

1975 

1976 self._lowerBound = lowerBound 

1977 self._upperBound = upperBound 

1978 self._boundHandling = boundHandling 

1979 

1980 @readonly 

1981 def LowerBound(self) -> V: 

1982 """ 

1983 Read-only property to access the range's lower bound. 

1984 

1985 :return: Lower bound of the version range. 

1986 """ 

1987 return self._lowerBound 

1988 

1989 @readonly 

1990 def UpperBound(self) -> V: 

1991 """ 

1992 Read-only property to access the range's upper bound. 

1993 

1994 :return: Upper bound of the version range. 

1995 """ 

1996 return self._upperBound 

1997 

1998 @readonly 

1999 def BoundHandling(self) -> RangeBoundHandling: 

2000 """ 

2001 Read-only property to access the range's bound handling strategy. 

2002 

2003 :return: The range's bound handling strategy. 

2004 """ 

2005 return self._boundHandling 

2006 

2007 def __and__(self, other: Any) -> "VersionRange[T]": 

2008 """ 

2009 Compute the intersection of two version ranges. 

2010 

2011 :param other: Second version range to intersect with. 

2012 :returns: Intersected version range. 

2013 :raises TypeError: If parameter 'other' is not of type :class:`VersionRange`. 

2014 :raises ValueError: If intersection is empty. 

2015 """ 

2016 if not isinstance(other, VersionRange): 2016 ↛ 2017line 2016 didn't jump to line 2017 because the condition on line 2016 was never true

2017 ex = TypeError(f"Parameter 'other' is not of type 'VersionRange'.") 

2018 if version_info >= (3, 11): # pragma: no cover 

2019 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2020 raise ex 

2021 

2022 if not (isinstance(other._lowerBound, self._lowerBound.__class__) and isinstance(self._lowerBound, other._lowerBound.__class__)): 2022 ↛ 2023line 2022 didn't jump to line 2023 because the condition on line 2022 was never true

2023 ex = TypeError(f"Parameter 'other's LowerBound and this range's 'LowerBound' are not compatible with each other.") 

2024 if version_info >= (3, 11): # pragma: no cover 

2025 ex.add_note( 

2026 f"Got type '{getFullyQualifiedName(other._lowerBound)}' for other.LowerBound and type '{getFullyQualifiedName(self._lowerBound)}' for self.LowerBound.") 

2027 raise ex 

2028 

2029 if other._lowerBound < self._lowerBound: 

2030 lBound = self._lowerBound 

2031 elif other._lowerBound in self: 2031 ↛ 2034line 2031 didn't jump to line 2034 because the condition on line 2031 was always true

2032 lBound = other._lowerBound 

2033 else: 

2034 raise ValueError() 

2035 

2036 if other._upperBound > self._upperBound: 

2037 uBound = self._upperBound 

2038 elif other._upperBound in self: 2038 ↛ 2041line 2038 didn't jump to line 2041 because the condition on line 2038 was always true

2039 uBound = other._upperBound 

2040 else: 

2041 raise ValueError() 

2042 

2043 return self.__class__(lBound, uBound) 

2044 

2045 def __lt__(self, other: Any) -> bool: 

2046 """ 

2047 Compare a version range and a version numbers if the version range is less than the second operand (version). 

2048 

2049 :param other: Operand to compare against. 

2050 :returns: ``True``, if version range is less than the second operand (version). 

2051 :raises TypeError: If parameter ``other`` is not of type :class:`Version`. 

2052 """ 

2053 # TODO: support VersionRange < VersionRange too 

2054 # TODO: support str, int, ... like Version ? 

2055 if not isinstance(other, Version): 

2056 ex = TypeError(f"Parameter 'other' is not of type 'Version'.") 

2057 if version_info >= (3, 11): # pragma: no cover 

2058 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2059 raise ex 

2060 

2061 if not (isinstance(other, self._lowerBound.__class__) and isinstance(self._lowerBound, other.__class__)): 2061 ↛ 2062line 2061 didn't jump to line 2062 because the condition on line 2061 was never true

2062 ex = TypeError(f"Parameter 'other' is not compatible with version range.") 

2063 if version_info >= (3, 11): # pragma: no cover 

2064 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2065 raise ex 

2066 

2067 return self._upperBound < other 

2068 

2069 def __le__(self, other: Any) -> bool: 

2070 """ 

2071 Compare a version range and a version numbers if the version range is less than or equal the second operand (version). 

2072 

2073 :param other: Operand to compare against. 

2074 :returns: ``True``, if version range is less than or equal the second operand (version). 

2075 :raises TypeError: If parameter ``other`` is not of type :class:`Version`. 

2076 """ 

2077 # TODO: support VersionRange < VersionRange too 

2078 # TODO: support str, int, ... like Version ? 

2079 if not isinstance(other, Version): 2079 ↛ 2080line 2079 didn't jump to line 2080 because the condition on line 2079 was never true

2080 ex = TypeError(f"Parameter 'other' is not of type 'Version'.") 

2081 if version_info >= (3, 11): # pragma: no cover 

2082 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2083 raise ex 

2084 

2085 if not (isinstance(other, self._lowerBound.__class__) and isinstance(self._lowerBound, other.__class__)): 2085 ↛ 2086line 2085 didn't jump to line 2086 because the condition on line 2085 was never true

2086 ex = TypeError(f"Parameter 'other' is not compatible with version range.") 

2087 if version_info >= (3, 11): # pragma: no cover 

2088 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2089 raise ex 

2090 

2091 if RangeBoundHandling.UpperBoundExclusive in self._boundHandling: 

2092 return self._upperBound < other 

2093 else: 

2094 return self._upperBound <= other 

2095 

2096 def __gt__(self, other: Any) -> bool: 

2097 """ 

2098 Compare a version range and a version numbers if the version range is greater than the second operand (version). 

2099 

2100 :param other: Operand to compare against. 

2101 :returns: ``True``, if version range is greater than the second operand (version). 

2102 :raises TypeError: If parameter ``other`` is not of type :class:`Version`. 

2103 """ 

2104 # TODO: support VersionRange < VersionRange too 

2105 # TODO: support str, int, ... like Version ? 

2106 if not isinstance(other, Version): 2106 ↛ 2107line 2106 didn't jump to line 2107 because the condition on line 2106 was never true

2107 ex = TypeError(f"Parameter 'other' is not of type 'Version'.") 

2108 if version_info >= (3, 11): # pragma: no cover 

2109 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2110 raise ex 

2111 

2112 if not (isinstance(other, self._upperBound.__class__) and isinstance(self._upperBound, other.__class__)): 2112 ↛ 2113line 2112 didn't jump to line 2113 because the condition on line 2112 was never true

2113 ex = TypeError(f"Parameter 'other' is not compatible with version range.") 

2114 if version_info >= (3, 11): # pragma: no cover 

2115 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2116 raise ex 

2117 

2118 return self._lowerBound > other 

2119 

2120 def __ge__(self, other: Any) -> bool: 

2121 """ 

2122 Compare a version range and a version numbers if the version range is greater than or equal the second operand (version). 

2123 

2124 :param other: Operand to compare against. 

2125 :returns: ``True``, if version range is greater than or equal the second operand (version). 

2126 :raises TypeError: If parameter ``other`` is not of type :class:`Version`. 

2127 """ 

2128 # TODO: support VersionRange < VersionRange too 

2129 # TODO: support str, int, ... like Version ? 

2130 if not isinstance(other, Version): 2130 ↛ 2131line 2130 didn't jump to line 2131 because the condition on line 2130 was never true

2131 ex = TypeError(f"Parameter 'other' is not of type 'Version'.") 

2132 if version_info >= (3, 11): # pragma: no cover 

2133 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2134 raise ex 

2135 

2136 if not (isinstance(other, self._upperBound.__class__) and isinstance(self._upperBound, other.__class__)): 2136 ↛ 2137line 2136 didn't jump to line 2137 because the condition on line 2136 was never true

2137 ex = TypeError(f"Parameter 'other' is not compatible with version range.") 

2138 if version_info >= (3, 11): # pragma: no cover 

2139 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2140 raise ex 

2141 

2142 if RangeBoundHandling.LowerBoundExclusive in self._boundHandling: 2142 ↛ 2143line 2142 didn't jump to line 2143 because the condition on line 2142 was never true

2143 return self._lowerBound > other 

2144 else: 

2145 return self._lowerBound >= other 

2146 

2147 def __contains__(self, version: Version) -> bool: 

2148 """ 

2149 Check if the version is in the version range. 

2150 

2151 :param version: Version to check. 

2152 :returns: ``True``, if version is in range. 

2153 :raises TypeError: If parameter ``version`` is not of type :class:`Version`. 

2154 """ 

2155 if not isinstance(version, Version): 2155 ↛ 2156line 2155 didn't jump to line 2156 because the condition on line 2155 was never true

2156 ex = TypeError(f"Parameter 'item' is not of type 'Version'.") 

2157 if version_info >= (3, 11): # pragma: no cover 

2158 ex.add_note(f"Got type '{getFullyQualifiedName(version)}'.") 

2159 raise ex 

2160 

2161 if self._boundHandling is RangeBoundHandling.BothBoundsInclusive: 2161 ↛ 2163line 2161 didn't jump to line 2163 because the condition on line 2161 was always true

2162 return self._lowerBound <= version <= self._upperBound 

2163 elif self._boundHandling is (RangeBoundHandling.LowerBoundInclusive | RangeBoundHandling.UpperBoundExclusive): 

2164 return self._lowerBound <= version < self._upperBound 

2165 elif self._boundHandling is (RangeBoundHandling.LowerBoundExclusive | RangeBoundHandling.UpperBoundInclusive): 

2166 return self._lowerBound < version <= self._upperBound 

2167 else: 

2168 return self._lowerBound < version < self._upperBound 

2169 

2170 

2171@export 

2172class VersionSet(Generic[V], metaclass=ExtendedType, slots=True): 

2173 """ 

2174 Representation of an ordered set of versions. 

2175 

2176 This version set works with :class:`SemanticVersion` and :class:`CalendarVersion` and its derived classes. 

2177 """ 

2178 _items: List[V] #: An ordered list of set members. 

2179 

2180 def __init__(self, versions: Union[Version, Iterable[V]]): 

2181 """ 

2182 Initializes a version set either by a single version or an iterable of versions. 

2183 

2184 :param versions: A single version or an iterable of versions. 

2185 :raises ValueError: If parameter ``versions`` is None`. 

2186 :raises TypeError: In case of a single version, if parameter ``version`` is not of type :class:`Version`. 

2187 :raises TypeError: In case of an iterable, if parameter ``versions`` containes elements, which are not of type :class:`Version`. 

2188 :raises TypeError: If parameter ``versions`` is neither a single version nor an iterable thereof. 

2189 """ 

2190 if versions is None: 

2191 raise ValueError(f"Parameter 'versions' is None.") 

2192 

2193 if isinstance(versions, Version): 

2194 self._items = [versions] 

2195 elif isinstance(versions, abc_Iterable): 2195 ↛ 2213line 2195 didn't jump to line 2213 because the condition on line 2195 was always true

2196 iterator = iter(versions) 

2197 try: 

2198 firstVersion = next(iterator) 

2199 except StopIteration: 

2200 self._items = [] 

2201 return 

2202 

2203 if not isinstance(firstVersion, Version): 2203 ↛ 2204line 2203 didn't jump to line 2204 because the condition on line 2203 was never true

2204 raise TypeError(f"First element in parameter 'versions' is not of type Version.") 

2205 

2206 baseType = firstVersion.__class__ 

2207 for version in iterator: 

2208 if not isinstance(version, baseType): 

2209 raise TypeError(f"Element from parameter 'versions' is not of type {baseType.__name__}") 

2210 

2211 self._items = list(sorted(versions)) 

2212 else: 

2213 raise TypeError(f"Parameter 'versions' is not an Iterable.") 

2214 

2215 def __and__(self, other: "VersionSet[V]") -> "VersionSet[T]": 

2216 """ 

2217 Compute intersection of two version sets. 

2218 

2219 :param other: Second set of versions. 

2220 :returns: Intersection of two version sets. 

2221 """ 

2222 selfIterator = self.__iter__() 

2223 otherIterator = other.__iter__() 

2224 

2225 result = [] 

2226 try: 

2227 selfValue = next(selfIterator) 

2228 otherValue = next(otherIterator) 

2229 

2230 while True: 

2231 if selfValue < otherValue: 

2232 selfValue = next(selfIterator) 

2233 elif otherValue < selfValue: 

2234 otherValue = next(otherIterator) 

2235 else: 

2236 result.append(selfValue) 

2237 selfValue = next(selfIterator) 

2238 otherValue = next(otherIterator) 

2239 

2240 except StopIteration: 

2241 pass 

2242 

2243 return VersionSet(result) 

2244 

2245 def __or__(self, other: "VersionSet[V]") -> "VersionSet[T]": 

2246 """ 

2247 Compute union of two version sets. 

2248 

2249 :param other: Second set of versions. 

2250 :returns: Union of two version sets. 

2251 """ 

2252 selfIterator = self.__iter__() 

2253 otherIterator = other.__iter__() 

2254 

2255 result = [] 

2256 try: 

2257 selfValue = next(selfIterator) 

2258 except StopIteration: 

2259 for otherValue in otherIterator: 

2260 result.append(otherValue) 

2261 

2262 try: 

2263 otherValue = next(otherIterator) 

2264 except StopIteration: 

2265 for selfValue in selfIterator: 

2266 result.append(selfValue) 

2267 

2268 while True: 

2269 if selfValue < otherValue: 

2270 result.append(selfValue) 

2271 try: 

2272 selfValue = next(selfIterator) 

2273 except StopIteration: 

2274 result.append(otherValue) 

2275 for otherValue in otherIterator: 2275 ↛ 2276line 2275 didn't jump to line 2276 because the loop on line 2275 never started

2276 result.append(otherValue) 

2277 

2278 break 

2279 elif otherValue < selfValue: 

2280 result.append(otherValue) 

2281 try: 

2282 otherValue = next(otherIterator) 

2283 except StopIteration: 

2284 result.append(selfValue) 

2285 for selfValue in selfIterator: 

2286 result.append(selfValue) 

2287 

2288 break 

2289 else: 

2290 result.append(selfValue) 

2291 try: 

2292 selfValue = next(selfIterator) 

2293 except StopIteration: 

2294 for otherValue in otherIterator: 2294 ↛ 2295line 2294 didn't jump to line 2295 because the loop on line 2294 never started

2295 result.append(otherValue) 

2296 

2297 break 

2298 

2299 try: 

2300 otherValue = next(otherIterator) 

2301 except StopIteration: 

2302 for selfValue in selfIterator: 

2303 result.append(selfValue) 

2304 

2305 break 

2306 

2307 return VersionSet(result) 

2308 

2309 def __lt__(self, other: Any) -> bool: 

2310 """ 

2311 Compare a version set and a version numbers if the version set is less than the second operand (version). 

2312 

2313 :param other: Operand to compare against. 

2314 :returns: ``True``, if version set is less than the second operand (version). 

2315 :raises TypeError: If parameter ``other`` is not of type :class:`Version`. 

2316 """ 

2317 # TODO: support VersionRange < VersionRange too 

2318 # TODO: support str, int, ... like Version ? 

2319 if not isinstance(other, Version): 

2320 ex = TypeError(f"Parameter 'other' is not of type 'Version'.") 

2321 if version_info >= (3, 11): # pragma: no cover 

2322 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2323 raise ex 

2324 

2325 return self._items[-1] < other 

2326 

2327 def __le__(self, other: Any) -> bool: 

2328 """ 

2329 Compare a version set and a version numbers if the version set is less than or equal the second operand (version). 

2330 

2331 :param other: Operand to compare against. 

2332 :returns: ``True``, if version set is less than or equal the second operand (version). 

2333 :raises TypeError: If parameter ``other`` is not of type :class:`Version`. 

2334 """ 

2335 # TODO: support VersionRange < VersionRange too 

2336 # TODO: support str, int, ... like Version ? 

2337 if not isinstance(other, Version): 2337 ↛ 2338line 2337 didn't jump to line 2338 because the condition on line 2337 was never true

2338 ex = TypeError(f"Parameter 'other' is not of type 'Version'.") 

2339 if version_info >= (3, 11): # pragma: no cover 

2340 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2341 raise ex 

2342 

2343 return self._items[-1] <= other 

2344 

2345 def __gt__(self, other: Any) -> bool: 

2346 """ 

2347 Compare a version set and a version numbers if the version set is greater than the second operand (version). 

2348 

2349 :param other: Operand to compare against. 

2350 :returns: ``True``, if version set is greater than the second operand (version). 

2351 :raises TypeError: If parameter ``other`` is not of type :class:`Version`. 

2352 """ 

2353 # TODO: support VersionRange < VersionRange too 

2354 # TODO: support str, int, ... like Version ? 

2355 if not isinstance(other, Version): 2355 ↛ 2356line 2355 didn't jump to line 2356 because the condition on line 2355 was never true

2356 ex = TypeError(f"Parameter 'other' is not of type 'Version'.") 

2357 if version_info >= (3, 11): # pragma: no cover 

2358 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2359 raise ex 

2360 

2361 return self._items[0] > other 

2362 

2363 def __ge__(self, other: Any) -> bool: 

2364 """ 

2365 Compare a version set and a version numbers if the version set is greater than or equal the second operand (version). 

2366 

2367 :param other: Operand to compare against. 

2368 :returns: ``True``, if version set is greater than or equal the second operand (version). 

2369 :raises TypeError: If parameter ``other`` is not of type :class:`Version`. 

2370 """ 

2371 # TODO: support VersionRange < VersionRange too 

2372 # TODO: support str, int, ... like Version ? 

2373 if not isinstance(other, Version): 2373 ↛ 2374line 2373 didn't jump to line 2374 because the condition on line 2373 was never true

2374 ex = TypeError(f"Parameter 'other' is not of type 'Version'.") 

2375 if version_info >= (3, 11): # pragma: no cover 

2376 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.") 

2377 raise ex 

2378 

2379 return self._items[0] >= other 

2380 

2381 def __contains__(self, version: V) -> bool: 

2382 """ 

2383 Checks if the version a member of the set. 

2384 

2385 :param version: The version to check. 

2386 :returns: ``True``, if the version is a member of the set. 

2387 """ 

2388 return version in self._items 

2389 

2390 def __len__(self) -> int: 

2391 """ 

2392 Returns the number of members in the set. 

2393 

2394 :returns: Number of set members. 

2395 """ 

2396 return len(self._items) 

2397 

2398 def __iter__(self) -> Iterator[V]: 

2399 """ 

2400 Returns an iterator to iterate all versions of this set from lowest to highest. 

2401 

2402 :returns: Iterator to iterate versions. 

2403 """ 

2404 return self._items.__iter__() 

2405 

2406 def __getitem__(self, index: int) -> V: 

2407 """ 

2408 Access to a version of a set by index. 

2409 

2410 :param index: The index of the version to access. 

2411 :returns: The indexed version. 

2412 

2413 .. hint:: Versions are ordered from lowest to highest version number. 

2414 """ 

2415 return self._items[index]