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

972 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-31 07:24 +0000

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

2# _____ _ _ __ __ _ _ # 

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

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

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

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

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

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

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

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

14# Copyright 2020-2026 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:: 

35 

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

37""" 

38from collections.abc import Iterable as abc_Iterable 

39from enum import Flag, Enum 

40from re import compile as re_compile, Pattern 

41from typing import Optional as Nullable, Union, Callable, Any, ClassVar, Generic, TypeVar, Iterable, Iterator, List 

42 

43from pyTooling.Decorators import export, readonly 

44from pyTooling.MetaClasses import ExtendedType, abstractmethod, mustoverride 

45from pyTooling.Exceptions import ToolingException 

46from pyTooling.Common import getFullyQualifiedName 

47 

48 

49@export 

50class Parts(Flag): 

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

52 Unknown = 0 #: Undocumented 

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

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

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

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

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

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

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

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

61 Level = 8 #: Release level is present. 

62 Dev = 16 #: Development part is present. 

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

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

65 Prefix = 128 #: Prefix is present. 

66 Postfix = 256 #: Postfix is present. 

67 Hash = 512 #: Hash is present. 

68# AHead = 256 

69 

70 

71@export 

72class ReleaseLevel(Enum): 

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

74 Final = 0 #: 

75 ReleaseCandidate = -10 #: 

76 Development = -20 #: 

77 Gamma = -30 #: 

78 Beta = -40 #: 

79 Alpha = -50 #: 

80 

81 def __eq__(self, other: Any) -> bool: 

82 """ 

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

84 

85 :param other: Operand to compare against. 

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

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

88 """ 

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

90 other = ReleaseLevel(other) 

91 

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

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

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

95 raise ex 

96 

97 return self is other 

98 

99 def __ne__(self, other: Any) -> bool: 

100 """ 

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

102 

103 :param other: Operand to compare against. 

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

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

106 """ 

107 if isinstance(other, str): 

108 other = ReleaseLevel(other) 

109 

110 if not isinstance(other, ReleaseLevel): 

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

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

113 raise ex 

114 

115 return self is not other 

116 

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

118 """ 

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

120 

121 :param other: Operand to compare against. 

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

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

124 """ 

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

126 other = ReleaseLevel(other) 

127 

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

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

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

131 raise ex 

132 

133 return self.value < other.value 

134 

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

136 """ 

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

138 

139 :param other: Operand to compare against. 

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

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

142 """ 

143 if isinstance(other, str): 

144 other = ReleaseLevel(other) 

145 

146 if not isinstance(other, ReleaseLevel): 

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

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

149 raise ex 

150 

151 return self.value <= other.value 

152 

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

154 """ 

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

156 

157 :param other: Operand to compare against. 

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

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

160 """ 

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

162 other = ReleaseLevel(other) 

163 

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

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

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

167 raise ex 

168 

169 return self.value > other.value 

170 

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

172 """ 

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

174 

175 :param other: Operand to compare against. 

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

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

178 """ 

179 if isinstance(other, str): 

180 other = ReleaseLevel(other) 

181 

182 if not isinstance(other, ReleaseLevel): 

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

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

185 raise ex 

186 

187 return self.value >= other.value 

188 

189 def __hash__(self) -> int: 

190 return hash(self.value) 

191 

192 def __str__(self) -> str: 

193 """ 

194 Returns the release level's string equivalent. 

195 

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

197 """ 

198 if self is ReleaseLevel.Final: 

199 return "final" 

200 elif self is ReleaseLevel.ReleaseCandidate: 

201 return "rc" 

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

203 return "dev" 

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

205 return "beta" 

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

207 return "alpha" 

208 

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

210 

211 

212@export 

213class Flags(Flag): 

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

215 NoVCS = 0 #: No Version Control System VCS 

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

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

218 

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

220 SVN = 32 #: Subversion (SVN) 

221 Git = 64 #: Git 

222 Hg = 128 #: Mercurial (Hg) 

223 

224 

225@export 

226def WordSizeValidator( 

227 bits: Nullable[int] = None, 

228 majorBits: Nullable[int] = None, 

229 minorBits: Nullable[int] = None, 

230 microBits: Nullable[int] = None, 

231 buildBits: Nullable[int] = None 

232): 

233 """ 

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

235 

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

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

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

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

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

241 :returns: A validation function for Version instances. 

242 """ 

243 majorMax = minorMax = microMax = buildMax = -1 

244 if bits is not None: 

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

246 

247 if majorBits is not None: 

248 majorMax = 2**majorBits - 1 

249 if minorBits is not None: 

250 minorMax = 2**minorBits - 1 

251 if microBits is not None: 

252 microMax = 2 ** microBits - 1 

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

254 buildMax = 2**buildBits - 1 

255 

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

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

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

259 

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

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

262 

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

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

265 

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

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

268 

269 return True 

270 

271 return validator 

272 

273 

274@export 

275def MaxValueValidator( 

276 max: Nullable[int] = None, 

277 majorMax: Nullable[int] = None, 

278 minorMax: Nullable[int] = None, 

279 microMax: Nullable[int] = None, 

280 buildMax: Nullable[int] = None 

281): 

282 """ 

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

284 

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

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

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

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

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

290 :returns: A validation function for Version instances. 

291 """ 

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

293 majorMax = minorMax = microMax = buildMax = max 

294 

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

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

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

298 

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

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

301 

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

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

304 

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

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

307 

308 return True 

309 

310 return validator 

311 

312 

313@export 

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

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

316 

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

318 

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

320 _prefix: str #: Prefix string 

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

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

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

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

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

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

327 _dev: int #: Development number 

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

329 _postfix: str #: Postfix string 

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

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

332 

333 def __init__( 

334 self, 

335 major: int, 

336 minor: Nullable[int] = None, 

337 micro: Nullable[int] = None, 

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

339 number: Nullable[int] = None, 

340 post: Nullable[int] = None, 

341 dev: Nullable[int] = None, 

342 *, 

343 build: Nullable[int] = None, 

344 postfix: Nullable[str] = None, 

345 prefix: Nullable[str] = None, 

346 hash: Nullable[str] = None, 

347 flags: Flags = Flags.NoVCS 

348 ) -> None: 

349 """ 

350 Initializes a version number representation. 

351 

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

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

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

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

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

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

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

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

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

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

362 :param hash: Postfix string. 

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

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

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

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

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

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

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

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

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

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

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

374 """ 

375 self.__hash = None 

376 

377 if not isinstance(major, int): 

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

379 elif major < 0: 

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

381 

382 self._parts = Parts.Major 

383 self._major = major 

384 

385 if minor is not None: 

386 if not isinstance(minor, int): 

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

388 elif minor < 0: 

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

390 

391 self._parts |= Parts.Minor 

392 self._minor = minor 

393 else: 

394 self._minor = 0 

395 

396 if micro is not None: 

397 if not isinstance(micro, int): 

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

399 elif micro < 0: 

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

401 

402 self._parts |= Parts.Micro 

403 self._micro = micro 

404 else: 

405 self._micro = 0 

406 

407 if level is None: 

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

409 elif not isinstance(level, ReleaseLevel): 

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

411 elif level is ReleaseLevel.Final: 

412 if number is not None: 

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

414 

415 self._parts |= Parts.Level 

416 self._releaseLevel = level 

417 self._releaseNumber = 0 

418 else: 

419 self._parts |= Parts.Level 

420 self._releaseLevel = level 

421 

422 if number is not None: 

423 if not isinstance(number, int): 

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

425 elif number < 0: 

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

427 

428 self._releaseNumber = number 

429 else: 

430 self._releaseNumber = 0 

431 

432 if dev is not None: 

433 if not isinstance(dev, int): 

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

435 elif dev < 0: 

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

437 

438 self._parts |= Parts.Dev 

439 self._dev = dev 

440 else: 

441 self._dev = 0 

442 

443 if post is not None: 

444 if not isinstance(post, int): 

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

446 elif post < 0: 

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

448 

449 self._parts |= Parts.Post 

450 self._post = post 

451 else: 

452 self._post = 0 

453 

454 if build is not None: 

455 if not isinstance(build, int): 

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

457 elif build < 0: 

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

459 

460 self._build = build 

461 self._parts |= Parts.Build 

462 else: 

463 self._build = 0 

464 

465 if postfix is not None: 

466 if not isinstance(postfix, str): 

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

468 

469 self._parts |= Parts.Postfix 

470 self._postfix = postfix 

471 else: 

472 self._postfix = "" 

473 

474 if prefix is not None: 

475 if not isinstance(prefix, str): 

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

477 

478 self._parts |= Parts.Prefix 

479 self._prefix = prefix 

480 else: 

481 self._prefix = "" 

482 

483 if hash is not None: 

484 if not isinstance(hash, str): 

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

486 

487 self._parts |= Parts.Hash 

488 self._hash = hash 

489 else: 

490 self._hash = "" 

491 

492 if flags is None: 

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

494 elif not isinstance(flags, Flags): 

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

496 

497 self._flags = flags 

498 

499 @classmethod 

500 @abstractmethod 

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

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

503 

504 @readonly 

505 def Parts(self) -> Parts: 

506 """ 

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

508 

509 :returns: A flag enumeration of used version number parts. 

510 """ 

511 return self._parts 

512 

513 @readonly 

514 def Prefix(self) -> str: 

515 """ 

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

517 

518 :returns: The prefix of the version number. 

519 """ 

520 return self._prefix 

521 

522 @readonly 

523 def Major(self) -> int: 

524 """ 

525 Read-only property to access the major number. 

526 

527 :returns: The major number. 

528 """ 

529 return self._major 

530 

531 @readonly 

532 def Minor(self) -> int: 

533 """ 

534 Read-only property to access the minor number. 

535 

536 :returns: The minor number. 

537 """ 

538 return self._minor 

539 

540 @readonly 

541 def Micro(self) -> int: 

542 """ 

543 Read-only property to access the micro number. 

544 

545 :returns: The micro number. 

546 """ 

547 return self._micro 

548 

549 @readonly 

550 def ReleaseLevel(self) -> ReleaseLevel: 

551 """ 

552 Read-only property to access the release level. 

553 

554 :returns: The release level. 

555 """ 

556 return self._releaseLevel 

557 

558 @readonly 

559 def ReleaseNumber(self) -> int: 

560 """ 

561 Read-only property to access the release number. 

562 

563 :returns: The release number. 

564 """ 

565 return self._releaseNumber 

566 

567 @readonly 

568 def Post(self) -> int: 

569 """ 

570 Read-only property to access the post number. 

571 

572 :returns: The post number. 

573 """ 

574 return self._post 

575 

576 @readonly 

577 def Dev(self) -> int: 

578 """ 

579 Read-only property to access the development number. 

580 

581 :returns: The development number. 

582 """ 

583 return self._dev 

584 

585 @readonly 

586 def Build(self) -> int: 

587 """ 

588 Read-only property to access the build number. 

589 

590 :returns: The build number. 

591 """ 

592 return self._build 

593 

594 @readonly 

595 def Postfix(self) -> str: 

596 """ 

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

598 

599 :returns: The postfix of the version number. 

600 """ 

601 return self._postfix 

602 

603 @readonly 

604 def Hash(self) -> str: 

605 """ 

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

607 

608 :returns: The hash. 

609 """ 

610 return self._hash 

611 

612 @readonly 

613 def Flags(self) -> Flags: 

614 """ 

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

616 

617 :returns: The flags of the version number. 

618 """ 

619 return self._flags 

620 

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

622 """ 

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

624 

625 :param left: Left operand. 

626 :param right: Right operand. 

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

628 """ 

629 return ( 

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

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

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

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

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

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

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

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

638 (left._postfix == right._postfix) 

639 ) 

640 

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

642 """ 

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

644 

645 :param left: Left operand. 

646 :param right: Right operand. 

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

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

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

650 """ 

651 if left._major < right._major: 

652 return True 

653 elif left._major > right._major: 

654 return False 

655 

656 if left._minor < right._minor: 

657 return True 

658 elif left._minor > right._minor: 

659 return False 

660 

661 if left._micro < right._micro: 

662 return True 

663 elif left._micro > right._micro: 

664 return False 

665 

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

667 return True 

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

669 return False 

670 

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

672 return True 

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

674 return False 

675 

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

677 return True 

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

679 return False 

680 

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

682 return True 

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

684 return False 

685 

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

687 return True 

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

689 return False 

690 

691 return None 

692 

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

694 exactMajor = Parts.Minor in expected._parts 

695 exactMinor = Parts.Micro in expected._parts 

696 

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

698 return False 

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

700 return False 

701 

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

703 return False 

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

705 return False 

706 

707 if Parts.Micro in expected._parts: 

708 return actual._micro >= expected._micro 

709 

710 return True 

711 

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

713 """ 

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

715 

716 .. topic:: Format Specifiers 

717 

718 * ``%p`` - prefix 

719 * ``%M`` - major number 

720 * ``%m`` - minor number 

721 * ``%u`` - micro number 

722 * ``%b`` - build number 

723 

724 :param formatSpec: The format specification. 

725 :returns: Formatted version number. 

726 """ 

727 if formatSpec == "": 

728 return self.__str__() 

729 

730 result = formatSpec 

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

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

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

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

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

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

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

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

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

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

741 

742 return result 

743 

744 @mustoverride 

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

746 """ 

747 Compare two version numbers for equality. 

748 

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

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

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

752 

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

754 number. 

755 

756 :param other: Operand to compare against. 

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

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

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

760 """ 

761 if other is None: 

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

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

764 pass 

765 elif isinstance(other, str): 

766 other = self.__class__.Parse(other) 

767 elif isinstance(other, int): 

768 other = self.__class__(major=other) 

769 else: 

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

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

772 raise ex 

773 

774 return self._equal(self, other) 

775 

776 @mustoverride 

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

778 """ 

779 Compare two version numbers for inequality. 

780 

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

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

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

784 

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

786 number. 

787 

788 :param other: Operand to compare against. 

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

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

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

792 """ 

793 if other is None: 

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

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

796 pass 

797 elif isinstance(other, str): 

798 other = self.__class__.Parse(other) 

799 elif isinstance(other, int): 

800 other = self.__class__(major=other) 

801 else: 

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

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

804 raise ex 

805 

806 return not self._equal(self, other) 

807 

808 @mustoverride 

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

810 """ 

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

812 

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

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

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

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

817 

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

819 number. 

820 

821 :param other: Operand to compare against. 

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

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

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

825 """ 

826 if other is None: 

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

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

829 pass 

830 elif isinstance(other, VersionRange): 

831 other = other._lowerBound 

832 elif isinstance(other, VersionSet): 

833 other = other._items[0] 

834 elif isinstance(other, str): 

835 other = self.__class__.Parse(other) 

836 elif isinstance(other, int): 

837 other = self.__class__(major=other) 

838 else: 

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

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

841 raise ex 

842 

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

844 

845 @mustoverride 

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

847 """ 

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

849 

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

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

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

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

854 

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

856 number. 

857 

858 :param other: Operand to compare against. 

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

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

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

862 """ 

863 equalValue = True 

864 if other is None: 

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

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

867 pass 

868 elif isinstance(other, VersionRange): 

869 equalValue = RangeBoundHandling.LowerBoundExclusive not in other._boundHandling 

870 other = other._lowerBound 

871 elif isinstance(other, VersionSet): 

872 other = other._items[0] 

873 elif isinstance(other, str): 

874 other = self.__class__.Parse(other) 

875 elif isinstance(other, int): 

876 other = self.__class__(major=other) 

877 else: 

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

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

880 raise ex 

881 

882 result = self._compare(self, other) 

883 return result if result is not None else equalValue 

884 

885 @mustoverride 

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

887 """ 

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

889 

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

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

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

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

894 

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

896 number. 

897 

898 :param other: Operand to compare against. 

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

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

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

902 """ 

903 if other is None: 

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

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

906 pass 

907 elif isinstance(other, VersionRange): 

908 other = other._upperBound 

909 elif isinstance(other, VersionSet): 

910 other = other._items[-1] 

911 elif isinstance(other, str): 

912 other = self.__class__.Parse(other) 

913 elif isinstance(other, int): 

914 other = self.__class__(major=other) 

915 else: 

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

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

918 raise ex 

919 

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

921 

922 @mustoverride 

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

924 """ 

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

926 

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

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

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

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

931 

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

933 number. 

934 

935 :param other: Operand to compare against. 

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

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

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

939 """ 

940 equalValue = True 

941 if other is None: 

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

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

944 pass 

945 elif isinstance(other, VersionRange): 

946 equalValue = RangeBoundHandling.UpperBoundExclusive not in other._boundHandling 

947 other = other._upperBound 

948 elif isinstance(other, VersionSet): 

949 other = other._items[-1] 

950 elif isinstance(other, str): 

951 other = self.__class__.Parse(other) 

952 elif isinstance(other, int): 

953 other = self.__class__(major=other) 

954 else: 

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

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

957 raise ex 

958 

959 result = self._compare(self, other) 

960 return not result if result is not None else equalValue 

961 

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

963 if other is None: 

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

965 elif isinstance(other, self.__class__): 

966 pass 

967 elif isinstance(other, str): 

968 other = self.__class__.Parse(other) 

969 elif isinstance(other, int): 

970 other = self.__class__(major=other) 

971 else: 

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

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

974 raise ex 

975 

976 return self._minimum(self, other) 

977 

978 def __hash__(self) -> int: 

979 if self.__hash is None: 

980 self.__hash = hash(( 

981 self._prefix, 

982 self._major, 

983 self._minor, 

984 self._micro, 

985 self._releaseLevel, 

986 self._releaseNumber, 

987 self._post, 

988 self._dev, 

989 self._build, 

990 self._postfix, 

991 self._hash, 

992 self._flags 

993 )) 

994 return self.__hash 

995 

996 

997@export 

998class SemanticVersion(Version): 

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

1000 

1001 _PATTERN: ClassVar[Pattern] = re_compile( 

1002 r"^" 

1003 r"(?P<prefix>rev|REV|[vViIrR])?" 

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

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

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

1007 r"(?:" 

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

1009 r"|" 

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

1011 r"|" 

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

1013 r")?" 

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

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

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

1017 r"$" 

1018 ) 

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

1020# ahead: int = 0 

1021 

1022 def __init__( 

1023 self, 

1024 major: int, 

1025 minor: Nullable[int] = None, 

1026 micro: Nullable[int] = None, 

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

1028 number: Nullable[int] = None, 

1029 post: Nullable[int] = None, 

1030 dev: Nullable[int] = None, 

1031 *, 

1032 build: Nullable[int] = None, 

1033 postfix: Nullable[str] = None, 

1034 prefix: Nullable[str] = None, 

1035 hash: Nullable[str] = None, 

1036 flags: Flags = Flags.NoVCS 

1037 ) -> None: 

1038 """ 

1039 Initializes a semantic version number representation. 

1040 

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

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

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

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

1045 :param level: tbd 

1046 :param number: tbd 

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

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

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

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

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

1052 :param hash: tbd 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1067 """ 

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

1069 

1070 @classmethod 

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

1072 """ 

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

1074 

1075 Allowed prefix characters: 

1076 

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

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

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

1080 * ``rev|REV`` - revision 

1081 

1082 :param versionString: The version string to parse. 

1083 :param validator: Optional, a validation function. 

1084 :returns: An object representing a semantic version. 

1085 :raises TypeError: When parameter ``versionString`` is not a string. 

1086 :raises ValueError: When parameter ``versionString`` is None. 

1087 :raises ValueError: When parameter ``versionString`` is empty. 

1088 """ 

1089 if versionString is None: 

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

1091 elif not isinstance(versionString, str): 

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

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

1094 raise ex 

1095 elif (versionString := versionString.strip()) == "": 

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

1097 

1098 if (match := cls._PATTERN.match(versionString)) is None: 

1099 ex = ValueError(f"Syntax error in parameter 'versionString': '{versionString}'") 

1100 ex.add_note(f"It may carry one of the prefixes 'v', 'i', 'r' or 'rev', e.g. 'v1.2.3'.") 

1101 raise ex 

1102 

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

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

1105 return None 

1106 

1107 try: 

1108 return int(value) 

1109 except ValueError as ex: # pragma: no cover 

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

1111 

1112 prefix = match["prefix"] 

1113 

1114 release = match["release"] 

1115 if release is not None: 

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

1117 releaseLevel = ReleaseLevel.Development 

1118 elif release == "final": 

1119 releaseLevel = ReleaseLevel.Final 

1120 else: # pragma: no cover 

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

1122 else: 

1123 level = match["level"] 

1124 if level is not None: 

1125 level = level.lower() 

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

1127 releaseLevel = ReleaseLevel.Alpha 

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

1129 releaseLevel = ReleaseLevel.Beta 

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

1131 releaseLevel = ReleaseLevel.Gamma 

1132 elif level == "rc": 

1133 releaseLevel = ReleaseLevel.ReleaseCandidate 

1134 else: # pragma: no cover 

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

1136 else: 

1137 releaseLevel = ReleaseLevel.Final 

1138 

1139 version = cls( 

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

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

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

1143 level=releaseLevel, 

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

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

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

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

1148 postfix=match["postfix"], 

1149 prefix=prefix if prefix != "" else None, 

1150 # hash=match["hash"], 

1151 flags=Flags.Clean 

1152 ) 

1153 

1154 if validator is not None and not validator(version): 1154 ↛ 1156line 1154 didn't jump to line 1156 because the condition on line 1154 was never true

1155 # TODO: VersionValidatorException 

1156 raise ValueError(f"Failed to validate version string '{versionString}'.") 

1157 

1158 return version 

1159 

1160 @readonly 

1161 def Patch(self) -> int: 

1162 """ 

1163 Read-only property to access the patch number. 

1164 

1165 The patch number is identical to the micro number. 

1166 

1167 :returns: The patch number. 

1168 """ 

1169 return self._micro 

1170 

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

1172 """ 

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

1174 

1175 :param left: Left operand. 

1176 :param right: Right operand. 

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

1178 """ 

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

1180 

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

1182 """ 

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

1184 

1185 :param left: Left operand. 

1186 :param right: Right operand. 

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

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

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

1190 """ 

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

1192 

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

1194 """ 

1195 Compare two version numbers for equality. 

1196 

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

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

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

1200 

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

1202 number. 

1203 

1204 :param other: Operand to compare against. 

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

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

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

1208 """ 

1209 return super().__eq__(other) 

1210 

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

1212 """ 

1213 Compare two version numbers for inequality. 

1214 

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

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

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

1218 

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

1220 number. 

1221 

1222 :param other: Operand to compare against. 

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

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

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

1226 """ 

1227 return super().__ne__(other) 

1228 

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

1230 """ 

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

1232 

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

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

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

1236 

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

1238 number. 

1239 

1240 :param other: Operand to compare against. 

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

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

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

1244 """ 

1245 return super().__lt__(other) 

1246 

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

1248 """ 

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

1250 

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

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

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

1254 

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

1256 number. 

1257 

1258 :param other: Operand to compare against. 

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

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

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

1262 """ 

1263 return super().__le__(other) 

1264 

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

1266 """ 

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

1268 

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

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

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

1272 

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

1274 number. 

1275 

1276 :param other: Operand to compare against. 

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

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

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

1280 """ 

1281 return super().__gt__(other) 

1282 

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

1284 """ 

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

1286 

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

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

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

1290 

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

1292 number. 

1293 

1294 :param other: Operand to compare against. 

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

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

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

1298 """ 

1299 return super().__ge__(other) 

1300 

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

1302 return super().__rshift__(other) 

1303 

1304 def __hash__(self) -> int: 

1305 return super().__hash__() 

1306 

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

1308 result = self._format(formatSpec) 

1309 

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

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

1312 

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

1314 

1315 def __repr__(self) -> str: 

1316 """ 

1317 Return a normalized string representation of this version number. 

1318 

1319 .. note:: 

1320 

1321 A prefix doesn't contribute to the version number's value, therefore it's not part of the normalized form. Use 

1322 :meth:`__str__` to render a version number including its prefix. 

1323 

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

1325 """ 

1326 return f"{self._major}.{self._minor}.{self._micro}" 

1327 

1328 def __str__(self) -> str: 

1329 """ 

1330 Return a string representation of this version number. 

1331 

1332 :returns: Version number representation. 

1333 """ 

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

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

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

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

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

1339 if self._releaseLevel is ReleaseLevel.Development: 

1340 result += "-dev" 

1341 elif self._releaseLevel is ReleaseLevel.Alpha: 

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

1343 elif self._releaseLevel is ReleaseLevel.Beta: 

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

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

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

1347 elif self._releaseLevel is ReleaseLevel.ReleaseCandidate: 

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

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

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

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

1352 

1353 return result 

1354 

1355 

1356@export 

1357class PythonVersion(SemanticVersion): 

1358 """ 

1359 Represents a Python version. 

1360 """ 

1361 

1362 @classmethod 

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

1364 """ 

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

1366 

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

1368 """ 

1369 from sys import version_info 

1370 

1371 if version_info.releaselevel == "final": 

1372 rl = ReleaseLevel.Final 

1373 number = None 

1374 else: # pragma: no cover 

1375 number = version_info.serial 

1376 

1377 if version_info.releaselevel == "alpha": 

1378 rl = ReleaseLevel.Alpha 

1379 elif version_info.releaselevel == "beta": 

1380 rl = ReleaseLevel.Beta 

1381 elif version_info.releaselevel == "candidate": 

1382 rl = ReleaseLevel.ReleaseCandidate 

1383 else: # pragma: no cover 

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

1385 

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

1387 

1388 def __hash__(self) -> int: 

1389 return super().__hash__() 

1390 

1391 def __str__(self) -> str: 

1392 """ 

1393 Return a string representation of this version number. 

1394 

1395 :returns: Version number representation. 

1396 """ 

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

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

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

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

1401 if self._releaseLevel is ReleaseLevel.Alpha: 1401 ↛ 1402line 1401 didn't jump to line 1402 because the condition on line 1401 was never true

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

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

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

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

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

1407 elif self._releaseLevel is ReleaseLevel.ReleaseCandidate: 1407 ↛ 1408line 1407 didn't jump to line 1408 because the condition on line 1407 was never true

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

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

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

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

1412 

1413 return result 

1414 

1415 

1416@export 

1417class CalendarVersion(Version): 

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

1419 

1420 _PARTCOUNT: ClassVar[int] = 3 #: Number of numeric parts a version number of this class can carry. 

1421 

1422 _PATTERN: ClassVar[Pattern] = re_compile( 

1423 r"^" 

1424 r"(?P<prefix>rev|REV|[vViIrR])?" 

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

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

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

1428 r"$" 

1429 ) 

1430 

1431 def __init__( 

1432 self, 

1433 major: int, 

1434 minor: Nullable[int] = None, 

1435 micro: Nullable[int] = None, 

1436 build: Nullable[int] = None, 

1437 flags: Flags = Flags.Clean, 

1438 prefix: Nullable[str] = None, 

1439 postfix: Nullable[str] = None 

1440 ) -> None: 

1441 """ 

1442 Initializes a calendar version number representation. 

1443 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1461 """ 

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

1463 

1464 @classmethod 

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

1466 """ 

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

1468 

1469 Allowed prefix characters: 

1470 

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

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

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

1474 * ``rev|REV`` - revision 

1475 

1476 A version number carries up to :attr:`_PARTCOUNT` numeric parts. :class:`YearMonthVersion`, 

1477 :class:`YearWeekVersion` and :class:`YearReleaseVersion` describe two parts, so a third part is rejected for 

1478 them. 

1479 

1480 :param versionString: The version string to parse. 

1481 :param validator: Optional, a validation function. 

1482 :returns: An object representing a calendar version. 

1483 :raises TypeError: If parameter ``versionString`` is not a string. 

1484 :raises ValueError: If parameter ``versionString`` is None. 

1485 :raises ValueError: If parameter ``versionString`` is empty. 

1486 :raises ValueError: If parameter ``versionString`` isn't a calendar version number. 

1487 :raises ValueError: If parameter ``versionString`` has more parts than the class describes. 

1488 """ 

1489 if versionString is None: 

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

1491 elif not isinstance(versionString, str): 

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

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

1494 raise ex 

1495 elif (versionString := versionString.strip()) == "": 

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

1497 

1498 if (match := cls._PATTERN.match(versionString)) is None: 

1499 ex = ValueError(f"Syntax error in parameter 'versionString': '{versionString}'") 

1500 ex.add_note(f"A calendar version number is made of up to {cls._PARTCOUNT} numeric parts, e.g. '2024.04'.") 

1501 ex.add_note(f"It may carry one of the prefixes 'v', 'i', 'r' or 'rev', e.g. 'v2024.04'.") 

1502 raise ex 

1503 

1504 prefix = match["prefix"] 

1505 minor = match["minor"] 

1506 micro = match["micro"] 

1507 

1508 if micro is not None and cls._PARTCOUNT < 3: 

1509 ex = ValueError(f"Version number '{versionString}' has 3 parts, but '{cls.__name__}' describes {cls._PARTCOUNT}.") 

1510 ex.add_note(f"Use 'CalendarVersion' or 'YearMonthDayVersion' to parse a 3-part calendar version number.") 

1511 raise ex 

1512 

1513 numbers = [int(match["major"]), 0 if minor is None else int(minor)] 

1514 if micro is not None: 

1515 numbers.append(int(micro)) 

1516 

1517 version = cls(*numbers, flags=Flags.Clean, prefix=prefix if prefix != "" else None) 

1518 

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

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

1521 

1522 return version 

1523 

1524 @readonly 

1525 def Year(self) -> int: 

1526 """ 

1527 Read-only property to access the year part. 

1528 

1529 :returns: The year part. 

1530 """ 

1531 return self._major 

1532 

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

1534 """ 

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

1536 

1537 :param left: Left parameter. 

1538 :param right: Right parameter. 

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

1540 """ 

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

1542 

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

1544 """ 

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

1546 

1547 :param left: Left parameter. 

1548 :param right: Right parameter. 

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

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

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

1552 """ 

1553 if left._major < right._major: 

1554 return True 

1555 elif left._major > right._major: 

1556 return False 

1557 

1558 if left._minor < right._minor: 

1559 return True 

1560 elif left._minor > right._minor: 

1561 return False 

1562 

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

1564 return True 

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

1566 return False 

1567 

1568 return None 

1569 

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

1571 """ 

1572 Compare two version numbers for equality. 

1573 

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

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

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

1577 

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

1579 number. 

1580 

1581 :param other: Parameter to compare against. 

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

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

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

1585 """ 

1586 return super().__eq__(other) 

1587 

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

1589 """ 

1590 Compare two version numbers for inequality. 

1591 

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

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

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

1595 

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

1597 number. 

1598 

1599 :param other: Parameter to compare against. 

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

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

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

1603 """ 

1604 return super().__ne__(other) 

1605 

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

1607 """ 

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

1609 

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

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

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

1613 

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

1615 number. 

1616 

1617 :param other: Parameter to compare against. 

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

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

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

1621 """ 

1622 return super().__lt__(other) 

1623 

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

1625 """ 

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

1627 

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

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

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

1631 

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

1633 number. 

1634 

1635 :param other: Parameter to compare against. 

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

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

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

1639 """ 

1640 return super().__le__(other) 

1641 

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

1643 """ 

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

1645 

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

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

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

1649 

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

1651 number. 

1652 

1653 :param other: Parameter to compare against. 

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

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

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

1657 """ 

1658 return super().__gt__(other) 

1659 

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

1661 """ 

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

1663 

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

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

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

1667 

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

1669 number. 

1670 

1671 :param other: Parameter to compare against. 

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

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

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

1675 """ 

1676 return super().__ge__(other) 

1677 

1678 def __hash__(self) -> int: 

1679 return super().__hash__() 

1680 

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

1682 """ 

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

1684 

1685 .. topic:: Format Specifiers 

1686 

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

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

1689 * ``%u`` - micro number (day) 

1690 

1691 :param formatSpec: The format specification. 

1692 :returns: Formatted version number. 

1693 """ 

1694 if formatSpec == "": 

1695 return self.__str__() 

1696 

1697 result = formatSpec 

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

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

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

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

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

1703 

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

1705 

1706 def __repr__(self) -> str: 

1707 """ 

1708 Return a normalized string representation of this version number. 

1709 

1710 .. note:: 

1711 

1712 A prefix doesn't contribute to the version number's value, therefore it's not part of the normalized form. Use 

1713 :meth:`__str__` to render a version number including its prefix. 

1714 

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

1716 """ 

1717 result = f"{self._major}.{self._minor}" 

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

1719 

1720 return result 

1721 

1722 def __str__(self) -> str: 

1723 """ 

1724 Return a string representation of this version number with only the present parts. 

1725 

1726 :returns: Version number representation including a prefix. 

1727 """ 

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

1729 result += f"{self._major}" 

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

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

1732 

1733 return result 

1734 

1735 

1736@export 

1737class YearMonthVersion(CalendarVersion): 

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

1739 

1740 _PARTCOUNT: ClassVar[int] = 2 #: A version number of this class carries year and month. 

1741 

1742 def __init__( 

1743 self, 

1744 year: int, 

1745 month: Nullable[int] = None, 

1746 build: Nullable[int] = None, 

1747 flags: Flags = Flags.Clean, 

1748 prefix: Nullable[str] = None, 

1749 postfix: Nullable[str] = None 

1750 ) -> None: 

1751 """ 

1752 Initializes a year-month version number representation. 

1753 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1770 """ 

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

1772 

1773 @readonly 

1774 def Month(self) -> int: 

1775 """ 

1776 Read-only property to access the month part. 

1777 

1778 :returns: The month part. 

1779 """ 

1780 return self._minor 

1781 

1782 def __hash__(self) -> int: 

1783 return super().__hash__() 

1784 

1785 

1786@export 

1787class YearWeekVersion(CalendarVersion): 

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

1789 

1790 _PARTCOUNT: ClassVar[int] = 2 #: A version number of this class carries year and week. 

1791 

1792 def __init__( 

1793 self, 

1794 year: int, 

1795 week: Nullable[int] = None, 

1796 build: Nullable[int] = None, 

1797 flags: Flags = Flags.Clean, 

1798 prefix: Nullable[str] = None, 

1799 postfix: Nullable[str] = None 

1800 ) -> None: 

1801 """ 

1802 Initializes a year-week version number representation. 

1803 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1820 """ 

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

1822 

1823 @readonly 

1824 def Week(self) -> int: 

1825 """ 

1826 Read-only property to access the week part. 

1827 

1828 :returns: The week part. 

1829 """ 

1830 return self._minor 

1831 

1832 def __hash__(self) -> int: 

1833 return super().__hash__() 

1834 

1835 

1836@export 

1837class YearReleaseVersion(CalendarVersion): 

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

1839 

1840 _PARTCOUNT: ClassVar[int] = 2 #: A version number of this class carries year and release. 

1841 

1842 def __init__( 

1843 self, 

1844 year: int, 

1845 release: Nullable[int] = None, 

1846 build: Nullable[int] = None, 

1847 flags: Flags = Flags.Clean, 

1848 prefix: Nullable[str] = None, 

1849 postfix: Nullable[str] = None 

1850 ) -> None: 

1851 """ 

1852 Initializes a year-release version number representation. 

1853 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1870 """ 

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

1872 

1873 @readonly 

1874 def Release(self) -> int: 

1875 """ 

1876 Read-only property to access the release number. 

1877 

1878 :returns: The release number. 

1879 """ 

1880 return self._minor 

1881 

1882 def __hash__(self) -> int: 

1883 return super().__hash__() 

1884 

1885 

1886@export 

1887class YearMonthDayVersion(CalendarVersion): 

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

1889 

1890 def __init__( 

1891 self, 

1892 year: int, 

1893 month: Nullable[int] = None, 

1894 day: Nullable[int] = None, 

1895 build: Nullable[int] = None, 

1896 flags: Flags = Flags.Clean, 

1897 prefix: Nullable[str] = None, 

1898 postfix: Nullable[str] = None 

1899 ) -> None: 

1900 """ 

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

1902 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1920 """ 

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

1922 

1923 @readonly 

1924 def Month(self) -> int: 

1925 """ 

1926 Read-only property to access the month part. 

1927 

1928 :returns: The month part. 

1929 """ 

1930 return self._minor 

1931 

1932 @readonly 

1933 def Day(self) -> int: 

1934 """ 

1935 Read-only property to access the day part. 

1936 

1937 :returns: The day part. 

1938 """ 

1939 return self._micro 

1940 

1941 def __hash__(self) -> int: 

1942 return super().__hash__() 

1943 

1944 

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

1946 

1947@export 

1948class RangeBoundHandling(Flag): 

1949 """ 

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

1951 

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

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

1954 """ 

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

1956 LowerBoundInclusive = 0 #: Lower bound is inclusive. 

1957 UpperBoundInclusive = 0 #: Upper bound is inclusive. 

1958 LowerBoundExclusive = 1 #: Lower bound is exclusive. 

1959 UpperBoundExclusive = 2 #: Upper bound is exclusive. 

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

1961 

1962 

1963@export 

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

1965 """ 

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

1967 

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

1969 """ 

1970 _lowerBound: V 

1971 _upperBound: V 

1972 _boundHandling: RangeBoundHandling 

1973 

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

1975 """ 

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

1977 

1978 :param lowerBound: lowest version (inclusive). 

1979 :param upperBound: hightest version (inclusive). 

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

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

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

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

1984 """ 

1985 if not isinstance(lowerBound, Version): 

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

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

1988 raise ex 

1989 

1990 if not isinstance(upperBound, Version): 

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

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

1993 raise ex 

1994 

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

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

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

1998 raise ex 

1999 

2000 if not (lowerBound <= upperBound): 

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

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

2003 raise ex 

2004 

2005 self._lowerBound = lowerBound 

2006 self._upperBound = upperBound 

2007 self._boundHandling = boundHandling 

2008 

2009 @property 

2010 def LowerBound(self) -> V: 

2011 """ 

2012 Property to access the range's lower bound. 

2013 

2014 :returns: Lower bound of the version range. 

2015 """ 

2016 return self._lowerBound 

2017 

2018 @LowerBound.setter 

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

2020 if not isinstance(value, Version): 

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

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

2023 raise ex 

2024 

2025 self._lowerBound = value 

2026 

2027 @property 

2028 def UpperBound(self) -> V: 

2029 """ 

2030 Property to access the range's upper bound. 

2031 

2032 :returns: Upper bound of the version range. 

2033 """ 

2034 return self._upperBound 

2035 

2036 @UpperBound.setter 

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

2038 if not isinstance(value, Version): 

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

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

2041 raise ex 

2042 

2043 self._upperBound = value 

2044 

2045 @property 

2046 def BoundHandling(self) -> RangeBoundHandling: 

2047 """ 

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

2049 

2050 :returns: The range's bound handling strategy. 

2051 """ 

2052 return self._boundHandling 

2053 

2054 @BoundHandling.setter 

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

2056 if not isinstance(value, RangeBoundHandling): 

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

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

2059 raise ex 

2060 

2061 self._boundHandling = value 

2062 

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

2064 """ 

2065 Compute the intersection of two version ranges. 

2066 

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

2068 :returns: Intersected version range. 

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

2070 :raises ValueError: If intersection is empty. 

2071 """ 

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

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

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

2075 raise ex 

2076 

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

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

2079 ex.add_note( 

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

2081 raise ex 

2082 

2083 if other._lowerBound < self._lowerBound: 

2084 lBound = self._lowerBound 

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

2086 lBound = other._lowerBound 

2087 else: 

2088 raise ValueError() 

2089 

2090 if other._upperBound > self._upperBound: 

2091 uBound = self._upperBound 

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

2093 uBound = other._upperBound 

2094 else: 

2095 raise ValueError() 

2096 

2097 return self.__class__(lBound, uBound) 

2098 

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

2100 """ 

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

2102 

2103 :param other: Operand to compare against. 

2104 :returns: ``True``, if version range is less than 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): 

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

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

2112 raise ex 

2113 

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

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

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

2117 raise ex 

2118 

2119 return self._upperBound < other 

2120 

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

2122 """ 

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

2124 

2125 :param other: Operand to compare against. 

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

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

2128 """ 

2129 # TODO: support VersionRange < VersionRange too 

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

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

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

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

2134 raise ex 

2135 

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

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

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

2139 raise ex 

2140 

2141 if RangeBoundHandling.UpperBoundExclusive in self._boundHandling: 

2142 return self._upperBound < other 

2143 else: 

2144 return self._upperBound <= other 

2145 

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

2147 """ 

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

2149 

2150 :param other: Operand to compare against. 

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

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

2153 """ 

2154 # TODO: support VersionRange < VersionRange too 

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

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

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

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

2159 raise ex 

2160 

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

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

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

2164 raise ex 

2165 

2166 return self._lowerBound > other 

2167 

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

2169 """ 

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

2171 

2172 :param other: Operand to compare against. 

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

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

2175 """ 

2176 # TODO: support VersionRange < VersionRange too 

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

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

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

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

2181 raise ex 

2182 

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

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

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

2186 raise ex 

2187 

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

2189 return self._lowerBound > other 

2190 else: 

2191 return self._lowerBound >= other 

2192 

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

2194 """ 

2195 Check if the version is in the version range. 

2196 

2197 :param version: Version to check. 

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

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

2200 """ 

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

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

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

2204 raise ex 

2205 

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

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

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

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

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

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

2212 else: 

2213 return self._lowerBound < version < self._upperBound 

2214 

2215 

2216@export 

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

2218 """ 

2219 Representation of an ordered set of versions. 

2220 

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

2222 """ 

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

2224 

2225 def __init__(self, versions: Union[Version, Iterable[V]]) -> None: 

2226 """ 

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

2228 

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

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

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

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

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

2234 """ 

2235 if versions is None: 

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

2237 

2238 if isinstance(versions, Version): 

2239 self._items = [versions] 

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

2241 iterator = iter(versions) 

2242 try: 

2243 firstVersion = next(iterator) 

2244 except StopIteration: 

2245 self._items = [] 

2246 return 

2247 

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

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

2250 

2251 baseType = firstVersion.__class__ 

2252 for version in iterator: 

2253 if not isinstance(version, baseType): 

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

2255 

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

2257 else: 

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

2259 

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

2261 """ 

2262 Compute intersection of two version sets. 

2263 

2264 :param other: Second set of versions. 

2265 :returns: Intersection of two version sets. 

2266 """ 

2267 selfIterator = self.__iter__() 

2268 otherIterator = other.__iter__() 

2269 

2270 result = [] 

2271 try: 

2272 selfValue = next(selfIterator) 

2273 otherValue = next(otherIterator) 

2274 

2275 while True: 

2276 if selfValue < otherValue: 

2277 selfValue = next(selfIterator) 

2278 elif otherValue < selfValue: 

2279 otherValue = next(otherIterator) 

2280 else: 

2281 result.append(selfValue) 

2282 selfValue = next(selfIterator) 

2283 otherValue = next(otherIterator) 

2284 

2285 except StopIteration: 

2286 pass 

2287 

2288 return VersionSet(result) 

2289 

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

2291 """ 

2292 Compute union of two version sets. 

2293 

2294 :param other: Second set of versions. 

2295 :returns: Union of two version sets. 

2296 """ 

2297 selfIterator = self.__iter__() 

2298 otherIterator = other.__iter__() 

2299 

2300 result = [] 

2301 try: 

2302 selfValue = next(selfIterator) 

2303 except StopIteration: 

2304 for otherValue in otherIterator: 

2305 result.append(otherValue) 

2306 

2307 try: 

2308 otherValue = next(otherIterator) 

2309 except StopIteration: 

2310 for selfValue in selfIterator: 

2311 result.append(selfValue) 

2312 

2313 while True: 

2314 if selfValue < otherValue: 

2315 result.append(selfValue) 

2316 try: 

2317 selfValue = next(selfIterator) 

2318 except StopIteration: 

2319 result.append(otherValue) 

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

2321 result.append(otherValue) 

2322 

2323 break 

2324 elif otherValue < selfValue: 

2325 result.append(otherValue) 

2326 try: 

2327 otherValue = next(otherIterator) 

2328 except StopIteration: 

2329 result.append(selfValue) 

2330 for selfValue in selfIterator: 

2331 result.append(selfValue) 

2332 

2333 break 

2334 else: 

2335 result.append(selfValue) 

2336 try: 

2337 selfValue = next(selfIterator) 

2338 except StopIteration: 

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

2340 result.append(otherValue) 

2341 

2342 break 

2343 

2344 try: 

2345 otherValue = next(otherIterator) 

2346 except StopIteration: 

2347 for selfValue in selfIterator: 

2348 result.append(selfValue) 

2349 

2350 break 

2351 

2352 return VersionSet(result) 

2353 

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

2355 """ 

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

2357 

2358 :param other: Operand to compare against. 

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

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

2361 """ 

2362 # TODO: support VersionRange < VersionRange too 

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

2364 if not isinstance(other, Version): 

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

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

2367 raise ex 

2368 

2369 return self._items[-1] < other 

2370 

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

2372 """ 

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

2374 

2375 :param other: Operand to compare against. 

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

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

2378 """ 

2379 # TODO: support VersionRange < VersionRange too 

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

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

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

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

2384 raise ex 

2385 

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

2387 

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

2389 """ 

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

2391 

2392 :param other: Operand to compare against. 

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

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

2395 """ 

2396 # TODO: support VersionRange < VersionRange too 

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

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

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

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

2401 raise ex 

2402 

2403 return self._items[0] > other 

2404 

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

2406 """ 

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

2408 

2409 :param other: Operand to compare against. 

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

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

2412 """ 

2413 # TODO: support VersionRange < VersionRange too 

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

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

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

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

2418 raise ex 

2419 

2420 return self._items[0] >= other 

2421 

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

2423 """ 

2424 Checks if the version a member of the set. 

2425 

2426 :param version: The version to check. 

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

2428 """ 

2429 return version in self._items 

2430 

2431 def __len__(self) -> int: 

2432 """ 

2433 Returns the number of members in the set. 

2434 

2435 :returns: Number of set members. 

2436 """ 

2437 return len(self._items) 

2438 

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

2440 """ 

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

2442 

2443 :returns: Iterator to iterate versions. 

2444 """ 

2445 return self._items.__iter__() 

2446 

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

2448 """ 

2449 Access to a version of a set by index. 

2450 

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

2452 :returns: The indexed version. 

2453 

2454 .. hint:: 

2455 

2456 Versions are ordered from lowest to highest version number. 

2457 """ 

2458 return self._items[index]