Skip to content

Commit a69b632

Browse files
committed
Add comparision between VersionInfo objects
1 parent c679d13 commit a69b632

File tree

2 files changed

+161
-65
lines changed

2 files changed

+161
-65
lines changed

semver.py

Lines changed: 100 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import collections
66
import re
7-
import sys
87

98

109
__version__ = '2.7.6'
@@ -59,26 +58,59 @@ def parse(version):
5958
return version_parts
6059

6160

62-
VersionInfo = collections.namedtuple(
63-
'VersionInfo', 'major minor patch prerelease build')
64-
65-
# Only change it for Python > 3 as it is readonly
66-
# for version 2
67-
if sys.version_info >= (3, 5):
68-
VersionInfo.__doc__ = """
69-
:param int major: version when you make incompatible API changes.
70-
:param int minor: version when you add functionality in
71-
a backwards-compatible manner.
72-
:param int patch: version when you make backwards-compatible bug fixes.
73-
:param str prerelease: an optional prerelease string
74-
:param str build: an optional build string
75-
76-
>>> import semver
77-
>>> ver = semver.parse('3.4.5-pre.2+build.4')
78-
>>> ver
79-
{'build': 'build.4', 'major': 3, 'minor': 4, 'patch': 5,
80-
'prerelease': 'pre.2'}
81-
"""
61+
class VersionInfo(collections.namedtuple(
62+
'VersionInfo', 'major minor patch prerelease build')):
63+
"""
64+
:param int major: version when you make incompatible API changes.
65+
:param int minor: version when you add functionality in
66+
a backwards-compatible manner.
67+
:param int patch: version when you make backwards-compatible bug fixes.
68+
:param str prerelease: an optional prerelease string
69+
:param str build: an optional build string
70+
71+
>>> import semver
72+
>>> ver = semver.parse('3.4.5-pre.2+build.4')
73+
>>> ver
74+
{'build': 'build.4', 'major': 3, 'minor': 4, 'patch': 5,
75+
'prerelease': 'pre.2'}
76+
"""
77+
__slots__ = ()
78+
79+
def __eq__(self, other):
80+
if not isinstance(other, (VersionInfo, dict)):
81+
return NotImplemented
82+
return _compare_by_keys(self._asdict(), _to_dict(other)) == 0
83+
84+
def __ne__(self, other):
85+
if not isinstance(other, (VersionInfo, dict)):
86+
return NotImplemented
87+
return _compare_by_keys(self._asdict(), _to_dict(other)) != 0
88+
89+
def __lt__(self, other):
90+
if not isinstance(other, (VersionInfo, dict)):
91+
return NotImplemented
92+
return _compare_by_keys(self._asdict(), _to_dict(other)) < 0
93+
94+
def __le__(self, other):
95+
if not isinstance(other, (VersionInfo, dict)):
96+
return NotImplemented
97+
return _compare_by_keys(self._asdict(), _to_dict(other)) <= 0
98+
99+
def __gt__(self, other):
100+
if not isinstance(other, (VersionInfo, dict)):
101+
return NotImplemented
102+
return _compare_by_keys(self._asdict(), _to_dict(other)) > 0
103+
104+
def __ge__(self, other):
105+
if not isinstance(other, (VersionInfo, dict)):
106+
return NotImplemented
107+
return _compare_by_keys(self._asdict(), _to_dict(other)) >= 0
108+
109+
110+
def _to_dict(obj):
111+
if isinstance(obj, VersionInfo):
112+
return obj._asdict()
113+
return obj
82114

83115

84116
def parse_version_info(version):
@@ -96,6 +128,52 @@ def parse_version_info(version):
96128
return version_info
97129

98130

131+
def _nat_cmp(a, b):
132+
def convert(text):
133+
return int(text) if re.match('[0-9]+', text) else text
134+
135+
def split_key(key):
136+
return [convert(c) for c in key.split('.')]
137+
138+
def cmp_prerelease_tag(a, b):
139+
if isinstance(a, int) and isinstance(b, int):
140+
return cmp(a, b)
141+
elif isinstance(a, int):
142+
return -1
143+
elif isinstance(b, int):
144+
return 1
145+
else:
146+
return cmp(a, b)
147+
148+
a, b = a or '', b or ''
149+
a_parts, b_parts = split_key(a), split_key(b)
150+
for sub_a, sub_b in zip(a_parts, b_parts):
151+
cmp_result = cmp_prerelease_tag(sub_a, sub_b)
152+
if cmp_result != 0:
153+
return cmp_result
154+
else:
155+
return cmp(len(a), len(b))
156+
157+
158+
def _compare_by_keys(d1, d2):
159+
for key in ['major', 'minor', 'patch']:
160+
v = cmp(d1.get(key), d2.get(key))
161+
if v:
162+
return v
163+
164+
rc1, rc2 = d1.get('prerelease'), d2.get('prerelease')
165+
rccmp = _nat_cmp(rc1, rc2)
166+
167+
if not rccmp:
168+
return 0
169+
if not rc1:
170+
return 1
171+
elif not rc2:
172+
return -1
173+
174+
return rccmp
175+
176+
99177
def compare(ver1, ver2):
100178
"""Compare two versions
101179
@@ -105,53 +183,10 @@ def compare(ver1, ver2):
105183
zero if ver1 == ver2 and strictly positive if ver1 > ver2
106184
:rtype: int
107185
"""
108-
def nat_cmp(a, b):
109-
def convert(text):
110-
return int(text) if re.match('[0-9]+', text) else text
111-
112-
def split_key(key):
113-
return [convert(c) for c in key.split('.')]
114-
115-
def cmp_prerelease_tag(a, b):
116-
if isinstance(a, int) and isinstance(b, int):
117-
return cmp(a, b)
118-
elif isinstance(a, int):
119-
return -1
120-
elif isinstance(b, int):
121-
return 1
122-
else:
123-
return cmp(a, b)
124-
125-
a, b = a or '', b or ''
126-
a_parts, b_parts = split_key(a), split_key(b)
127-
for sub_a, sub_b in zip(a_parts, b_parts):
128-
cmp_result = cmp_prerelease_tag(sub_a, sub_b)
129-
if cmp_result != 0:
130-
return cmp_result
131-
else:
132-
return cmp(len(a), len(b))
133-
134-
def compare_by_keys(d1, d2):
135-
for key in ['major', 'minor', 'patch']:
136-
v = cmp(d1.get(key), d2.get(key))
137-
if v:
138-
return v
139-
140-
rc1, rc2 = d1.get('prerelease'), d2.get('prerelease')
141-
rccmp = nat_cmp(rc1, rc2)
142-
143-
if not rccmp:
144-
return 0
145-
if not rc1:
146-
return 1
147-
elif not rc2:
148-
return -1
149-
150-
return rccmp
151186

152187
v1, v2 = parse(ver1), parse(ver2)
153188

154-
return compare_by_keys(v1, v2)
189+
return _compare_by_keys(v1, v2)
155190

156191

157192
def match(version, match_expr):

tests.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from semver import bump_build
1212
from semver import min_ver
1313
from semver import max_ver
14+
from semver import VersionInfo
1415

1516

1617
SEMVERFUNCS = [
@@ -270,3 +271,63 @@ def test_should_bump_build():
270271
assert bump_build('3.4.5-rc.1+0009.dev') == '3.4.5-rc.1+0010.dev'
271272
assert bump_build('3.4.5-rc.1') == '3.4.5-rc.1+build.1'
272273
assert bump_build('3.4.5') == '3.4.5+build.1'
274+
275+
276+
def test_should_compare_version_info_objects():
277+
v1 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None)
278+
v2 = VersionInfo(
279+
major=0, minor=10, patch=4, prerelease='beta.1', build=None)
280+
281+
# use `not` to enforce using comparision operators
282+
assert v1 != v2
283+
assert v1 > v2
284+
assert v1 >= v2
285+
assert not(v1 < v2)
286+
assert not(v1 <= v2)
287+
assert not(v1 == v2)
288+
289+
v3 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None)
290+
291+
assert not(v1 != v3)
292+
assert not(v1 > v3)
293+
assert v1 >= v3
294+
assert not(v1 < v3)
295+
assert v1 <= v3
296+
assert v1 == v3
297+
298+
v4 = VersionInfo(major=0, minor=10, patch=5, prerelease=None, build=None)
299+
assert v1 != v4
300+
assert not(v1 > v4)
301+
assert not(v1 >= v4)
302+
assert v1 < v4
303+
assert v1 <= v4
304+
assert not(v1 == v4)
305+
306+
307+
def test_should_compare_version_dictionaries():
308+
v1 = VersionInfo(major=0, minor=10, patch=4, prerelease=None, build=None)
309+
v2 = dict(major=0, minor=10, patch=4, prerelease='beta.1', build=None)
310+
311+
assert v1 != v2
312+
assert v1 > v2
313+
assert v1 >= v2
314+
assert not(v1 < v2)
315+
assert not(v1 <= v2)
316+
assert not(v1 == v2)
317+
318+
v3 = dict(major=0, minor=10, patch=4, prerelease=None, build=None)
319+
320+
assert not(v1 != v3)
321+
assert not(v1 > v3)
322+
assert v1 >= v3
323+
assert not(v1 < v3)
324+
assert v1 <= v3
325+
assert v1 == v3
326+
327+
v4 = dict(major=0, minor=10, patch=5, prerelease=None, build=None)
328+
assert v1 != v4
329+
assert not(v1 > v4)
330+
assert not(v1 >= v4)
331+
assert v1 < v4
332+
assert v1 <= v4
333+
assert not(v1 == v4)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy