Skip to content

Commit 5d7aff1

Browse files
authored
Merge pull request #106 from TG1999/fix_comparison
Fix comparison in version subclasses
2 parents d38d9fd + 279c8cd commit 5d7aff1

8 files changed

Lines changed: 345 additions & 146 deletions

src/univers/versions.py

Lines changed: 51 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
#
55
# Visit https://aboutcode.org and https://github.com/nexB/univers for support and download.
66

7-
import functools
8-
from functools import total_ordering
9-
107
import attr
118
import semantic_version
129
from packaging import version as packaging_version
@@ -59,7 +56,7 @@ def is_valid_alpine_version(s):
5956
return str(i) == left
6057

6158

62-
@attr.s(frozen=True, order=False, hash=True)
59+
@attr.s(frozen=True, order=True, eq=True, hash=True)
6360
class Version:
6461
"""
6562
Base version mixin to subclass for each version syntax implementation.
@@ -73,14 +70,16 @@ class Version:
7370
"""
7471

7572
# the original string used to build this Version
76-
string = attr.ib(type=str)
73+
string = attr.ib(type=str, eq=False, order=False, hash=False)
7774

7875
# the normalized string for this Version, stored without spaces and
7976
# lowercased. Any leading v is removed too.
80-
normalized_string = attr.ib(type=str, default=None, repr=False)
77+
normalized_string = attr.ib(
78+
type=str, default=None, repr=False, eq=False, order=False, hash=False
79+
)
8180

8281
# a comparable scheme-specific version object constructed from the version string
83-
value = attr.ib(default=None, repr=False)
82+
value = attr.ib(default=None, repr=False, eq=True, order=True, hash=True)
8483

8584
def __attrs_post_init__(self):
8685
normalized_string = self.normalize(self.string)
@@ -110,7 +109,7 @@ def normalize(cls, string):
110109
Return a normalized version string from ``string ``. Subclass can override.
111110
"""
112111
# FIXME: Is removing spaces and strip v the right thing to do?
113-
return remove_spaces(string).rstrip("v ").strip()
112+
return remove_spaces(string).lstrip("vV")
114113

115114
@classmethod
116115
def build_value(self, string):
@@ -133,33 +132,7 @@ def satisfies(self, constraint):
133132
def __str__(self):
134133
return str(self.value)
135134

136-
def __eq__(self, other):
137-
if not isinstance(other, self.__class__):
138-
return NotImplemented
139-
return self.value.__eq__(other.value)
140-
141-
def __lt__(self, other):
142-
if not isinstance(other, self.__class__):
143-
return NotImplemented
144-
return self.value.__lt__(other.value)
145-
146-
def __gt__(self, other):
147-
if not isinstance(other, self.__class__):
148-
return NotImplemented
149-
return self.value.__gt__(other.value)
150-
151-
def __le__(self, other):
152-
if not isinstance(other, self.__class__):
153-
return NotImplemented
154-
return self.value.__le__(other.value)
155-
156-
def __ge__(self, other):
157-
if not isinstance(other, self.__class__):
158-
return NotImplemented
159-
return self.value.__ge__(other.value)
160-
161135

162-
@attr.s(frozen=True, order=False, hash=True)
163136
class GenericVersion(Version):
164137
@classmethod
165138
def is_valid(cls, string):
@@ -174,13 +147,13 @@ def is_valid(cls, string):
174147
return super(GenericVersion, cls).is_valid(string)
175148

176149

177-
@attr.s(frozen=True, order=False, eq=False, hash=True)
178150
class PypiVersion(Version):
179151
"""
180152
Python PEP 440 version as implemented in packaging with fallback to "legacy"
181153
"""
182154

183155
# TODO: ensure we deal with triple equal
156+
# TODO: use packvers and handle legacy versions
184157

185158
@classmethod
186159
def build_value(cls, string):
@@ -199,18 +172,22 @@ def is_valid(cls, string):
199172
except packaging_version.InvalidVersion:
200173
return False
201174

202-
return False
175+
176+
class EnhancedSemanticVersion(semantic_version.Version):
177+
@property
178+
def precedence_key(self):
179+
key = super(EnhancedSemanticVersion, self).precedence_key
180+
return key + (self.build or ())
203181

204182

205-
@attr.s(frozen=True, order=False, eq=False, hash=True)
206183
class SemverVersion(Version):
207184
"""
208185
Strict semver v2.0 with 3 segments.
209186
"""
210187

211188
@classmethod
212189
def build_value(cls, string):
213-
return semantic_version.Version.coerce(string)
190+
return EnhancedSemanticVersion.coerce(string)
214191

215192
@classmethod
216193
def is_valid(cls, string):
@@ -241,13 +218,13 @@ def build(self):
241218
return self.value and self.value.build
242219

243220
def next_major(self):
244-
return self.value and self.value.next_major()
221+
return self.value and SemverVersion(str(self.value.next_major()))
245222

246223
def next_minor(self):
247-
return self.value and self.value.next_minor()
224+
return self.value and SemverVersion(str(self.value.next_minor()))
248225

249226
def next_patch(self):
250-
return self.value and self.value.next_patch()
227+
return self.value and SemverVersion(str(self.value.next_patch()))
251228

252229

253230
def is_even(s):
@@ -265,7 +242,6 @@ def is_even(s):
265242
return (int(s) % 2) == 0
266243

267244

268-
@attr.s(frozen=True, order=False, eq=False, hash=True)
269245
class NginxVersion(SemverVersion):
270246
"""
271247
Semver with 3 segments and extra attribute for stable vs. unstable branches
@@ -279,7 +255,6 @@ def is_stable(self):
279255
return is_even(self.minor)
280256

281257

282-
@attr.s(frozen=True, order=False, eq=False, hash=True)
283258
class RubygemsVersion(Version):
284259
"""
285260
Rubygems encourages semver version but does not enforce it.
@@ -296,7 +271,6 @@ def is_valid(cls, string):
296271
return gem.GemVersion.is_correct(string)
297272

298273

299-
@attr.s(frozen=True, order=False, eq=False, hash=True)
300274
class ArchLinuxVersion(Version):
301275
def __eq__(self, other):
302276
if not isinstance(other, self.__class__):
@@ -324,7 +298,6 @@ def __ge__(self, other):
324298
return arch.vercmp(self.value, other.value) >= 0
325299

326300

327-
@attr.s(frozen=True, order=False, eq=False, hash=True)
328301
class DebianVersion(Version):
329302
@classmethod
330303
def build_value(cls, string):
@@ -335,7 +308,6 @@ def is_valid(cls, string):
335308
return debian.Version.is_valid(string)
336309

337310

338-
@attr.s(frozen=True, order=False, eq=False, hash=True)
339311
class MavenVersion(Version):
340312
# See https://maven.apache.org/enforcer/enforcer-rules/versionRanges.html
341313
# https://github.com/apache/maven/tree/master/maven-artifact/src/main/java/org/apache/maven/artifact/versioning
@@ -344,10 +316,16 @@ class MavenVersion(Version):
344316
def build_value(cls, string):
345317
return maven.Version(string)
346318

319+
@classmethod
320+
def is_valid(cls, string):
321+
try:
322+
cls.build_value(string)
323+
return True
324+
except ValueError:
325+
return False
326+
347327

348328
# We will use total ordering to sort the versions, since these versions also consider prereleases.
349-
@attr.s(frozen=True, order=False, eq=False, hash=True)
350-
@functools.total_ordering
351329
class NugetVersion(Version):
352330
# See https://docs.microsoft.com/en-us/nuget/concepts/package-versioning
353331

@@ -363,33 +341,17 @@ def is_valid(cls, string):
363341
except ValueError:
364342
return False
365343

366-
def __str__(self):
367-
return str(self.string)
368-
369-
def __lt__(self, other):
370-
return nuget.Version.from_string(self.string) < nuget.Version.from_string(other.string)
371-
372-
def __eq__(self, other):
373-
return nuget.Version.from_string(self.string) == nuget.Version.from_string(other.string)
374-
375344

376-
@attr.s(frozen=True, order=False, eq=False, hash=True)
377345
class RpmVersion(Version):
378346
"""
379347
Represent an RPM version.
380-
381-
For example::
382-
383-
# 1:1.1.4|>=2.8.16|<=2.8.16-z
384348
"""
385349

386350
@classmethod
387351
def build_value(cls, string):
388352
return rpm.RpmVersion.from_string(string)
389353

390354

391-
@total_ordering
392-
@attr.s(frozen=True, order=False, eq=False, hash=True)
393355
class GentooVersion(Version):
394356
@classmethod
395357
def is_valid(cls, string):
@@ -405,44 +367,30 @@ def __lt__(self, other):
405367
return NotImplemented
406368
return gentoo.vercmp(self.value, other.value) == -1
407369

408-
409-
@attr.s(frozen=True, order=False, eq=False, hash=True)
410-
class AlpineLinuxVersion(Version):
411-
@classmethod
412-
def is_valid(cls, string):
413-
return is_valid_alpine_version(string) and gentoo.is_valid(string)
414-
415-
def __eq__(self, other):
370+
def __gt__(self, other):
416371
if not isinstance(other, self.__class__):
417372
return NotImplemented
418-
return gentoo.vercmp(self.value, other.value) == 0
373+
return gentoo.vercmp(self.value, other.value) == 1
419374

420-
def __lt__(self, other):
421-
if not isinstance(other, self.__class__):
422-
return NotImplemented
423-
return gentoo.vercmp(self.value, other.value) < 0
424375

425-
def __gt__(self, other):
426-
if not isinstance(other, self.__class__):
427-
return NotImplemented
428-
return gentoo.vercmp(self.value, other.value) > 0
376+
class AlpineLinuxVersion(GentooVersion):
377+
@classmethod
378+
def is_valid(cls, string):
379+
return is_valid_alpine_version(string) and gentoo.is_valid(string)
429380

430381

431-
@attr.s(frozen=True, order=False, eq=False, hash=True)
432382
class ComposerVersion(SemverVersion):
433383
@classmethod
434384
def build_value(cls, string):
435-
return semantic_version.Version.coerce(string.lstrip("vV"))
385+
return super().build_value(string.lstrip("vV"))
436386

437387

438-
@attr.s(frozen=True, order=False, eq=False, hash=True)
439388
class GolangVersion(SemverVersion):
440389
@classmethod
441390
def build_value(cls, string):
442-
return semantic_version.Version.coerce(string.lstrip("vV"))
391+
return super().build_value(string.lstrip("vV"))
443392

444393

445-
@attr.s(frozen=True, order=False, eq=False, hash=True)
446394
class LegacyOpensslVersion(Version):
447395
"""
448396
Represent an Legacy Openssl Version.
@@ -558,7 +506,6 @@ def is_prerelease(self):
558506
return self.patch.startswith(("-beta", "-alpha"))
559507

560508

561-
@attr.s(frozen=True, order=False, eq=False, hash=True)
562509
class OpensslVersion(Version):
563510
"""
564511
Internally tracks two types of openssl versions
@@ -653,8 +600,6 @@ def __ge__(self, other):
653600
return isinstance(self.value, SemverVersion)
654601

655602

656-
@attr.s(frozen=True, order=False, eq=False, hash=True)
657-
@total_ordering
658603
class ConanVersion(Version):
659604
@classmethod
660605
def build_value(cls, string):
@@ -740,38 +685,21 @@ def next_patch(self):
740685
def bump(self, index):
741686
return self.value and self.value.bump(index)
742687

743-
def __eq__(self, other):
744-
if other is None:
745-
return False
746-
if not isinstance(other, ConanVersion):
747-
other = ConanVersion.build_value(other)
748-
return self.value == other
749-
return self.value == other.value
750688

751-
def __lt__(self, other):
752-
if other is None:
753-
return False
754-
if not isinstance(other, ConanVersion):
755-
other = ConanVersion(str(other))
756-
return self.value < other.value
757-
758-
def __le__(self, other):
759-
if other is None:
760-
return False
761-
if not isinstance(other, ConanVersion):
762-
other = ConanVersion(str(other))
763-
return self.value <= other.value
764-
765-
def __gt__(self, other):
766-
if other is None:
767-
return False
768-
if not isinstance(other, ConanVersion):
769-
other = ConanVersion(str(other))
770-
return self.value > other.value
771-
772-
def __ge__(self, other):
773-
if other is None:
774-
return False
775-
if not isinstance(other, ConanVersion):
776-
other = ConanVersion(str(other))
777-
return self.value >= other.value
689+
AVAILABLE_VERSIONS = [
690+
SemverVersion,
691+
GolangVersion,
692+
PypiVersion,
693+
GenericVersion,
694+
ComposerVersion,
695+
NginxVersion,
696+
ArchLinuxVersion,
697+
DebianVersion,
698+
RpmVersion,
699+
MavenVersion,
700+
NugetVersion,
701+
GentooVersion,
702+
OpensslVersion,
703+
LegacyOpensslVersion,
704+
AlpineLinuxVersion,
705+
]

tests/data/conan_advisory.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2446,7 +2446,7 @@
24462446
"test_index": 350,
24472447
"scheme": "conan",
24482448
"native": ">=1.0.2 <=1.0.2v",
2449-
"expected_vers": "vers:conan/<=1.0.2|>=1.0.2",
2449+
"expected_vers": "vers:conan/>=1.0.2|<=1.0.2v",
24502450
"gitlab_scheme": "conan"
24512451
},
24522452
{

0 commit comments

Comments
 (0)