Coverage for pyTooling / Versioning / __init__.py: 83%
972 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-07 17:18 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-07 17:18 +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.
34.. hint::
36 See :ref:`high-level help <VERSIONING>` for explanations and usage examples.
37"""
38from collections.abc import Iterable as abc_Iterable
39from enum import Flag, Enum
40from re import compile as re_compile
41from typing import Optional as Nullable, Union, Callable, Any, Generic, TypeVar, Iterable, Iterator, List
43from pyTooling.Decorators import export, readonly
44from pyTooling.MetaClasses import ExtendedType, abstractmethod, mustoverride
45from pyTooling.Exceptions import ToolingException
46from pyTooling.Common import getFullyQualifiedName
49@export
50class Parts(Flag):
51 """Enumeration describing parts of a version number that can be present."""
52 Unknown = 0 #: Undocumented
53 Major = 1 #: Major number is present. (e.g. X in ``vX.0.0``).
54 Year = 1 #: Year is present. (e.g. X in ``XXXX.10``).
55 Minor = 2 #: Minor number is present. (e.g. Y in ``v0.Y.0``).
56 Month = 2 #: Month is present. (e.g. X in ``2024.YY``).
57 Week = 2 #: Week is present. (e.g. X in ``2024.YY``).
58 Micro = 4 #: Patch number is present. (e.g. Z in ``v0.0.Z``).
59 Patch = 4 #: Patch number is present. (e.g. Z in ``v0.0.Z``).
60 Day = 4 #: Day is present. (e.g. X in ``2024.10.ZZ``).
61 Level = 8 #: Release level is present.
62 Dev = 16 #: Development part is present.
63 Build = 32 #: Build number is present. (e.g. bbbb in ``v0.0.0.bbbb``)
64 Post = 64 #: Post-release number is present.
65 Prefix = 128 #: Prefix is present.
66 Postfix = 256 #: Postfix is present.
67 Hash = 512 #: Hash is present.
68# AHead = 256
71@export
72class ReleaseLevel(Enum):
73 """Enumeration describing the version's maturity level."""
74 Final = 0 #:
75 ReleaseCandidate = -10 #:
76 Development = -20 #:
77 Gamma = -30 #:
78 Beta = -40 #:
79 Alpha = -50 #:
81 def __eq__(self, other: Any) -> bool:
82 """
83 Compare two release levels if the level is equal to the second operand.
85 :param other: Operand to compare against.
86 :returns: ``True``, if release level is equal the second operand's release level.
87 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
88 """
89 if isinstance(other, str): 89 ↛ 90line 89 didn't jump to line 90 because the condition on line 89 was never true
90 other = ReleaseLevel(other)
92 if not isinstance(other, ReleaseLevel): 92 ↛ 93line 92 didn't jump to line 93 because the condition on line 92 was never true
93 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by == operator.")
94 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
95 raise ex
97 return self is other
99 def __ne__(self, other: Any) -> bool:
100 """
101 Compare two release levels if the level is unequal to the second operand.
103 :param other: Operand to compare against.
104 :returns: ``True``, if release level is unequal the second operand's release level.
105 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
106 """
107 if isinstance(other, str):
108 other = ReleaseLevel(other)
110 if not isinstance(other, ReleaseLevel):
111 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by != operator.")
112 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
113 raise ex
115 return self is not other
117 def __lt__(self, other: Any) -> bool:
118 """
119 Compare two release levels if the level is less than the second operand.
121 :param other: Operand to compare against.
122 :returns: ``True``, if release level is less than the second operand.
123 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
124 """
125 if isinstance(other, str): 125 ↛ 126line 125 didn't jump to line 126 because the condition on line 125 was never true
126 other = ReleaseLevel(other)
128 if not isinstance(other, ReleaseLevel): 128 ↛ 129line 128 didn't jump to line 129 because the condition on line 128 was never true
129 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by < operator.")
130 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
131 raise ex
133 return self.value < other.value
135 def __le__(self, other: Any) -> bool:
136 """
137 Compare two release levels if the level is less than or equal the second operand.
139 :param other: Operand to compare against.
140 :returns: ``True``, if release level is less than or equal the second operand.
141 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
142 """
143 if isinstance(other, str):
144 other = ReleaseLevel(other)
146 if not isinstance(other, ReleaseLevel):
147 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by <=>= operator.")
148 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
149 raise ex
151 return self.value <= other.value
153 def __gt__(self, other: Any) -> bool:
154 """
155 Compare two release levels if the level is greater than the second operand.
157 :param other: Operand to compare against.
158 :returns: ``True``, if release level is greater than the second operand.
159 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
160 """
161 if isinstance(other, str): 161 ↛ 162line 161 didn't jump to line 162 because the condition on line 161 was never true
162 other = ReleaseLevel(other)
164 if not isinstance(other, ReleaseLevel): 164 ↛ 165line 164 didn't jump to line 165 because the condition on line 164 was never true
165 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by > operator.")
166 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
167 raise ex
169 return self.value > other.value
171 def __ge__(self, other: Any) -> bool:
172 """
173 Compare two release levels if the level is greater than or equal the second operand.
175 :param other: Operand to compare against.
176 :returns: ``True``, if release level is greater than or equal the second operand.
177 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
178 """
179 if isinstance(other, str):
180 other = ReleaseLevel(other)
182 if not isinstance(other, ReleaseLevel):
183 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by >= operator.")
184 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
185 raise ex
187 return self.value >= other.value
189 def __hash__(self) -> int:
190 return hash(self.value)
192 def __str__(self) -> str:
193 """
194 Returns the release level's string equivalent.
196 :returns: The string equivalent of the release level.
197 """
198 if self is ReleaseLevel.Final:
199 return "final"
200 elif self is ReleaseLevel.ReleaseCandidate:
201 return "rc"
202 elif self is ReleaseLevel.Development: 202 ↛ 203line 202 didn't jump to line 203 because the condition on line 202 was never true
203 return "dev"
204 elif self is ReleaseLevel.Beta: 204 ↛ 205line 204 didn't jump to line 205 because the condition on line 204 was never true
205 return "beta"
206 elif self is ReleaseLevel.Alpha: 206 ↛ 209line 206 didn't jump to line 209 because the condition on line 206 was always true
207 return "alpha"
209 raise ToolingException(f"Unknown ReleaseLevel '{self.name}'.")
212@export
213class Flags(Flag):
214 """State enumeration, if a (tagged) version is build from a clean or dirty working directory."""
215 NoVCS = 0 #: No Version Control System VCS
216 Clean = 1 #: A versioned build was created from a *clean* working directory.
217 Dirty = 2 #: A versioned build was created from a *dirty* working directory.
219 CVS = 16 #: Concurrent Versions System (CVS)
220 SVN = 32 #: Subversion (SVN)
221 Git = 64 #: Git
222 Hg = 128 #: Mercurial (Hg)
225@export
226def WordSizeValidator(
227 bits: Nullable[int] = None,
228 majorBits: Nullable[int] = None,
229 minorBits: Nullable[int] = None,
230 microBits: Nullable[int] = None,
231 buildBits: Nullable[int] = None
232):
233 """
234 A factory function to return a validator for Version instances for a positive integer range based on word-sizes in bits.
236 :param bits: Number of bits to encode any positive version number part.
237 :param majorBits: Number of bits to encode a positive major number in a version.
238 :param minorBits: Number of bits to encode a positive minor number in a version.
239 :param microBits: Number of bits to encode a positive micro number in a version.
240 :param buildBits: Number of bits to encode a positive build number in a version.
241 :return: A validation function for Version instances.
242 """
243 majorMax = minorMax = microMax = buildMax = -1
244 if bits is not None:
245 majorMax = minorMax = microMax = buildMax = 2**bits - 1
247 if majorBits is not None:
248 majorMax = 2**majorBits - 1
249 if minorBits is not None:
250 minorMax = 2**minorBits - 1
251 if microBits is not None:
252 microMax = 2 ** microBits - 1
253 if buildBits is not None: 253 ↛ 254line 253 didn't jump to line 254 because the condition on line 253 was never true
254 buildMax = 2**buildBits - 1
256 def validator(version: SemanticVersion) -> bool:
257 if Parts.Major in version._parts and version._major > majorMax:
258 raise ValueError(f"Field 'Version.Major' > {majorMax}.")
260 if Parts.Minor in version._parts and version._minor > minorMax:
261 raise ValueError(f"Field 'Version.Minor' > {minorMax}.")
263 if Parts.Micro in version._parts and version._micro > microMax:
264 raise ValueError(f"Field 'Version.Micro' > {microMax}.")
266 if Parts.Build in version._parts and version._build > buildMax: 266 ↛ 267line 266 didn't jump to line 267 because the condition on line 266 was never true
267 raise ValueError(f"Field 'Version.Build' > {buildMax}.")
269 return True
271 return validator
274@export
275def MaxValueValidator(
276 max: Nullable[int] = None,
277 majorMax: Nullable[int] = None,
278 minorMax: Nullable[int] = None,
279 microMax: Nullable[int] = None,
280 buildMax: Nullable[int] = None
281):
282 """
283 A factory function to return a validator for Version instances checking for a positive integer range [0..max].
285 :param max: The upper bound for any positive version number part.
286 :param majorMax: The upper bound for the positive major number.
287 :param minorMax: The upper bound for the positive minor number.
288 :param microMax: The upper bound for the positive micro number.
289 :param buildMax: The upper bound for the positive build number.
290 :return: A validation function for Version instances.
291 """
292 if max is not None: 292 ↛ 295line 292 didn't jump to line 295 because the condition on line 292 was always true
293 majorMax = minorMax = microMax = buildMax = max
295 def validator(version: SemanticVersion) -> bool:
296 if Parts.Major in version._parts and version._major > majorMax:
297 raise ValueError(f"Field 'Version.Major' > {majorMax}.")
299 if Parts.Minor in version._parts and version._minor > minorMax:
300 raise ValueError(f"Field 'Version.Minor' > {minorMax}.")
302 if Parts.Micro in version._parts and version._micro > microMax:
303 raise ValueError(f"Field 'Version.Micro' > {microMax}.")
305 if Parts.Build in version._parts and version._build > buildMax: 305 ↛ 306line 305 didn't jump to line 306 because the condition on line 305 was never true
306 raise ValueError(f"Field 'Version.Build' > {buildMax}.")
308 return True
310 return validator
313@export
314class Version(metaclass=ExtendedType, slots=True):
315 """Base-class for a version representation."""
317 __hash: Nullable[int] #: once computed hash of the object
319 _parts: Parts #: Integer flag enumeration of present parts in a version number.
320 _prefix: str #: Prefix string
321 _major: int #: Major number part of the version number.
322 _minor: int #: Minor number part of the version number.
323 _micro: int #: Micro number part of the version number.
324 _releaseLevel: ReleaseLevel #: Release level (alpha, beta, rc, final, ...).
325 _releaseNumber: int #: Release number (Python calls this a serial).
326 _post: int #: Post-release version number part.
327 _dev: int #: Development number
328 _build: int #: Build number part of the version number.
329 _postfix: str #: Postfix string
330 _hash: str #: Hash from version control system.
331 _flags: Flags #: State if the version in a working directory is clean or dirty compared to a tagged version.
333 def __init__(
334 self,
335 major: int,
336 minor: Nullable[int] = None,
337 micro: Nullable[int] = None,
338 level: Nullable[ReleaseLevel] = ReleaseLevel.Final,
339 number: Nullable[int] = None,
340 post: Nullable[int] = None,
341 dev: Nullable[int] = None,
342 *,
343 build: Nullable[int] = None,
344 postfix: Nullable[str] = None,
345 prefix: Nullable[str] = None,
346 hash: Nullable[str] = None,
347 flags: Flags = Flags.NoVCS
348 ) -> None:
349 """
350 Initializes a version number representation.
352 :param major: Major number part of the version number.
353 :param minor: Minor number part of the version number.
354 :param micro: Micro (patch) number part of the version number.
355 :param level: Release level (alpha, beta, release candidate, final, ...) of the version number.
356 :param number: Release number part (in combination with release level) of the version number.
357 :param post: Post number part of the version number.
358 :param dev: Development number part of the version number.
359 :param build: Build number part of the version number.
360 :param postfix: The version number's postfix.
361 :param prefix: The version number's prefix.
362 :param hash: Postfix string.
363 :param flags: The version number's flags.
364 :raises TypeError: If parameter 'major' is not of type int.
365 :raises ValueError: If parameter 'major' is a negative number.
366 :raises TypeError: If parameter 'minor' is not of type int.
367 :raises ValueError: If parameter 'minor' is a negative number.
368 :raises TypeError: If parameter 'micro' is not of type int.
369 :raises ValueError: If parameter 'micro' is a negative number.
370 :raises TypeError: If parameter 'build' is not of type int.
371 :raises ValueError: If parameter 'build' is a negative number.
372 :raises TypeError: If parameter 'prefix' is not of type str.
373 :raises TypeError: If parameter 'postfix' is not of type str.
374 """
375 self.__hash = None
377 if not isinstance(major, int):
378 raise TypeError("Parameter 'major' is not of type 'int'.")
379 elif major < 0:
380 raise ValueError("Parameter 'major' is negative.")
382 self._parts = Parts.Major
383 self._major = major
385 if minor is not None:
386 if not isinstance(minor, int):
387 raise TypeError("Parameter 'minor' is not of type 'int'.")
388 elif minor < 0:
389 raise ValueError("Parameter 'minor' is negative.")
391 self._parts |= Parts.Minor
392 self._minor = minor
393 else:
394 self._minor = 0
396 if micro is not None:
397 if not isinstance(micro, int):
398 raise TypeError("Parameter 'micro' is not of type 'int'.")
399 elif micro < 0:
400 raise ValueError("Parameter 'micro' is negative.")
402 self._parts |= Parts.Micro
403 self._micro = micro
404 else:
405 self._micro = 0
407 if level is None:
408 raise ValueError("Parameter 'level' is None.")
409 elif not isinstance(level, ReleaseLevel):
410 raise TypeError("Parameter 'level' is not of type 'ReleaseLevel'.")
411 elif level is ReleaseLevel.Final:
412 if number is not None:
413 raise ValueError("Parameter 'number' must be None, if parameter 'level' is 'Final'.")
415 self._parts |= Parts.Level
416 self._releaseLevel = level
417 self._releaseNumber = 0
418 else:
419 self._parts |= Parts.Level
420 self._releaseLevel = level
422 if number is not None:
423 if not isinstance(number, int):
424 raise TypeError("Parameter 'number' is not of type 'int'.")
425 elif number < 0:
426 raise ValueError("Parameter 'number' is negative.")
428 self._releaseNumber = number
429 else:
430 self._releaseNumber = 0
432 if dev is not None:
433 if not isinstance(dev, int):
434 raise TypeError("Parameter 'dev' is not of type 'int'.")
435 elif dev < 0:
436 raise ValueError("Parameter 'dev' is negative.")
438 self._parts |= Parts.Dev
439 self._dev = dev
440 else:
441 self._dev = 0
443 if post is not None:
444 if not isinstance(post, int):
445 raise TypeError("Parameter 'post' is not of type 'int'.")
446 elif post < 0:
447 raise ValueError("Parameter 'post' is negative.")
449 self._parts |= Parts.Post
450 self._post = post
451 else:
452 self._post = 0
454 if build is not None:
455 if not isinstance(build, int):
456 raise TypeError("Parameter 'build' is not of type 'int'.")
457 elif build < 0:
458 raise ValueError("Parameter 'build' is negative.")
460 self._build = build
461 self._parts |= Parts.Build
462 else:
463 self._build = 0
465 if postfix is not None:
466 if not isinstance(postfix, str):
467 raise TypeError("Parameter 'postfix' is not of type 'str'.")
469 self._parts |= Parts.Postfix
470 self._postfix = postfix
471 else:
472 self._postfix = ""
474 if prefix is not None:
475 if not isinstance(prefix, str):
476 raise TypeError("Parameter 'prefix' is not of type 'str'.")
478 self._parts |= Parts.Prefix
479 self._prefix = prefix
480 else:
481 self._prefix = ""
483 if hash is not None:
484 if not isinstance(hash, str):
485 raise TypeError("Parameter 'hash' is not of type 'str'.")
487 self._parts |= Parts.Hash
488 self._hash = hash
489 else:
490 self._hash = ""
492 if flags is None:
493 raise ValueError("Parameter 'flags' is None.")
494 elif not isinstance(flags, Flags):
495 raise TypeError("Parameter 'flags' is not of type 'Flags'.")
497 self._flags = flags
499 @classmethod
500 @abstractmethod
501 def Parse(cls, versionString: Nullable[str], validator: Nullable[Callable[["SemanticVersion"], bool]] = None) -> "Version":
502 """Parse a version string and return a Version instance."""
504 @readonly
505 def Parts(self) -> Parts:
506 """
507 Read-only property to access the used parts of this version number.
509 :return: A flag enumeration of used version number parts.
510 """
511 return self._parts
513 @readonly
514 def Prefix(self) -> str:
515 """
516 Read-only property to access the version number's prefix.
518 :return: The prefix of the version number.
519 """
520 return self._prefix
522 @readonly
523 def Major(self) -> int:
524 """
525 Read-only property to access the major number.
527 :return: The major number.
528 """
529 return self._major
531 @readonly
532 def Minor(self) -> int:
533 """
534 Read-only property to access the minor number.
536 :return: The minor number.
537 """
538 return self._minor
540 @readonly
541 def Micro(self) -> int:
542 """
543 Read-only property to access the micro number.
545 :return: The micro number.
546 """
547 return self._micro
549 @readonly
550 def ReleaseLevel(self) -> ReleaseLevel:
551 """
552 Read-only property to access the release level.
554 :return: The release level.
555 """
556 return self._releaseLevel
558 @readonly
559 def ReleaseNumber(self) -> int:
560 """
561 Read-only property to access the release number.
563 :return: The release number.
564 """
565 return self._releaseNumber
567 @readonly
568 def Post(self) -> int:
569 """
570 Read-only property to access the post number.
572 :return: The post number.
573 """
574 return self._post
576 @readonly
577 def Dev(self) -> int:
578 """
579 Read-only property to access the development number.
581 :return: The development number.
582 """
583 return self._dev
585 @readonly
586 def Build(self) -> int:
587 """
588 Read-only property to access the build number.
590 :return: The build number.
591 """
592 return self._build
594 @readonly
595 def Postfix(self) -> str:
596 """
597 Read-only property to access the version number's postfix.
599 :return: The postfix of the version number.
600 """
601 return self._postfix
603 @readonly
604 def Hash(self) -> str:
605 """
606 Read-only property to access the version number's hash.
608 :return: The hash.
609 """
610 return self._hash
612 @readonly
613 def Flags(self) -> Flags:
614 """
615 Read-only property to access the version number's flags.
617 :return: The flags of the version number.
618 """
619 return self._flags
621 def _equal(self, left: "Version", right: "Version") -> Nullable[bool]:
622 """
623 Private helper method to compute the equality of two :class:`Version` instances.
625 :param left: Left operand.
626 :param right: Right operand.
627 :returns: ``True``, if ``left`` is equal to ``right``, otherwise it's ``False``.
628 """
629 return (
630 (left._major == right._major) and
631 (left._minor == right._minor) and
632 (left._micro == right._micro) and
633 (left._releaseLevel == right._releaseLevel) and
634 (left._releaseNumber == right._releaseNumber) and
635 (left._post == right._post) and
636 (left._dev == right._dev) and
637 (left._build == right._build) and
638 (left._postfix == right._postfix)
639 )
641 def _compare(self, left: "Version", right: "Version") -> Nullable[bool]:
642 """
643 Private helper method to compute the comparison of two :class:`Version` instances.
645 :param left: Left operand.
646 :param right: Right operand.
647 :returns: ``True``, if ``left`` is smaller than ``right``. |br|
648 False if ``left`` is greater than ``right``. |br|
649 Otherwise it's None (both operands are equal).
650 """
651 if left._major < right._major:
652 return True
653 elif left._major > right._major:
654 return False
656 if left._minor < right._minor:
657 return True
658 elif left._minor > right._minor:
659 return False
661 if left._micro < right._micro:
662 return True
663 elif left._micro > right._micro:
664 return False
666 if left._releaseLevel < right._releaseLevel: 666 ↛ 667line 666 didn't jump to line 667 because the condition on line 666 was never true
667 return True
668 elif left._releaseLevel > right._releaseLevel: 668 ↛ 669line 668 didn't jump to line 669 because the condition on line 668 was never true
669 return False
671 if left._releaseNumber < right._releaseNumber: 671 ↛ 672line 671 didn't jump to line 672 because the condition on line 671 was never true
672 return True
673 elif left._releaseNumber > right._releaseNumber: 673 ↛ 674line 673 didn't jump to line 674 because the condition on line 673 was never true
674 return False
676 if left._post < right._post: 676 ↛ 677line 676 didn't jump to line 677 because the condition on line 676 was never true
677 return True
678 elif left._post > right._post: 678 ↛ 679line 678 didn't jump to line 679 because the condition on line 678 was never true
679 return False
681 if left._dev < right._dev: 681 ↛ 682line 681 didn't jump to line 682 because the condition on line 681 was never true
682 return True
683 elif left._dev > right._dev: 683 ↛ 684line 683 didn't jump to line 684 because the condition on line 683 was never true
684 return False
686 if left._build < right._build: 686 ↛ 687line 686 didn't jump to line 687 because the condition on line 686 was never true
687 return True
688 elif left._build > right._build: 688 ↛ 689line 688 didn't jump to line 689 because the condition on line 688 was never true
689 return False
691 return None
693 def _minimum(self, actual: "Version", expected: "Version") -> Nullable[bool]:
694 exactMajor = Parts.Minor in expected._parts
695 exactMinor = Parts.Micro in expected._parts
697 if exactMajor and actual._major != expected._major: 697 ↛ 698line 697 didn't jump to line 698 because the condition on line 697 was never true
698 return False
699 elif not exactMajor and actual._major < expected._major:
700 return False
702 if exactMinor and actual._minor != expected._minor: 702 ↛ 703line 702 didn't jump to line 703 because the condition on line 702 was never true
703 return False
704 elif not exactMinor and actual._minor < expected._minor:
705 return False
707 if Parts.Micro in expected._parts:
708 return actual._micro >= expected._micro
710 return True
712 def _format(self, formatSpec: str) -> str:
713 """
714 Return a string representation of this version number according to the format specification.
716 .. topic:: Format Specifiers
718 * ``%p`` - prefix
719 * ``%M`` - major number
720 * ``%m`` - minor number
721 * ``%u`` - micro number
722 * ``%b`` - build number
724 :param formatSpec: The format specification.
725 :return: Formatted version number.
726 """
727 if formatSpec == "":
728 return self.__str__()
730 result = formatSpec
731 result = result.replace("%p", str(self._prefix))
732 result = result.replace("%M", str(self._major))
733 result = result.replace("%m", str(self._minor))
734 result = result.replace("%u", str(self._micro))
735 result = result.replace("%b", str(self._build))
736 result = result.replace("%r", str(self._releaseLevel)[0])
737 result = result.replace("%R", str(self._releaseLevel))
738 result = result.replace("%n", str(self._releaseNumber))
739 result = result.replace("%d", str(self._dev))
740 result = result.replace("%P", str(self._postfix))
742 return result
744 @mustoverride
745 def __eq__(self, other: Union["Version", str, int, None]) -> bool:
746 """
747 Compare two version numbers for equality.
749 The second operand should be an instance of :class:`Version`, but ``str`` and ``int`` are accepted, too. |br|
750 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
751 number is assumed (all other parts are zero).
753 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
754 number.
756 :param other: Operand to compare against.
757 :returns: ``True``, if both version numbers are equal.
758 :raises ValueError: If parameter ``other`` is None.
759 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`str` or :class:`ìnt`.
760 """
761 if other is None:
762 raise ValueError(f"Second operand is None.")
763 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
764 pass
765 elif isinstance(other, str):
766 other = self.__class__.Parse(other)
767 elif isinstance(other, int):
768 other = self.__class__(major=other)
769 else:
770 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by == operator.")
771 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, str, int")
772 raise ex
774 return self._equal(self, other)
776 @mustoverride
777 def __ne__(self, other: Union["Version", str, int, None]) -> bool:
778 """
779 Compare two version numbers for inequality.
781 The second operand should be an instance of :class:`Version`, but ``str`` and ``int`` are accepted, too. |br|
782 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
783 number is assumed (all other parts are zero).
785 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
786 number.
788 :param other: Operand to compare against.
789 :returns: ``True``, if both version numbers are not equal.
790 :raises ValueError: If parameter ``other`` is None.
791 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`str` or :class:`ìnt`.
792 """
793 if other is None:
794 raise ValueError(f"Second operand is None.")
795 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
796 pass
797 elif isinstance(other, str):
798 other = self.__class__.Parse(other)
799 elif isinstance(other, int):
800 other = self.__class__(major=other)
801 else:
802 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by == operator.")
803 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, str, int")
804 raise ex
806 return not self._equal(self, other)
808 @mustoverride
809 def __lt__(self, other: Union["Version", str, int, None]) -> bool:
810 """
811 Compare two version numbers if the version is less than the second operand.
813 The second operand should be an instance of :class:`Version`, but :class:`VersionRange`, :class:`VersionSet`,
814 ``str`` and ``int`` are accepted, too. |br|
815 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
816 number is assumed (all other parts are zero).
818 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
819 number.
821 :param other: Operand to compare against.
822 :returns: ``True``, if version is less than the second operand.
823 :raises ValueError: If parameter ``other`` is None.
824 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`.
825 """
826 if other is None:
827 raise ValueError(f"Second operand is None.")
828 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
829 pass
830 elif isinstance(other, VersionRange):
831 other = other._lowerBound
832 elif isinstance(other, VersionSet):
833 other = other._items[0]
834 elif isinstance(other, str):
835 other = self.__class__.Parse(other)
836 elif isinstance(other, int):
837 other = self.__class__(major=other)
838 else:
839 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by < operator.")
840 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int")
841 raise ex
843 return self._compare(self, other) is True
845 @mustoverride
846 def __le__(self, other: Union["Version", str, int, None]) -> bool:
847 """
848 Compare two version numbers if the version is less than or equal the second operand.
850 The second operand should be an instance of :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, but
851 ``str`` and ``int`` are accepted, too. |br|
852 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
853 number is assumed (all other parts are zero).
855 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
856 number.
858 :param other: Operand to compare against.
859 :returns: ``True``, if version is less than or equal the second operand.
860 :raises ValueError: If parameter ``other`` is None.
861 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`.
862 """
863 equalValue = True
864 if other is None:
865 raise ValueError(f"Second operand is None.")
866 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
867 pass
868 elif isinstance(other, VersionRange):
869 equalValue = RangeBoundHandling.LowerBoundExclusive not in other._boundHandling
870 other = other._lowerBound
871 elif isinstance(other, VersionSet):
872 other = other._items[0]
873 elif isinstance(other, str):
874 other = self.__class__.Parse(other)
875 elif isinstance(other, int):
876 other = self.__class__(major=other)
877 else:
878 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by <= operator.")
879 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int")
880 raise ex
882 result = self._compare(self, other)
883 return result if result is not None else equalValue
885 @mustoverride
886 def __gt__(self, other: Union["Version", str, int, None]) -> bool:
887 """
888 Compare two version numbers if the version is greater than the second operand.
890 The second operand should be an instance of :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, but
891 ``str`` and ``int`` are accepted, too. |br|
892 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
893 number is assumed (all other parts are zero).
895 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
896 number.
898 :param other: Operand to compare against.
899 :returns: ``True``, if version is greater than the second operand.
900 :raises ValueError: If parameter ``other`` is None.
901 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`.
902 """
903 if other is None:
904 raise ValueError(f"Second operand is None.")
905 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
906 pass
907 elif isinstance(other, VersionRange):
908 other = other._upperBound
909 elif isinstance(other, VersionSet):
910 other = other._items[-1]
911 elif isinstance(other, str):
912 other = self.__class__.Parse(other)
913 elif isinstance(other, int):
914 other = self.__class__(major=other)
915 else:
916 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by > operator.")
917 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int")
918 raise ex
920 return self._compare(self, other) is False
922 @mustoverride
923 def __ge__(self, other: Union["Version", str, int, None]) -> bool:
924 """
925 Compare two version numbers if the version is greater than or equal the second operand.
927 The second operand should be an instance of :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, but
928 ``str`` and ``int`` are accepted, too. |br|
929 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
930 number is assumed (all other parts are zero).
932 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
933 number.
935 :param other: Operand to compare against.
936 :returns: ``True``, if version is greater than or equal the second operand.
937 :raises ValueError: If parameter ``other`` is None.
938 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`.
939 """
940 equalValue = True
941 if other is None:
942 raise ValueError(f"Second operand is None.")
943 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
944 pass
945 elif isinstance(other, VersionRange):
946 equalValue = RangeBoundHandling.UpperBoundExclusive not in other._boundHandling
947 other = other._upperBound
948 elif isinstance(other, VersionSet):
949 other = other._items[-1]
950 elif isinstance(other, str):
951 other = self.__class__.Parse(other)
952 elif isinstance(other, int):
953 other = self.__class__(major=other)
954 else:
955 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by >= operator.")
956 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int")
957 raise ex
959 result = self._compare(self, other)
960 return not result if result is not None else equalValue
962 def __rshift__(self, other: Union["Version", str, int, None]) -> bool:
963 if other is None:
964 raise ValueError(f"Second operand is None.")
965 elif isinstance(other, self.__class__):
966 pass
967 elif isinstance(other, str):
968 other = self.__class__.Parse(other)
969 elif isinstance(other, int):
970 other = self.__class__(major=other)
971 else:
972 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by >> operator.")
973 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, str, int")
974 raise ex
976 return self._minimum(self, other)
978 def __hash__(self) -> int:
979 if self.__hash is None:
980 self.__hash = hash((
981 self._prefix,
982 self._major,
983 self._minor,
984 self._micro,
985 self._releaseLevel,
986 self._releaseNumber,
987 self._post,
988 self._dev,
989 self._build,
990 self._postfix,
991 self._hash,
992 self._flags
993 ))
994 return self.__hash
997@export
998class SemanticVersion(Version):
999 """Representation of a semantic version number like ``3.7.12``."""
1001 _PATTERN = re_compile(
1002 r"^"
1003 r"(?P<prefix>[a-zA-Z]*)"
1004 r"(?P<major>\d+)"
1005 r"(?:\.(?P<minor>\d+))?"
1006 r"(?:\.(?P<micro>\d+))?"
1007 r"(?:"
1008 r"(?:\.(?P<build>\d+))"
1009 r"|"
1010 r"(?:[-](?P<release>dev|final))"
1011 r"|"
1012 r"(?:(?P<delim1>[\.\-]?)(?P<level>alpha|beta|gamma|a|b|c|rc|pl)(?P<number>\d+))"
1013 r")?"
1014 r"(?:(?P<delim2>[\.\-]post)(?P<post>\d+))?"
1015 r"(?:(?P<delim3>[\.\-]dev)(?P<dev>\d+))?"
1016 r"(?:(?P<delim4>[\.\-\+])(?P<postfix>\w+))?"
1017 r"$"
1018 )
1019# QUESTION: was this how many commits a version is ahead of the last tagged version?
1020# ahead: int = 0
1022 def __init__(
1023 self,
1024 major: int,
1025 minor: Nullable[int] = None,
1026 micro: Nullable[int] = None,
1027 level: Nullable[ReleaseLevel] = ReleaseLevel.Final,
1028 number: Nullable[int] = None,
1029 post: Nullable[int] = None,
1030 dev: Nullable[int] = None,
1031 *,
1032 build: Nullable[int] = None,
1033 postfix: Nullable[str] = None,
1034 prefix: Nullable[str] = None,
1035 hash: Nullable[str] = None,
1036 flags: Flags = Flags.NoVCS
1037 ) -> None:
1038 """
1039 Initializes a semantic version number representation.
1041 :param major: Major number part of the version number.
1042 :param minor: Minor number part of the version number.
1043 :param micro: Micro (patch) number part of the version number.
1044 :param build: Build number part of the version number.
1045 :param level: tbd
1046 :param number: tbd
1047 :param post: Post number part of the version number.
1048 :param dev: Development number part of the version number.
1049 :param prefix: The version number's prefix.
1050 :param postfix: The version number's postfix.
1051 :param flags: The version number's flags.
1052 :param hash: tbd
1053 :raises TypeError: If parameter 'major' is not of type int.
1054 :raises ValueError: If parameter 'major' is a negative number.
1055 :raises TypeError: If parameter 'minor' is not of type int.
1056 :raises ValueError: If parameter 'minor' is a negative number.
1057 :raises TypeError: If parameter 'micro' is not of type int.
1058 :raises ValueError: If parameter 'micro' is a negative number.
1059 :raises TypeError: If parameter 'build' is not of type int.
1060 :raises ValueError: If parameter 'build' is a negative number.
1061 :raises TypeError: If parameter 'post' is not of type int.
1062 :raises ValueError: If parameter 'post' is a negative number.
1063 :raises TypeError: If parameter 'dev' is not of type int.
1064 :raises ValueError: If parameter 'dev' is a negative number.
1065 :raises TypeError: If parameter 'prefix' is not of type str.
1066 :raises TypeError: If parameter 'postfix' is not of type str.
1067 """
1068 super().__init__(major, minor, micro, level, number, post, dev, build=build, postfix=postfix, prefix=prefix, hash=hash, flags=flags)
1070 @classmethod
1071 def Parse(cls, versionString: Nullable[str], validator: Nullable[Callable[["SemanticVersion"], bool]] = None) -> "SemanticVersion":
1072 """
1073 Parse a version string and return a :class:`SemanticVersion` instance.
1075 Allowed prefix characters:
1077 * ``v|V`` - version, public version, public release
1078 * ``i|I`` - internal version, internal release
1079 * ``r|R`` - release, revision
1080 * ``rev|REV`` - revision
1082 :param versionString: The version string to parse.
1083 :param validator: Optional, a validation function.
1084 :returns: An object representing a semantic version.
1085 :raises TypeError: When parameter ``versionString`` is not a string.
1086 :raises ValueError: When parameter ``versionString`` is None.
1087 :raises ValueError: When parameter ``versionString`` is empty.
1088 """
1089 if versionString is None:
1090 raise ValueError("Parameter 'versionString' is None.")
1091 elif not isinstance(versionString, str):
1092 ex = TypeError(f"Parameter 'versionString' is not of type 'str'.")
1093 ex.add_note(f"Got type '{getFullyQualifiedName(versionString)}'.")
1094 raise ex
1095 elif (versionString := versionString.strip()) == "":
1096 raise ValueError("Parameter 'versionString' is empty.")
1098 if (match := cls._PATTERN.match(versionString)) is None:
1099 raise ValueError(f"Syntax error in parameter 'versionString': '{versionString}'")
1101 def toInt(value: Nullable[str]) -> Nullable[int]:
1102 if value is None or value == "":
1103 return None
1105 try:
1106 return int(value)
1107 except ValueError as ex: # pragma: no cover
1108 raise ValueError(f"Invalid part '{value}' in version number '{versionString}'.") from ex
1110 release = match["release"]
1111 if release is not None:
1112 if release == "dev": 1112 ↛ 1114line 1112 didn't jump to line 1114 because the condition on line 1112 was always true
1113 releaseLevel = ReleaseLevel.Development
1114 elif release == "final":
1115 releaseLevel = ReleaseLevel.Final
1116 else: # pragma: no cover
1117 raise ValueError(f"Unknown release level '{release}' in version number '{versionString}'.")
1118 else:
1119 level = match["level"]
1120 if level is not None:
1121 level = level.lower()
1122 if level == "a" or level == "alpha":
1123 releaseLevel = ReleaseLevel.Alpha
1124 elif level == "b" or level == "beta":
1125 releaseLevel = ReleaseLevel.Beta
1126 elif level == "c" or level == "gamma":
1127 releaseLevel = ReleaseLevel.Gamma
1128 elif level == "rc":
1129 releaseLevel = ReleaseLevel.ReleaseCandidate
1130 else: # pragma: no cover
1131 raise ValueError(f"Unknown release level '{level}' in version number '{versionString}'.")
1132 else:
1133 releaseLevel = ReleaseLevel.Final
1135 version = cls(
1136 major=toInt(match["major"]),
1137 minor=toInt(match["minor"]),
1138 micro=toInt(match["micro"]),
1139 level=releaseLevel,
1140 number=toInt(match["number"]),
1141 post=toInt(match["post"]),
1142 dev=toInt(match["dev"]),
1143 build=toInt(match["build"]),
1144 postfix=match["postfix"],
1145 prefix=match["prefix"],
1146 # hash=match["hash"],
1147 flags=Flags.Clean
1148 )
1150 if validator is not None and not validator(version): 1150 ↛ 1152line 1150 didn't jump to line 1152 because the condition on line 1150 was never true
1151 # TODO: VersionValidatorException
1152 raise ValueError(f"Failed to validate version string '{versionString}'.")
1154 return version
1156 @readonly
1157 def Patch(self) -> int:
1158 """
1159 Read-only property to access the patch number.
1161 The patch number is identical to the micro number.
1163 :return: The patch number.
1164 """
1165 return self._micro
1167 def _equal(self, left: "SemanticVersion", right: "SemanticVersion") -> Nullable[bool]:
1168 """
1169 Private helper method to compute the equality of two :class:`SemanticVersion` instances.
1171 :param left: Left operand.
1172 :param right: Right operand.
1173 :returns: ``True``, if ``left`` is equal to ``right``, otherwise it's ``False``.
1174 """
1175 return super()._equal(left, right)
1177 def _compare(self, left: "SemanticVersion", right: "SemanticVersion") -> Nullable[bool]:
1178 """
1179 Private helper method to compute the comparison of two :class:`SemanticVersion` instances.
1181 :param left: Left operand.
1182 :param right: Right operand.
1183 :returns: ``True``, if ``left`` is smaller than ``right``. |br|
1184 False if ``left`` is greater than ``right``. |br|
1185 Otherwise it's None (both operands are equal).
1186 """
1187 return super()._compare(left, right)
1189 def __eq__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1190 """
1191 Compare two version numbers for equality.
1193 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1194 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1195 number is assumed (all other parts are zero).
1197 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1198 number.
1200 :param other: Operand to compare against.
1201 :returns: ``True``, if both version numbers are equal.
1202 :raises ValueError: If parameter ``other`` is None.
1203 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1204 """
1205 return super().__eq__(other)
1207 def __ne__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1208 """
1209 Compare two version numbers for inequality.
1211 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1212 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1213 number is assumed (all other parts are zero).
1215 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1216 number.
1218 :param other: Operand to compare against.
1219 :returns: ``True``, if both version numbers are not equal.
1220 :raises ValueError: If parameter ``other`` is None.
1221 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1222 """
1223 return super().__ne__(other)
1225 def __lt__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1226 """
1227 Compare two version numbers if the version is less than the second operand.
1229 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1230 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1231 number is assumed (all other parts are zero).
1233 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1234 number.
1236 :param other: Operand to compare against.
1237 :returns: ``True``, if version is less than the second operand.
1238 :raises ValueError: If parameter ``other`` is None.
1239 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1240 """
1241 return super().__lt__(other)
1243 def __le__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1244 """
1245 Compare two version numbers if the version is less than or equal the second operand.
1247 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1248 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1249 number is assumed (all other parts are zero).
1251 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1252 number.
1254 :param other: Operand to compare against.
1255 :returns: ``True``, if version is less than or equal the second operand.
1256 :raises ValueError: If parameter ``other`` is None.
1257 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1258 """
1259 return super().__le__(other)
1261 def __gt__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1262 """
1263 Compare two version numbers if the version is greater than the second operand.
1265 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1266 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1267 number is assumed (all other parts are zero).
1269 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1270 number.
1272 :param other: Operand to compare against.
1273 :returns: ``True``, if version is greater than the second operand.
1274 :raises ValueError: If parameter ``other`` is None.
1275 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1276 """
1277 return super().__gt__(other)
1279 def __ge__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1280 """
1281 Compare two version numbers if the version is greater than or equal the second operand.
1283 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1284 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1285 number is assumed (all other parts are zero).
1287 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1288 number.
1290 :param other: Operand to compare against.
1291 :returns: ``True``, if version is greater than or equal the second operand.
1292 :raises ValueError: If parameter ``other`` is None.
1293 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1294 """
1295 return super().__ge__(other)
1297 def __rshift__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1298 return super().__rshift__(other)
1300 def __hash__(self) -> int:
1301 return super().__hash__()
1303 def __format__(self, formatSpec: str) -> str:
1304 result = self._format(formatSpec)
1306 if (pos := result.find("%")) != -1 and result[pos + 1] != "%": # pragma: no cover
1307 raise ValueError(f"Unknown format specifier '%{result[pos + 1]}' in '{formatSpec}'.")
1309 return result.replace("%%", "%")
1311 def __repr__(self) -> str:
1312 """
1313 Return a string representation of this version number without prefix ``v``.
1315 :returns: Raw version number representation without a prefix.
1316 """
1317 return f"{self._prefix if Parts.Prefix in self._parts else ''}{self._major}.{self._minor}.{self._micro}"
1319 def __str__(self) -> str:
1320 """
1321 Return a string representation of this version number.
1323 :returns: Version number representation.
1324 """
1325 result = self._prefix if Parts.Prefix in self._parts else ""
1326 result += f"{self._major}" # major is always present
1327 result += f".{self._minor}" if Parts.Minor in self._parts else ""
1328 result += f".{self._micro}" if Parts.Micro in self._parts else ""
1329 result += f".{self._build}" if Parts.Build in self._parts else ""
1330 if self._releaseLevel is ReleaseLevel.Development:
1331 result += "-dev"
1332 elif self._releaseLevel is ReleaseLevel.Alpha:
1333 result += f".alpha{self._releaseNumber}"
1334 elif self._releaseLevel is ReleaseLevel.Beta:
1335 result += f".beta{self._releaseNumber}"
1336 elif self._releaseLevel is ReleaseLevel.Gamma: 1336 ↛ 1337line 1336 didn't jump to line 1337 because the condition on line 1336 was never true
1337 result += f".gamma{self._releaseNumber}"
1338 elif self._releaseLevel is ReleaseLevel.ReleaseCandidate:
1339 result += f".rc{self._releaseNumber}"
1340 result += f".post{self._post}" if Parts.Post in self._parts else ""
1341 result += f".dev{self._dev}" if Parts.Dev in self._parts else ""
1342 result += f"+{self._postfix}" if Parts.Postfix in self._parts else ""
1344 return result
1347@export
1348class PythonVersion(SemanticVersion):
1349 """
1350 Represents a Python version.
1351 """
1353 @classmethod
1354 def FromSysVersionInfo(cls) -> "PythonVersion":
1355 """
1356 Create a Python version from :data:`sys.version_info`.
1358 :returns: A PythonVersion instance of the current Python interpreter's version.
1359 """
1360 from sys import version_info
1362 if version_info.releaselevel == "final":
1363 rl = ReleaseLevel.Final
1364 number = None
1365 else: # pragma: no cover
1366 number = version_info.serial
1368 if version_info.releaselevel == "alpha":
1369 rl = ReleaseLevel.Alpha
1370 elif version_info.releaselevel == "beta":
1371 rl = ReleaseLevel.Beta
1372 elif version_info.releaselevel == "candidate":
1373 rl = ReleaseLevel.ReleaseCandidate
1374 else: # pragma: no cover
1375 raise ToolingException(f"Unsupported release level '{version_info.releaselevel}'.")
1377 return cls(version_info.major, version_info.minor, version_info.micro, level=rl, number=number)
1379 def __hash__(self) -> int:
1380 return super().__hash__()
1382 def __str__(self) -> str:
1383 """
1384 Return a string representation of this version number.
1386 :returns: Version number representation.
1387 """
1388 result = self._prefix if Parts.Prefix in self._parts else ""
1389 result += f"{self._major}" # major is always present
1390 result += f".{self._minor}" if Parts.Minor in self._parts else ""
1391 result += f".{self._micro}" if Parts.Micro in self._parts else ""
1392 if self._releaseLevel is ReleaseLevel.Alpha: 1392 ↛ 1393line 1392 didn't jump to line 1393 because the condition on line 1392 was never true
1393 result += f"a{self._releaseNumber}"
1394 elif self._releaseLevel is ReleaseLevel.Beta: 1394 ↛ 1395line 1394 didn't jump to line 1395 because the condition on line 1394 was never true
1395 result += f"b{self._releaseNumber}"
1396 elif self._releaseLevel is ReleaseLevel.Gamma: 1396 ↛ 1397line 1396 didn't jump to line 1397 because the condition on line 1396 was never true
1397 result += f"c{self._releaseNumber}"
1398 elif self._releaseLevel is ReleaseLevel.ReleaseCandidate: 1398 ↛ 1399line 1398 didn't jump to line 1399 because the condition on line 1398 was never true
1399 result += f"rc{self._releaseNumber}"
1400 result += f".post{self._post}" if Parts.Post in self._parts else ""
1401 result += f".dev{self._dev}" if Parts.Dev in self._parts else ""
1402 result += f"+{self._postfix}" if Parts.Postfix in self._parts else ""
1404 return result
1407@export
1408class CalendarVersion(Version):
1409 """Representation of a calendar version number like ``2021.10``."""
1411 def __init__(
1412 self,
1413 major: int,
1414 minor: Nullable[int] = None,
1415 micro: Nullable[int] = None,
1416 build: Nullable[int] = None,
1417 flags: Flags = Flags.Clean,
1418 prefix: Nullable[str] = None,
1419 postfix: Nullable[str] = None
1420 ) -> None:
1421 """
1422 Initializes a calendar version number representation.
1424 :param major: Major number part of the version number.
1425 :param minor: Minor number part of the version number.
1426 :param micro: Micro (patch) number part of the version number.
1427 :param build: Build number part of the version number.
1428 :param flags: The version number's flags.
1429 :param prefix: The version number's prefix.
1430 :param postfix: The version number's postfix.
1431 :raises TypeError: If parameter 'major' is not of type int.
1432 :raises ValueError: If parameter 'major' is a negative number.
1433 :raises TypeError: If parameter 'minor' is not of type int.
1434 :raises ValueError: If parameter 'minor' is a negative number.
1435 :raises TypeError: If parameter 'micro' is not of type int.
1436 :raises ValueError: If parameter 'micro' is a negative number.
1437 :raises TypeError: If parameter 'build' is not of type int.
1438 :raises ValueError: If parameter 'build' is a negative number.
1439 :raises TypeError: If parameter 'prefix' is not of type str.
1440 :raises TypeError: If parameter 'postfix' is not of type str.
1441 """
1442 super().__init__(major, minor, micro, build=build, postfix=postfix, prefix=prefix, flags=flags)
1444 @classmethod
1445 def Parse(cls, versionString: Nullable[str], validator: Nullable[Callable[["CalendarVersion"], bool]] = None) -> "CalendarVersion":
1446 """
1447 Parse a version string and return a :class:`CalendarVersion` instance.
1449 :param versionString: The version string to parse.
1450 :returns: An object representing a calendar version.
1451 :raises TypeError: If parameter ``other`` is not a string.
1452 :raises ValueError: If parameter ``other`` is None.
1453 :raises ValueError: If parameter ``other`` is empty.
1454 """
1455 parts = Parts.Unknown
1457 if versionString is None:
1458 raise ValueError("Parameter 'versionString' is None.")
1459 elif not isinstance(versionString, str):
1460 ex = TypeError(f"Parameter 'versionString' is not of type 'str'.")
1461 ex.add_note(f"Got type '{getFullyQualifiedName(versionString)}'.")
1462 raise ex
1463 elif versionString == "":
1464 raise ValueError("Parameter 'versionString' is empty.")
1466 split = versionString.split(".")
1467 length = len(split)
1468 major = int(split[0])
1469 minor = 0
1470 parts |= Parts.Major
1472 if length >= 2:
1473 minor = int(split[1])
1474 parts |= Parts.Minor
1476 flags = Flags.Clean
1478 version = cls(major, minor, flags=flags)
1479 if validator is not None and not validator(version):
1480 raise ValueError(f"Failed to validate version string '{versionString}'.") # pragma: no cover
1482 return version
1484 @property
1485 def Year(self) -> int:
1486 """
1487 Read-only property to access the year part.
1489 :return: The year part.
1490 """
1491 return self._major
1493 def _equal(self, left: "CalendarVersion", right: "CalendarVersion") -> Nullable[bool]:
1494 """
1495 Private helper method to compute the equality of two :class:`CalendarVersion` instances.
1497 :param left: Left parameter.
1498 :param right: Right parameter.
1499 :returns: ``True``, if ``left`` is equal to ``right``, otherwise it's ``False``.
1500 """
1501 return (left._major == right._major) and (left._minor == right._minor) and (left._micro == right._micro)
1503 def _compare(self, left: "CalendarVersion", right: "CalendarVersion") -> Nullable[bool]:
1504 """
1505 Private helper method to compute the comparison of two :class:`CalendarVersion` instances.
1507 :param left: Left parameter.
1508 :param right: Right parameter.
1509 :returns: ``True``, if ``left`` is smaller than ``right``. |br|
1510 False if ``left`` is greater than ``right``. |br|
1511 Otherwise it's None (both parameters are equal).
1512 """
1513 if left._major < right._major:
1514 return True
1515 elif left._major > right._major:
1516 return False
1518 if left._minor < right._minor:
1519 return True
1520 elif left._minor > right._minor:
1521 return False
1523 if left._micro < right._micro: 1523 ↛ 1524line 1523 didn't jump to line 1524 because the condition on line 1523 was never true
1524 return True
1525 elif left._micro > right._micro: 1525 ↛ 1526line 1525 didn't jump to line 1526 because the condition on line 1525 was never true
1526 return False
1528 return None
1530 def __eq__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1531 """
1532 Compare two version numbers for equality.
1534 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1535 In case of ``str``, it's tried to parse the string as a calendar version number. In case of ``int``, a single major
1536 number is assumed (all other parts are zero).
1538 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1539 number.
1541 :param other: Parameter to compare against.
1542 :returns: ``True``, if both version numbers are equal.
1543 :raises ValueError: If parameter ``other`` is None.
1544 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1545 """
1546 return super().__eq__(other)
1548 def __ne__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1549 """
1550 Compare two version numbers for inequality.
1552 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1553 In case of ``str``, it's tried to parse the string as a calendar version number. In case of ``int``, a single major
1554 number is assumed (all other parts are zero).
1556 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1557 number.
1559 :param other: Parameter to compare against.
1560 :returns: ``True``, if both version numbers are not equal.
1561 :raises ValueError: If parameter ``other`` is None.
1562 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1563 """
1564 return super().__ne__(other)
1566 def __lt__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1567 """
1568 Compare two version numbers if the version is less than the second operand.
1570 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1571 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1572 number is assumed (all other parts are zero).
1574 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1575 number.
1577 :param other: Parameter to compare against.
1578 :returns: ``True``, if version is less than the second operand.
1579 :raises ValueError: If parameter ``other`` is None.
1580 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1581 """
1582 return super().__lt__(other)
1584 def __le__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1585 """
1586 Compare two version numbers if the version is less than or equal the second operand.
1588 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1589 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1590 number is assumed (all other parts are zero).
1592 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1593 number.
1595 :param other: Parameter to compare against.
1596 :returns: ``True``, if version is less than or equal the second operand.
1597 :raises ValueError: If parameter ``other`` is None.
1598 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1599 """
1600 return super().__le__(other)
1602 def __gt__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1603 """
1604 Compare two version numbers if the version is greater than the second operand.
1606 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1607 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1608 number is assumed (all other parts are zero).
1610 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1611 number.
1613 :param other: Parameter to compare against.
1614 :returns: ``True``, if version is greater than the second operand.
1615 :raises ValueError: If parameter ``other`` is None.
1616 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1617 """
1618 return super().__gt__(other)
1620 def __ge__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1621 """
1622 Compare two version numbers if the version is greater than or equal the second operand.
1624 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1625 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1626 number is assumed (all other parts are zero).
1628 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1629 number.
1631 :param other: Parameter to compare against.
1632 :returns: ``True``, if version is greater than or equal the second operand.
1633 :raises ValueError: If parameter ``other`` is None.
1634 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1635 """
1636 return super().__ge__(other)
1638 def __hash__(self) -> int:
1639 return super().__hash__()
1641 def __format__(self, formatSpec: str) -> str:
1642 """
1643 Return a string representation of this version number according to the format specification.
1645 .. topic:: Format Specifiers
1647 * ``%M`` - major number (year)
1648 * ``%m`` - minor number (month/week)
1650 :param formatSpec: The format specification.
1651 :return: Formatted version number.
1652 """
1653 if formatSpec == "":
1654 return self.__str__()
1656 result = formatSpec
1657 # result = result.replace("%P", str(self._prefix))
1658 result = result.replace("%M", str(self._major))
1659 result = result.replace("%m", str(self._minor))
1660 # result = result.replace("%p", str(self._pre))
1662 return result.replace("%%", "%")
1664 def __repr__(self) -> str:
1665 """
1666 Return a string representation of this version number without prefix ``v``.
1668 :returns: Raw version number representation without a prefix.
1669 """
1670 return f"{self._major}.{self._minor}"
1672 def __str__(self) -> str:
1673 """
1674 Return a string representation of this version number with prefix ``v``.
1676 :returns: Version number representation including a prefix.
1677 """
1678 result = f"{self._major}"
1679 result += f".{self._minor}" if Parts.Minor in self._parts else ""
1681 return result
1684@export
1685class YearMonthVersion(CalendarVersion):
1686 """Representation of a calendar version number made of year and month like ``2021.10``."""
1688 def __init__(
1689 self,
1690 year: int,
1691 month: Nullable[int] = None,
1692 build: Nullable[int] = None,
1693 flags: Flags = Flags.Clean,
1694 prefix: Nullable[str] = None,
1695 postfix: Nullable[str] = None
1696 ) -> None:
1697 """
1698 Initializes a year-month version number representation.
1700 :param year: Year part of the version number.
1701 :param month: Month part of the version number.
1702 :param build: Build number part of the version number.
1703 :param flags: The version number's flags.
1704 :param prefix: The version number's prefix.
1705 :param postfix: The version number's postfix.
1706 :raises TypeError: If parameter 'major' is not of type int.
1707 :raises ValueError: If parameter 'major' is a negative number.
1708 :raises TypeError: If parameter 'minor' is not of type int.
1709 :raises ValueError: If parameter 'minor' is a negative number.
1710 :raises TypeError: If parameter 'micro' is not of type int.
1711 :raises ValueError: If parameter 'micro' is a negative number.
1712 :raises TypeError: If parameter 'build' is not of type int.
1713 :raises ValueError: If parameter 'build' is a negative number.
1714 :raises TypeError: If parameter 'prefix' is not of type str.
1715 :raises TypeError: If parameter 'postfix' is not of type str.
1716 """
1717 super().__init__(year, month, 0, build, flags, prefix, postfix)
1719 @property
1720 def Month(self) -> int:
1721 """
1722 Read-only property to access the month part.
1724 :return: The month part.
1725 """
1726 return self._minor
1728 def __hash__(self) -> int:
1729 return super().__hash__()
1732@export
1733class YearWeekVersion(CalendarVersion):
1734 """Representation of a calendar version number made of year and week like ``2021.47``."""
1736 def __init__(
1737 self,
1738 year: int,
1739 week: Nullable[int] = None,
1740 build: Nullable[int] = None,
1741 flags: Flags = Flags.Clean,
1742 prefix: Nullable[str] = None,
1743 postfix: Nullable[str] = None
1744 ) -> None:
1745 """
1746 Initializes a year-week version number representation.
1748 :param year: Year part of the version number.
1749 :param week: Week part of the version number.
1750 :param build: Build number part of the version number.
1751 :param flags: The version number's flags.
1752 :param prefix: The version number's prefix.
1753 :param postfix: The version number's postfix.
1754 :raises TypeError: If parameter 'major' is not of type int.
1755 :raises ValueError: If parameter 'major' is a negative number.
1756 :raises TypeError: If parameter 'minor' is not of type int.
1757 :raises ValueError: If parameter 'minor' is a negative number.
1758 :raises TypeError: If parameter 'micro' is not of type int.
1759 :raises ValueError: If parameter 'micro' is a negative number.
1760 :raises TypeError: If parameter 'build' is not of type int.
1761 :raises ValueError: If parameter 'build' is a negative number.
1762 :raises TypeError: If parameter 'prefix' is not of type str.
1763 :raises TypeError: If parameter 'postfix' is not of type str.
1764 """
1765 super().__init__(year, week, 0, build, flags, prefix, postfix)
1767 @property
1768 def Week(self) -> int:
1769 """
1770 Read-only property to access the week part.
1772 :return: The week part.
1773 """
1774 return self._minor
1776 def __hash__(self) -> int:
1777 return super().__hash__()
1780@export
1781class YearReleaseVersion(CalendarVersion):
1782 """Representation of a calendar version number made of year and release per year like ``2021.2``."""
1784 def __init__(
1785 self,
1786 year: int,
1787 release: Nullable[int] = None,
1788 build: Nullable[int] = None,
1789 flags: Flags = Flags.Clean,
1790 prefix: Nullable[str] = None,
1791 postfix: Nullable[str] = None
1792 ) -> None:
1793 """
1794 Initializes a year-release version number representation.
1796 :param year: Year part of the version number.
1797 :param release: Release number of the version number.
1798 :param build: Build number part of the version number.
1799 :param flags: The version number's flags.
1800 :param prefix: The version number's prefix.
1801 :param postfix: The version number's postfix.
1802 :raises TypeError: If parameter 'major' is not of type int.
1803 :raises ValueError: If parameter 'major' is a negative number.
1804 :raises TypeError: If parameter 'minor' is not of type int.
1805 :raises ValueError: If parameter 'minor' is a negative number.
1806 :raises TypeError: If parameter 'micro' is not of type int.
1807 :raises ValueError: If parameter 'micro' is a negative number.
1808 :raises TypeError: If parameter 'build' is not of type int.
1809 :raises ValueError: If parameter 'build' is a negative number.
1810 :raises TypeError: If parameter 'prefix' is not of type str.
1811 :raises TypeError: If parameter 'postfix' is not of type str.
1812 """
1813 super().__init__(year, release, 0, build, flags, prefix, postfix)
1815 @property
1816 def Release(self) -> int:
1817 """
1818 Read-only property to access the release number.
1820 :return: The release number.
1821 """
1822 return self._minor
1824 def __hash__(self) -> int:
1825 return super().__hash__()
1828@export
1829class YearMonthDayVersion(CalendarVersion):
1830 """Representation of a calendar version number made of year, month and day like ``2021.10.15``."""
1832 def __init__(
1833 self,
1834 year: int,
1835 month: Nullable[int] = None,
1836 day: Nullable[int] = None,
1837 build: Nullable[int] = None,
1838 flags: Flags = Flags.Clean,
1839 prefix: Nullable[str] = None,
1840 postfix: Nullable[str] = None
1841 ) -> None:
1842 """
1843 Initializes a year-month-day version number representation.
1845 :param year: Year part of the version number.
1846 :param month: Month part of the version number.
1847 :param day: Day part of the version number.
1848 :param build: Build number part of the version number.
1849 :param flags: The version number's flags.
1850 :param prefix: The version number's prefix.
1851 :param postfix: The version number's postfix.
1852 :raises TypeError: If parameter 'major' is not of type int.
1853 :raises ValueError: If parameter 'major' is a negative number.
1854 :raises TypeError: If parameter 'minor' is not of type int.
1855 :raises ValueError: If parameter 'minor' is a negative number.
1856 :raises TypeError: If parameter 'micro' is not of type int.
1857 :raises ValueError: If parameter 'micro' is a negative number.
1858 :raises TypeError: If parameter 'build' is not of type int.
1859 :raises ValueError: If parameter 'build' is a negative number.
1860 :raises TypeError: If parameter 'prefix' is not of type str.
1861 :raises TypeError: If parameter 'postfix' is not of type str.
1862 """
1863 super().__init__(year, month, day, build, flags, prefix, postfix)
1865 @property
1866 def Month(self) -> int:
1867 """
1868 Read-only property to access the month part.
1870 :return: The month part.
1871 """
1872 return self._minor
1874 @property
1875 def Day(self) -> int:
1876 """
1877 Read-only property to access the day part.
1879 :return: The day part.
1880 """
1881 return self._micro
1883 def __hash__(self) -> int:
1884 return super().__hash__()
1887V = TypeVar("V", bound=Version)
1889@export
1890class RangeBoundHandling(Flag):
1891 """
1892 A flag defining how to handle bounds in a range.
1894 If a bound is inclusive, the bound's value is within the range. If a bound is exclusive, the bound's value is the
1895 first value outside the range. Inclusive and exclusive behavior can be mixed for lower and upper bounds.
1896 """
1897 BothBoundsInclusive = 0 #: Lower and upper bound are inclusive.
1898 LowerBoundInclusive = 0 #: Lower bound is inclusive.
1899 UpperBoundInclusive = 0 #: Upper bound is inclusive.
1900 LowerBoundExclusive = 1 #: Lower bound is exclusive.
1901 UpperBoundExclusive = 2 #: Upper bound is exclusive.
1902 BothBoundsExclusive = 3 #: Lower and upper bound are exclusive.
1905@export
1906class VersionRange(Generic[V], metaclass=ExtendedType, slots=True):
1907 """
1908 Representation of a version range described by a lower bound and upper bound version.
1910 This version range works with :class:`SemanticVersion` and :class:`CalendarVersion` and its derived classes.
1911 """
1912 _lowerBound: V
1913 _upperBound: V
1914 _boundHandling: RangeBoundHandling
1916 def __init__(self, lowerBound: V, upperBound: V, boundHandling: RangeBoundHandling = RangeBoundHandling.BothBoundsInclusive) -> None:
1917 """
1918 Initializes a version range described by a lower and upper bound.
1920 :param lowerBound: lowest version (inclusive).
1921 :param upperBound: hightest version (inclusive).
1922 :raises TypeError: If parameter ``lowerBound`` is not of type :class:`Version`.
1923 :raises TypeError: If parameter ``upperBound`` is not of type :class:`Version`.
1924 :raises TypeError: If parameter ``lowerBound`` and ``upperBound`` are unrelated types.
1925 :raises ValueError: If parameter ``lowerBound`` isn't less than or equal to ``upperBound``.
1926 """
1927 if not isinstance(lowerBound, Version):
1928 ex = TypeError(f"Parameter 'lowerBound' is not of type 'Version'.")
1929 ex.add_note(f"Got type '{getFullyQualifiedName(lowerBound)}'.")
1930 raise ex
1932 if not isinstance(upperBound, Version):
1933 ex = TypeError(f"Parameter 'upperBound' is not of type 'Version'.")
1934 ex.add_note(f"Got type '{getFullyQualifiedName(upperBound)}'.")
1935 raise ex
1937 if not ((lBC := lowerBound.__class__) is (uBC := upperBound.__class__) or issubclass(lBC, uBC) or issubclass(uBC, lBC)):
1938 ex = TypeError(f"Parameters 'lowerBound' and 'upperBound' are not compatible with each other.")
1939 ex.add_note(f"Got type '{getFullyQualifiedName(lowerBound)}' for lowerBound and type '{getFullyQualifiedName(upperBound)}' for upperBound.")
1940 raise ex
1942 if not (lowerBound <= upperBound):
1943 ex = ValueError(f"Parameter 'lowerBound' isn't less than parameter 'upperBound'.")
1944 ex.add_note(f"Got '{lowerBound}' for lowerBound and '{upperBound}' for upperBound.")
1945 raise ex
1947 self._lowerBound = lowerBound
1948 self._upperBound = upperBound
1949 self._boundHandling = boundHandling
1951 @property
1952 def LowerBound(self) -> V:
1953 """
1954 Property to access the range's lower bound.
1956 :return: Lower bound of the version range.
1957 """
1958 return self._lowerBound
1960 @LowerBound.setter
1961 def LowerBound(self, value: V) -> None:
1962 if not isinstance(value, Version):
1963 ex = TypeError(f"Parameter 'value' is not of type 'Version'.")
1964 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
1965 raise ex
1967 self._lowerBound = value
1969 @readonly
1970 def UpperBound(self) -> V:
1971 """
1972 Property to access the range's upper bound.
1974 :return: Upper bound of the version range.
1975 """
1976 return self._upperBound
1978 @UpperBound.setter
1979 def UpperBound(self, value: V) -> None:
1980 if not isinstance(value, Version):
1981 ex = TypeError(f"Parameter 'value' is not of type 'Version'.")
1982 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
1983 raise ex
1985 self._upperBound = value
1987 @readonly
1988 def BoundHandling(self) -> RangeBoundHandling:
1989 """
1990 Property to access the range's bound handling strategy.
1992 :return: The range's bound handling strategy.
1993 """
1994 return self._boundHandling
1996 @BoundHandling.setter
1997 def BoundHandling(self, value: RangeBoundHandling) -> None:
1998 if not isinstance(value, RangeBoundHandling):
1999 ex = TypeError(f"Parameter 'value' is not of type 'RangeBoundHandling'.")
2000 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
2001 raise ex
2003 self._boundHandling = value
2005 def __and__(self, other: Any) -> "VersionRange[T]":
2006 """
2007 Compute the intersection of two version ranges.
2009 :param other: Second version range to intersect with.
2010 :returns: Intersected version range.
2011 :raises TypeError: If parameter 'other' is not of type :class:`VersionRange`.
2012 :raises ValueError: If intersection is empty.
2013 """
2014 if not isinstance(other, VersionRange): 2014 ↛ 2015line 2014 didn't jump to line 2015 because the condition on line 2014 was never true
2015 ex = TypeError(f"Parameter 'other' is not of type 'VersionRange'.")
2016 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2017 raise ex
2019 if not (isinstance(other._lowerBound, self._lowerBound.__class__) and isinstance(self._lowerBound, other._lowerBound.__class__)): 2019 ↛ 2020line 2019 didn't jump to line 2020 because the condition on line 2019 was never true
2020 ex = TypeError(f"Parameter 'other's LowerBound and this range's 'LowerBound' are not compatible with each other.")
2021 ex.add_note(
2022 f"Got type '{getFullyQualifiedName(other._lowerBound)}' for other.LowerBound and type '{getFullyQualifiedName(self._lowerBound)}' for self.LowerBound.")
2023 raise ex
2025 if other._lowerBound < self._lowerBound:
2026 lBound = self._lowerBound
2027 elif other._lowerBound in self: 2027 ↛ 2030line 2027 didn't jump to line 2030 because the condition on line 2027 was always true
2028 lBound = other._lowerBound
2029 else:
2030 raise ValueError()
2032 if other._upperBound > self._upperBound:
2033 uBound = self._upperBound
2034 elif other._upperBound in self: 2034 ↛ 2037line 2034 didn't jump to line 2037 because the condition on line 2034 was always true
2035 uBound = other._upperBound
2036 else:
2037 raise ValueError()
2039 return self.__class__(lBound, uBound)
2041 def __lt__(self, other: Any) -> bool:
2042 """
2043 Compare a version range and a version numbers if the version range is less than the second operand (version).
2045 :param other: Operand to compare against.
2046 :returns: ``True``, if version range is less than the second operand (version).
2047 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2048 """
2049 # TODO: support VersionRange < VersionRange too
2050 # TODO: support str, int, ... like Version ?
2051 if not isinstance(other, Version):
2052 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2053 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2054 raise ex
2056 if not (isinstance(other, self._lowerBound.__class__) and isinstance(self._lowerBound, other.__class__)): 2056 ↛ 2057line 2056 didn't jump to line 2057 because the condition on line 2056 was never true
2057 ex = TypeError(f"Parameter 'other' is not compatible with version range.")
2058 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2059 raise ex
2061 return self._upperBound < other
2063 def __le__(self, other: Any) -> bool:
2064 """
2065 Compare a version range and a version numbers if the version range is less than or equal the second operand (version).
2067 :param other: Operand to compare against.
2068 :returns: ``True``, if version range is less than or equal the second operand (version).
2069 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2070 """
2071 # TODO: support VersionRange < VersionRange too
2072 # TODO: support str, int, ... like Version ?
2073 if not isinstance(other, Version): 2073 ↛ 2074line 2073 didn't jump to line 2074 because the condition on line 2073 was never true
2074 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2075 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2076 raise ex
2078 if not (isinstance(other, self._lowerBound.__class__) and isinstance(self._lowerBound, other.__class__)): 2078 ↛ 2079line 2078 didn't jump to line 2079 because the condition on line 2078 was never true
2079 ex = TypeError(f"Parameter 'other' is not compatible with version range.")
2080 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2081 raise ex
2083 if RangeBoundHandling.UpperBoundExclusive in self._boundHandling:
2084 return self._upperBound < other
2085 else:
2086 return self._upperBound <= other
2088 def __gt__(self, other: Any) -> bool:
2089 """
2090 Compare a version range and a version numbers if the version range is greater than the second operand (version).
2092 :param other: Operand to compare against.
2093 :returns: ``True``, if version range is greater than the second operand (version).
2094 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2095 """
2096 # TODO: support VersionRange < VersionRange too
2097 # TODO: support str, int, ... like Version ?
2098 if not isinstance(other, Version): 2098 ↛ 2099line 2098 didn't jump to line 2099 because the condition on line 2098 was never true
2099 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2100 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2101 raise ex
2103 if not (isinstance(other, self._upperBound.__class__) and isinstance(self._upperBound, other.__class__)): 2103 ↛ 2104line 2103 didn't jump to line 2104 because the condition on line 2103 was never true
2104 ex = TypeError(f"Parameter 'other' is not compatible with version range.")
2105 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2106 raise ex
2108 return self._lowerBound > other
2110 def __ge__(self, other: Any) -> bool:
2111 """
2112 Compare a version range and a version numbers if the version range is greater than or equal the second operand (version).
2114 :param other: Operand to compare against.
2115 :returns: ``True``, if version range is greater than or equal the second operand (version).
2116 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2117 """
2118 # TODO: support VersionRange < VersionRange too
2119 # TODO: support str, int, ... like Version ?
2120 if not isinstance(other, Version): 2120 ↛ 2121line 2120 didn't jump to line 2121 because the condition on line 2120 was never true
2121 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2122 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2123 raise ex
2125 if not (isinstance(other, self._upperBound.__class__) and isinstance(self._upperBound, other.__class__)): 2125 ↛ 2126line 2125 didn't jump to line 2126 because the condition on line 2125 was never true
2126 ex = TypeError(f"Parameter 'other' is not compatible with version range.")
2127 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2128 raise ex
2130 if RangeBoundHandling.LowerBoundExclusive in self._boundHandling: 2130 ↛ 2131line 2130 didn't jump to line 2131 because the condition on line 2130 was never true
2131 return self._lowerBound > other
2132 else:
2133 return self._lowerBound >= other
2135 def __contains__(self, version: Version) -> bool:
2136 """
2137 Check if the version is in the version range.
2139 :param version: Version to check.
2140 :returns: ``True``, if version is in range.
2141 :raises TypeError: If parameter ``version`` is not of type :class:`Version`.
2142 """
2143 if not isinstance(version, Version): 2143 ↛ 2144line 2143 didn't jump to line 2144 because the condition on line 2143 was never true
2144 ex = TypeError(f"Parameter 'item' is not of type 'Version'.")
2145 ex.add_note(f"Got type '{getFullyQualifiedName(version)}'.")
2146 raise ex
2148 if self._boundHandling is RangeBoundHandling.BothBoundsInclusive: 2148 ↛ 2150line 2148 didn't jump to line 2150 because the condition on line 2148 was always true
2149 return self._lowerBound <= version <= self._upperBound
2150 elif self._boundHandling is (RangeBoundHandling.LowerBoundInclusive | RangeBoundHandling.UpperBoundExclusive):
2151 return self._lowerBound <= version < self._upperBound
2152 elif self._boundHandling is (RangeBoundHandling.LowerBoundExclusive | RangeBoundHandling.UpperBoundInclusive):
2153 return self._lowerBound < version <= self._upperBound
2154 else:
2155 return self._lowerBound < version < self._upperBound
2158@export
2159class VersionSet(Generic[V], metaclass=ExtendedType, slots=True):
2160 """
2161 Representation of an ordered set of versions.
2163 This version set works with :class:`SemanticVersion` and :class:`CalendarVersion` and its derived classes.
2164 """
2165 _items: List[V] #: An ordered list of set members.
2167 def __init__(self, versions: Union[Version, Iterable[V]]) -> None:
2168 """
2169 Initializes a version set either by a single version or an iterable of versions.
2171 :param versions: A single version or an iterable of versions.
2172 :raises ValueError: If parameter ``versions`` is None`.
2173 :raises TypeError: In case of a single version, if parameter ``version`` is not of type :class:`Version`.
2174 :raises TypeError: In case of an iterable, if parameter ``versions`` containes elements, which are not of type :class:`Version`.
2175 :raises TypeError: If parameter ``versions`` is neither a single version nor an iterable thereof.
2176 """
2177 if versions is None:
2178 raise ValueError(f"Parameter 'versions' is None.")
2180 if isinstance(versions, Version):
2181 self._items = [versions]
2182 elif isinstance(versions, abc_Iterable): 2182 ↛ 2200line 2182 didn't jump to line 2200 because the condition on line 2182 was always true
2183 iterator = iter(versions)
2184 try:
2185 firstVersion = next(iterator)
2186 except StopIteration:
2187 self._items = []
2188 return
2190 if not isinstance(firstVersion, Version): 2190 ↛ 2191line 2190 didn't jump to line 2191 because the condition on line 2190 was never true
2191 raise TypeError(f"First element in parameter 'versions' is not of type Version.")
2193 baseType = firstVersion.__class__
2194 for version in iterator:
2195 if not isinstance(version, baseType):
2196 raise TypeError(f"Element from parameter 'versions' is not of type {baseType.__name__}")
2198 self._items = list(sorted(versions))
2199 else:
2200 raise TypeError(f"Parameter 'versions' is not an Iterable.")
2202 def __and__(self, other: "VersionSet[V]") -> "VersionSet[T]":
2203 """
2204 Compute intersection of two version sets.
2206 :param other: Second set of versions.
2207 :returns: Intersection of two version sets.
2208 """
2209 selfIterator = self.__iter__()
2210 otherIterator = other.__iter__()
2212 result = []
2213 try:
2214 selfValue = next(selfIterator)
2215 otherValue = next(otherIterator)
2217 while True:
2218 if selfValue < otherValue:
2219 selfValue = next(selfIterator)
2220 elif otherValue < selfValue:
2221 otherValue = next(otherIterator)
2222 else:
2223 result.append(selfValue)
2224 selfValue = next(selfIterator)
2225 otherValue = next(otherIterator)
2227 except StopIteration:
2228 pass
2230 return VersionSet(result)
2232 def __or__(self, other: "VersionSet[V]") -> "VersionSet[T]":
2233 """
2234 Compute union of two version sets.
2236 :param other: Second set of versions.
2237 :returns: Union of two version sets.
2238 """
2239 selfIterator = self.__iter__()
2240 otherIterator = other.__iter__()
2242 result = []
2243 try:
2244 selfValue = next(selfIterator)
2245 except StopIteration:
2246 for otherValue in otherIterator:
2247 result.append(otherValue)
2249 try:
2250 otherValue = next(otherIterator)
2251 except StopIteration:
2252 for selfValue in selfIterator:
2253 result.append(selfValue)
2255 while True:
2256 if selfValue < otherValue:
2257 result.append(selfValue)
2258 try:
2259 selfValue = next(selfIterator)
2260 except StopIteration:
2261 result.append(otherValue)
2262 for otherValue in otherIterator: 2262 ↛ 2263line 2262 didn't jump to line 2263 because the loop on line 2262 never started
2263 result.append(otherValue)
2265 break
2266 elif otherValue < selfValue:
2267 result.append(otherValue)
2268 try:
2269 otherValue = next(otherIterator)
2270 except StopIteration:
2271 result.append(selfValue)
2272 for selfValue in selfIterator:
2273 result.append(selfValue)
2275 break
2276 else:
2277 result.append(selfValue)
2278 try:
2279 selfValue = next(selfIterator)
2280 except StopIteration:
2281 for otherValue in otherIterator: 2281 ↛ 2282line 2281 didn't jump to line 2282 because the loop on line 2281 never started
2282 result.append(otherValue)
2284 break
2286 try:
2287 otherValue = next(otherIterator)
2288 except StopIteration:
2289 for selfValue in selfIterator:
2290 result.append(selfValue)
2292 break
2294 return VersionSet(result)
2296 def __lt__(self, other: Any) -> bool:
2297 """
2298 Compare a version set and a version numbers if the version set is less than the second operand (version).
2300 :param other: Operand to compare against.
2301 :returns: ``True``, if version set is less than the second operand (version).
2302 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2303 """
2304 # TODO: support VersionRange < VersionRange too
2305 # TODO: support str, int, ... like Version ?
2306 if not isinstance(other, Version):
2307 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2308 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2309 raise ex
2311 return self._items[-1] < other
2313 def __le__(self, other: Any) -> bool:
2314 """
2315 Compare a version set and a version numbers if the version set is less than or equal the second operand (version).
2317 :param other: Operand to compare against.
2318 :returns: ``True``, if version set is less than or equal the second operand (version).
2319 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2320 """
2321 # TODO: support VersionRange < VersionRange too
2322 # TODO: support str, int, ... like Version ?
2323 if not isinstance(other, Version): 2323 ↛ 2324line 2323 didn't jump to line 2324 because the condition on line 2323 was never true
2324 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2325 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2326 raise ex
2328 return self._items[-1] <= other
2330 def __gt__(self, other: Any) -> bool:
2331 """
2332 Compare a version set and a version numbers if the version set is greater than the second operand (version).
2334 :param other: Operand to compare against.
2335 :returns: ``True``, if version set is greater than the second operand (version).
2336 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2337 """
2338 # TODO: support VersionRange < VersionRange too
2339 # TODO: support str, int, ... like Version ?
2340 if not isinstance(other, Version): 2340 ↛ 2341line 2340 didn't jump to line 2341 because the condition on line 2340 was never true
2341 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2342 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2343 raise ex
2345 return self._items[0] > other
2347 def __ge__(self, other: Any) -> bool:
2348 """
2349 Compare a version set and a version numbers if the version set is greater than or equal the second operand (version).
2351 :param other: Operand to compare against.
2352 :returns: ``True``, if version set is greater than or equal the second operand (version).
2353 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2354 """
2355 # TODO: support VersionRange < VersionRange too
2356 # TODO: support str, int, ... like Version ?
2357 if not isinstance(other, Version): 2357 ↛ 2358line 2357 didn't jump to line 2358 because the condition on line 2357 was never true
2358 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2359 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2360 raise ex
2362 return self._items[0] >= other
2364 def __contains__(self, version: V) -> bool:
2365 """
2366 Checks if the version a member of the set.
2368 :param version: The version to check.
2369 :returns: ``True``, if the version is a member of the set.
2370 """
2371 return version in self._items
2373 def __len__(self) -> int:
2374 """
2375 Returns the number of members in the set.
2377 :returns: Number of set members.
2378 """
2379 return len(self._items)
2381 def __iter__(self) -> Iterator[V]:
2382 """
2383 Returns an iterator to iterate all versions of this set from lowest to highest.
2385 :returns: Iterator to iterate versions.
2386 """
2387 return self._items.__iter__()
2389 def __getitem__(self, index: int) -> V:
2390 """
2391 Access to a version of a set by index.
2393 :param index: The index of the version to access.
2394 :returns: The indexed version.
2396 .. hint::
2398 Versions are ordered from lowest to highest version number.
2399 """
2400 return self._items[index]