Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c58e709

Browse filesBrowse 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 <lexi@lexi.org.uk> Co-authored-by: Thomas Laferriere <tlaferriere@users.noreply.github.com> Co-authored-by: Raphael Krupinski <rafalkrupinski@users.noreply.github.com>
1 parent 3ec0131 commit c58e709
Copy full SHA for c58e709

11 files changed

+166
-10
lines changed

‎.gitignore

Copy file name to clipboardExpand all lines: .gitignore
+3Lines 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

Copy file name to clipboard
+1Lines 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

Copy file name to clipboard
+6Lines 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

Copy file name to clipboardExpand all lines: docs/conf.py
+2-2Lines 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

Copy file name to clipboardExpand all lines: docs/migration/migratetosemver3.rst
+10-2Lines 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>`.
+55Lines 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

Copy file name to clipboardExpand all lines: docs/usage/check-valid-semver-version.rst
+2-2Lines 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

Copy file name to clipboardExpand all lines: docs/usage/index.rst
+1Lines 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

Copy file name to clipboardExpand all lines: src/semver/cli.py
+1-1Lines 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

Copy file name to clipboardExpand all lines: src/semver/version.py
+35-1Lines 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

Copy file name to clipboardExpand all lines: tests/test_semver.py
+50-2Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.