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

1022 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-11 23:59 +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 

38.. seealso:: 

39 

40 :mod:`pyTooling.Packaging` 

41 |rarr| Reading a package's version from its dunder variables. 

42 :mod:`pyTooling.Dependency` 

43 |rarr| Resolving requirements against these version numbers. 

44""" 

45from __future__ import annotations 

46 

47from collections.abc import Iterable as abc_Iterable 

48from enum import Flag, Enum 

49from re import compile as re_compile, Pattern 

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

51 

52from pyTooling.Decorators import export, readonly 

53from pyTooling.MetaClasses import ExtendedType, abstractmethod, mustoverride 

54from pyTooling.Exceptions import ToolingException 

55from pyTooling.Common import getFullyQualifiedName 

56 

57 

58@export 

59class VersionValidatorException(ToolingException): 

60 """ 

61 Raised when a parsed version is rejected by the validator it was parsed with. 

62 

63 The version string itself was well-formed - it parsed - so this is not a :exc:`ValueError` about the input, but 

64 a statement that the resulting version is not acceptable to the caller. The version that failed is carried in 

65 :attr:`Version`, so a caller can report what was wrong with it. 

66 """ 

67 

68 _version: Nullable[Version] #: The version rejected by a validator. 

69 

70 def __init__(self, message: str, /, *, version: Nullable[Version] = None) -> None: 

71 """ 

72 Initializes the exception with the rejected version. 

73 

74 :param message: The exception's message. 

75 :param version: Optional, the version the validator rejected. 

76 """ 

77 super().__init__(message) 

78 self._version = version 

79 

80 @readonly 

81 def Version(self) -> Nullable[Version]: 

82 """ 

83 Read-only property to access the version the validator rejected (:attr:`_version`). 

84 

85 :returns: The rejected version, or ``None`` if it wasn't recorded. 

86 """ 

87 return self._version 

88 

89 

90@export 

91class Parts(Flag): 

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

93 Unknown = 0 #: Undocumented 

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

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

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

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

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

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

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

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

102 Level = 8 #: Release level is present. 

103 Dev = 16 #: Development part is present. 

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

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

106 Prefix = 128 #: Prefix is present. 

107 Postfix = 256 #: Postfix is present. 

108 Hash = 512 #: Hash is present. 

109# AHead = 256 

110 

111 

112@export 

113class ReleaseLevel(Enum): 

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

115 Final = 0 #: 

116 ReleaseCandidate = -10 #: 

117 Development = -20 #: 

118 Gamma = -30 #: 

119 Beta = -40 #: 

120 Alpha = -50 #: 

121 

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

123 """ 

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

125 

126 :param other: Operand to compare against. 

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

128 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or string. 

129 """ 

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

131 other = ReleaseLevel(other) 

132 

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

134 ex = TypeError(f"Second operand is not supported by == operator.") 

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

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

137 raise ex 

138 

139 return self is other 

140 

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

142 """ 

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

144 

145 :param other: Operand to compare against. 

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

147 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or string. 

148 """ 

149 if isinstance(other, str): 

150 other = ReleaseLevel(other) 

151 

152 if not isinstance(other, ReleaseLevel): 

153 ex = TypeError(f"Second operand is not supported by != operator.") 

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

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

156 raise ex 

157 

158 return self is not other 

159 

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

161 """ 

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

163 

164 :param other: Operand to compare against. 

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

166 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or string. 

167 """ 

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

169 other = ReleaseLevel(other) 

170 

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

172 ex = TypeError(f"Second operand is not supported by < operator.") 

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

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

175 raise ex 

176 

177 return self.value < other.value 

178 

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

180 """ 

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

182 

183 :param other: Operand to compare against. 

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

185 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or string. 

186 """ 

187 if isinstance(other, str): 

188 other = ReleaseLevel(other) 

189 

190 if not isinstance(other, ReleaseLevel): 

191 ex = TypeError(f"Second operand is not supported by <=>= operator.") 

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

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

194 raise ex 

195 

196 return self.value <= other.value 

197 

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

199 """ 

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

201 

202 :param other: Operand to compare against. 

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

204 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or string. 

205 """ 

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

207 other = ReleaseLevel(other) 

208 

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

210 ex = TypeError(f"Second operand is not supported by > operator.") 

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

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

213 raise ex 

214 

215 return self.value > other.value 

216 

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

218 """ 

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

220 

221 :param other: Operand to compare against. 

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

223 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or string. 

224 """ 

225 if isinstance(other, str): 

226 other = ReleaseLevel(other) 

227 

228 if not isinstance(other, ReleaseLevel): 

229 ex = TypeError(f"Second operand is not supported by >= operator.") 

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

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

232 raise ex 

233 

234 return self.value >= other.value 

235 

236 def __hash__(self) -> int: 

237 """ 

238 Compute a hash for this release level, so it can be used as a key in a dictionary or an element of a set. 

239 

240 The hash is derived from the release level's value, so two release levels compare and hash alike. 

241 

242 :returns: Hash of the release level's value. 

243 """ 

244 return hash(self.value) 

245 

246 def __str__(self) -> str: 

247 """ 

248 Returns the release level's string equivalent. 

249 

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

251 :raises ToolingException: If the release level is unknown, so it has no string equivalent. 

252 """ 

253 if self is ReleaseLevel.Final: 

254 return "final" 

255 elif self is ReleaseLevel.ReleaseCandidate: 

256 return "rc" 

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

258 return "dev" 

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

260 return "beta" 

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

262 return "alpha" 

263 

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

265 

266 

267@export 

268class Flags(Flag): 

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

270 NoVCS = 0 #: No Version Control System VCS 

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

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

273 

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

275 SVN = 32 #: Subversion (SVN) 

276 Git = 64 #: Git 

277 Hg = 128 #: Mercurial (Hg) 

278 

279 

280@export 

281def WordSizeValidator( 

282 bits: Nullable[int] = None, 

283 majorBits: Nullable[int] = None, 

284 minorBits: Nullable[int] = None, 

285 microBits: Nullable[int] = None, 

286 buildBits: Nullable[int] = None 

287): 

288 """ 

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

290 

291 :param bits: Optional, number of bits to encode any positive version number part. 

292 :param majorBits: Optional, number of bits to encode a positive major number in a version. 

293 :param minorBits: Optional, number of bits to encode a positive minor number in a version. 

294 :param microBits: Optional, number of bits to encode a positive micro number in a version. 

295 :param buildBits: Optional, number of bits to encode a positive build number in a version. 

296 :returns: A validation function for Version instances. 

297 """ 

298 majorMax = minorMax = microMax = buildMax = -1 

299 if bits is not None: 

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

301 

302 if majorBits is not None: 

303 majorMax = 2**majorBits - 1 

304 if minorBits is not None: 

305 minorMax = 2**minorBits - 1 

306 if microBits is not None: 

307 microMax = 2 ** microBits - 1 

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

309 buildMax = 2**buildBits - 1 

310 

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

312 """ 

313 Validator function, which checks each version part against the maximum its word size allows. 

314 

315 :param version: Optional, the version to validate. 

316 :returns: ``True``, if every part fits into its word size. 

317 :raises ValueError: If a part exceeds the maximum value of its word size. 

318 """ 

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

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

321 

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

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

324 

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

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

327 

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

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

330 

331 return True 

332 

333 return validator 

334 

335 

336@export 

337def MaxValueValidator( 

338 max: Nullable[int] = None, 

339 majorMax: Nullable[int] = None, 

340 minorMax: Nullable[int] = None, 

341 microMax: Nullable[int] = None, 

342 buildMax: Nullable[int] = None 

343): 

344 """ 

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

346 

347 :param max: Optional, the upper bound for any positive version number part. 

348 :param majorMax: Optional, the upper bound for the positive major number. 

349 :param minorMax: Optional, the upper bound for the positive minor number. 

350 :param microMax: Optional, the upper bound for the positive micro number. 

351 :param buildMax: Optional, the upper bound for the positive build number. 

352 :returns: A validation function for Version instances. 

353 """ 

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

355 majorMax = minorMax = microMax = buildMax = max 

356 

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

358 """ 

359 Validator function, which checks each version part against its maximum value. 

360 

361 :param version: Optional, the version to validate. 

362 :returns: ``True``, if every part is within its maximum. 

363 :raises ValueError: If a part exceeds its maximum value. 

364 """ 

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

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

367 

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

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

370 

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

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

373 

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

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

376 

377 return True 

378 

379 return validator 

380 

381 

382@export 

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

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

385 

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

387 

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

389 _prefix: str #: Prefix string 

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

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

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

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

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

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

396 _dev: int #: Development number 

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

398 _postfix: str #: Postfix string 

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

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

401 

402 def __init__( 

403 self, 

404 major: int, 

405 minor: Nullable[int] = None, 

406 micro: Nullable[int] = None, 

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

408 number: Nullable[int] = None, 

409 post: Nullable[int] = None, 

410 dev: Nullable[int] = None, 

411 *, 

412 build: Nullable[int] = None, 

413 postfix: Nullable[str] = None, 

414 prefix: Nullable[str] = None, 

415 hash: Nullable[str] = None, 

416 flags: Flags = Flags.NoVCS 

417 ) -> None: 

418 """ 

419 Initializes a version number representation. 

420 

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

422 :param minor: Optional, minor number part of the version number. 

423 :param micro: Optional, micro (patch) number part of the version number. 

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

425 :param number: Optional, release number part (in combination with release level) of the version number. 

426 :param post: Optional, post number part of the version number. 

427 :param dev: Optional, development number part of the version number. 

428 :param build: Optional, build number part of the version number. 

429 :param postfix: Optional, the version number's postfix. 

430 :param prefix: Optional, the version number's prefix. 

431 :param hash: Optional, postfix string. 

432 :param flags: Optional, the version number's flags. 

433 :raises TypeError: If parameter 'major' is not of type integer. 

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

435 :raises TypeError: If parameter 'minor' is not of type integer. 

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

437 :raises TypeError: If parameter 'micro' is not of type integer. 

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

439 :raises TypeError: If parameter 'build' is not of type integer. 

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

441 :raises TypeError: If parameter 'prefix' is not of type string. 

442 :raises TypeError: If parameter 'postfix' is not of type string. 

443 """ 

444 self.__hash = None 

445 

446 if not isinstance(major, int): 

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

448 elif major < 0: 

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

450 

451 self._parts = Parts.Major 

452 self._major = major 

453 

454 if minor is not None: 

455 if not isinstance(minor, int): 

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

457 elif minor < 0: 

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

459 

460 self._parts |= Parts.Minor 

461 self._minor = minor 

462 else: 

463 self._minor = 0 

464 

465 if micro is not None: 

466 if not isinstance(micro, int): 

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

468 elif micro < 0: 

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

470 

471 self._parts |= Parts.Micro 

472 self._micro = micro 

473 else: 

474 self._micro = 0 

475 

476 if level is None: 

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

478 elif not isinstance(level, ReleaseLevel): 

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

480 elif level is ReleaseLevel.Final: 

481 if number is not None: 

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

483 

484 self._parts |= Parts.Level 

485 self._releaseLevel = level 

486 self._releaseNumber = 0 

487 else: 

488 self._parts |= Parts.Level 

489 self._releaseLevel = level 

490 

491 if number is not None: 

492 if not isinstance(number, int): 

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

494 elif number < 0: 

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

496 

497 self._releaseNumber = number 

498 else: 

499 self._releaseNumber = 0 

500 

501 if dev is not None: 

502 if not isinstance(dev, int): 

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

504 elif dev < 0: 

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

506 

507 self._parts |= Parts.Dev 

508 self._dev = dev 

509 else: 

510 self._dev = 0 

511 

512 if post is not None: 

513 if not isinstance(post, int): 

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

515 elif post < 0: 

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

517 

518 self._parts |= Parts.Post 

519 self._post = post 

520 else: 

521 self._post = 0 

522 

523 if build is not None: 

524 if not isinstance(build, int): 

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

526 elif build < 0: 

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

528 

529 self._build = build 

530 self._parts |= Parts.Build 

531 else: 

532 self._build = 0 

533 

534 if postfix is not None: 

535 if not isinstance(postfix, str): 

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

537 

538 self._parts |= Parts.Postfix 

539 self._postfix = postfix 

540 else: 

541 self._postfix = "" 

542 

543 if prefix is not None: 

544 if not isinstance(prefix, str): 

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

546 

547 self._parts |= Parts.Prefix 

548 self._prefix = prefix 

549 else: 

550 self._prefix = "" 

551 

552 if hash is not None: 

553 if not isinstance(hash, str): 

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

555 

556 self._parts |= Parts.Hash 

557 self._hash = hash 

558 else: 

559 self._hash = "" 

560 

561 if flags is None: 

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

563 elif not isinstance(flags, Flags): 

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

565 

566 self._flags = flags 

567 

568 @classmethod 

569 @abstractmethod 

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

571 """ 

572 Parse a version string and return a Version instance. 

573 

574 :param versionString: The version string to parse. 

575 :param validator: Optional, validator rejecting a parsed version, e.g. by word size or maximum value. 

576 :returns: The parsed version number. 

577 """ 

578 

579 @readonly 

580 def Parts(self) -> Parts: 

581 """ 

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

583 

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

585 """ 

586 return self._parts 

587 

588 @readonly 

589 def Prefix(self) -> str: 

590 """ 

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

592 

593 :returns: The prefix of the version number. 

594 """ 

595 return self._prefix 

596 

597 @readonly 

598 def Major(self) -> int: 

599 """ 

600 Read-only property to access the major number. 

601 

602 :returns: The major number. 

603 """ 

604 return self._major 

605 

606 @readonly 

607 def Minor(self) -> int: 

608 """ 

609 Read-only property to access the minor number. 

610 

611 :returns: The minor number. 

612 """ 

613 return self._minor 

614 

615 @readonly 

616 def Micro(self) -> int: 

617 """ 

618 Read-only property to access the micro number. 

619 

620 :returns: The micro number. 

621 """ 

622 return self._micro 

623 

624 @readonly 

625 def ReleaseLevel(self) -> ReleaseLevel: 

626 """ 

627 Read-only property to access the release level. 

628 

629 :returns: The release level. 

630 """ 

631 return self._releaseLevel 

632 

633 @readonly 

634 def ReleaseNumber(self) -> int: 

635 """ 

636 Read-only property to access the release number. 

637 

638 :returns: The release number. 

639 """ 

640 return self._releaseNumber 

641 

642 @readonly 

643 def Post(self) -> int: 

644 """ 

645 Read-only property to access the post number. 

646 

647 :returns: The post number. 

648 """ 

649 return self._post 

650 

651 @readonly 

652 def Dev(self) -> int: 

653 """ 

654 Read-only property to access the development number. 

655 

656 :returns: The development number. 

657 """ 

658 return self._dev 

659 

660 @readonly 

661 def Build(self) -> int: 

662 """ 

663 Read-only property to access the build number. 

664 

665 :returns: The build number. 

666 """ 

667 return self._build 

668 

669 @readonly 

670 def Postfix(self) -> str: 

671 """ 

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

673 

674 :returns: The postfix of the version number. 

675 """ 

676 return self._postfix 

677 

678 @readonly 

679 def Hash(self) -> str: 

680 """ 

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

682 

683 :returns: The hash. 

684 """ 

685 return self._hash 

686 

687 @readonly 

688 def Flags(self) -> Flags: 

689 """ 

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

691 

692 :returns: The flags of the version number. 

693 """ 

694 return self._flags 

695 

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

697 """ 

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

699 

700 :param left: Left operand. 

701 :param right: Right operand. 

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

703 """ 

704 return ( 

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

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

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

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

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

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

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

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

713 (left._postfix == right._postfix) 

714 ) 

715 

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

717 """ 

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

719 

720 :param left: Left operand. 

721 :param right: Right operand. 

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

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

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

725 """ 

726 if left._major < right._major: 

727 return True 

728 elif left._major > right._major: 

729 return False 

730 

731 if left._minor < right._minor: 

732 return True 

733 elif left._minor > right._minor: 

734 return False 

735 

736 if left._micro < right._micro: 

737 return True 

738 elif left._micro > right._micro: 

739 return False 

740 

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

742 return True 

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

744 return False 

745 

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

747 return True 

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

749 return False 

750 

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

752 return True 

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

754 return False 

755 

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

757 return True 

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

759 return False 

760 

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

762 return True 

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

764 return False 

765 

766 return None 

767 

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

769 """ 

770 Check if a version fulfills a minimum requirement. 

771 

772 How exact the comparison is depends on how detailed the expected version is: a minor number in the expectation 

773 requires an exact major number, and a micro number requires an exact minor number. 

774 

775 :param actual: The version to check. 

776 :param expected: The minimum version, whose parts decide how exact the comparison is. 

777 :returns: ``True``, if the actual version fulfills the expectation. 

778 """ 

779 exactMajor = Parts.Minor in expected._parts 

780 exactMinor = Parts.Micro in expected._parts 

781 

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

783 return False 

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

785 return False 

786 

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

788 return False 

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

790 return False 

791 

792 if Parts.Micro in expected._parts: 

793 return actual._micro >= expected._micro 

794 

795 return True 

796 

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

798 """ 

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

800 

801 .. topic:: Format Specifiers 

802 

803 * ``%p`` - prefix 

804 * ``%M`` - major number 

805 * ``%m`` - minor number 

806 * ``%u`` - micro number 

807 * ``%b`` - build number 

808 

809 :param formatSpec: The format specification. 

810 :returns: Formatted version number. 

811 """ 

812 if formatSpec == "": 

813 return self.__str__() 

814 

815 result = formatSpec 

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

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

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

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

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

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

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

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

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

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

826 

827 return result 

828 

829 @mustoverride 

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

831 """ 

832 Compare two version numbers for equality. 

833 

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

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

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

837 

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

839 number. 

840 

841 :param other: Operand to compare against. 

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

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

844 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, string or integer. 

845 """ 

846 if other is None: 

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

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

849 pass 

850 elif isinstance(other, str): 

851 other = self.__class__.Parse(other) 

852 elif isinstance(other, int): 

853 other = self.__class__(major=other) 

854 else: 

855 ex = TypeError(f"Second operand is not supported by == operator.") 

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

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

858 raise ex 

859 

860 return self._equal(self, other) 

861 

862 @mustoverride 

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

864 """ 

865 Compare two version numbers for inequality. 

866 

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

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

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

870 

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

872 number. 

873 

874 :param other: Operand to compare against. 

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

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

877 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, string or integer. 

878 """ 

879 if other is None: 

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

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

882 pass 

883 elif isinstance(other, str): 

884 other = self.__class__.Parse(other) 

885 elif isinstance(other, int): 

886 other = self.__class__(major=other) 

887 else: 

888 ex = TypeError(f"Second operand is not supported by == operator.") 

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

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

891 raise ex 

892 

893 return not self._equal(self, other) 

894 

895 @mustoverride 

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

897 """ 

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

899 

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

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

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

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

904 

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

906 number. 

907 

908 :param other: Operand to compare against. 

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

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

911 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, 

912 :class:`VersionSet`, string or integer. 

913 """ 

914 if other is None: 

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

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

917 pass 

918 elif isinstance(other, VersionRange): 

919 other = other._lowerBound 

920 elif isinstance(other, VersionSet): 

921 other = other._items[0] 

922 elif isinstance(other, str): 

923 other = self.__class__.Parse(other) 

924 elif isinstance(other, int): 

925 other = self.__class__(major=other) 

926 else: 

927 ex = TypeError(f"Second operand is not supported by < operator.") 

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

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

930 raise ex 

931 

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

933 

934 @mustoverride 

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

936 """ 

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

938 

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

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

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

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

943 

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

945 number. 

946 

947 :param other: Operand to compare against. 

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

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

950 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, 

951 :class:`VersionSet`, string or integer. 

952 """ 

953 equalValue = True 

954 if other is None: 

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

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

957 pass 

958 elif isinstance(other, VersionRange): 

959 equalValue = RangeBoundHandling.LowerBoundExclusive not in other._boundHandling 

960 other = other._lowerBound 

961 elif isinstance(other, VersionSet): 

962 other = other._items[0] 

963 elif isinstance(other, str): 

964 other = self.__class__.Parse(other) 

965 elif isinstance(other, int): 

966 other = self.__class__(major=other) 

967 else: 

968 ex = TypeError(f"Second operand is not supported by <= operator.") 

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

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

971 raise ex 

972 

973 result = self._compare(self, other) 

974 return result if result is not None else equalValue 

975 

976 @mustoverride 

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

978 """ 

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

980 

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

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

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

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

985 

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

987 number. 

988 

989 :param other: Operand to compare against. 

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

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

992 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, 

993 :class:`VersionSet`, string or integer. 

994 """ 

995 if other is None: 

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

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

998 pass 

999 elif isinstance(other, VersionRange): 

1000 other = other._upperBound 

1001 elif isinstance(other, VersionSet): 

1002 other = other._items[-1] 

1003 elif isinstance(other, str): 

1004 other = self.__class__.Parse(other) 

1005 elif isinstance(other, int): 

1006 other = self.__class__(major=other) 

1007 else: 

1008 ex = TypeError(f"Second operand is not supported by > operator.") 

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

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

1011 raise ex 

1012 

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

1014 

1015 @mustoverride 

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

1017 """ 

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

1019 

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

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

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

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

1024 

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

1026 number. 

1027 

1028 :param other: Operand to compare against. 

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

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

1031 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, 

1032 :class:`VersionSet`, string or integer. 

1033 """ 

1034 equalValue = True 

1035 if other is None: 

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

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

1038 pass 

1039 elif isinstance(other, VersionRange): 

1040 equalValue = RangeBoundHandling.UpperBoundExclusive not in other._boundHandling 

1041 other = other._upperBound 

1042 elif isinstance(other, VersionSet): 

1043 other = other._items[-1] 

1044 elif isinstance(other, str): 

1045 other = self.__class__.Parse(other) 

1046 elif isinstance(other, int): 

1047 other = self.__class__(major=other) 

1048 else: 

1049 ex = TypeError(f"Second operand is not supported by >= operator.") 

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

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

1052 raise ex 

1053 

1054 result = self._compare(self, other) 

1055 return not result if result is not None else equalValue 

1056 

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

1058 """ 

1059 Return the minimum of this version and a second operand. 

1060 

1061 :param other: Second operand, a version, a version string or a major version number. 

1062 :returns: ``True``, if this version is the minimum of both operands. 

1063 :raises ValueError: If the second operand is ``None``. 

1064 :raises TypeError: If the second operand is not a version, a string or an integer. 

1065 """ 

1066 if other is None: 

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

1068 elif isinstance(other, self.__class__): 

1069 pass 

1070 elif isinstance(other, str): 

1071 other = self.__class__.Parse(other) 

1072 elif isinstance(other, int): 

1073 other = self.__class__(major=other) 

1074 else: 

1075 ex = TypeError(f"Second operand is not supported by >> operator.") 

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

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

1078 raise ex 

1079 

1080 return self._minimum(self, other) 

1081 

1082 def __hash__(self) -> int: 

1083 """ 

1084 Compute a hash for this version number and cache it. 

1085 

1086 All parts of the version are part of the hash, so two versions differing in a postfix or a build number don't 

1087 collide. 

1088 

1089 :returns: Hash of this version number. 

1090 """ 

1091 if self.__hash is None: 

1092 self.__hash = hash(( 

1093 self._prefix, 

1094 self._major, 

1095 self._minor, 

1096 self._micro, 

1097 self._releaseLevel, 

1098 self._releaseNumber, 

1099 self._post, 

1100 self._dev, 

1101 self._build, 

1102 self._postfix, 

1103 self._hash, 

1104 self._flags 

1105 )) 

1106 return self.__hash 

1107 

1108 

1109@export 

1110class SemanticVersion(Version): 

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

1112 

1113 _PATTERN: ClassVar[Pattern] = re_compile( 

1114 r"^" 

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

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

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

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

1119 r"(?:" 

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

1121 r"|" 

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

1123 r"|" 

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

1125 r")?" 

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

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

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

1129 r"$" 

1130 ) #: Regular expression to parse a semantic version from a string. 

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

1132# ahead: int = 0 

1133 

1134 def __init__( 

1135 self, 

1136 major: int, 

1137 minor: Nullable[int] = None, 

1138 micro: Nullable[int] = None, 

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

1140 number: Nullable[int] = None, 

1141 post: Nullable[int] = None, 

1142 dev: Nullable[int] = None, 

1143 *, 

1144 build: Nullable[int] = None, 

1145 postfix: Nullable[str] = None, 

1146 prefix: Nullable[str] = None, 

1147 hash: Nullable[str] = None, 

1148 flags: Flags = Flags.NoVCS 

1149 ) -> None: 

1150 """ 

1151 Initializes a semantic version number representation. 

1152 

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

1154 :param minor: Optional, minor number part of the version number. 

1155 :param micro: Optional, micro (patch) number part of the version number. 

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

1157 :param number: Optional, number within the release level, e.g. ``2`` in ``rc2``. 

1158 :param post: Optional, post number part of the version number. 

1159 :param dev: Optional, development number part of the version number. 

1160 :param build: Optional, build number part of the version number. 

1161 :param postfix: Optional, the version number's postfix. 

1162 :param prefix: Optional, the version number's prefix. 

1163 :param hash: Optional, hash of the version control system's commit this version was built from. 

1164 :param flags: Optional, the version number's flags. 

1165 :raises TypeError: If parameter 'major' is not of type integer. 

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

1167 :raises TypeError: If parameter 'minor' is not of type integer. 

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

1169 :raises TypeError: If parameter 'micro' is not of type integer. 

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

1171 :raises TypeError: If parameter 'build' is not of type integer. 

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

1173 :raises TypeError: If parameter 'post' is not of type integer. 

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

1175 :raises TypeError: If parameter 'dev' is not of type integer. 

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

1177 :raises TypeError: If parameter 'prefix' is not of type string. 

1178 :raises TypeError: If parameter 'postfix' is not of type string. 

1179 """ 

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

1181 

1182 @classmethod 

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

1184 """ 

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

1186 

1187 Allowed prefix characters: 

1188 

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

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

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

1192 * ``rev|REV`` - revision 

1193 

1194 :param versionString: The version string to parse. 

1195 :param validator: Optional, a validation function. 

1196 :returns: An object representing a semantic version. 

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

1198 :raises ValueError: When parameter ``versionString`` is None or empty. 

1199 :raises ValueError: When parameter ``versionString`` isn't a semantic version number. |br| 

1200 It may carry one of the prefixes ``v``, ``i``, ``r`` or ``rev``, e.g. ``v1.2.3``. 

1201 :raises VersionValidatorException: When the parsed version is rejected by ``validator``. 

1202 """ 

1203 if versionString is None: 

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

1205 elif not isinstance(versionString, str): 

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

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

1208 raise ex 

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

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

1211 

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

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

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

1215 raise ex 

1216 

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

1218 """ 

1219 Nested function converting an optional part of a version string to an integer. 

1220 

1221 :param value: The matched part, or ``None`` if the pattern didn't match it. 

1222 :returns: The part as an integer, or ``None`` if it wasn't present. 

1223 :raises ValueError: If the part isn't a number. 

1224 """ 

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

1226 return None 

1227 

1228 try: 

1229 return int(value) 

1230 except ValueError as ex: # pragma: no cover 

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

1232 

1233 prefix = match["prefix"] 

1234 

1235 release = match["release"] 

1236 if release is not None: 

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

1238 releaseLevel = ReleaseLevel.Development 

1239 elif release == "final": 

1240 releaseLevel = ReleaseLevel.Final 

1241 else: # pragma: no cover 

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

1243 else: 

1244 level = match["level"] 

1245 if level is not None: 

1246 level = level.lower() 

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

1248 releaseLevel = ReleaseLevel.Alpha 

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

1250 releaseLevel = ReleaseLevel.Beta 

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

1252 releaseLevel = ReleaseLevel.Gamma 

1253 elif level == "rc": 

1254 releaseLevel = ReleaseLevel.ReleaseCandidate 

1255 else: # pragma: no cover 

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

1257 else: 

1258 releaseLevel = ReleaseLevel.Final 

1259 

1260 version = cls( 

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

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

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

1264 level=releaseLevel, 

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

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

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

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

1269 postfix=match["postfix"], 

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

1271 # hash=match["hash"], 

1272 flags=Flags.Clean 

1273 ) 

1274 

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

1276 raise VersionValidatorException(f"Failed to validate version string '{versionString}'.", version=version) 

1277 

1278 return version 

1279 

1280 @readonly 

1281 def Patch(self) -> int: 

1282 """ 

1283 Read-only property to access the patch number. 

1284 

1285 The patch number is identical to the micro number. 

1286 

1287 :returns: The patch number. 

1288 """ 

1289 return self._micro 

1290 

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

1292 """ 

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

1294 

1295 :param left: Left operand. 

1296 :param right: Right operand. 

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

1298 """ 

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

1300 

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

1302 """ 

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

1304 

1305 :param left: Left operand. 

1306 :param right: Right operand. 

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

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

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

1310 """ 

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

1312 

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

1314 """ 

1315 Compare two version numbers for equality. 

1316 

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

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

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

1320 

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

1322 number. 

1323 

1324 :param other: Operand to compare against. 

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

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

1327 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, string or integer. 

1328 """ 

1329 return super().__eq__(other) 

1330 

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

1332 """ 

1333 Compare two version numbers for inequality. 

1334 

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

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

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

1338 

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

1340 number. 

1341 

1342 :param other: Operand to compare against. 

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

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

1345 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, string or integer. 

1346 """ 

1347 return super().__ne__(other) 

1348 

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

1350 """ 

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

1352 

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

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

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

1356 

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

1358 number. 

1359 

1360 :param other: Operand to compare against. 

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

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

1363 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, string or integer. 

1364 """ 

1365 return super().__lt__(other) 

1366 

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

1368 """ 

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

1370 

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

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

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

1374 

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

1376 number. 

1377 

1378 :param other: Operand to compare against. 

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

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

1381 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, string or integer. 

1382 """ 

1383 return super().__le__(other) 

1384 

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

1386 """ 

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

1388 

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

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

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

1392 

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

1394 number. 

1395 

1396 :param other: Operand to compare against. 

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

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

1399 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, string or integer. 

1400 """ 

1401 return super().__gt__(other) 

1402 

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

1404 """ 

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

1406 

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

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

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

1410 

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

1412 number. 

1413 

1414 :param other: Operand to compare against. 

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

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

1417 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, string or integer. 

1418 """ 

1419 return super().__ge__(other) 

1420 

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

1422 """ 

1423 Return the minimum of this semantic version and a second operand. 

1424 

1425 :param other: Second operand, a version, a version string or a major version number. 

1426 :returns: ``True``, if this version is the minimum of both operands. 

1427 :raises ValueError: If the second operand is ``None``. 

1428 :raises TypeError: If the second operand is not a version, a string or an integer. 

1429 """ 

1430 return super().__rshift__(other) 

1431 

1432 def __hash__(self) -> int: 

1433 """ 

1434 Compute a hash for this version number. 

1435 

1436 The derived class re-implements :meth:`__eq__`, so Python would otherwise drop the inherited hash and make the 

1437 version unhashable. 

1438 

1439 :returns: Hash of this version number. 

1440 """ 

1441 return super().__hash__() 

1442 

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

1444 """ 

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

1446 

1447 :param formatSpec: The format specification, using ``%``-placeholders for the version's parts. 

1448 :returns: Formatted version number. 

1449 :raises ValueError: If the format specification contains an unknown placeholder. 

1450 """ 

1451 result = self._format(formatSpec) 

1452 

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

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

1455 

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

1457 

1458 def __repr__(self) -> str: 

1459 """ 

1460 Return a normalized string representation of this version number. 

1461 

1462 .. note:: 

1463 

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

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

1466 

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

1468 """ 

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

1470 

1471 def __str__(self) -> str: 

1472 """ 

1473 Return a string representation of this version number. 

1474 

1475 :returns: Version number representation. 

1476 """ 

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

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

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

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

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

1482 if self._releaseLevel is ReleaseLevel.Development: 

1483 result += "-dev" 

1484 elif self._releaseLevel is ReleaseLevel.Alpha: 

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

1486 elif self._releaseLevel is ReleaseLevel.Beta: 

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

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

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

1490 elif self._releaseLevel is ReleaseLevel.ReleaseCandidate: 

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

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

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

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

1495 

1496 return result 

1497 

1498 

1499@export 

1500class PythonVersion(SemanticVersion): 

1501 """ 

1502 Represents a Python version. 

1503 """ 

1504 

1505 @classmethod 

1506 def FromSysVersionInfo(cls) -> PythonVersion: 

1507 """ 

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

1509 

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

1511 :raises ToolingException: If the interpreter reports a release level this class doesn't know. 

1512 """ 

1513 from sys import version_info 

1514 

1515 if version_info.releaselevel == "final": 

1516 rl = ReleaseLevel.Final 

1517 number = None 

1518 else: # pragma: no cover 

1519 number = version_info.serial 

1520 

1521 if version_info.releaselevel == "alpha": 

1522 rl = ReleaseLevel.Alpha 

1523 elif version_info.releaselevel == "beta": 

1524 rl = ReleaseLevel.Beta 

1525 elif version_info.releaselevel == "candidate": 

1526 rl = ReleaseLevel.ReleaseCandidate 

1527 else: # pragma: no cover 

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

1529 

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

1531 

1532 def __hash__(self) -> int: 

1533 """ 

1534 Compute a hash for this version number. 

1535 

1536 The derived class re-implements :meth:`__eq__`, so Python would otherwise drop the inherited hash and make the 

1537 version unhashable. 

1538 

1539 :returns: Hash of this version number. 

1540 """ 

1541 return super().__hash__() 

1542 

1543 def __str__(self) -> str: 

1544 """ 

1545 Return a string representation of this version number. 

1546 

1547 :returns: Version number representation. 

1548 """ 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1564 

1565 return result 

1566 

1567 

1568@export 

1569class CalendarVersion(Version): 

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

1571 

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

1573 

1574 _PATTERN: ClassVar[Pattern] = re_compile( 

1575 r"^" 

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

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

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

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

1580 r"$" 

1581 ) #: Regular expression to parse a calendar version from a string. 

1582 

1583 def __init__( 

1584 self, 

1585 major: int, 

1586 minor: Nullable[int] = None, 

1587 micro: Nullable[int] = None, 

1588 build: Nullable[int] = None, 

1589 flags: Flags = Flags.Clean, 

1590 prefix: Nullable[str] = None, 

1591 postfix: Nullable[str] = None 

1592 ) -> None: 

1593 """ 

1594 Initializes a calendar version number representation. 

1595 

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

1597 :param minor: Optional, minor number part of the version number. 

1598 :param micro: Optional, micro (patch) number part of the version number. 

1599 :param build: Optional, build number part of the version number. 

1600 :param flags: Optional, the version number's flags. 

1601 :param prefix: Optional, the version number's prefix. 

1602 :param postfix: Optional, the version number's postfix. 

1603 :raises TypeError: If parameter 'major' is not of type integer. 

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

1605 :raises TypeError: If parameter 'minor' is not of type integer. 

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

1607 :raises TypeError: If parameter 'micro' is not of type integer. 

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

1609 :raises TypeError: If parameter 'build' is not of type integer. 

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

1611 :raises TypeError: If parameter 'prefix' is not of type string. 

1612 :raises TypeError: If parameter 'postfix' is not of type string. 

1613 """ 

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

1615 

1616 @classmethod 

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

1618 """ 

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

1620 

1621 Allowed prefix characters: 

1622 

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

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

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

1626 * ``rev|REV`` - revision 

1627 

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

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

1630 them. 

1631 

1632 :param versionString: The version string to parse. 

1633 :param validator: Optional, a validation function. 

1634 :returns: An object representing a calendar version. 

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

1636 :raises ValueError: If parameter ``versionString`` is None or empty. 

1637 :raises ValueError: If parameter ``versionString`` isn't a calendar version number. |br| 

1638 It may carry one of the prefixes ``v``, ``i``, ``r`` or ``rev``, e.g. 

1639 ``v2024.04``. 

1640 :raises ValueError: If parameter ``versionString`` has more parts than the class describes. |br| 

1641 Use :class:`CalendarVersion` or :class:`YearMonthDayVersion` to parse a 

1642 three-part calendar version number. 

1643 :raises VersionValidatorException: If the parsed version is rejected by ``validator``. 

1644 """ 

1645 if versionString is None: 

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

1647 elif not isinstance(versionString, str): 

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

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

1650 raise ex 

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

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

1653 

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

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

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

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

1658 raise ex 

1659 

1660 prefix = match["prefix"] 

1661 minor = match["minor"] 

1662 micro = match["micro"] 

1663 

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

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

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

1667 raise ex 

1668 

1669 numbers = [int(match["major"])] 

1670 if minor is not None: 

1671 numbers.append(int(minor)) 

1672 if micro is not None: 

1673 numbers.append(int(micro)) 

1674 

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

1676 

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

1678 raise VersionValidatorException(f"Failed to validate version string '{versionString}'.", version=version) 

1679 

1680 return version 

1681 

1682 @readonly 

1683 def Year(self) -> int: 

1684 """ 

1685 Read-only property to access the year part. 

1686 

1687 :returns: The year part. 

1688 """ 

1689 return self._major 

1690 

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

1692 """ 

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

1694 

1695 :param left: Left parameter. 

1696 :param right: Right parameter. 

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

1698 """ 

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

1700 

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

1702 """ 

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

1704 

1705 :param left: Left parameter. 

1706 :param right: Right parameter. 

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

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

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

1710 """ 

1711 if left._major < right._major: 

1712 return True 

1713 elif left._major > right._major: 

1714 return False 

1715 

1716 if left._minor < right._minor: 

1717 return True 

1718 elif left._minor > right._minor: 

1719 return False 

1720 

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

1722 return True 

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

1724 return False 

1725 

1726 return None 

1727 

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

1729 """ 

1730 Compare two version numbers for equality. 

1731 

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

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

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

1735 

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

1737 number. 

1738 

1739 :param other: Parameter to compare against. 

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

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

1742 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, string or integer. 

1743 """ 

1744 return super().__eq__(other) 

1745 

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

1747 """ 

1748 Compare two version numbers for inequality. 

1749 

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

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

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

1753 

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

1755 number. 

1756 

1757 :param other: Parameter to compare against. 

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

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

1760 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, string or integer. 

1761 """ 

1762 return super().__ne__(other) 

1763 

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

1765 """ 

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

1767 

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

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

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

1771 

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

1773 number. 

1774 

1775 :param other: Parameter to compare against. 

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

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

1778 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, string or integer. 

1779 """ 

1780 return super().__lt__(other) 

1781 

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

1783 """ 

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

1785 

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

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

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

1789 

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

1791 number. 

1792 

1793 :param other: Parameter to compare against. 

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

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

1796 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, string or integer. 

1797 """ 

1798 return super().__le__(other) 

1799 

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

1801 """ 

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

1803 

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

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

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

1807 

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

1809 number. 

1810 

1811 :param other: Parameter to compare against. 

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

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

1814 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, string or integer. 

1815 """ 

1816 return super().__gt__(other) 

1817 

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

1819 """ 

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

1821 

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

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

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

1825 

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

1827 number. 

1828 

1829 :param other: Parameter to compare against. 

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

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

1832 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, string or integer. 

1833 """ 

1834 return super().__ge__(other) 

1835 

1836 def __hash__(self) -> int: 

1837 """ 

1838 Compute a hash for this version number. 

1839 

1840 The derived class re-implements :meth:`__eq__`, so Python would otherwise drop the inherited hash and make the 

1841 version unhashable. 

1842 

1843 :returns: Hash of this version number. 

1844 """ 

1845 return super().__hash__() 

1846 

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

1848 """ 

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

1850 

1851 .. topic:: Format Specifiers 

1852 

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

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

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

1856 

1857 :param formatSpec: The format specification. 

1858 :returns: Formatted version number. 

1859 """ 

1860 if formatSpec == "": 

1861 return self.__str__() 

1862 

1863 result = formatSpec 

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

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

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

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

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

1869 

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

1871 

1872 def __repr__(self) -> str: 

1873 """ 

1874 Return a normalized string representation of this version number. 

1875 

1876 .. note:: 

1877 

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

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

1880 

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

1882 """ 

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

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

1885 

1886 return result 

1887 

1888 def __str__(self) -> str: 

1889 """ 

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

1891 

1892 :returns: Version number representation including a prefix. 

1893 """ 

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

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

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

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

1898 

1899 return result 

1900 

1901 

1902@export 

1903class YearMonthVersion(CalendarVersion): 

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

1905 

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

1907 

1908 def __init__( 

1909 self, 

1910 year: int, 

1911 month: Nullable[int] = None, 

1912 build: Nullable[int] = None, 

1913 flags: Flags = Flags.Clean, 

1914 prefix: Nullable[str] = None, 

1915 postfix: Nullable[str] = None 

1916 ) -> None: 

1917 """ 

1918 Initializes a year-month version number representation. 

1919 

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

1921 :param month: Optional, month part of the version number. 

1922 :param build: Optional, build number part of the version number. 

1923 :param flags: Optional, the version number's flags. 

1924 :param prefix: Optional, the version number's prefix. 

1925 :param postfix: Optional, the version number's postfix. 

1926 :raises TypeError: If parameter 'major' is not of type integer. 

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

1928 :raises TypeError: If parameter 'minor' is not of type integer. 

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

1930 :raises TypeError: If parameter 'micro' is not of type integer. 

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

1932 :raises TypeError: If parameter 'build' is not of type integer. 

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

1934 :raises TypeError: If parameter 'prefix' is not of type string. 

1935 :raises TypeError: If parameter 'postfix' is not of type string. 

1936 """ 

1937 super().__init__(year, month, None, build, flags, prefix, postfix) 

1938 

1939 @readonly 

1940 def Month(self) -> int: 

1941 """ 

1942 Read-only property to access the month part. 

1943 

1944 :returns: The month part. 

1945 """ 

1946 return self._minor 

1947 

1948 def __hash__(self) -> int: 

1949 """ 

1950 Compute a hash for this version number. 

1951 

1952 The derived class re-implements :meth:`__eq__`, so Python would otherwise drop the inherited hash and make the 

1953 version unhashable. 

1954 

1955 :returns: Hash of this version number. 

1956 """ 

1957 return super().__hash__() 

1958 

1959 

1960@export 

1961class YearWeekVersion(CalendarVersion): 

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

1963 

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

1965 

1966 def __init__( 

1967 self, 

1968 year: int, 

1969 week: Nullable[int] = None, 

1970 build: Nullable[int] = None, 

1971 flags: Flags = Flags.Clean, 

1972 prefix: Nullable[str] = None, 

1973 postfix: Nullable[str] = None 

1974 ) -> None: 

1975 """ 

1976 Initializes a year-week version number representation. 

1977 

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

1979 :param week: Optional, week part of the version number. 

1980 :param build: Optional, build number part of the version number. 

1981 :param flags: Optional, the version number's flags. 

1982 :param prefix: Optional, the version number's prefix. 

1983 :param postfix: Optional, the version number's postfix. 

1984 :raises TypeError: If parameter 'major' is not of type integer. 

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

1986 :raises TypeError: If parameter 'minor' is not of type integer. 

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

1988 :raises TypeError: If parameter 'micro' is not of type integer. 

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

1990 :raises TypeError: If parameter 'build' is not of type integer. 

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

1992 :raises TypeError: If parameter 'prefix' is not of type string. 

1993 :raises TypeError: If parameter 'postfix' is not of type string. 

1994 """ 

1995 super().__init__(year, week, None, build, flags, prefix, postfix) 

1996 

1997 @readonly 

1998 def Week(self) -> int: 

1999 """ 

2000 Read-only property to access the week part. 

2001 

2002 :returns: The week part. 

2003 """ 

2004 return self._minor 

2005 

2006 def __hash__(self) -> int: 

2007 """ 

2008 Compute a hash for this version number. 

2009 

2010 The derived class re-implements :meth:`__eq__`, so Python would otherwise drop the inherited hash and make the 

2011 version unhashable. 

2012 

2013 :returns: Hash of this version number. 

2014 """ 

2015 return super().__hash__() 

2016 

2017 

2018@export 

2019class YearReleaseVersion(CalendarVersion): 

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

2021 

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

2023 

2024 def __init__( 

2025 self, 

2026 year: int, 

2027 release: Nullable[int] = None, 

2028 build: Nullable[int] = None, 

2029 flags: Flags = Flags.Clean, 

2030 prefix: Nullable[str] = None, 

2031 postfix: Nullable[str] = None 

2032 ) -> None: 

2033 """ 

2034 Initializes a year-release version number representation. 

2035 

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

2037 :param release: Optional, release number of the version number. 

2038 :param build: Optional, build number part of the version number. 

2039 :param flags: Optional, the version number's flags. 

2040 :param prefix: Optional, the version number's prefix. 

2041 :param postfix: Optional, the version number's postfix. 

2042 :raises TypeError: If parameter 'major' is not of type integer. 

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

2044 :raises TypeError: If parameter 'minor' is not of type integer. 

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

2046 :raises TypeError: If parameter 'micro' is not of type integer. 

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

2048 :raises TypeError: If parameter 'build' is not of type integer. 

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

2050 :raises TypeError: If parameter 'prefix' is not of type string. 

2051 :raises TypeError: If parameter 'postfix' is not of type string. 

2052 """ 

2053 super().__init__(year, release, None, build, flags, prefix, postfix) 

2054 

2055 @readonly 

2056 def Release(self) -> int: 

2057 """ 

2058 Read-only property to access the release number. 

2059 

2060 :returns: The release number. 

2061 """ 

2062 return self._minor 

2063 

2064 def __hash__(self) -> int: 

2065 """ 

2066 Compute a hash for this version number. 

2067 

2068 The derived class re-implements :meth:`__eq__`, so Python would otherwise drop the inherited hash and make the 

2069 version unhashable. 

2070 

2071 :returns: Hash of this version number. 

2072 """ 

2073 return super().__hash__() 

2074 

2075 

2076@export 

2077class YearMonthDayVersion(CalendarVersion): 

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

2079 

2080 def __init__( 

2081 self, 

2082 year: int, 

2083 month: Nullable[int] = None, 

2084 day: Nullable[int] = None, 

2085 build: Nullable[int] = None, 

2086 flags: Flags = Flags.Clean, 

2087 prefix: Nullable[str] = None, 

2088 postfix: Nullable[str] = None 

2089 ) -> None: 

2090 """ 

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

2092 

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

2094 :param month: Optional, month part of the version number. 

2095 :param day: Optional, day part of the version number. 

2096 :param build: Optional, build number part of the version number. 

2097 :param flags: Optional, the version number's flags. 

2098 :param prefix: Optional, the version number's prefix. 

2099 :param postfix: Optional, the version number's postfix. 

2100 :raises TypeError: If parameter 'major' is not of type integer. 

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

2102 :raises TypeError: If parameter 'minor' is not of type integer. 

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

2104 :raises TypeError: If parameter 'micro' is not of type integer. 

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

2106 :raises TypeError: If parameter 'build' is not of type integer. 

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

2108 :raises TypeError: If parameter 'prefix' is not of type string. 

2109 :raises TypeError: If parameter 'postfix' is not of type string. 

2110 """ 

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

2112 

2113 @readonly 

2114 def Month(self) -> int: 

2115 """ 

2116 Read-only property to access the month part. 

2117 

2118 :returns: The month part. 

2119 """ 

2120 return self._minor 

2121 

2122 @readonly 

2123 def Day(self) -> int: 

2124 """ 

2125 Read-only property to access the day part. 

2126 

2127 :returns: The day part. 

2128 """ 

2129 return self._micro 

2130 

2131 def __hash__(self) -> int: 

2132 """ 

2133 Compute a hash for this version number. 

2134 

2135 The derived class re-implements :meth:`__eq__`, so Python would otherwise drop the inherited hash and make the 

2136 version unhashable. 

2137 

2138 :returns: Hash of this version number. 

2139 """ 

2140 return super().__hash__() 

2141 

2142 

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

2144 

2145@export 

2146class RangeBoundHandling(Flag): 

2147 """ 

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

2149 

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

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

2152 """ 

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

2154 LowerBoundInclusive = 0 #: Lower bound is inclusive. 

2155 UpperBoundInclusive = 0 #: Upper bound is inclusive. 

2156 LowerBoundExclusive = 1 #: Lower bound is exclusive. 

2157 UpperBoundExclusive = 2 #: Upper bound is exclusive. 

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

2159 

2160 

2161@export 

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

2163 """ 

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

2165 

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

2167 """ 

2168 _lowerBound: V #: Lower bound of the version range. 

2169 _upperBound: V #: Upper bound of the version range. 

2170 _boundHandling: RangeBoundHandling #: Strategy deciding whether the bounds are part of the range. 

2171 

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

2173 """ 

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

2175 

2176 :param lowerBound: lowest version (inclusive). 

2177 :param upperBound: hightest version (inclusive). 

2178 :param boundHandling: Optional, strategy deciding whether the bounds are part of the range. 

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

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

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

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

2183 """ 

2184 if not isinstance(lowerBound, Version): 

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

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

2187 raise ex 

2188 

2189 if not isinstance(upperBound, Version): 

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

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

2192 raise ex 

2193 

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

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

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

2197 raise ex 

2198 

2199 if not (lowerBound <= upperBound): 

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

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

2202 raise ex 

2203 

2204 self._lowerBound = lowerBound 

2205 self._upperBound = upperBound 

2206 self._boundHandling = boundHandling 

2207 

2208 @property 

2209 def LowerBound(self) -> V: 

2210 """ 

2211 Property to access the range's lower bound. 

2212 

2213 :returns: Lower bound of the version range. 

2214 :raises TypeError: If an assigned value is not of type :class:`Version`. 

2215 """ 

2216 return self._lowerBound 

2217 

2218 @LowerBound.setter 

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

2220 if not isinstance(value, Version): 

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

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

2223 raise ex 

2224 

2225 self._lowerBound = value 

2226 

2227 @property 

2228 def UpperBound(self) -> V: 

2229 """ 

2230 Property to access the range's upper bound. 

2231 

2232 :returns: Upper bound of the version range. 

2233 :raises TypeError: If an assigned value is not of type :class:`Version`. 

2234 """ 

2235 return self._upperBound 

2236 

2237 @UpperBound.setter 

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

2239 if not isinstance(value, Version): 

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

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

2242 raise ex 

2243 

2244 self._upperBound = value 

2245 

2246 @property 

2247 def BoundHandling(self) -> RangeBoundHandling: 

2248 """ 

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

2250 

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

2252 :raises TypeError: If an assigned value is not of type :class:`RangeBoundHandling`. 

2253 """ 

2254 return self._boundHandling 

2255 

2256 @BoundHandling.setter 

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

2258 if not isinstance(value, RangeBoundHandling): 

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

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

2261 raise ex 

2262 

2263 self._boundHandling = value 

2264 

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

2266 """ 

2267 Compute the intersection of two version ranges. 

2268 

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

2270 :returns: Intersected version range. 

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

2272 :raises ValueError: If intersection is empty. 

2273 """ 

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

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

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

2277 raise ex 

2278 

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

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

2281 ex.add_note( 

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

2283 raise ex 

2284 

2285 if other._lowerBound < self._lowerBound: 

2286 lBound = self._lowerBound 

2287 elif other._lowerBound in self: 

2288 lBound = other._lowerBound 

2289 else: 

2290 ex = ValueError("The intersection of both version ranges is empty.") 

2291 ex.add_note(f"Got value '{other._lowerBound}' for other's lower bound.") 

2292 ex.add_note(f"This range's upper bound is '{self._upperBound}'.") 

2293 raise ex 

2294 

2295 if other._upperBound > self._upperBound: 

2296 uBound = self._upperBound 

2297 elif other._upperBound in self: 

2298 uBound = other._upperBound 

2299 else: 

2300 ex = ValueError("The intersection of both version ranges is empty.") 

2301 ex.add_note(f"Got value '{other._upperBound}' for other's upper bound.") 

2302 ex.add_note(f"This range's lower bound is '{self._lowerBound}'.") 

2303 raise ex 

2304 

2305 return self.__class__(lBound, uBound) 

2306 

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

2308 """ 

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

2310 

2311 :param other: Operand to compare against. 

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

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

2314 """ 

2315 # TODO: support VersionRange < VersionRange too 

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

2317 if not isinstance(other, Version): 

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

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

2320 raise ex 

2321 

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

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

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

2325 raise ex 

2326 

2327 return self._upperBound < other 

2328 

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

2330 """ 

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

2332 

2333 :param other: Operand to compare against. 

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

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

2336 """ 

2337 # TODO: support VersionRange < VersionRange too 

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

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

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

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

2342 raise ex 

2343 

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

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

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

2347 raise ex 

2348 

2349 if RangeBoundHandling.UpperBoundExclusive in self._boundHandling: 

2350 return self._upperBound < other 

2351 else: 

2352 return self._upperBound <= other 

2353 

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

2355 """ 

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

2357 

2358 :param other: Operand to compare against. 

2359 :returns: ``True``, if version range is greater 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): 2364 ↛ 2365line 2364 didn't jump to line 2365 because the condition on line 2364 was never true

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 if not (isinstance(other, self._upperBound.__class__) and isinstance(self._upperBound, other.__class__)): 2369 ↛ 2370line 2369 didn't jump to line 2370 because the condition on line 2369 was never true

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

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

2372 raise ex 

2373 

2374 return self._lowerBound > other 

2375 

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

2377 """ 

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

2379 

2380 :param other: Operand to compare against. 

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

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

2383 """ 

2384 # TODO: support VersionRange < VersionRange too 

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

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

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

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

2389 raise ex 

2390 

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

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

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

2394 raise ex 

2395 

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

2397 return self._lowerBound > other 

2398 else: 

2399 return self._lowerBound >= other 

2400 

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

2402 """ 

2403 Check if the version is in the version range. 

2404 

2405 :param version: Optional, version to check. 

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

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

2408 """ 

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

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

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

2412 raise ex 

2413 

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

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

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

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

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

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

2420 else: 

2421 return self._lowerBound < version < self._upperBound 

2422 

2423 

2424@export 

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

2426 """ 

2427 Representation of an ordered set of versions. 

2428 

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

2430 """ 

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

2432 

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

2434 """ 

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

2436 

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

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

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

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

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

2442 """ 

2443 if versions is None: 

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

2445 

2446 if isinstance(versions, Version): 

2447 self._items = [versions] 

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

2449 iterator = iter(versions) 

2450 try: 

2451 firstVersion = next(iterator) 

2452 except StopIteration: 

2453 self._items = [] 

2454 return 

2455 

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

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

2458 

2459 baseType = firstVersion.__class__ 

2460 for version in iterator: 

2461 if not isinstance(version, baseType): 

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

2463 

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

2465 else: 

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

2467 

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

2469 """ 

2470 Compute intersection of two version sets. 

2471 

2472 :param other: Second set of versions. 

2473 :returns: Intersection of two version sets. 

2474 """ 

2475 selfIterator = self.__iter__() 

2476 otherIterator = other.__iter__() 

2477 

2478 result = [] 

2479 try: 

2480 selfValue = next(selfIterator) 

2481 otherValue = next(otherIterator) 

2482 

2483 while True: 

2484 if selfValue < otherValue: 

2485 selfValue = next(selfIterator) 

2486 elif otherValue < selfValue: 

2487 otherValue = next(otherIterator) 

2488 else: 

2489 result.append(selfValue) 

2490 selfValue = next(selfIterator) 

2491 otherValue = next(otherIterator) 

2492 

2493 except StopIteration: 

2494 pass 

2495 

2496 return VersionSet(result) 

2497 

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

2499 """ 

2500 Compute union of two version sets. 

2501 

2502 :param other: Second set of versions. 

2503 :returns: Union of two version sets. 

2504 """ 

2505 selfIterator = self.__iter__() 

2506 otherIterator = other.__iter__() 

2507 

2508 result = [] 

2509 try: 

2510 selfValue = next(selfIterator) 

2511 except StopIteration: 

2512 for otherValue in otherIterator: 

2513 result.append(otherValue) 

2514 

2515 try: 

2516 otherValue = next(otherIterator) 

2517 except StopIteration: 

2518 for selfValue in selfIterator: 

2519 result.append(selfValue) 

2520 

2521 while True: 

2522 if selfValue < otherValue: 

2523 result.append(selfValue) 

2524 try: 

2525 selfValue = next(selfIterator) 

2526 except StopIteration: 

2527 result.append(otherValue) 

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

2529 result.append(otherValue) 

2530 

2531 break 

2532 elif otherValue < selfValue: 

2533 result.append(otherValue) 

2534 try: 

2535 otherValue = next(otherIterator) 

2536 except StopIteration: 

2537 result.append(selfValue) 

2538 for selfValue in selfIterator: 

2539 result.append(selfValue) 

2540 

2541 break 

2542 else: 

2543 result.append(selfValue) 

2544 try: 

2545 selfValue = next(selfIterator) 

2546 except StopIteration: 

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

2548 result.append(otherValue) 

2549 

2550 break 

2551 

2552 try: 

2553 otherValue = next(otherIterator) 

2554 except StopIteration: 

2555 for selfValue in selfIterator: 

2556 result.append(selfValue) 

2557 

2558 break 

2559 

2560 return VersionSet(result) 

2561 

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

2563 """ 

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

2565 

2566 :param other: Operand to compare against. 

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

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

2569 """ 

2570 # TODO: support VersionRange < VersionRange too 

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

2572 if not isinstance(other, Version): 

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

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

2575 raise ex 

2576 

2577 return self._items[-1] < other 

2578 

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

2580 """ 

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

2582 

2583 :param other: Operand to compare against. 

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

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

2586 """ 

2587 # TODO: support VersionRange < VersionRange too 

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

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

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

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

2592 raise ex 

2593 

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

2595 

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

2597 """ 

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

2599 

2600 :param other: Operand to compare against. 

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

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

2603 """ 

2604 # TODO: support VersionRange < VersionRange too 

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

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

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

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

2609 raise ex 

2610 

2611 return self._items[0] > other 

2612 

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

2614 """ 

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

2616 

2617 :param other: Operand to compare against. 

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

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

2620 """ 

2621 # TODO: support VersionRange < VersionRange too 

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

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

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

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

2626 raise ex 

2627 

2628 return self._items[0] >= other 

2629 

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

2631 """ 

2632 Checks if the version a member of the set. 

2633 

2634 :param version: Optional, the version to check. 

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

2636 """ 

2637 return version in self._items 

2638 

2639 def __len__(self) -> int: 

2640 """ 

2641 Returns the number of members in the set. 

2642 

2643 :returns: Number of set members. 

2644 """ 

2645 return len(self._items) 

2646 

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

2648 """ 

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

2650 

2651 :returns: Iterator to iterate versions. 

2652 """ 

2653 return self._items.__iter__() 

2654 

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

2656 """ 

2657 Access to a version of a set by index. 

2658 

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

2660 :returns: The indexed version. 

2661 

2662 .. hint:: 

2663 

2664 Versions are ordered from lowest to highest version number. 

2665 """ 

2666 return self._items[index]