Skip to content

Commit 6ecc067

Browse files
committed
Improve util.version
* Increase flexibility of `Version` class, - allowing it to be applied not only to packages but also to version strings - adding support for versions that include suffixes (e.g. "1.2.3rc1"), which are ignored for comparison purposes * Fix `is_equal` not applying integer conversion to parts before comparison
1 parent ee27499 commit 6ecc067

File tree

2 files changed

+53
-17
lines changed

2 files changed

+53
-17
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
# Changelog
22

3-
## Unreleased Changes
3+
## Unreleased
4+
5+
### Improvements/Changes
6+
7+
* `util.version`: Increase flexibility of `Version` class,
8+
* allowing it to be applied not only to packages but also to version strings
9+
* adding support for versions that include suffixes (e.g. "1.2.3rc1"), which are ignored for comparison purposes
410

511
### Fixes
612

713
* `util.logging`:
814
* `SuspendedLoggersContext`: No longer modify `loggerDict` as this destroyed logger hierarchy integrity;
915
only modify the root logger handlers (which is sufficient to suspend logging)
16+
* `util.version`:
17+
* `Version`: Fix `is_equal` not applying integer conversion to parts before comparison
1018

1119
## v1.6.0 (2025-10-20)
1220

src/sensai/util/version.py

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,48 @@
1-
from .string import ToStringMixin
2-
3-
4-
class Version(ToStringMixin):
1+
class Version:
52
"""
6-
Assists in checking the version of a Python package based on the __version__ attribute
3+
Represents a version, specifically the numeric components of a version string.
4+
5+
Suffixes like "rc1" or "-dev" are ignored, i.e. for a version string like "1.2.3rc1",
6+
the components are [1, 2, 3].
77
"""
8-
def __init__(self, package):
8+
9+
def __init__(self, package_or_version: object | str):
910
"""
10-
:param package: the package object
11+
:param package_or_version: a package object (with a `__version__` attribute) or a version string like "1.2.3".
12+
If a version contains a suffix (like "1.2.3rc1" or "1.2.3-dev"), the suffix is ignored.
1113
"""
12-
self.components = package.__version__.split(".")
14+
if isinstance(package_or_version, str):
15+
version_string = package_or_version
16+
elif hasattr(package_or_version, "__version__"):
17+
package_version_string = getattr(package_or_version, "__version__", None)
18+
if package_version_string is None:
19+
raise ValueError(f"The given package object {package_or_version} has no __version__ attribute")
20+
version_string = package_version_string
21+
else:
22+
raise ValueError("The given argument must be either a version string or a package object with a __version__ attribute")
23+
self.version_string = version_string
24+
self.components = self._get_version_components(version_string)
25+
26+
def __str__(self) -> str:
27+
return self.version_string
1328

14-
def __str__(self):
15-
return ".".join(self.components)
29+
@staticmethod
30+
def _get_version_components(version_string: str) -> list[int]:
31+
components = version_string.split(".")
32+
int_components = []
33+
for c in components:
34+
num_str = ""
35+
for ch in c:
36+
if ch.isdigit():
37+
num_str += ch
38+
else:
39+
break
40+
if num_str == "":
41+
break
42+
int_components.append(int(num_str))
43+
return int_components
1644

17-
def is_at_least(self, *components: int):
45+
def is_at_least(self, *components: int) -> bool:
1846
"""
1947
Checks this version against the given version components.
2048
This version object must contain at least the respective number of components
@@ -23,14 +51,14 @@ def is_at_least(self, *components: int):
2351
:return: True if the version is at least the given version, False otherwise
2452
"""
2553
for i, desired_min_version in enumerate(components):
26-
actual_version = int(self.components[i])
54+
actual_version = self.components[i]
2755
if actual_version < desired_min_version:
2856
return False
2957
elif actual_version > desired_min_version:
3058
return True
3159
return True
3260

33-
def is_at_most(self, *components: int):
61+
def is_at_most(self, *components: int) -> bool:
3462
"""
3563
Checks this version against the given version components.
3664
This version object must contain at least the respective number of components
@@ -39,19 +67,19 @@ def is_at_most(self, *components: int):
3967
:return: True if the version is at most the given version, False otherwise
4068
"""
4169
for i, desired_max_version in enumerate(components):
42-
actual_version = int(self.components[i])
70+
actual_version = self.components[i]
4371
if actual_version > desired_max_version:
4472
return False
4573
elif actual_version < desired_max_version:
4674
return True
4775
return True
4876

49-
def is_equal(self, *components: int):
77+
def is_equal(self, *components: int) -> bool:
5078
"""
5179
Checks this version against the given version components.
5280
This version object must contain at least the respective number of components
5381
5482
:param components: version components in order (i.e. major, minor, patch, etc.)
5583
:return: True if the version is the given version, False otherwise
5684
"""
57-
return self.components[:len(components)] == list(components)
85+
return self.components[: len(components)] == list(components)

0 commit comments

Comments
 (0)