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

938 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-04 21:48 +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 @property 

1981 def LowerBound(self) -> V: 

1982 """ 

1983 Property to access the range's lower bound. 

1984 

1985 :return: Lower bound of the version range. 

1986 """ 

1987 return self._lowerBound 

1988 

1989 @LowerBound.setter 

1990 def LowerBound(self, value: V) -> None: 

1991 if not isinstance(value, Version): 

1992 ex = TypeError(f"Parameter 'value' is not of type 'Version'.") 

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

1994 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") 

1995 raise ex 

1996 

1997 self._lowerBound = value 

1998 

1999 @readonly 

2000 def UpperBound(self) -> V: 

2001 """ 

2002 Property to access the range's upper bound. 

2003 

2004 :return: Upper bound of the version range. 

2005 """ 

2006 return self._upperBound 

2007 

2008 @UpperBound.setter 

2009 def UpperBound(self, value: V) -> None: 

2010 if not isinstance(value, Version): 

2011 ex = TypeError(f"Parameter 'value' is not of type 'Version'.") 

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

2013 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") 

2014 raise ex 

2015 

2016 self._upperBound = value 

2017 

2018 @readonly 

2019 def BoundHandling(self) -> RangeBoundHandling: 

2020 """ 

2021 Property to access the range's bound handling strategy. 

2022 

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

2024 """ 

2025 return self._boundHandling 

2026 

2027 @BoundHandling.setter 

2028 def BoundHandling(self, value: RangeBoundHandling) -> None: 

2029 if not isinstance(value, RangeBoundHandling): 

2030 ex = TypeError(f"Parameter 'value' is not of type 'RangeBoundHandling'.") 

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

2032 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.") 

2033 raise ex 

2034 

2035 self._boundHandling = value 

2036 

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

2038 """ 

2039 Compute the intersection of two version ranges. 

2040 

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

2042 :returns: Intersected version range. 

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

2044 :raises ValueError: If intersection is empty. 

2045 """ 

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

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

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

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

2050 raise ex 

2051 

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

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

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

2055 ex.add_note( 

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

2057 raise ex 

2058 

2059 if other._lowerBound < self._lowerBound: 

2060 lBound = self._lowerBound 

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

2062 lBound = other._lowerBound 

2063 else: 

2064 raise ValueError() 

2065 

2066 if other._upperBound > self._upperBound: 

2067 uBound = self._upperBound 

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

2069 uBound = other._upperBound 

2070 else: 

2071 raise ValueError() 

2072 

2073 return self.__class__(lBound, uBound) 

2074 

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

2076 """ 

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

2078 

2079 :param other: Operand to compare against. 

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

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

2082 """ 

2083 # TODO: support VersionRange < VersionRange too 

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

2085 if not isinstance(other, Version): 

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

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

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

2089 raise ex 

2090 

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

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

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

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

2095 raise ex 

2096 

2097 return self._upperBound < other 

2098 

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

2100 """ 

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

2102 

2103 :param other: Operand to compare against. 

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

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

2106 """ 

2107 # TODO: support VersionRange < VersionRange too 

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

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

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

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

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

2113 raise ex 

2114 

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

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

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

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

2119 raise ex 

2120 

2121 if RangeBoundHandling.UpperBoundExclusive in self._boundHandling: 

2122 return self._upperBound < other 

2123 else: 

2124 return self._upperBound <= other 

2125 

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

2127 """ 

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

2129 

2130 :param other: Operand to compare against. 

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

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

2133 """ 

2134 # TODO: support VersionRange < VersionRange too 

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

2136 if not isinstance(other, Version): 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 of type 'Version'.") 

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

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

2140 raise ex 

2141 

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

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

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

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

2146 raise ex 

2147 

2148 return self._lowerBound > other 

2149 

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

2151 """ 

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

2153 

2154 :param other: Operand to compare against. 

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

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

2157 """ 

2158 # TODO: support VersionRange < VersionRange too 

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

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

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

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

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

2164 raise ex 

2165 

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

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

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

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

2170 raise ex 

2171 

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

2173 return self._lowerBound > other 

2174 else: 

2175 return self._lowerBound >= other 

2176 

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

2178 """ 

2179 Check if the version is in the version range. 

2180 

2181 :param version: Version to check. 

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

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

2184 """ 

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

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

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

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

2189 raise ex 

2190 

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

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

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

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

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

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

2197 else: 

2198 return self._lowerBound < version < self._upperBound 

2199 

2200 

2201@export 

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

2203 """ 

2204 Representation of an ordered set of versions. 

2205 

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

2207 """ 

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

2209 

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

2211 """ 

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

2213 

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

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

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

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

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

2219 """ 

2220 if versions is None: 

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

2222 

2223 if isinstance(versions, Version): 

2224 self._items = [versions] 

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

2226 iterator = iter(versions) 

2227 try: 

2228 firstVersion = next(iterator) 

2229 except StopIteration: 

2230 self._items = [] 

2231 return 

2232 

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

2234 raise TypeError(f"First element in parameter 'versions' is not of type Version.") 

2235 

2236 baseType = firstVersion.__class__ 

2237 for version in iterator: 

2238 if not isinstance(version, baseType): 

2239 raise TypeError(f"Element from parameter 'versions' is not of type {baseType.__name__}") 

2240 

2241 self._items = list(sorted(versions)) 

2242 else: 

2243 raise TypeError(f"Parameter 'versions' is not an Iterable.") 

2244 

2245 def __and__(self, other: "VersionSet[V]") -> "VersionSet[T]": 

2246 """ 

2247 Compute intersection of two version sets. 

2248 

2249 :param other: Second set of versions. 

2250 :returns: Intersection of two version sets. 

2251 """ 

2252 selfIterator = self.__iter__() 

2253 otherIterator = other.__iter__() 

2254 

2255 result = [] 

2256 try: 

2257 selfValue = next(selfIterator) 

2258 otherValue = next(otherIterator) 

2259 

2260 while True: 

2261 if selfValue < otherValue: 

2262 selfValue = next(selfIterator) 

2263 elif otherValue < selfValue: 

2264 otherValue = next(otherIterator) 

2265 else: 

2266 result.append(selfValue) 

2267 selfValue = next(selfIterator) 

2268 otherValue = next(otherIterator) 

2269 

2270 except StopIteration: 

2271 pass 

2272 

2273 return VersionSet(result) 

2274 

2275 def __or__(self, other: "VersionSet[V]") -> "VersionSet[T]": 

2276 """ 

2277 Compute union of two version sets. 

2278 

2279 :param other: Second set of versions. 

2280 :returns: Union of two version sets. 

2281 """ 

2282 selfIterator = self.__iter__() 

2283 otherIterator = other.__iter__() 

2284 

2285 result = [] 

2286 try: 

2287 selfValue = next(selfIterator) 

2288 except StopIteration: 

2289 for otherValue in otherIterator: 

2290 result.append(otherValue) 

2291 

2292 try: 

2293 otherValue = next(otherIterator) 

2294 except StopIteration: 

2295 for selfValue in selfIterator: 

2296 result.append(selfValue) 

2297 

2298 while True: 

2299 if selfValue < otherValue: 

2300 result.append(selfValue) 

2301 try: 

2302 selfValue = next(selfIterator) 

2303 except StopIteration: 

2304 result.append(otherValue) 

2305 for otherValue in otherIterator: 2305 ↛ 2306line 2305 didn't jump to line 2306 because the loop on line 2305 never started

2306 result.append(otherValue) 

2307 

2308 break 

2309 elif otherValue < selfValue: 

2310 result.append(otherValue) 

2311 try: 

2312 otherValue = next(otherIterator) 

2313 except StopIteration: 

2314 result.append(selfValue) 

2315 for selfValue in selfIterator: 

2316 result.append(selfValue) 

2317 

2318 break 

2319 else: 

2320 result.append(selfValue) 

2321 try: 

2322 selfValue = next(selfIterator) 

2323 except StopIteration: 

2324 for otherValue in otherIterator: 2324 ↛ 2325line 2324 didn't jump to line 2325 because the loop on line 2324 never started

2325 result.append(otherValue) 

2326 

2327 break 

2328 

2329 try: 

2330 otherValue = next(otherIterator) 

2331 except StopIteration: 

2332 for selfValue in selfIterator: 

2333 result.append(selfValue) 

2334 

2335 break 

2336 

2337 return VersionSet(result) 

2338 

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

2340 """ 

2341 Compare a version set and a version numbers if the version set is less than the second operand (version). 

2342 

2343 :param other: Operand to compare against. 

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

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

2346 """ 

2347 # TODO: support VersionRange < VersionRange too 

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

2349 if not isinstance(other, Version): 

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

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

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

2353 raise ex 

2354 

2355 return self._items[-1] < other 

2356 

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

2358 """ 

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

2360 

2361 :param other: Operand to compare against. 

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

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

2364 """ 

2365 # TODO: support VersionRange < VersionRange too 

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

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

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

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

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

2371 raise ex 

2372 

2373 return self._items[-1] <= other 

2374 

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

2376 """ 

2377 Compare a version set and a version numbers if the version set is greater than the second operand (version). 

2378 

2379 :param other: Operand to compare against. 

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

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

2382 """ 

2383 # TODO: support VersionRange < VersionRange too 

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

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

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

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

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

2389 raise ex 

2390 

2391 return self._items[0] > other 

2392 

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

2394 """ 

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

2396 

2397 :param other: Operand to compare against. 

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

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

2400 """ 

2401 # TODO: support VersionRange < VersionRange too 

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

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

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

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

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

2407 raise ex 

2408 

2409 return self._items[0] >= other 

2410 

2411 def __contains__(self, version: V) -> bool: 

2412 """ 

2413 Checks if the version a member of the set. 

2414 

2415 :param version: The version to check. 

2416 :returns: ``True``, if the version is a member of the set. 

2417 """ 

2418 return version in self._items 

2419 

2420 def __len__(self) -> int: 

2421 """ 

2422 Returns the number of members in the set. 

2423 

2424 :returns: Number of set members. 

2425 """ 

2426 return len(self._items) 

2427 

2428 def __iter__(self) -> Iterator[V]: 

2429 """ 

2430 Returns an iterator to iterate all versions of this set from lowest to highest. 

2431 

2432 :returns: Iterator to iterate versions. 

2433 """ 

2434 return self._items.__iter__() 

2435 

2436 def __getitem__(self, index: int) -> V: 

2437 """ 

2438 Access to a version of a set by index. 

2439 

2440 :param index: The index of the version to access. 

2441 :returns: The indexed version. 

2442 

2443 .. hint:: Versions are ordered from lowest to highest version number. 

2444 """ 

2445 return self._items[index]