Coverage for pyTooling / Versioning / __init__.py: 82%
974 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-21 22:22 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-21 22:22 +0000
1# ==================================================================================================================== #
2# _____ _ _ __ __ _ _ #
3# _ __ _ |_ _|__ ___ | (_)_ __ __ \ \ / /__ _ __ ___(_) ___ _ __ (_)_ __ __ _ #
4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` \ \ / / _ \ '__/ __| |/ _ \| '_ \| | '_ \ / _` | #
5# | |_) | |_| || | (_) | (_) | | | | | | (_| |\ V / __/ | \__ \ | (_) | | | | | | | | (_| | #
6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)_/ \___|_| |___/_|\___/|_| |_|_|_| |_|\__, | #
7# |_| |___/ |___/ |___/ #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2020-2025 Patrick Lehmann - Bötzingen, Germany #
15# #
16# Licensed under the Apache License, Version 2.0 (the "License"); #
17# you may not use this file except in compliance with the License. #
18# You may obtain a copy of the License at #
19# #
20# http://www.apache.org/licenses/LICENSE-2.0 #
21# #
22# Unless required by applicable law or agreed to in writing, software #
23# distributed under the License is distributed on an "AS IS" BASIS, #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25# See the License for the specific language governing permissions and #
26# limitations under the License. #
27# #
28# SPDX-License-Identifier: Apache-2.0 #
29# ==================================================================================================================== #
30#
31"""
32Implementation of semantic and date versioning version-numbers.
34.. hint:: See :ref:`high-level help <VERSIONING>` for explanations and usage examples.
35"""
36from collections.abc import Iterable as abc_Iterable
37from enum import Flag, Enum
38from re import compile as re_compile
39from typing import Optional as Nullable, Union, Callable, Any, Generic, TypeVar, Iterable, Iterator, List
41try:
42 from pyTooling.Decorators import export, readonly
43 from pyTooling.MetaClasses import ExtendedType, abstractmethod, mustoverride
44 from pyTooling.Exceptions import ToolingException
45 from pyTooling.Common import getFullyQualifiedName
46except (ImportError, ModuleNotFoundError): # pragma: no cover
47 print("[pyTooling.Versioning] Could not import from 'pyTooling.*'!")
49 try:
50 from Decorators import export, readonly
51 from MetaClasses import ExtendedType, abstractmethod, mustoverride
52 from Exceptions import ToolingException
53 from Common import getFullyQualifiedName
54 except (ImportError, ModuleNotFoundError) as ex: # pragma: no cover
55 print("[pyTooling.Versioning] Could not import directly!")
56 raise ex
59@export
60class Parts(Flag):
61 """Enumeration describing parts of a version number that can be present."""
62 Unknown = 0 #: Undocumented
63 Major = 1 #: Major number is present. (e.g. X in ``vX.0.0``).
64 Year = 1 #: Year is present. (e.g. X in ``XXXX.10``).
65 Minor = 2 #: Minor number is present. (e.g. Y in ``v0.Y.0``).
66 Month = 2 #: Month is present. (e.g. X in ``2024.YY``).
67 Week = 2 #: Week is present. (e.g. X in ``2024.YY``).
68 Micro = 4 #: Patch number is present. (e.g. Z in ``v0.0.Z``).
69 Patch = 4 #: Patch number is present. (e.g. Z in ``v0.0.Z``).
70 Day = 4 #: Day is present. (e.g. X in ``2024.10.ZZ``).
71 Level = 8 #: Release level is present.
72 Dev = 16 #: Development part is present.
73 Build = 32 #: Build number is present. (e.g. bbbb in ``v0.0.0.bbbb``)
74 Post = 64 #: Post-release number is present.
75 Prefix = 128 #: Prefix is present.
76 Postfix = 256 #: Postfix is present.
77 Hash = 512 #: Hash is present.
78# AHead = 256
81@export
82class ReleaseLevel(Enum):
83 """Enumeration describing the version's maturity level."""
84 Final = 0 #:
85 ReleaseCandidate = -10 #:
86 Development = -20 #:
87 Gamma = -30 #:
88 Beta = -40 #:
89 Alpha = -50 #:
91 def __eq__(self, other: Any):
92 """
93 Compare two release levels if the level is equal to the second operand.
95 :param other: Operand to compare against.
96 :returns: ``True``, if release level is equal the second operand's release level.
97 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
98 """
99 if isinstance(other, str): 99 ↛ 100line 99 didn't jump to line 100 because the condition on line 99 was never true
100 other = ReleaseLevel(other)
102 if not isinstance(other, ReleaseLevel): 102 ↛ 103line 102 didn't jump to line 103 because the condition on line 102 was never true
103 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by == operator.")
104 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
105 raise ex
107 return self is other
109 def __ne__(self, other: Any):
110 """
111 Compare two release levels if the level is unequal to the second operand.
113 :param other: Operand to compare against.
114 :returns: ``True``, if release level is unequal the second operand's release level.
115 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
116 """
117 if isinstance(other, str):
118 other = ReleaseLevel(other)
120 if not isinstance(other, ReleaseLevel):
121 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by != operator.")
122 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
123 raise ex
125 return self is not other
127 def __lt__(self, other: Any):
128 """
129 Compare two release levels if the level is less than the second operand.
131 :param other: Operand to compare against.
132 :returns: ``True``, if release level is less than the second operand.
133 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
134 """
135 if isinstance(other, str): 135 ↛ 136line 135 didn't jump to line 136 because the condition on line 135 was never true
136 other = ReleaseLevel(other)
138 if not isinstance(other, ReleaseLevel): 138 ↛ 139line 138 didn't jump to line 139 because the condition on line 138 was never true
139 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by < operator.")
140 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
141 raise ex
143 return self.value < other.value
145 def __le__(self, other: Any):
146 """
147 Compare two release levels if the level is less than or equal the second operand.
149 :param other: Operand to compare against.
150 :returns: ``True``, if release level is less than or equal the second operand.
151 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
152 """
153 if isinstance(other, str):
154 other = ReleaseLevel(other)
156 if not isinstance(other, ReleaseLevel):
157 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by <=>= operator.")
158 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
159 raise ex
161 return self.value <= other.value
163 def __gt__(self, other: Any):
164 """
165 Compare two release levels if the level is greater than the second operand.
167 :param other: Operand to compare against.
168 :returns: ``True``, if release level is greater than the second operand.
169 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
170 """
171 if isinstance(other, str): 171 ↛ 172line 171 didn't jump to line 172 because the condition on line 171 was never true
172 other = ReleaseLevel(other)
174 if not isinstance(other, ReleaseLevel): 174 ↛ 175line 174 didn't jump to line 175 because the condition on line 174 was never true
175 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by > operator.")
176 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
177 raise ex
179 return self.value > other.value
181 def __ge__(self, other: Any):
182 """
183 Compare two release levels if the level is greater than or equal the second operand.
185 :param other: Operand to compare against.
186 :returns: ``True``, if release level is greater than or equal the second operand.
187 :raises TypeError: If parameter ``other`` is not of type :class:`ReleaseLevel` or :class:`str`.
188 """
189 if isinstance(other, str):
190 other = ReleaseLevel(other)
192 if not isinstance(other, ReleaseLevel):
193 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by >= operator.")
194 ex.add_note(f"Supported types for second operand: {self.__class__.__name__} or 'str'.")
195 raise ex
197 return self.value >= other.value
199 def __hash__(self) -> int:
200 return hash(self.value)
202 def __str__(self) -> str:
203 """
204 Returns the release level's string equivalent.
206 :returns: The string equivalent of the release level.
207 """
208 if self is ReleaseLevel.Final:
209 return "final"
210 elif self is ReleaseLevel.ReleaseCandidate:
211 return "rc"
212 elif self is ReleaseLevel.Development: 212 ↛ 213line 212 didn't jump to line 213 because the condition on line 212 was never true
213 return "dev"
214 elif self is ReleaseLevel.Beta: 214 ↛ 215line 214 didn't jump to line 215 because the condition on line 214 was never true
215 return "beta"
216 elif self is ReleaseLevel.Alpha: 216 ↛ 219line 216 didn't jump to line 219 because the condition on line 216 was always true
217 return "alpha"
219 raise ToolingException(f"Unknown ReleaseLevel '{self.name}'.")
222@export
223class Flags(Flag):
224 """State enumeration, if a (tagged) version is build from a clean or dirty working directory."""
225 NoVCS = 0 #: No Version Control System VCS
226 Clean = 1 #: A versioned build was created from a *clean* working directory.
227 Dirty = 2 #: A versioned build was created from a *dirty* working directory.
229 CVS = 16 #: Concurrent Versions System (CVS)
230 SVN = 32 #: Subversion (SVN)
231 Git = 64 #: Git
232 Hg = 128 #: Mercurial (Hg)
235@export
236def WordSizeValidator(
237 bits: Nullable[int] = None,
238 majorBits: Nullable[int] = None,
239 minorBits: Nullable[int] = None,
240 microBits: Nullable[int] = None,
241 buildBits: Nullable[int] = None
242):
243 """
244 A factory function to return a validator for Version instances for a positive integer range based on word-sizes in bits.
246 :param bits: Number of bits to encode any positive version number part.
247 :param majorBits: Number of bits to encode a positive major number in a version.
248 :param minorBits: Number of bits to encode a positive minor number in a version.
249 :param microBits: Number of bits to encode a positive micro number in a version.
250 :param buildBits: Number of bits to encode a positive build number in a version.
251 :return: A validation function for Version instances.
252 """
253 majorMax = minorMax = microMax = buildMax = -1
254 if bits is not None:
255 majorMax = minorMax = microMax = buildMax = 2**bits - 1
257 if majorBits is not None:
258 majorMax = 2**majorBits - 1
259 if minorBits is not None:
260 minorMax = 2**minorBits - 1
261 if microBits is not None:
262 microMax = 2 ** microBits - 1
263 if buildBits is not None: 263 ↛ 264line 263 didn't jump to line 264 because the condition on line 263 was never true
264 buildMax = 2**buildBits - 1
266 def validator(version: SemanticVersion) -> bool:
267 if Parts.Major in version._parts and version._major > majorMax:
268 raise ValueError(f"Field 'Version.Major' > {majorMax}.")
270 if Parts.Minor in version._parts and version._minor > minorMax:
271 raise ValueError(f"Field 'Version.Minor' > {minorMax}.")
273 if Parts.Micro in version._parts and version._micro > microMax:
274 raise ValueError(f"Field 'Version.Micro' > {microMax}.")
276 if Parts.Build in version._parts and version._build > buildMax: 276 ↛ 277line 276 didn't jump to line 277 because the condition on line 276 was never true
277 raise ValueError(f"Field 'Version.Build' > {buildMax}.")
279 return True
281 return validator
284@export
285def MaxValueValidator(
286 max: Nullable[int] = None,
287 majorMax: Nullable[int] = None,
288 minorMax: Nullable[int] = None,
289 microMax: Nullable[int] = None,
290 buildMax: Nullable[int] = None
291):
292 """
293 A factory function to return a validator for Version instances checking for a positive integer range [0..max].
295 :param max: The upper bound for any positive version number part.
296 :param majorMax: The upper bound for the positive major number.
297 :param minorMax: The upper bound for the positive minor number.
298 :param microMax: The upper bound for the positive micro number.
299 :param buildMax: The upper bound for the positive build number.
300 :return: A validation function for Version instances.
301 """
302 if max is not None: 302 ↛ 305line 302 didn't jump to line 305 because the condition on line 302 was always true
303 majorMax = minorMax = microMax = buildMax = max
305 def validator(version: SemanticVersion) -> bool:
306 if Parts.Major in version._parts and version._major > majorMax:
307 raise ValueError(f"Field 'Version.Major' > {majorMax}.")
309 if Parts.Minor in version._parts and version._minor > minorMax:
310 raise ValueError(f"Field 'Version.Minor' > {minorMax}.")
312 if Parts.Micro in version._parts and version._micro > microMax:
313 raise ValueError(f"Field 'Version.Micro' > {microMax}.")
315 if Parts.Build in version._parts and version._build > buildMax: 315 ↛ 316line 315 didn't jump to line 316 because the condition on line 315 was never true
316 raise ValueError(f"Field 'Version.Build' > {buildMax}.")
318 return True
320 return validator
323@export
324class Version(metaclass=ExtendedType, slots=True):
325 """Base-class for a version representation."""
327 __hash: Nullable[int] #: once computed hash of the object
329 _parts: Parts #: Integer flag enumeration of present parts in a version number.
330 _prefix: str #: Prefix string
331 _major: int #: Major number part of the version number.
332 _minor: int #: Minor number part of the version number.
333 _micro: int #: Micro number part of the version number.
334 _releaseLevel: ReleaseLevel #: Release level (alpha, beta, rc, final, ...).
335 _releaseNumber: int #: Release number (Python calls this a serial).
336 _post: int #: Post-release version number part.
337 _dev: int #: Development number
338 _build: int #: Build number part of the version number.
339 _postfix: str #: Postfix string
340 _hash: str #: Hash from version control system.
341 _flags: Flags #: State if the version in a working directory is clean or dirty compared to a tagged version.
343 def __init__(
344 self,
345 major: int,
346 minor: Nullable[int] = None,
347 micro: Nullable[int] = None,
348 level: Nullable[ReleaseLevel] = ReleaseLevel.Final,
349 number: Nullable[int] = None,
350 post: Nullable[int] = None,
351 dev: Nullable[int] = None,
352 *,
353 build: Nullable[int] = None,
354 postfix: Nullable[str] = None,
355 prefix: Nullable[str] = None,
356 hash: Nullable[str] = None,
357 flags: Flags = Flags.NoVCS
358 ) -> None:
359 """
360 Initializes a version number representation.
362 :param major: Major number part of the version number.
363 :param minor: Minor number part of the version number.
364 :param micro: Micro (patch) number part of the version number.
365 :param level: Release level (alpha, beta, release candidate, final, ...) of the version number.
366 :param number: Release number part (in combination with release level) of the version number.
367 :param post: Post number part of the version number.
368 :param dev: Development number part of the version number.
369 :param build: Build number part of the version number.
370 :param postfix: The version number's postfix.
371 :param prefix: The version number's prefix.
372 :param hash: Postfix string.
373 :param flags: The version number's flags.
374 :raises TypeError: If parameter 'major' is not of type int.
375 :raises ValueError: If parameter 'major' is a negative number.
376 :raises TypeError: If parameter 'minor' is not of type int.
377 :raises ValueError: If parameter 'minor' is a negative number.
378 :raises TypeError: If parameter 'micro' is not of type int.
379 :raises ValueError: If parameter 'micro' is a negative number.
380 :raises TypeError: If parameter 'build' is not of type int.
381 :raises ValueError: If parameter 'build' is a negative number.
382 :raises TypeError: If parameter 'prefix' is not of type str.
383 :raises TypeError: If parameter 'postfix' is not of type str.
384 """
385 self.__hash = None
387 if not isinstance(major, int):
388 raise TypeError("Parameter 'major' is not of type 'int'.")
389 elif major < 0:
390 raise ValueError("Parameter 'major' is negative.")
392 self._parts = Parts.Major
393 self._major = major
395 if minor is not None:
396 if not isinstance(minor, int):
397 raise TypeError("Parameter 'minor' is not of type 'int'.")
398 elif minor < 0:
399 raise ValueError("Parameter 'minor' is negative.")
401 self._parts |= Parts.Minor
402 self._minor = minor
403 else:
404 self._minor = 0
406 if micro is not None:
407 if not isinstance(micro, int):
408 raise TypeError("Parameter 'micro' is not of type 'int'.")
409 elif micro < 0:
410 raise ValueError("Parameter 'micro' is negative.")
412 self._parts |= Parts.Micro
413 self._micro = micro
414 else:
415 self._micro = 0
417 if level is None:
418 raise ValueError("Parameter 'level' is None.")
419 elif not isinstance(level, ReleaseLevel):
420 raise TypeError("Parameter 'level' is not of type 'ReleaseLevel'.")
421 elif level is ReleaseLevel.Final:
422 if number is not None:
423 raise ValueError("Parameter 'number' must be None, if parameter 'level' is 'Final'.")
425 self._parts |= Parts.Level
426 self._releaseLevel = level
427 self._releaseNumber = 0
428 else:
429 self._parts |= Parts.Level
430 self._releaseLevel = level
432 if number is not None:
433 if not isinstance(number, int):
434 raise TypeError("Parameter 'number' is not of type 'int'.")
435 elif number < 0:
436 raise ValueError("Parameter 'number' is negative.")
438 self._releaseNumber = number
439 else:
440 self._releaseNumber = 0
442 if dev is not None:
443 if not isinstance(dev, int):
444 raise TypeError("Parameter 'dev' is not of type 'int'.")
445 elif dev < 0:
446 raise ValueError("Parameter 'dev' is negative.")
448 self._parts |= Parts.Dev
449 self._dev = dev
450 else:
451 self._dev = 0
453 if post is not None:
454 if not isinstance(post, int):
455 raise TypeError("Parameter 'post' is not of type 'int'.")
456 elif post < 0:
457 raise ValueError("Parameter 'post' is negative.")
459 self._parts |= Parts.Post
460 self._post = post
461 else:
462 self._post = 0
464 if build is not None:
465 if not isinstance(build, int):
466 raise TypeError("Parameter 'build' is not of type 'int'.")
467 elif build < 0:
468 raise ValueError("Parameter 'build' is negative.")
470 self._build = build
471 self._parts |= Parts.Build
472 else:
473 self._build = 0
475 if postfix is not None:
476 if not isinstance(postfix, str):
477 raise TypeError("Parameter 'postfix' is not of type 'str'.")
479 self._parts |= Parts.Postfix
480 self._postfix = postfix
481 else:
482 self._postfix = ""
484 if prefix is not None:
485 if not isinstance(prefix, str):
486 raise TypeError("Parameter 'prefix' is not of type 'str'.")
488 self._parts |= Parts.Prefix
489 self._prefix = prefix
490 else:
491 self._prefix = ""
493 if hash is not None:
494 if not isinstance(hash, str):
495 raise TypeError("Parameter 'hash' is not of type 'str'.")
497 self._parts |= Parts.Hash
498 self._hash = hash
499 else:
500 self._hash = ""
502 if flags is None:
503 raise ValueError("Parameter 'flags' is None.")
504 elif not isinstance(flags, Flags):
505 raise TypeError("Parameter 'flags' is not of type 'Flags'.")
507 self._flags = flags
509 @classmethod
510 @abstractmethod
511 def Parse(cls, versionString: Nullable[str], validator: Nullable[Callable[["SemanticVersion"], bool]] = None) -> "Version":
512 """Parse a version string and return a Version instance."""
514 @readonly
515 def Parts(self) -> Parts:
516 """
517 Read-only property to access the used parts of this version number.
519 :return: A flag enumeration of used version number parts.
520 """
521 return self._parts
523 @readonly
524 def Prefix(self) -> str:
525 """
526 Read-only property to access the version number's prefix.
528 :return: The prefix of the version number.
529 """
530 return self._prefix
532 @readonly
533 def Major(self) -> int:
534 """
535 Read-only property to access the major number.
537 :return: The major number.
538 """
539 return self._major
541 @readonly
542 def Minor(self) -> int:
543 """
544 Read-only property to access the minor number.
546 :return: The minor number.
547 """
548 return self._minor
550 @readonly
551 def Micro(self) -> int:
552 """
553 Read-only property to access the micro number.
555 :return: The micro number.
556 """
557 return self._micro
559 @readonly
560 def ReleaseLevel(self) -> ReleaseLevel:
561 """
562 Read-only property to access the release level.
564 :return: The release level.
565 """
566 return self._releaseLevel
568 @readonly
569 def ReleaseNumber(self) -> int:
570 """
571 Read-only property to access the release number.
573 :return: The release number.
574 """
575 return self._releaseNumber
577 @readonly
578 def Post(self) -> int:
579 """
580 Read-only property to access the post number.
582 :return: The post number.
583 """
584 return self._post
586 @readonly
587 def Dev(self) -> int:
588 """
589 Read-only property to access the development number.
591 :return: The development number.
592 """
593 return self._dev
595 @readonly
596 def Build(self) -> int:
597 """
598 Read-only property to access the build number.
600 :return: The build number.
601 """
602 return self._build
604 @readonly
605 def Postfix(self) -> str:
606 """
607 Read-only property to access the version number's postfix.
609 :return: The postfix of the version number.
610 """
611 return self._postfix
613 @readonly
614 def Hash(self) -> str:
615 """
616 Read-only property to access the version number's hash.
618 :return: The hash.
619 """
620 return self._hash
622 @readonly
623 def Flags(self) -> Flags:
624 """
625 Read-only property to access the version number's flags.
627 :return: The flags of the version number.
628 """
629 return self._flags
631 def _equal(self, left: "Version", right: "Version") -> Nullable[bool]:
632 """
633 Private helper method to compute the equality of two :class:`Version` instances.
635 :param left: Left operand.
636 :param right: Right operand.
637 :returns: ``True``, if ``left`` is equal to ``right``, otherwise it's ``False``.
638 """
639 return (
640 (left._major == right._major) and
641 (left._minor == right._minor) and
642 (left._micro == right._micro) and
643 (left._releaseLevel == right._releaseLevel) and
644 (left._releaseNumber == right._releaseNumber) and
645 (left._post == right._post) and
646 (left._dev == right._dev) and
647 (left._build == right._build) and
648 (left._postfix == right._postfix)
649 )
651 def _compare(self, left: "Version", right: "Version") -> Nullable[bool]:
652 """
653 Private helper method to compute the comparison of two :class:`Version` instances.
655 :param left: Left operand.
656 :param right: Right operand.
657 :returns: ``True``, if ``left`` is smaller than ``right``. |br|
658 False if ``left`` is greater than ``right``. |br|
659 Otherwise it's None (both operands are equal).
660 """
661 if left._major < right._major:
662 return True
663 elif left._major > right._major:
664 return False
666 if left._minor < right._minor:
667 return True
668 elif left._minor > right._minor:
669 return False
671 if left._micro < right._micro:
672 return True
673 elif left._micro > right._micro:
674 return False
676 if left._releaseLevel < right._releaseLevel: 676 ↛ 677line 676 didn't jump to line 677 because the condition on line 676 was never true
677 return True
678 elif left._releaseLevel > right._releaseLevel: 678 ↛ 679line 678 didn't jump to line 679 because the condition on line 678 was never true
679 return False
681 if left._releaseNumber < right._releaseNumber: 681 ↛ 682line 681 didn't jump to line 682 because the condition on line 681 was never true
682 return True
683 elif left._releaseNumber > right._releaseNumber: 683 ↛ 684line 683 didn't jump to line 684 because the condition on line 683 was never true
684 return False
686 if left._post < right._post: 686 ↛ 687line 686 didn't jump to line 687 because the condition on line 686 was never true
687 return True
688 elif left._post > right._post: 688 ↛ 689line 688 didn't jump to line 689 because the condition on line 688 was never true
689 return False
691 if left._dev < right._dev: 691 ↛ 692line 691 didn't jump to line 692 because the condition on line 691 was never true
692 return True
693 elif left._dev > right._dev: 693 ↛ 694line 693 didn't jump to line 694 because the condition on line 693 was never true
694 return False
696 if left._build < right._build: 696 ↛ 697line 696 didn't jump to line 697 because the condition on line 696 was never true
697 return True
698 elif left._build > right._build: 698 ↛ 699line 698 didn't jump to line 699 because the condition on line 698 was never true
699 return False
701 return None
703 def _minimum(self, actual: "Version", expected: "Version") -> Nullable[bool]:
704 exactMajor = Parts.Minor in expected._parts
705 exactMinor = Parts.Micro in expected._parts
707 if exactMajor and actual._major != expected._major: 707 ↛ 708line 707 didn't jump to line 708 because the condition on line 707 was never true
708 return False
709 elif not exactMajor and actual._major < expected._major:
710 return False
712 if exactMinor and actual._minor != expected._minor: 712 ↛ 713line 712 didn't jump to line 713 because the condition on line 712 was never true
713 return False
714 elif not exactMinor and actual._minor < expected._minor:
715 return False
717 if Parts.Micro in expected._parts:
718 return actual._micro >= expected._micro
720 return True
722 def _format(self, formatSpec: str) -> str:
723 """
724 Return a string representation of this version number according to the format specification.
726 .. topic:: Format Specifiers
728 * ``%p`` - prefix
729 * ``%M`` - major number
730 * ``%m`` - minor number
731 * ``%u`` - micro number
732 * ``%b`` - build number
734 :param formatSpec: The format specification.
735 :return: Formatted version number.
736 """
737 if formatSpec == "":
738 return self.__str__()
740 result = formatSpec
741 result = result.replace("%p", str(self._prefix))
742 result = result.replace("%M", str(self._major))
743 result = result.replace("%m", str(self._minor))
744 result = result.replace("%u", str(self._micro))
745 result = result.replace("%b", str(self._build))
746 result = result.replace("%r", str(self._releaseLevel)[0])
747 result = result.replace("%R", str(self._releaseLevel))
748 result = result.replace("%n", str(self._releaseNumber))
749 result = result.replace("%d", str(self._dev))
750 result = result.replace("%P", str(self._postfix))
752 return result
754 @mustoverride
755 def __eq__(self, other: Union["Version", str, int, None]) -> bool:
756 """
757 Compare two version numbers for equality.
759 The second operand should be an instance of :class:`Version`, but ``str`` and ``int`` are accepted, too. |br|
760 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
761 number is assumed (all other parts are zero).
763 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
764 number.
766 :param other: Operand to compare against.
767 :returns: ``True``, if both version numbers are equal.
768 :raises ValueError: If parameter ``other`` is None.
769 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`str` or :class:`ìnt`.
770 """
771 if other is None:
772 raise ValueError(f"Second operand is None.")
773 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
774 pass
775 elif isinstance(other, str):
776 other = self.__class__.Parse(other)
777 elif isinstance(other, int):
778 other = self.__class__(major=other)
779 else:
780 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by == operator.")
781 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, str, int")
782 raise ex
784 return self._equal(self, other)
786 @mustoverride
787 def __ne__(self, other: Union["Version", str, int, None]) -> bool:
788 """
789 Compare two version numbers for inequality.
791 The second operand should be an instance of :class:`Version`, but ``str`` and ``int`` are accepted, too. |br|
792 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
793 number is assumed (all other parts are zero).
795 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
796 number.
798 :param other: Operand to compare against.
799 :returns: ``True``, if both version numbers are not equal.
800 :raises ValueError: If parameter ``other`` is None.
801 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`str` or :class:`ìnt`.
802 """
803 if other is None:
804 raise ValueError(f"Second operand is None.")
805 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
806 pass
807 elif isinstance(other, str):
808 other = self.__class__.Parse(other)
809 elif isinstance(other, int):
810 other = self.__class__(major=other)
811 else:
812 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by == operator.")
813 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, str, int")
814 raise ex
816 return not self._equal(self, other)
818 @mustoverride
819 def __lt__(self, other: Union["Version", str, int, None]) -> bool:
820 """
821 Compare two version numbers if the version is less than the second operand.
823 The second operand should be an instance of :class:`Version`, but :class:`VersionRange`, :class:`VersionSet`,
824 ``str`` and ``int`` are accepted, too. |br|
825 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
826 number is assumed (all other parts are zero).
828 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
829 number.
831 :param other: Operand to compare against.
832 :returns: ``True``, if version is less than the second operand.
833 :raises ValueError: If parameter ``other`` is None.
834 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`.
835 """
836 if other is None:
837 raise ValueError(f"Second operand is None.")
838 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
839 pass
840 elif isinstance(other, VersionRange):
841 other = other._lowerBound
842 elif isinstance(other, VersionSet):
843 other = other._items[0]
844 elif isinstance(other, str):
845 other = self.__class__.Parse(other)
846 elif isinstance(other, int):
847 other = self.__class__(major=other)
848 else:
849 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by < operator.")
850 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int")
851 raise ex
853 return self._compare(self, other) is True
855 @mustoverride
856 def __le__(self, other: Union["Version", str, int, None]) -> bool:
857 """
858 Compare two version numbers if the version is less than or equal the second operand.
860 The second operand should be an instance of :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, but
861 ``str`` and ``int`` are accepted, too. |br|
862 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
863 number is assumed (all other parts are zero).
865 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
866 number.
868 :param other: Operand to compare against.
869 :returns: ``True``, if version is less than or equal the second operand.
870 :raises ValueError: If parameter ``other`` is None.
871 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`.
872 """
873 equalValue = True
874 if other is None:
875 raise ValueError(f"Second operand is None.")
876 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
877 pass
878 elif isinstance(other, VersionRange):
879 equalValue = RangeBoundHandling.LowerBoundExclusive not in other._boundHandling
880 other = other._lowerBound
881 elif isinstance(other, VersionSet):
882 other = other._items[0]
883 elif isinstance(other, str):
884 other = self.__class__.Parse(other)
885 elif isinstance(other, int):
886 other = self.__class__(major=other)
887 else:
888 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by <= operator.")
889 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int")
890 raise ex
892 result = self._compare(self, other)
893 return result if result is not None else equalValue
895 @mustoverride
896 def __gt__(self, other: Union["Version", str, int, None]) -> bool:
897 """
898 Compare two version numbers if the version is greater than the second operand.
900 The second operand should be an instance of :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, but
901 ``str`` and ``int`` are accepted, too. |br|
902 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
903 number is assumed (all other parts are zero).
905 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
906 number.
908 :param other: Operand to compare against.
909 :returns: ``True``, if version is greater than the second operand.
910 :raises ValueError: If parameter ``other`` is None.
911 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`.
912 """
913 if other is None:
914 raise ValueError(f"Second operand is None.")
915 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
916 pass
917 elif isinstance(other, VersionRange):
918 other = other._upperBound
919 elif isinstance(other, VersionSet):
920 other = other._items[-1]
921 elif isinstance(other, str):
922 other = self.__class__.Parse(other)
923 elif isinstance(other, int):
924 other = self.__class__(major=other)
925 else:
926 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by > operator.")
927 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int")
928 raise ex
930 return self._compare(self, other) is False
932 @mustoverride
933 def __ge__(self, other: Union["Version", str, int, None]) -> bool:
934 """
935 Compare two version numbers if the version is greater than or equal the second operand.
937 The second operand should be an instance of :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, but
938 ``str`` and ``int`` are accepted, too. |br|
939 In case of ``str``, it's tried to parse the string as a version number. In case of ``int``, a single major
940 number is assumed (all other parts are zero).
942 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
943 number.
945 :param other: Operand to compare against.
946 :returns: ``True``, if version is greater than or equal the second operand.
947 :raises ValueError: If parameter ``other`` is None.
948 :raises TypeError: If parameter ``other`` is not of type :class:`Version`, :class:`VersionRange`, :class:`VersionSet`, :class:`str` or :class:`ìnt`.
949 """
950 equalValue = True
951 if other is None:
952 raise ValueError(f"Second operand is None.")
953 elif ((sC := self.__class__) is (oC := other.__class__) or issubclass(sC, oC) or issubclass(oC, sC)):
954 pass
955 elif isinstance(other, VersionRange):
956 equalValue = RangeBoundHandling.UpperBoundExclusive not in other._boundHandling
957 other = other._upperBound
958 elif isinstance(other, VersionSet):
959 other = other._items[-1]
960 elif isinstance(other, str):
961 other = self.__class__.Parse(other)
962 elif isinstance(other, int):
963 other = self.__class__(major=other)
964 else:
965 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by >= operator.")
966 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, VersionRange, VersionSet, str, int")
967 raise ex
969 result = self._compare(self, other)
970 return not result if result is not None else equalValue
972 def __rshift__(self, other: Union["Version", str, int, None]) -> bool:
973 if other is None:
974 raise ValueError(f"Second operand is None.")
975 elif isinstance(other, self.__class__):
976 pass
977 elif isinstance(other, str):
978 other = self.__class__.Parse(other)
979 elif isinstance(other, int):
980 other = self.__class__(major=other)
981 else:
982 ex = TypeError(f"Second operand of type '{other.__class__.__name__}' is not supported by %= operator.")
983 ex.add_note(f"Supported types for second operand: {self.__class__.__name__}, str, int")
984 raise ex
986 return self._minimum(self, other)
988 def __hash__(self) -> int:
989 if self.__hash is None: 989 ↛ 1004line 989 didn't jump to line 1004 because the condition on line 989 was always true
990 self.__hash = hash((
991 self._prefix,
992 self._major,
993 self._minor,
994 self._micro,
995 self._releaseLevel,
996 self._releaseNumber,
997 self._post,
998 self._dev,
999 self._build,
1000 self._postfix,
1001 self._hash,
1002 self._flags
1003 ))
1004 return self.__hash
1007@export
1008class SemanticVersion(Version):
1009 """Representation of a semantic version number like ``3.7.12``."""
1011 _PATTERN = re_compile(
1012 r"^"
1013 r"(?P<prefix>[a-zA-Z]*)"
1014 r"(?P<major>\d+)"
1015 r"(?:\.(?P<minor>\d+))?"
1016 r"(?:\.(?P<micro>\d+))?"
1017 r"(?:"
1018 r"(?:\.(?P<build>\d+))"
1019 r"|"
1020 r"(?:[-](?P<release>dev|final))"
1021 r"|"
1022 r"(?:(?P<delim1>[\.\-]?)(?P<level>alpha|beta|gamma|a|b|c|rc|pl)(?P<number>\d+))"
1023 r")?"
1024 r"(?:(?P<delim2>[\.\-]post)(?P<post>\d+))?"
1025 r"(?:(?P<delim3>[\.\-]dev)(?P<dev>\d+))?"
1026 r"(?:(?P<delim4>[\.\-\+])(?P<postfix>\w+))?"
1027 r"$"
1028 )
1029# QUESTION: was this how many commits a version is ahead of the last tagged version?
1030# ahead: int = 0
1032 def __init__(
1033 self,
1034 major: int,
1035 minor: Nullable[int] = None,
1036 micro: Nullable[int] = None,
1037 level: Nullable[ReleaseLevel] = ReleaseLevel.Final,
1038 number: Nullable[int] = None,
1039 post: Nullable[int] = None,
1040 dev: Nullable[int] = None,
1041 *,
1042 build: Nullable[int] = None,
1043 postfix: Nullable[str] = None,
1044 prefix: Nullable[str] = None,
1045 hash: Nullable[str] = None,
1046 flags: Flags = Flags.NoVCS
1047 ) -> None:
1048 """
1049 Initializes a semantic version number representation.
1051 :param major: Major number part of the version number.
1052 :param minor: Minor number part of the version number.
1053 :param micro: Micro (patch) number part of the version number.
1054 :param build: Build number part of the version number.
1055 :param level: tbd
1056 :param number: tbd
1057 :param post: Post number part of the version number.
1058 :param dev: Development number part of the version number.
1059 :param prefix: The version number's prefix.
1060 :param postfix: The version number's postfix.
1061 :param flags: The version number's flags.
1062 :param hash: tbd
1063 :raises TypeError: If parameter 'major' is not of type int.
1064 :raises ValueError: If parameter 'major' is a negative number.
1065 :raises TypeError: If parameter 'minor' is not of type int.
1066 :raises ValueError: If parameter 'minor' is a negative number.
1067 :raises TypeError: If parameter 'micro' is not of type int.
1068 :raises ValueError: If parameter 'micro' is a negative number.
1069 :raises TypeError: If parameter 'build' is not of type int.
1070 :raises ValueError: If parameter 'build' is a negative number.
1071 :raises TypeError: If parameter 'post' is not of type int.
1072 :raises ValueError: If parameter 'post' is a negative number.
1073 :raises TypeError: If parameter 'dev' is not of type int.
1074 :raises ValueError: If parameter 'dev' is a negative number.
1075 :raises TypeError: If parameter 'prefix' is not of type str.
1076 :raises TypeError: If parameter 'postfix' is not of type str.
1077 """
1078 super().__init__(major, minor, micro, level, number, post, dev, build=build, postfix=postfix, prefix=prefix, hash=hash, flags=flags)
1080 @classmethod
1081 def Parse(cls, versionString: Nullable[str], validator: Nullable[Callable[["SemanticVersion"], bool]] = None) -> "SemanticVersion":
1082 """
1083 Parse a version string and return a :class:`SemanticVersion` instance.
1085 Allowed prefix characters:
1087 * ``v|V`` - version, public version, public release
1088 * ``i|I`` - internal version, internal release
1089 * ``r|R`` - release, revision
1090 * ``rev|REV`` - revision
1092 :param versionString: The version string to parse.
1093 :returns: An object representing a semantic version.
1094 :raises TypeError: If parameter ``other`` is not a string.
1095 :raises ValueError: If parameter ``other`` is None.
1096 :raises ValueError: If parameter ``other`` is empty.
1097 """
1098 if versionString is None:
1099 raise ValueError("Parameter 'versionString' is None.")
1100 elif not isinstance(versionString, str):
1101 ex = TypeError(f"Parameter 'versionString' is not of type 'str'.")
1102 ex.add_note(f"Got type '{getFullyQualifiedName(versionString)}'.")
1103 raise ex
1105 versionString = versionString.strip()
1106 if versionString == "":
1107 raise ValueError("Parameter 'versionString' is empty.")
1109 match = cls._PATTERN.match(versionString)
1110 if match is None:
1111 raise ValueError(f"Syntax error in parameter 'versionString': '{versionString}'")
1113 def toInt(value: Nullable[str]) -> Nullable[int]:
1114 if value is None or value == "":
1115 return None
1116 try:
1117 return int(value)
1118 except ValueError as ex: # pragma: no cover
1119 raise ValueError(f"Invalid part '{value}' in version number '{versionString}'.") from ex
1121 release = match["release"]
1122 if release is not None:
1123 if release == "dev": 1123 ↛ 1125line 1123 didn't jump to line 1125 because the condition on line 1123 was always true
1124 releaseLevel = ReleaseLevel.Development
1125 elif release == "final":
1126 releaseLevel = ReleaseLevel.Final
1127 else: # pragma: no cover
1128 raise ValueError(f"Unknown release level '{release}' in version number '{versionString}'.")
1129 else:
1130 level = match["level"]
1131 if level is not None:
1132 level = level.lower()
1133 if level == "a" or level == "alpha":
1134 releaseLevel = ReleaseLevel.Alpha
1135 elif level == "b" or level == "beta":
1136 releaseLevel = ReleaseLevel.Beta
1137 elif level == "c" or level == "gamma":
1138 releaseLevel = ReleaseLevel.Gamma
1139 elif level == "rc":
1140 releaseLevel = ReleaseLevel.ReleaseCandidate
1141 else: # pragma: no cover
1142 raise ValueError(f"Unknown release level '{level}' in version number '{versionString}'.")
1143 else:
1144 releaseLevel = ReleaseLevel.Final
1146 version = cls(
1147 major=toInt(match["major"]),
1148 minor=toInt(match["minor"]),
1149 micro=toInt(match["micro"]),
1150 level=releaseLevel,
1151 number=toInt(match["number"]),
1152 post=toInt(match["post"]),
1153 dev=toInt(match["dev"]),
1154 build=toInt(match["build"]),
1155 postfix=match["postfix"],
1156 prefix=match["prefix"],
1157 # hash=match["hash"],
1158 flags=Flags.Clean
1159 )
1160 if validator is not None and not validator(version):
1161 raise ValueError(f"Failed to validate version string '{versionString}'.") # pragma: no cover
1163 return version
1165 @readonly
1166 def Patch(self) -> int:
1167 """
1168 Read-only property to access the patch number.
1170 The patch number is identical to the micro number.
1172 :return: The patch number.
1173 """
1174 return self._micro
1176 def _equal(self, left: "SemanticVersion", right: "SemanticVersion") -> Nullable[bool]:
1177 """
1178 Private helper method to compute the equality of two :class:`SemanticVersion` instances.
1180 :param left: Left operand.
1181 :param right: Right operand.
1182 :returns: ``True``, if ``left`` is equal to ``right``, otherwise it's ``False``.
1183 """
1184 return super()._equal(left, right)
1186 def _compare(self, left: "SemanticVersion", right: "SemanticVersion") -> Nullable[bool]:
1187 """
1188 Private helper method to compute the comparison of two :class:`SemanticVersion` instances.
1190 :param left: Left operand.
1191 :param right: Right operand.
1192 :returns: ``True``, if ``left`` is smaller than ``right``. |br|
1193 False if ``left`` is greater than ``right``. |br|
1194 Otherwise it's None (both operands are equal).
1195 """
1196 return super()._compare(left, right)
1198 def __eq__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1199 """
1200 Compare two version numbers for equality.
1202 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1203 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1204 number is assumed (all other parts are zero).
1206 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1207 number.
1209 :param other: Operand to compare against.
1210 :returns: ``True``, if both version numbers are equal.
1211 :raises ValueError: If parameter ``other`` is None.
1212 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1213 """
1214 return super().__eq__(other)
1216 def __ne__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1217 """
1218 Compare two version numbers for inequality.
1220 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1221 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1222 number is assumed (all other parts are zero).
1224 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1225 number.
1227 :param other: Operand to compare against.
1228 :returns: ``True``, if both version numbers are not equal.
1229 :raises ValueError: If parameter ``other`` is None.
1230 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1231 """
1232 return super().__ne__(other)
1234 def __lt__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1235 """
1236 Compare two version numbers if the version is less than the second operand.
1238 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1239 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1240 number is assumed (all other parts are zero).
1242 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1243 number.
1245 :param other: Operand to compare against.
1246 :returns: ``True``, if version is less than the second operand.
1247 :raises ValueError: If parameter ``other`` is None.
1248 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1249 """
1250 return super().__lt__(other)
1252 def __le__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1253 """
1254 Compare two version numbers if the version is less than or equal the second operand.
1256 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1257 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1258 number is assumed (all other parts are zero).
1260 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1261 number.
1263 :param other: Operand to compare against.
1264 :returns: ``True``, if version is less than or equal the second operand.
1265 :raises ValueError: If parameter ``other`` is None.
1266 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1267 """
1268 return super().__le__(other)
1270 def __gt__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1271 """
1272 Compare two version numbers if the version is greater than the second operand.
1274 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1275 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1276 number is assumed (all other parts are zero).
1278 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1279 number.
1281 :param other: Operand to compare against.
1282 :returns: ``True``, if version is greater than the second operand.
1283 :raises ValueError: If parameter ``other`` is None.
1284 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1285 """
1286 return super().__gt__(other)
1288 def __ge__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1289 """
1290 Compare two version numbers if the version is greater than or equal the second operand.
1292 The second operand should be an instance of :class:`SemanticVersion`, but ``str`` and ``int`` are accepted, too. |br|
1293 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1294 number is assumed (all other parts are zero).
1296 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1297 number.
1299 :param other: Operand to compare against.
1300 :returns: ``True``, if version is greater than or equal the second operand.
1301 :raises ValueError: If parameter ``other`` is None.
1302 :raises TypeError: If parameter ``other`` is not of type :class:`SemanticVersion`, :class:`str` or :class:`ìnt`.
1303 """
1304 return super().__ge__(other)
1306 def __rshift__(self, other: Union["SemanticVersion", str, int, None]) -> bool:
1307 return super().__rshift__(other)
1309 def __hash__(self) -> int:
1310 return super().__hash__()
1312 def __format__(self, formatSpec: str) -> str:
1313 result = self._format(formatSpec)
1315 if (pos := result.find("%")) != -1 and result[pos + 1] != "%": # pragma: no cover
1316 raise ValueError(f"Unknown format specifier '%{result[pos + 1]}' in '{formatSpec}'.")
1318 return result.replace("%%", "%")
1320 def __repr__(self) -> str:
1321 """
1322 Return a string representation of this version number without prefix ``v``.
1324 :returns: Raw version number representation without a prefix.
1325 """
1326 return f"{self._prefix if Parts.Prefix in self._parts else ''}{self._major}.{self._minor}.{self._micro}"
1328 def __str__(self) -> str:
1329 """
1330 Return a string representation of this version number.
1332 :returns: Version number representation.
1333 """
1334 result = self._prefix if Parts.Prefix in self._parts else ""
1335 result += f"{self._major}" # major is always present
1336 result += f".{self._minor}" if Parts.Minor in self._parts else ""
1337 result += f".{self._micro}" if Parts.Micro in self._parts else ""
1338 result += f".{self._build}" if Parts.Build in self._parts else ""
1339 if self._releaseLevel is ReleaseLevel.Development:
1340 result += "-dev"
1341 elif self._releaseLevel is ReleaseLevel.Alpha:
1342 result += f".alpha{self._releaseNumber}"
1343 elif self._releaseLevel is ReleaseLevel.Beta:
1344 result += f".beta{self._releaseNumber}"
1345 elif self._releaseLevel is ReleaseLevel.Gamma: 1345 ↛ 1346line 1345 didn't jump to line 1346 because the condition on line 1345 was never true
1346 result += f".gamma{self._releaseNumber}"
1347 elif self._releaseLevel is ReleaseLevel.ReleaseCandidate:
1348 result += f".rc{self._releaseNumber}"
1349 result += f".post{self._post}" if Parts.Post in self._parts else ""
1350 result += f".dev{self._dev}" if Parts.Dev in self._parts else ""
1351 result += f"+{self._postfix}" if Parts.Postfix in self._parts else ""
1353 return result
1356@export
1357class PythonVersion(SemanticVersion):
1358 """
1359 Represents a Python version.
1360 """
1362 @classmethod
1363 def FromSysVersionInfo(cls) -> "PythonVersion":
1364 """
1365 Create a Python version from :data:`sys.version_info`.
1367 :returns: A PythonVersion instance of the current Python interpreter's version.
1368 """
1369 from sys import version_info
1371 if version_info.releaselevel == "final":
1372 rl = ReleaseLevel.Final
1373 number = None
1374 else: # pragma: no cover
1375 number = version_info.serial
1377 if version_info.releaselevel == "alpha":
1378 rl = ReleaseLevel.Alpha
1379 elif version_info.releaselevel == "beta":
1380 rl = ReleaseLevel.Beta
1381 elif version_info.releaselevel == "candidate":
1382 rl = ReleaseLevel.ReleaseCandidate
1383 else: # pragma: no cover
1384 raise ToolingException(f"Unsupported release level '{version_info.releaselevel}'.")
1386 return cls(version_info.major, version_info.minor, version_info.micro, level=rl, number=number)
1388 def __hash__(self) -> int:
1389 return super().__hash__()
1391 def __str__(self) -> str:
1392 """
1393 Return a string representation of this version number.
1395 :returns: Version number representation.
1396 """
1397 result = self._prefix if Parts.Prefix in self._parts else ""
1398 result += f"{self._major}" # major is always present
1399 result += f".{self._minor}" if Parts.Minor in self._parts else ""
1400 result += f".{self._micro}" if Parts.Micro in self._parts else ""
1401 if self._releaseLevel is ReleaseLevel.Alpha:
1402 result += f"a{self._releaseNumber}"
1403 elif self._releaseLevel is ReleaseLevel.Beta:
1404 result += f"b{self._releaseNumber}"
1405 elif self._releaseLevel is ReleaseLevel.Gamma:
1406 result += f"c{self._releaseNumber}"
1407 elif self._releaseLevel is ReleaseLevel.ReleaseCandidate:
1408 result += f"rc{self._releaseNumber}"
1409 result += f".post{self._post}" if Parts.Post in self._parts else ""
1410 result += f".dev{self._dev}" if Parts.Dev in self._parts else ""
1411 result += f"+{self._postfix}" if Parts.Postfix in self._parts else ""
1413 return result
1416@export
1417class CalendarVersion(Version):
1418 """Representation of a calendar version number like ``2021.10``."""
1420 def __init__(
1421 self,
1422 major: int,
1423 minor: Nullable[int] = None,
1424 micro: Nullable[int] = None,
1425 build: Nullable[int] = None,
1426 flags: Flags = Flags.Clean,
1427 prefix: Nullable[str] = None,
1428 postfix: Nullable[str] = None
1429 ) -> None:
1430 """
1431 Initializes a calendar version number representation.
1433 :param major: Major number part of the version number.
1434 :param minor: Minor number part of the version number.
1435 :param micro: Micro (patch) number part of the version number.
1436 :param build: Build number part of the version number.
1437 :param flags: The version number's flags.
1438 :param prefix: The version number's prefix.
1439 :param postfix: The version number's postfix.
1440 :raises TypeError: If parameter 'major' is not of type int.
1441 :raises ValueError: If parameter 'major' is a negative number.
1442 :raises TypeError: If parameter 'minor' is not of type int.
1443 :raises ValueError: If parameter 'minor' is a negative number.
1444 :raises TypeError: If parameter 'micro' is not of type int.
1445 :raises ValueError: If parameter 'micro' is a negative number.
1446 :raises TypeError: If parameter 'build' is not of type int.
1447 :raises ValueError: If parameter 'build' is a negative number.
1448 :raises TypeError: If parameter 'prefix' is not of type str.
1449 :raises TypeError: If parameter 'postfix' is not of type str.
1450 """
1451 super().__init__(major, minor, micro, build=build, postfix=postfix, prefix=prefix, flags=flags)
1453 @classmethod
1454 def Parse(cls, versionString: Nullable[str], validator: Nullable[Callable[["CalendarVersion"], bool]] = None) -> "CalendarVersion":
1455 """
1456 Parse a version string and return a :class:`CalendarVersion` instance.
1458 :param versionString: The version string to parse.
1459 :returns: An object representing a calendar version.
1460 :raises TypeError: If parameter ``other`` is not a string.
1461 :raises ValueError: If parameter ``other`` is None.
1462 :raises ValueError: If parameter ``other`` is empty.
1463 """
1464 parts = Parts.Unknown
1466 if versionString is None:
1467 raise ValueError("Parameter 'versionString' is None.")
1468 elif not isinstance(versionString, str):
1469 ex = TypeError(f"Parameter 'versionString' is not of type 'str'.")
1470 ex.add_note(f"Got type '{getFullyQualifiedName(versionString)}'.")
1471 raise ex
1472 elif versionString == "":
1473 raise ValueError("Parameter 'versionString' is empty.")
1475 split = versionString.split(".")
1476 length = len(split)
1477 major = int(split[0])
1478 minor = 0
1479 parts |= Parts.Major
1481 if length >= 2:
1482 minor = int(split[1])
1483 parts |= Parts.Minor
1485 flags = Flags.Clean
1487 version = cls(major, minor, flags=flags)
1488 if validator is not None and not validator(version):
1489 raise ValueError(f"Failed to validate version string '{versionString}'.") # pragma: no cover
1491 return version
1493 @property
1494 def Year(self) -> int:
1495 """
1496 Read-only property to access the year part.
1498 :return: The year part.
1499 """
1500 return self._major
1502 def _equal(self, left: "CalendarVersion", right: "CalendarVersion") -> Nullable[bool]:
1503 """
1504 Private helper method to compute the equality of two :class:`CalendarVersion` instances.
1506 :param left: Left parameter.
1507 :param right: Right parameter.
1508 :returns: ``True``, if ``left`` is equal to ``right``, otherwise it's ``False``.
1509 """
1510 return (left._major == right._major) and (left._minor == right._minor) and (left._micro == right._micro)
1512 def _compare(self, left: "CalendarVersion", right: "CalendarVersion") -> Nullable[bool]:
1513 """
1514 Private helper method to compute the comparison of two :class:`CalendarVersion` instances.
1516 :param left: Left parameter.
1517 :param right: Right parameter.
1518 :returns: ``True``, if ``left`` is smaller than ``right``. |br|
1519 False if ``left`` is greater than ``right``. |br|
1520 Otherwise it's None (both parameters are equal).
1521 """
1522 if left._major < right._major:
1523 return True
1524 elif left._major > right._major:
1525 return False
1527 if left._minor < right._minor:
1528 return True
1529 elif left._minor > right._minor:
1530 return False
1532 if left._micro < right._micro: 1532 ↛ 1533line 1532 didn't jump to line 1533 because the condition on line 1532 was never true
1533 return True
1534 elif left._micro > right._micro: 1534 ↛ 1535line 1534 didn't jump to line 1535 because the condition on line 1534 was never true
1535 return False
1537 return None
1539 def __eq__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1540 """
1541 Compare two version numbers for equality.
1543 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1544 In case of ``str``, it's tried to parse the string as a calendar version number. In case of ``int``, a single major
1545 number is assumed (all other parts are zero).
1547 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1548 number.
1550 :param other: Parameter to compare against.
1551 :returns: ``True``, if both version numbers are equal.
1552 :raises ValueError: If parameter ``other`` is None.
1553 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1554 """
1555 return super().__eq__(other)
1557 def __ne__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1558 """
1559 Compare two version numbers for inequality.
1561 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1562 In case of ``str``, it's tried to parse the string as a calendar version number. In case of ``int``, a single major
1563 number is assumed (all other parts are zero).
1565 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1566 number.
1568 :param other: Parameter to compare against.
1569 :returns: ``True``, if both version numbers are not equal.
1570 :raises ValueError: If parameter ``other`` is None.
1571 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1572 """
1573 return super().__ne__(other)
1575 def __lt__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1576 """
1577 Compare two version numbers if the version is less than the second operand.
1579 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1580 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1581 number is assumed (all other parts are zero).
1583 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1584 number.
1586 :param other: Parameter to compare against.
1587 :returns: ``True``, if version is less than the second operand.
1588 :raises ValueError: If parameter ``other`` is None.
1589 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1590 """
1591 return super().__lt__(other)
1593 def __le__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1594 """
1595 Compare two version numbers if the version is less than or equal the second operand.
1597 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1598 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1599 number is assumed (all other parts are zero).
1601 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1602 number.
1604 :param other: Parameter to compare against.
1605 :returns: ``True``, if version is less than or equal the second operand.
1606 :raises ValueError: If parameter ``other`` is None.
1607 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1608 """
1609 return super().__le__(other)
1611 def __gt__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1612 """
1613 Compare two version numbers if the version is greater than the second operand.
1615 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1616 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1617 number is assumed (all other parts are zero).
1619 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1620 number.
1622 :param other: Parameter to compare against.
1623 :returns: ``True``, if version is greater than the second operand.
1624 :raises ValueError: If parameter ``other`` is None.
1625 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1626 """
1627 return super().__gt__(other)
1629 def __ge__(self, other: Union["CalendarVersion", str, int, None]) -> bool:
1630 """
1631 Compare two version numbers if the version is greater than or equal the second operand.
1633 The second operand should be an instance of :class:`CalendarVersion`, but ``str`` and ``int`` are accepted, too. |br|
1634 In case of ``str``, it's tried to parse the string as a semantic version number. In case of ``int``, a single major
1635 number is assumed (all other parts are zero).
1637 ``float`` is not supported, due to rounding issues when converting the fractional part of the float to a minor
1638 number.
1640 :param other: Parameter to compare against.
1641 :returns: ``True``, if version is greater than or equal the second operand.
1642 :raises ValueError: If parameter ``other`` is None.
1643 :raises TypeError: If parameter ``other`` is not of type :class:`CalendarVersion`, :class:`str` or :class:`ìnt`.
1644 """
1645 return super().__ge__(other)
1647 def __hash__(self) -> int:
1648 return super().__hash__()
1650 def __format__(self, formatSpec: str) -> str:
1651 """
1652 Return a string representation of this version number according to the format specification.
1654 .. topic:: Format Specifiers
1656 * ``%M`` - major number (year)
1657 * ``%m`` - minor number (month/week)
1659 :param formatSpec: The format specification.
1660 :return: Formatted version number.
1661 """
1662 if formatSpec == "":
1663 return self.__str__()
1665 result = formatSpec
1666 # result = result.replace("%P", str(self._prefix))
1667 result = result.replace("%M", str(self._major))
1668 result = result.replace("%m", str(self._minor))
1669 # result = result.replace("%p", str(self._pre))
1671 return result.replace("%%", "%")
1673 def __repr__(self) -> str:
1674 """
1675 Return a string representation of this version number without prefix ``v``.
1677 :returns: Raw version number representation without a prefix.
1678 """
1679 return f"{self._major}.{self._minor}"
1681 def __str__(self) -> str:
1682 """
1683 Return a string representation of this version number with prefix ``v``.
1685 :returns: Version number representation including a prefix.
1686 """
1687 result = f"{self._major}"
1688 result += f".{self._minor}" if Parts.Minor in self._parts else ""
1690 return result
1693@export
1694class YearMonthVersion(CalendarVersion):
1695 """Representation of a calendar version number made of year and month like ``2021.10``."""
1697 def __init__(
1698 self,
1699 year: int,
1700 month: Nullable[int] = None,
1701 build: Nullable[int] = None,
1702 flags: Flags = Flags.Clean,
1703 prefix: Nullable[str] = None,
1704 postfix: Nullable[str] = None
1705 ) -> None:
1706 """
1707 Initializes a year-month version number representation.
1709 :param year: Year part of the version number.
1710 :param month: Month part of the version number.
1711 :param build: Build number part of the version number.
1712 :param flags: The version number's flags.
1713 :param prefix: The version number's prefix.
1714 :param postfix: The version number's postfix.
1715 :raises TypeError: If parameter 'major' is not of type int.
1716 :raises ValueError: If parameter 'major' is a negative number.
1717 :raises TypeError: If parameter 'minor' is not of type int.
1718 :raises ValueError: If parameter 'minor' is a negative number.
1719 :raises TypeError: If parameter 'micro' is not of type int.
1720 :raises ValueError: If parameter 'micro' is a negative number.
1721 :raises TypeError: If parameter 'build' is not of type int.
1722 :raises ValueError: If parameter 'build' is a negative number.
1723 :raises TypeError: If parameter 'prefix' is not of type str.
1724 :raises TypeError: If parameter 'postfix' is not of type str.
1725 """
1726 super().__init__(year, month, 0, build, flags, prefix, postfix)
1728 @property
1729 def Month(self) -> int:
1730 """
1731 Read-only property to access the month part.
1733 :return: The month part.
1734 """
1735 return self._minor
1737 def __hash__(self) -> int:
1738 return super().__hash__()
1741@export
1742class YearWeekVersion(CalendarVersion):
1743 """Representation of a calendar version number made of year and week like ``2021.47``."""
1745 def __init__(
1746 self,
1747 year: int,
1748 week: Nullable[int] = None,
1749 build: Nullable[int] = None,
1750 flags: Flags = Flags.Clean,
1751 prefix: Nullable[str] = None,
1752 postfix: Nullable[str] = None
1753 ) -> None:
1754 """
1755 Initializes a year-week version number representation.
1757 :param year: Year part of the version number.
1758 :param week: Week part of the version number.
1759 :param build: Build number part of the version number.
1760 :param flags: The version number's flags.
1761 :param prefix: The version number's prefix.
1762 :param postfix: The version number's postfix.
1763 :raises TypeError: If parameter 'major' is not of type int.
1764 :raises ValueError: If parameter 'major' is a negative number.
1765 :raises TypeError: If parameter 'minor' is not of type int.
1766 :raises ValueError: If parameter 'minor' is a negative number.
1767 :raises TypeError: If parameter 'micro' is not of type int.
1768 :raises ValueError: If parameter 'micro' is a negative number.
1769 :raises TypeError: If parameter 'build' is not of type int.
1770 :raises ValueError: If parameter 'build' is a negative number.
1771 :raises TypeError: If parameter 'prefix' is not of type str.
1772 :raises TypeError: If parameter 'postfix' is not of type str.
1773 """
1774 super().__init__(year, week, 0, build, flags, prefix, postfix)
1776 @property
1777 def Week(self) -> int:
1778 """
1779 Read-only property to access the week part.
1781 :return: The week part.
1782 """
1783 return self._minor
1785 def __hash__(self) -> int:
1786 return super().__hash__()
1789@export
1790class YearReleaseVersion(CalendarVersion):
1791 """Representation of a calendar version number made of year and release per year like ``2021.2``."""
1793 def __init__(
1794 self,
1795 year: int,
1796 release: Nullable[int] = None,
1797 build: Nullable[int] = None,
1798 flags: Flags = Flags.Clean,
1799 prefix: Nullable[str] = None,
1800 postfix: Nullable[str] = None
1801 ) -> None:
1802 """
1803 Initializes a year-release version number representation.
1805 :param year: Year part of the version number.
1806 :param release: Release number of the version number.
1807 :param build: Build number part of the version number.
1808 :param flags: The version number's flags.
1809 :param prefix: The version number's prefix.
1810 :param postfix: The version number's postfix.
1811 :raises TypeError: If parameter 'major' is not of type int.
1812 :raises ValueError: If parameter 'major' is a negative number.
1813 :raises TypeError: If parameter 'minor' is not of type int.
1814 :raises ValueError: If parameter 'minor' is a negative number.
1815 :raises TypeError: If parameter 'micro' is not of type int.
1816 :raises ValueError: If parameter 'micro' is a negative number.
1817 :raises TypeError: If parameter 'build' is not of type int.
1818 :raises ValueError: If parameter 'build' is a negative number.
1819 :raises TypeError: If parameter 'prefix' is not of type str.
1820 :raises TypeError: If parameter 'postfix' is not of type str.
1821 """
1822 super().__init__(year, release, 0, build, flags, prefix, postfix)
1824 @property
1825 def Release(self) -> int:
1826 """
1827 Read-only property to access the release number.
1829 :return: The release number.
1830 """
1831 return self._minor
1833 def __hash__(self) -> int:
1834 return super().__hash__()
1837@export
1838class YearMonthDayVersion(CalendarVersion):
1839 """Representation of a calendar version number made of year, month and day like ``2021.10.15``."""
1841 def __init__(
1842 self,
1843 year: int,
1844 month: Nullable[int] = None,
1845 day: Nullable[int] = None,
1846 build: Nullable[int] = None,
1847 flags: Flags = Flags.Clean,
1848 prefix: Nullable[str] = None,
1849 postfix: Nullable[str] = None
1850 ) -> None:
1851 """
1852 Initializes a year-month-day version number representation.
1854 :param year: Year part of the version number.
1855 :param month: Month part of the version number.
1856 :param day: Day part of the version number.
1857 :param build: Build number part of the version number.
1858 :param flags: The version number's flags.
1859 :param prefix: The version number's prefix.
1860 :param postfix: The version number's postfix.
1861 :raises TypeError: If parameter 'major' is not of type int.
1862 :raises ValueError: If parameter 'major' is a negative number.
1863 :raises TypeError: If parameter 'minor' is not of type int.
1864 :raises ValueError: If parameter 'minor' is a negative number.
1865 :raises TypeError: If parameter 'micro' is not of type int.
1866 :raises ValueError: If parameter 'micro' is a negative number.
1867 :raises TypeError: If parameter 'build' is not of type int.
1868 :raises ValueError: If parameter 'build' is a negative number.
1869 :raises TypeError: If parameter 'prefix' is not of type str.
1870 :raises TypeError: If parameter 'postfix' is not of type str.
1871 """
1872 super().__init__(year, month, day, build, flags, prefix, postfix)
1874 @property
1875 def Month(self) -> int:
1876 """
1877 Read-only property to access the month part.
1879 :return: The month part.
1880 """
1881 return self._minor
1883 @property
1884 def Day(self) -> int:
1885 """
1886 Read-only property to access the day part.
1888 :return: The day part.
1889 """
1890 return self._micro
1892 def __hash__(self) -> int:
1893 return super().__hash__()
1896V = TypeVar("V", bound=Version)
1898@export
1899class RangeBoundHandling(Flag):
1900 """
1901 A flag defining how to handle bounds in a range.
1903 If a bound is inclusive, the bound's value is within the range. If a bound is exclusive, the bound's value is the
1904 first value outside the range. Inclusive and exclusive behavior can be mixed for lower and upper bounds.
1905 """
1906 BothBoundsInclusive = 0 #: Lower and upper bound are inclusive.
1907 LowerBoundInclusive = 0 #: Lower bound is inclusive.
1908 UpperBoundInclusive = 0 #: Upper bound is inclusive.
1909 LowerBoundExclusive = 1 #: Lower bound is exclusive.
1910 UpperBoundExclusive = 2 #: Upper bound is exclusive.
1911 BothBoundsExclusive = 3 #: Lower and upper bound are exclusive.
1914@export
1915class VersionRange(Generic[V], metaclass=ExtendedType, slots=True):
1916 """
1917 Representation of a version range described by a lower bound and upper bound version.
1919 This version range works with :class:`SemanticVersion` and :class:`CalendarVersion` and its derived classes.
1920 """
1921 _lowerBound: V
1922 _upperBound: V
1923 _boundHandling: RangeBoundHandling
1925 def __init__(self, lowerBound: V, upperBound: V, boundHandling: RangeBoundHandling = RangeBoundHandling.BothBoundsInclusive) -> None:
1926 """
1927 Initializes a version range described by a lower and upper bound.
1929 :param lowerBound: lowest version (inclusive).
1930 :param upperBound: hightest version (inclusive).
1931 :raises TypeError: If parameter ``lowerBound`` is not of type :class:`Version`.
1932 :raises TypeError: If parameter ``upperBound`` is not of type :class:`Version`.
1933 :raises TypeError: If parameter ``lowerBound`` and ``upperBound`` are unrelated types.
1934 :raises ValueError: If parameter ``lowerBound`` isn't less than or equal to ``upperBound``.
1935 """
1936 if not isinstance(lowerBound, Version):
1937 ex = TypeError(f"Parameter 'lowerBound' is not of type 'Version'.")
1938 ex.add_note(f"Got type '{getFullyQualifiedName(lowerBound)}'.")
1939 raise ex
1941 if not isinstance(upperBound, Version):
1942 ex = TypeError(f"Parameter 'upperBound' is not of type 'Version'.")
1943 ex.add_note(f"Got type '{getFullyQualifiedName(upperBound)}'.")
1944 raise ex
1946 if not ((lBC := lowerBound.__class__) is (uBC := upperBound.__class__) or issubclass(lBC, uBC) or issubclass(uBC, lBC)):
1947 ex = TypeError(f"Parameters 'lowerBound' and 'upperBound' are not compatible with each other.")
1948 ex.add_note(f"Got type '{getFullyQualifiedName(lowerBound)}' for lowerBound and type '{getFullyQualifiedName(upperBound)}' for upperBound.")
1949 raise ex
1951 if not (lowerBound <= upperBound):
1952 ex = ValueError(f"Parameter 'lowerBound' isn't less than parameter 'upperBound'.")
1953 ex.add_note(f"Got '{lowerBound}' for lowerBound and '{upperBound}' for upperBound.")
1954 raise ex
1956 self._lowerBound = lowerBound
1957 self._upperBound = upperBound
1958 self._boundHandling = boundHandling
1960 @property
1961 def LowerBound(self) -> V:
1962 """
1963 Property to access the range's lower bound.
1965 :return: Lower bound of the version range.
1966 """
1967 return self._lowerBound
1969 @LowerBound.setter
1970 def LowerBound(self, value: V) -> None:
1971 if not isinstance(value, Version):
1972 ex = TypeError(f"Parameter 'value' is not of type 'Version'.")
1973 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
1974 raise ex
1976 self._lowerBound = value
1978 @readonly
1979 def UpperBound(self) -> V:
1980 """
1981 Property to access the range's upper bound.
1983 :return: Upper bound of the version range.
1984 """
1985 return self._upperBound
1987 @UpperBound.setter
1988 def UpperBound(self, value: V) -> None:
1989 if not isinstance(value, Version):
1990 ex = TypeError(f"Parameter 'value' is not of type 'Version'.")
1991 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
1992 raise ex
1994 self._upperBound = value
1996 @readonly
1997 def BoundHandling(self) -> RangeBoundHandling:
1998 """
1999 Property to access the range's bound handling strategy.
2001 :return: The range's bound handling strategy.
2002 """
2003 return self._boundHandling
2005 @BoundHandling.setter
2006 def BoundHandling(self, value: RangeBoundHandling) -> None:
2007 if not isinstance(value, RangeBoundHandling):
2008 ex = TypeError(f"Parameter 'value' is not of type 'RangeBoundHandling'.")
2009 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
2010 raise ex
2012 self._boundHandling = value
2014 def __and__(self, other: Any) -> "VersionRange[T]":
2015 """
2016 Compute the intersection of two version ranges.
2018 :param other: Second version range to intersect with.
2019 :returns: Intersected version range.
2020 :raises TypeError: If parameter 'other' is not of type :class:`VersionRange`.
2021 :raises ValueError: If intersection is empty.
2022 """
2023 if not isinstance(other, VersionRange): 2023 ↛ 2024line 2023 didn't jump to line 2024 because the condition on line 2023 was never true
2024 ex = TypeError(f"Parameter 'other' is not of type 'VersionRange'.")
2025 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2026 raise ex
2028 if not (isinstance(other._lowerBound, self._lowerBound.__class__) and isinstance(self._lowerBound, other._lowerBound.__class__)): 2028 ↛ 2029line 2028 didn't jump to line 2029 because the condition on line 2028 was never true
2029 ex = TypeError(f"Parameter 'other's LowerBound and this range's 'LowerBound' are not compatible with each other.")
2030 ex.add_note(
2031 f"Got type '{getFullyQualifiedName(other._lowerBound)}' for other.LowerBound and type '{getFullyQualifiedName(self._lowerBound)}' for self.LowerBound.")
2032 raise ex
2034 if other._lowerBound < self._lowerBound:
2035 lBound = self._lowerBound
2036 elif other._lowerBound in self: 2036 ↛ 2039line 2036 didn't jump to line 2039 because the condition on line 2036 was always true
2037 lBound = other._lowerBound
2038 else:
2039 raise ValueError()
2041 if other._upperBound > self._upperBound:
2042 uBound = self._upperBound
2043 elif other._upperBound in self: 2043 ↛ 2046line 2043 didn't jump to line 2046 because the condition on line 2043 was always true
2044 uBound = other._upperBound
2045 else:
2046 raise ValueError()
2048 return self.__class__(lBound, uBound)
2050 def __lt__(self, other: Any) -> bool:
2051 """
2052 Compare a version range and a version numbers if the version range is less than the second operand (version).
2054 :param other: Operand to compare against.
2055 :returns: ``True``, if version range is less than the second operand (version).
2056 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2057 """
2058 # TODO: support VersionRange < VersionRange too
2059 # TODO: support str, int, ... like Version ?
2060 if not isinstance(other, Version):
2061 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2062 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2063 raise ex
2065 if not (isinstance(other, self._lowerBound.__class__) and isinstance(self._lowerBound, other.__class__)): 2065 ↛ 2066line 2065 didn't jump to line 2066 because the condition on line 2065 was never true
2066 ex = TypeError(f"Parameter 'other' is not compatible with version range.")
2067 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2068 raise ex
2070 return self._upperBound < other
2072 def __le__(self, other: Any) -> bool:
2073 """
2074 Compare a version range and a version numbers if the version range is less than or equal the second operand (version).
2076 :param other: Operand to compare against.
2077 :returns: ``True``, if version range is less than or equal the second operand (version).
2078 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2079 """
2080 # TODO: support VersionRange < VersionRange too
2081 # TODO: support str, int, ... like Version ?
2082 if not isinstance(other, Version): 2082 ↛ 2083line 2082 didn't jump to line 2083 because the condition on line 2082 was never true
2083 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2084 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2085 raise ex
2087 if not (isinstance(other, self._lowerBound.__class__) and isinstance(self._lowerBound, other.__class__)): 2087 ↛ 2088line 2087 didn't jump to line 2088 because the condition on line 2087 was never true
2088 ex = TypeError(f"Parameter 'other' is not compatible with version range.")
2089 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2090 raise ex
2092 if RangeBoundHandling.UpperBoundExclusive in self._boundHandling:
2093 return self._upperBound < other
2094 else:
2095 return self._upperBound <= other
2097 def __gt__(self, other: Any) -> bool:
2098 """
2099 Compare a version range and a version numbers if the version range is greater than the second operand (version).
2101 :param other: Operand to compare against.
2102 :returns: ``True``, if version range is greater than the second operand (version).
2103 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2104 """
2105 # TODO: support VersionRange < VersionRange too
2106 # TODO: support str, int, ... like Version ?
2107 if not isinstance(other, Version): 2107 ↛ 2108line 2107 didn't jump to line 2108 because the condition on line 2107 was never true
2108 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2109 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2110 raise ex
2112 if not (isinstance(other, self._upperBound.__class__) and isinstance(self._upperBound, other.__class__)): 2112 ↛ 2113line 2112 didn't jump to line 2113 because the condition on line 2112 was never true
2113 ex = TypeError(f"Parameter 'other' is not compatible with version range.")
2114 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2115 raise ex
2117 return self._lowerBound > other
2119 def __ge__(self, other: Any) -> bool:
2120 """
2121 Compare a version range and a version numbers if the version range is greater than or equal the second operand (version).
2123 :param other: Operand to compare against.
2124 :returns: ``True``, if version range is greater than or equal the second operand (version).
2125 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2126 """
2127 # TODO: support VersionRange < VersionRange too
2128 # TODO: support str, int, ... like Version ?
2129 if not isinstance(other, Version): 2129 ↛ 2130line 2129 didn't jump to line 2130 because the condition on line 2129 was never true
2130 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2131 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2132 raise ex
2134 if not (isinstance(other, self._upperBound.__class__) and isinstance(self._upperBound, other.__class__)): 2134 ↛ 2135line 2134 didn't jump to line 2135 because the condition on line 2134 was never true
2135 ex = TypeError(f"Parameter 'other' is not compatible with version range.")
2136 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2137 raise ex
2139 if RangeBoundHandling.LowerBoundExclusive in self._boundHandling: 2139 ↛ 2140line 2139 didn't jump to line 2140 because the condition on line 2139 was never true
2140 return self._lowerBound > other
2141 else:
2142 return self._lowerBound >= other
2144 def __contains__(self, version: Version) -> bool:
2145 """
2146 Check if the version is in the version range.
2148 :param version: Version to check.
2149 :returns: ``True``, if version is in range.
2150 :raises TypeError: If parameter ``version`` is not of type :class:`Version`.
2151 """
2152 if not isinstance(version, Version): 2152 ↛ 2153line 2152 didn't jump to line 2153 because the condition on line 2152 was never true
2153 ex = TypeError(f"Parameter 'item' is not of type 'Version'.")
2154 ex.add_note(f"Got type '{getFullyQualifiedName(version)}'.")
2155 raise ex
2157 if self._boundHandling is RangeBoundHandling.BothBoundsInclusive: 2157 ↛ 2159line 2157 didn't jump to line 2159 because the condition on line 2157 was always true
2158 return self._lowerBound <= version <= self._upperBound
2159 elif self._boundHandling is (RangeBoundHandling.LowerBoundInclusive | RangeBoundHandling.UpperBoundExclusive):
2160 return self._lowerBound <= version < self._upperBound
2161 elif self._boundHandling is (RangeBoundHandling.LowerBoundExclusive | RangeBoundHandling.UpperBoundInclusive):
2162 return self._lowerBound < version <= self._upperBound
2163 else:
2164 return self._lowerBound < version < self._upperBound
2167@export
2168class VersionSet(Generic[V], metaclass=ExtendedType, slots=True):
2169 """
2170 Representation of an ordered set of versions.
2172 This version set works with :class:`SemanticVersion` and :class:`CalendarVersion` and its derived classes.
2173 """
2174 _items: List[V] #: An ordered list of set members.
2176 def __init__(self, versions: Union[Version, Iterable[V]]):
2177 """
2178 Initializes a version set either by a single version or an iterable of versions.
2180 :param versions: A single version or an iterable of versions.
2181 :raises ValueError: If parameter ``versions`` is None`.
2182 :raises TypeError: In case of a single version, if parameter ``version`` is not of type :class:`Version`.
2183 :raises TypeError: In case of an iterable, if parameter ``versions`` containes elements, which are not of type :class:`Version`.
2184 :raises TypeError: If parameter ``versions`` is neither a single version nor an iterable thereof.
2185 """
2186 if versions is None:
2187 raise ValueError(f"Parameter 'versions' is None.")
2189 if isinstance(versions, Version):
2190 self._items = [versions]
2191 elif isinstance(versions, abc_Iterable): 2191 ↛ 2209line 2191 didn't jump to line 2209 because the condition on line 2191 was always true
2192 iterator = iter(versions)
2193 try:
2194 firstVersion = next(iterator)
2195 except StopIteration:
2196 self._items = []
2197 return
2199 if not isinstance(firstVersion, Version): 2199 ↛ 2200line 2199 didn't jump to line 2200 because the condition on line 2199 was never true
2200 raise TypeError(f"First element in parameter 'versions' is not of type Version.")
2202 baseType = firstVersion.__class__
2203 for version in iterator:
2204 if not isinstance(version, baseType):
2205 raise TypeError(f"Element from parameter 'versions' is not of type {baseType.__name__}")
2207 self._items = list(sorted(versions))
2208 else:
2209 raise TypeError(f"Parameter 'versions' is not an Iterable.")
2211 def __and__(self, other: "VersionSet[V]") -> "VersionSet[T]":
2212 """
2213 Compute intersection of two version sets.
2215 :param other: Second set of versions.
2216 :returns: Intersection of two version sets.
2217 """
2218 selfIterator = self.__iter__()
2219 otherIterator = other.__iter__()
2221 result = []
2222 try:
2223 selfValue = next(selfIterator)
2224 otherValue = next(otherIterator)
2226 while True:
2227 if selfValue < otherValue:
2228 selfValue = next(selfIterator)
2229 elif otherValue < selfValue:
2230 otherValue = next(otherIterator)
2231 else:
2232 result.append(selfValue)
2233 selfValue = next(selfIterator)
2234 otherValue = next(otherIterator)
2236 except StopIteration:
2237 pass
2239 return VersionSet(result)
2241 def __or__(self, other: "VersionSet[V]") -> "VersionSet[T]":
2242 """
2243 Compute union of two version sets.
2245 :param other: Second set of versions.
2246 :returns: Union of two version sets.
2247 """
2248 selfIterator = self.__iter__()
2249 otherIterator = other.__iter__()
2251 result = []
2252 try:
2253 selfValue = next(selfIterator)
2254 except StopIteration:
2255 for otherValue in otherIterator:
2256 result.append(otherValue)
2258 try:
2259 otherValue = next(otherIterator)
2260 except StopIteration:
2261 for selfValue in selfIterator:
2262 result.append(selfValue)
2264 while True:
2265 if selfValue < otherValue:
2266 result.append(selfValue)
2267 try:
2268 selfValue = next(selfIterator)
2269 except StopIteration:
2270 result.append(otherValue)
2271 for otherValue in otherIterator: 2271 ↛ 2272line 2271 didn't jump to line 2272 because the loop on line 2271 never started
2272 result.append(otherValue)
2274 break
2275 elif otherValue < selfValue:
2276 result.append(otherValue)
2277 try:
2278 otherValue = next(otherIterator)
2279 except StopIteration:
2280 result.append(selfValue)
2281 for selfValue in selfIterator:
2282 result.append(selfValue)
2284 break
2285 else:
2286 result.append(selfValue)
2287 try:
2288 selfValue = next(selfIterator)
2289 except StopIteration:
2290 for otherValue in otherIterator: 2290 ↛ 2291line 2290 didn't jump to line 2291 because the loop on line 2290 never started
2291 result.append(otherValue)
2293 break
2295 try:
2296 otherValue = next(otherIterator)
2297 except StopIteration:
2298 for selfValue in selfIterator:
2299 result.append(selfValue)
2301 break
2303 return VersionSet(result)
2305 def __lt__(self, other: Any) -> bool:
2306 """
2307 Compare a version set and a version numbers if the version set is less than the second operand (version).
2309 :param other: Operand to compare against.
2310 :returns: ``True``, if version set is less than the second operand (version).
2311 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2312 """
2313 # TODO: support VersionRange < VersionRange too
2314 # TODO: support str, int, ... like Version ?
2315 if not isinstance(other, Version):
2316 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2317 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2318 raise ex
2320 return self._items[-1] < other
2322 def __le__(self, other: Any) -> bool:
2323 """
2324 Compare a version set and a version numbers if the version set is less than or equal the second operand (version).
2326 :param other: Operand to compare against.
2327 :returns: ``True``, if version set is less than or equal the second operand (version).
2328 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2329 """
2330 # TODO: support VersionRange < VersionRange too
2331 # TODO: support str, int, ... like Version ?
2332 if not isinstance(other, Version): 2332 ↛ 2333line 2332 didn't jump to line 2333 because the condition on line 2332 was never true
2333 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2334 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2335 raise ex
2337 return self._items[-1] <= other
2339 def __gt__(self, other: Any) -> bool:
2340 """
2341 Compare a version set and a version numbers if the version set is greater than the second operand (version).
2343 :param other: Operand to compare against.
2344 :returns: ``True``, if version set is greater than the second operand (version).
2345 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2346 """
2347 # TODO: support VersionRange < VersionRange too
2348 # TODO: support str, int, ... like Version ?
2349 if not isinstance(other, Version): 2349 ↛ 2350line 2349 didn't jump to line 2350 because the condition on line 2349 was never true
2350 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2351 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2352 raise ex
2354 return self._items[0] > other
2356 def __ge__(self, other: Any) -> bool:
2357 """
2358 Compare a version set and a version numbers if the version set is greater than or equal the second operand (version).
2360 :param other: Operand to compare against.
2361 :returns: ``True``, if version set is greater than or equal the second operand (version).
2362 :raises TypeError: If parameter ``other`` is not of type :class:`Version`.
2363 """
2364 # TODO: support VersionRange < VersionRange too
2365 # TODO: support str, int, ... like Version ?
2366 if not isinstance(other, Version): 2366 ↛ 2367line 2366 didn't jump to line 2367 because the condition on line 2366 was never true
2367 ex = TypeError(f"Parameter 'other' is not of type 'Version'.")
2368 ex.add_note(f"Got type '{getFullyQualifiedName(other)}'.")
2369 raise ex
2371 return self._items[0] >= other
2373 def __contains__(self, version: V) -> bool:
2374 """
2375 Checks if the version a member of the set.
2377 :param version: The version to check.
2378 :returns: ``True``, if the version is a member of the set.
2379 """
2380 return version in self._items
2382 def __len__(self) -> int:
2383 """
2384 Returns the number of members in the set.
2386 :returns: Number of set members.
2387 """
2388 return len(self._items)
2390 def __iter__(self) -> Iterator[V]:
2391 """
2392 Returns an iterator to iterate all versions of this set from lowest to highest.
2394 :returns: Iterator to iterate versions.
2395 """
2396 return self._items.__iter__()
2398 def __getitem__(self, index: int) -> V:
2399 """
2400 Access to a version of a set by index.
2402 :param index: The index of the version to access.
2403 :returns: The indexed version.
2405 .. hint:: Versions are ordered from lowest to highest version number.
2406 """
2407 return self._items[index]