Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c58e709

Browse files
tomschrLexicalitytlaferriererafalkrupinski
committed
Fix #284: implement "is_compatible" with method
* Implement Version.is_compatible() method * Update test cases * Update documentation * Rename isvalid method to is_valid to make it consistent with is_compatible The algorithm checks: * if the two majors are different, it's incompatible * if the two majors are equal, but the minor of the new version is lower than the old, it's incompatible * if both prereleases are present and different, it's incompatible * otherwise it's compatible The algorithm does *not* check patches! Co-authored-by: Lexi Robinson <[email protected]> Co-authored-by: Thomas Laferriere <[email protected]> Co-authored-by: Raphael Krupinski <[email protected]>
1 parent 3ec0131 commit c58e709

File tree

11 files changed

+166
-10
lines changed

11 files changed

+166
-10
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,6 @@ fabric.properties
264264
*.diff
265265
docs/_api
266266
!docs/_api/semver.__about__.rst
267+
268+
# For node
269+
node_modules/

changelog.d/284.doc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Document deprecation of :meth:`Version.isvalid`.

changelog.d/284.feature.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Implement :meth:`Version.is_compatible <semver.version.Version.is_compatible>` to make "is self compatible with X".
2+
3+
Rename :meth:`Version.isvalid <semver.version.Version.isvalid>`
4+
to :meth:`Version.is_valid <semver.version.Version.is_valid>`
5+
for consistency reasons and deprecate the use of
6+
:meth:`Version.isvalid`.

docs/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ def find_version(*file_paths):
118118
# Markup to shorten external links
119119
# See https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html
120120
extlinks = {
121-
"gh": ("https://github.com/python-semver/python-semver/issues/%s", "#"),
122-
"pr": ("https://github.com/python-semver/python-semver/pull/%s", "PR #"),
121+
"gh": ("https://github.com/python-semver/python-semver/issues/%s", "#%s"),
122+
"pr": ("https://github.com/python-semver/python-semver/pull/%s", "PR #%s"),
123123
}
124124

125125
# -- Options for HTML output ----------------------------------------------

docs/migration/migratetosemver3.rst

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
Migrating from semver2 to semver3
44
=================================
55

6-
This document describes the visible differences for
6+
This section describes the visible differences for
77
users and how your code stays compatible for semver3.
8+
Some changes are backward incompatible.
89

910
Although the development team tries to make the transition
1011
to semver3 as smooth as possible, at some point change
@@ -34,9 +35,16 @@ Use semver.cli instead of semver
3435
--------------------------------
3536

3637
All functions related to CLI parsing are moved to :mod:`semver.cli`.
37-
If you are such functions, like :func:`semver.cmd_bump <semver.cli.cmd_bump>`,
38+
If you need such functions, like :func:`semver.cmd_bump <semver.cli.cmd_bump>`,
3839
import it from :mod:`semver.cli` in the future:
3940

4041
.. code-block:: python
4142
4243
from semver.cli import cmd_bump
44+
45+
46+
Use semver.Version.is_valid instead of semver.Version.isvalid
47+
-------------------------------------------------------------
48+
49+
The pull request :pr:`284` introduced the method :meth:`Version.is_compatible <semver.Version.is_compatible>`. To keep consistency, the development team
50+
decided to rename the :meth:`isvalid <semver.Version.isvalid>` to :meth:`is_valid <semver.Version.is_valid>`.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
Checking for a Compatible Semver Version
2+
========================================
3+
4+
In case you need to check if a semver version is compatible
5+
with another semver version, use :meth:`Version.is_compatible <semver.Version.is_compatible>`.
6+
7+
A version `a` is compatible with another version `b` if:
8+
9+
* Both are of type :class:`Version <semver.version.Version>`.
10+
* If the two majors parts are different, it's incompatible.
11+
* If the two major parts are equal, but the minor of `b` is
12+
lower than `a`, it's incompatible.
13+
* If both pre-releases are present and different, it's incompatible.
14+
* In all other cases, it's compatible.
15+
16+
Keep in mind, the method *does not* check patches!
17+
18+
19+
.. code-block:: python
20+
21+
# Two different majors:
22+
>>> a = Version(1, 1, 1) # This is "A"
23+
>>> b = Version(2, 0, 0) # This is "B"
24+
>>> a.is_compatible(b)
25+
False
26+
27+
# The same two majors and minors:
28+
>>> b = Version(1, 1, 0) # This is another "B"
29+
>>> a.is_compatible(b)
30+
True
31+
32+
# The same two majors, but B.minor < A.minor:
33+
>>> b = Version(1, 0, 0) # This is another "B"
34+
>>> a.is_compatible(b)
35+
False
36+
37+
# Checking compatibility between release and pre-release:
38+
>>> b = Version(1,0,0,'rc1.')
39+
>>> a.is_compatible(b)
40+
False
41+
42+
# But pre-release and release:
43+
>>> b.is_compatible(a)
44+
False
45+
46+
All major zero versions are incompatible with anything but itself:
47+
48+
.. code-block:: python
49+
50+
>>> Version(0,1,0).is_compatible(Version(0,1,1))
51+
False
52+
53+
# Only identical versions are compatible for major zero versions:
54+
>>> Version(0,1,0).is_compatible(Version(0,1,0))
55+
True

docs/usage/check-valid-semver-version.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ classmethod :func:`Version.isvalid <semver.version.Version.isvalid>`:
66

77
.. code-block:: python
88
9-
>>> Version.isvalid("1.0.0")
9+
>>> Version.is_valid("1.0.0")
1010
True
11-
>>> Version.isvalid("invalid")
11+
>>> Version.is_valid("invalid")
1212
False

docs/usage/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Using semver
88
create-a-version
99
parse-version-string
1010
check-valid-semver-version
11+
check-compatible-semver-version
1112
access-parts-of-a-version
1213
access-parts-through-index
1314
replace-parts-of-a-version

src/semver/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def cmd_check(args: argparse.Namespace) -> None:
5454
5555
:param args: The parsed arguments
5656
"""
57-
if Version.isvalid(args.version):
57+
if Version.is_valid(args.version):
5858
return None
5959
raise ValueError("Invalid version %r" % args.version)
6060

src/semver/version.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ def replace(self, **parts: Union[int, Optional[str]]) -> "Version":
645645
raise TypeError(error)
646646

647647
@classmethod
648-
def isvalid(cls, version: str) -> bool:
648+
def is_valid(cls, version: str) -> bool:
649649
"""
650650
Check if the string is a valid semver version.
651651
@@ -661,6 +661,40 @@ def isvalid(cls, version: str) -> bool:
661661
except ValueError:
662662
return False
663663

664+
def is_compatible(self, new: 'Version') -> bool:
665+
"""
666+
Check if current version is compatible with new.
667+
668+
The algorithm checks:
669+
670+
* if the two majors are different, it's incompatible
671+
* if the two majors are equal, but the minor of ``new``
672+
is lower than self.minor, it's incompatible
673+
* if both pre-releases are present and different,
674+
it's incompatible
675+
* otherwise it's compatible
676+
677+
The algorithm does *not* check patches.
678+
679+
:param new: the new version to check for compatibility
680+
:return: True, if new is compatible with the old version,
681+
otherwise False
682+
"""
683+
if not isinstance(new, type(self)):
684+
raise TypeError(
685+
f"Expected a Version type but got {type(new)}"
686+
)
687+
688+
# All major-0 versions should be incompatible with anything but itself
689+
if (0 == self.major == new.major) and (self[:3] != new[:3]):
690+
return False
691+
692+
return (
693+
(self.major == new.major)
694+
and (new.minor >= self.minor)
695+
and (self.prerelease == new.prerelease)
696+
)
697+
664698

665699
#: Keep the VersionInfo name for compatibility
666700
VersionInfo = Version

tests/test_semver.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,58 @@ def test_should_be_able_to_use_integers_as_prerelease_build():
7373

7474

7575
def test_should_versioninfo_isvalid():
76-
assert Version.isvalid("1.0.0") is True
77-
assert Version.isvalid("foo") is False
76+
assert Version.is_valid("1.0.0") is True
77+
assert Version.is_valid("foo") is False
7878

7979

8080
def test_versioninfo_compare_should_raise_when_passed_invalid_value():
8181
with pytest.raises(TypeError):
8282
Version(1, 2, 3).compare(4)
83+
84+
85+
@pytest.mark.parametrize(
86+
"old, new",
87+
[
88+
((1, 2, 3), (1, 2, 3)),
89+
((1, 2, 3), (1, 2, 4)),
90+
((1, 2, 4), (1, 2, 3)),
91+
((1, 2, 3, "rc.0"), (1, 2, 4, "rc.0")),
92+
((0, 1, 0), (0, 1, 0)),
93+
],
94+
)
95+
def test_should_succeed_compatible_match(old, new):
96+
old = Version(*old)
97+
new = Version(*new)
98+
assert old.is_compatible(new)
99+
100+
101+
@pytest.mark.parametrize(
102+
"old, new",
103+
[
104+
((1, 1, 0), (1, 0, 0)),
105+
((2, 0, 0), (1, 5, 0)),
106+
((1, 2, 3, "rc.1"), (1, 2, 3, "rc.0")),
107+
((1, 2, 3, "rc.1"), (1, 2, 4, "rc.0")),
108+
((0, 1, 0), (0, 1, 1)),
109+
((1, 0, 0), (1, 0, 0, "rc1")),
110+
((1, 0, 0, "rc1"), (1, 0, 0)),
111+
]
112+
)
113+
def test_should_fail_compatible_match(old, new):
114+
old = Version(*old)
115+
new = Version(*new)
116+
assert not old.is_compatible(new)
117+
118+
119+
@pytest.mark.parametrize(
120+
"wrongtype",
121+
[
122+
"wrongtype",
123+
dict(a=2),
124+
list(),
125+
]
126+
)
127+
def test_should_fail_with_incompatible_type_for_compatible_match(wrongtype):
128+
with pytest.raises(TypeError, match="Expected a Version type .*"):
129+
v = Version(1, 2, 3)
130+
v.is_compatible(wrongtype)

0 commit comments

Comments
 (0)