Skip to content

Commit 22ff5af

Browse files
authored
Merge pull request #245 from tomschr/bugfix/244-missing-list-and-str
Fix #244: Allow list & str for comparison
2 parents 2ef2eea + 7057dc5 commit 22ff5af

4 files changed

Lines changed: 139 additions & 31 deletions

File tree

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Bug Fixes
2626

2727
* :gh:`224` (:pr:`226`): In ``setup.py``, replaced in class ``clean``,
2828
``super(CleanCommand, self).run()`` with ``CleanCommand.run(self)``
29+
* :gh:`244` (:pr:`245`): Allow comparison with ``VersionInfo``, tuple/list, dict, and string.
2930

3031

3132
Additions

docs/usage.rst

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ To compare two versions depends on your type:
370370
The return value is negative if ``version1 < version2``, zero if
371371
``version1 == version2`` and strictly positive if ``version1 > version2``.
372372

373-
* **Two** :class:`semver.VersionInfo` **types**
373+
* **Two** :class:`semver.VersionInfo` **instances**
374374

375375
Use the specific operator. Currently, the operators ``<``,
376376
``<=``, ``>``, ``>=``, ``==``, and ``!=`` are supported::
@@ -382,24 +382,68 @@ To compare two versions depends on your type:
382382
>>> v1 > v2
383383
False
384384

385-
* **A** :class:`semver.VersionInfo` **type and a** ``tuple``
385+
* **A** :class:`semver.VersionInfo` **type and a** :func:`tuple` **or** :func:`list`
386386

387387
Use the operator as with two :class:`semver.VersionInfo` types::
388388

389389
>>> v = semver.VersionInfo.parse("3.4.5")
390390
>>> v > (1, 0)
391391
True
392-
>>> v < (3, 5)
392+
>>> v < [3, 5]
393393
True
394394

395395
The opposite does also work::
396396

397397
>>> (1, 0) < v
398398
True
399-
>>> (3, 5) > v
399+
>>> [3, 5] > v
400400
True
401401

402-
Other types cannot be compared (like dictionaries, lists etc).
402+
* **A** :class:`semver.VersionInfo` **type and a** :func:`str`
403+
404+
You can use also raw strings to compare::
405+
406+
>>> v > "1.0.0"
407+
True
408+
>>> v < "3.5.0"
409+
True
410+
411+
The opposite does also work::
412+
413+
>>> "1.0.0" < v
414+
True
415+
>>> "3.5.0" > v
416+
True
417+
418+
However, if you compare incomplete strings, you get a :class:`ValueError` exception::
419+
420+
>>> v > "1.0"
421+
Traceback (most recent call last):
422+
...
423+
ValueError: 1.0 is not valid SemVer string
424+
425+
* **A** :class:`semver.VersionInfo` **type and a** :func:`dict`
426+
427+
You can also use a dictionary. In contrast to strings, you can have an "incomplete"
428+
version (as the other parts are set to zero)::
429+
430+
>>> v > dict(major=1)
431+
True
432+
433+
The opposite does also work::
434+
435+
>>> dict(major=1) < v
436+
True
437+
438+
If the dictionary contains unknown keys, you get a :class:`TypeError` exception::
439+
440+
>>> v > dict(major=1, unknown=42)
441+
Traceback (most recent call last):
442+
...
443+
TypeError: __init__() got an unexpected keyword argument 'unknown'
444+
445+
446+
Other types cannot be compared.
403447

404448
If you need to convert some types into other, refer to :ref:`sec.convert.versions`.
405449

semver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def comparator(operator):
112112

113113
@wraps(operator)
114114
def wrapper(self, other):
115-
comparable_types = (VersionInfo, dict, tuple)
115+
comparable_types = (VersionInfo, dict, tuple, list, str)
116116
if not isinstance(other, comparable_types):
117117
raise TypeError(
118118
"other type %r must be in %r" % (type(other), comparable_types)

test_semver.py

Lines changed: 88 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -557,37 +557,100 @@ def test_should_compare_version_dictionaries():
557557
assert not (v1 == v4)
558558

559559

560-
def test_should_compare_version_tuples():
561-
v0 = VersionInfo(major=0, minor=4, patch=5, prerelease="pre.2", build="build.4")
562-
v1 = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4")
563-
for t in (
560+
@pytest.mark.parametrize(
561+
"t", # fmt: off
562+
(
564563
(1, 0, 0),
565564
(1, 0),
566565
(1,),
567566
(1, 0, 0, "pre.2"),
568567
(1, 0, 0, "pre.2", "build.4"),
569-
):
570-
assert v0 < t
571-
assert v0 <= t
572-
assert v0 != t
573-
assert not v0 == t
574-
assert v1 > t
575-
assert v1 >= t
576-
# Symmetric
577-
assert t > v0
578-
assert t >= v0
579-
assert t < v1
580-
assert t <= v1
581-
assert t != v0
582-
assert not t == v0
583-
584-
585-
def test_should_not_allow_to_compare_version_with_string():
568+
), # fmt: on
569+
)
570+
def test_should_compare_version_tuples(t):
571+
v0 = VersionInfo(major=0, minor=4, patch=5, prerelease="pre.2", build="build.4")
586572
v1 = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4")
587-
with pytest.raises(TypeError):
588-
v1 > "1.0.0"
589-
with pytest.raises(TypeError):
590-
"1.0.0" > v1
573+
574+
assert v0 < t
575+
assert v0 <= t
576+
assert v0 != t
577+
assert not v0 == t
578+
assert v1 > t
579+
assert v1 >= t
580+
# Symmetric
581+
assert t > v0
582+
assert t >= v0
583+
assert t < v1
584+
assert t <= v1
585+
assert t != v0
586+
assert not t == v0
587+
588+
589+
@pytest.mark.parametrize(
590+
"lst", # fmt: off
591+
(
592+
[1, 0, 0],
593+
[1, 0],
594+
[1],
595+
[1, 0, 0, "pre.2"],
596+
[1, 0, 0, "pre.2", "build.4"],
597+
), # fmt: on
598+
)
599+
def test_should_compare_version_list(lst):
600+
v0 = VersionInfo(major=0, minor=4, patch=5, prerelease="pre.2", build="build.4")
601+
v1 = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4")
602+
603+
assert v0 < lst
604+
assert v0 <= lst
605+
assert v0 != lst
606+
assert not v0 == lst
607+
assert v1 > lst
608+
assert v1 >= lst
609+
# Symmetric
610+
assert lst > v0
611+
assert lst >= v0
612+
assert lst < v1
613+
assert lst <= v1
614+
assert lst != v0
615+
assert not lst == v0
616+
617+
618+
@pytest.mark.parametrize(
619+
"s", # fmt: off
620+
(
621+
"1.0.0",
622+
# "1.0",
623+
# "1",
624+
"1.0.0-pre.2",
625+
"1.0.0-pre.2+build.4",
626+
), # fmt: on
627+
)
628+
def test_should_compare_version_string(s):
629+
v0 = VersionInfo(major=0, minor=4, patch=5, prerelease="pre.2", build="build.4")
630+
v1 = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4")
631+
632+
assert v0 < s
633+
assert v0 <= s
634+
assert v0 != s
635+
assert not v0 == s
636+
assert v1 > s
637+
assert v1 >= s
638+
# Symmetric
639+
assert s > v0
640+
assert s >= v0
641+
assert s < v1
642+
assert s <= v1
643+
assert s != v0
644+
assert not s == v0
645+
646+
647+
@pytest.mark.parametrize("s", ("1", "1.0", "1.0.x"))
648+
def test_should_not_allow_to_compare_invalid_versionstring(s):
649+
v = VersionInfo(major=3, minor=4, patch=5, prerelease="pre.2", build="build.4")
650+
with pytest.raises(ValueError):
651+
v < s
652+
with pytest.raises(ValueError):
653+
s > v
591654

592655

593656
def test_should_not_allow_to_compare_version_with_int():

0 commit comments

Comments
 (0)