-
Notifications
You must be signed in to change notification settings - Fork 570
CASSPYTHON-10 Update cassandra.util.Version to better support Cassandra version strings #1273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
972a262
9195f76
99a95ed
d63ec96
21c19ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1692,54 +1692,43 @@ def __repr__(self): | |||||||||
| self.lower_bound, self.upper_bound, self.value | ||||||||||
| ) | ||||||||||
|
|
||||||||||
| VERSION_REGEX = re.compile("(\\d+)\\.(\\d+)(\\.\\d+)?(\\.\\d+)?([~\\-]\\w[.\\w]*(?:-\\w[.\\w]*)*)?(\\+[.\\w]+)?") | ||||||||||
|
|
||||||||||
| @total_ordering | ||||||||||
| class Version(object): | ||||||||||
| """ | ||||||||||
| Internal minimalist class to compare versions. | ||||||||||
| A valid version is: <int>.<int>.<int>.<int or str>. | ||||||||||
|
|
||||||||||
| TODO: when python2 support is removed, use packaging.version. | ||||||||||
| """ | ||||||||||
|
|
||||||||||
| _version = None | ||||||||||
| major = None | ||||||||||
| minor = 0 | ||||||||||
| patch = 0 | ||||||||||
| build = 0 | ||||||||||
| prerelease = 0 | ||||||||||
|
|
||||||||||
| def __init__(self, version): | ||||||||||
| self._version = version | ||||||||||
| if '-' in version: | ||||||||||
| version_without_prerelease, self.prerelease = version.split('-', 1) | ||||||||||
| else: | ||||||||||
| version_without_prerelease = version | ||||||||||
| parts = list(reversed(version_without_prerelease.split('.'))) | ||||||||||
| if len(parts) > 4: | ||||||||||
| prerelease_string = "-{}".format(self.prerelease) if self.prerelease else "" | ||||||||||
| log.warning("Unrecognized version: {}. Only 4 components plus prerelease are supported. " | ||||||||||
| "Assuming version as {}{}".format(version, '.'.join(parts[:-5:-1]), prerelease_string)) | ||||||||||
|
|
||||||||||
| match = VERSION_REGEX.match(version) | ||||||||||
| if not match: | ||||||||||
| raise ValueError("Version string did not match expected format") | ||||||||||
|
Comment on lines
+1695
to
+1705
|
||||||||||
| raise ValueError("Version string did not match expected format") | |
| raise ValueError("Version string did not match expected format: {!r}".format(version)) |
Copilot
AI
Mar 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid bare except: here. These blocks will also swallow unexpected exceptions and make debugging harder. Since _cleanup_int/_cleanup_str already handle None, the try/except may be unnecessary; otherwise, catch specific exceptions (e.g. ValueError, TypeError) and consider whether an invalid numeric component should raise vs. default to 0.
Copilot
AI
Mar 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_cleanup_str uses a parameter named str, which shadows Python’s built-in str type. Rename the parameter to avoid shadowing.
| def _cleanup_str(self, str): | |
| return str[1:] if str else 0 | |
| def _cleanup_str(self, s): | |
| return s[1:] if s else 0 |
Copilot
AI
Mar 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Version.__hash__ returns self._version (a str). In Python 3, __hash__ must return an int, and it also needs to be consistent with __eq__. As written, calling hash(Version(...)) will raise TypeError, and even if fixed to return a string hash, it would still violate the hash/eq contract for versions that compare equal but have different original strings (e.g. 4.0-SNAPSHOT vs 4.0.0-SNAPSHOT).
| return self._version | |
| # Hash based on the same components used for equality, to satisfy the hash/eq contract. | |
| return hash((self.major, self.minor, self.patch, self.build, self.prerelease)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Python tuples can be compared directly, so something like this should work
def __gt__(self, other):
# Compare as tuples
return (self.major, self.minor, self.patch, self.build, not self.prerelease) <
(other.major, other.minor, other.patch, other.build, not other.prerelease)
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -209,21 +209,25 @@ class VersionTests(unittest.TestCase): | |||
|
|
||||
| def test_version_parsing(self): | ||||
| versions = [ | ||||
| ('2.0.0', (2, 0, 0, 0, 0)), | ||||
| ('3.1.0', (3, 1, 0, 0, 0)), | ||||
| ('2.4.54', (2, 4, 54, 0, 0)), | ||||
| ('3.1.1.12', (3, 1, 1, 12, 0)), | ||||
| ('3.55.1.build12', (3, 55, 1, 'build12', 0)), | ||||
| ('3.55.1.20190429-TEST', (3, 55, 1, 20190429, 'TEST')), | ||||
| ('4.0-SNAPSHOT', (4, 0, 0, 0, 'SNAPSHOT')), | ||||
| # Test cases here adapted from the Java driver cases | ||||
| # (https://github.com/apache/cassandra-java-driver/blob/4.19.2/core/src/test/java/com/datastax/oss/driver/api/core/VersionTest.java) | ||||
| ('1.2.19', (1, 2, 19, 0, 0)), | ||||
| ('1.2', (1, 2, 0, 0, 0)), | ||||
| ('1.2-beta1-SNAPSHOT', (1, 2, 0, 0, 'beta1-SNAPSHOT')), | ||||
| ('1.2~beta1-SNAPSHOT', (1, 2, 0, 0, 'beta1-SNAPSHOT')), | ||||
| ('1.2.19.2-SNAPSHOT', (1, 2, 19, 2, 'SNAPSHOT')), | ||||
|
|
||||
| # We also include a few test cases from the former impl of this class, mainly to note differences in behaviours | ||||
|
|
||||
| # Note that prerelease tags are expected to start with a hyphen or tilde so the expected tag is | ||||
| # lost in all cases below | ||||
| ('3.55.1.build12', (3, 55, 1, 0, 0)), | ||||
| ('1.0.5.4.3', (1, 0, 5, 4, 0)), | ||||
| ('1-SNAPSHOT', (1, 0, 0, 0, 'SNAPSHOT')), | ||||
| ('4.0.1.2.3.4.5-ABC-123-SNAP-TEST.blah', (4, 0, 1, 2, 'ABC-123-SNAP-TEST.blah')), | ||||
| ('2.1.hello', (2, 1, 0, 0, 0)), | ||||
| ('2.test.1', (2, 0, 0, 0, 0)), | ||||
| ('2.1.hello', (2, 1, 0, 0, 0)) | ||||
| ] | ||||
|
|
||||
| for str_version, expected_result in versions: | ||||
| print(str_version) | ||||
| v = Version(str_version) | ||||
|
Comment on lines
229
to
231
|
||||
| self.assertEqual(str_version, str(v)) | ||||
| self.assertEqual(v.major, expected_result[0]) | ||||
|
|
@@ -232,9 +236,18 @@ def test_version_parsing(self): | |||
| self.assertEqual(v.build, expected_result[3]) | ||||
| self.assertEqual(v.prerelease, expected_result[4]) | ||||
|
|
||||
| # not supported version formats | ||||
| with self.assertRaises(ValueError): | ||||
| Version('test.1.0') | ||||
| # Note that a few of these formats used to be supported when this class was based on the Python versioning scheme. | ||||
| # This has been updated to more directly correspond to the Cassandra versioning scheme. See CASSPYTHON-10 for more | ||||
| # detail. | ||||
| unsupported_versions = [ | ||||
| "test.1.0", | ||||
| '2.test.1' | ||||
| ] | ||||
|
|
||||
| for v in unsupported_versions: | ||||
| print(v) | ||||
|
||||
| print(v) |
Copilot
AI
Mar 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the new normalization behavior (e.g. Version('4.0.0-SNAPSHOT') == Version('4.0-SNAPSHOT')), add a unit test that hash() works and is consistent with equality for equivalent versions. This would catch regressions where __hash__ raises or returns inconsistent values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new
Versionimplementation removed the class docstring entirely. Sincecassandra.util.Versionis used outside this module (e.g. incassandra.metadataand unit tests), it should keep an up-to-date docstring describing the supported version formats and comparison semantics.