8000 Fix #284: implement "is_compatible" with method · python-semver/python-semver@edb81ba · GitHub
[go: up one dir, main page]

Skip to content

Commit edb81ba

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 <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 edb81ba

File tree

11 files changed

+137
-10
lines changed
  • src/semver
  • tests
  • 11 files changed

    +137
    -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: 35 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,35 @@
    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 `b` if:
    8+
    9+
    * If the two majors parts are different, it's incompatible.
    10+
    * If the two major parts are equal, but the minor of `b` is
    11+
    lower than `a`, it's incompatible.
    12+
    * If both pre-releases are present and different, it's incompatible.
    13+
    * In all other cases, it's compatible.
    14+
    15+
    Keep in mind, the method *does not* check patches!
    16+
    17+
    For example,
    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

    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: 30 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,35 @@ 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+
    return (
    688+
    (self.major == new.major)
    689+
    and (new.minor >= self.minor)
    690+
    and (self.prerelease == new.prerelease)
    691+
    )
    692+
    664693

    665694
    #: Keep the VersionInfo name for compatibility
    666695
    VersionInfo = Version

    tests/test_semver.py

    Lines changed: 46 additions & 2 deletions
    Original file line numberDiff line numberDiff line change
    @@ -73,10 +73,54 @@ 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, "rc.1"), (0, 0, 0, "rc.2")),
    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+
    ]
    109+
    )
    110+
    def test_should_fail_compatible_match(old, new):
    111+
    old = Version(*old)
    112+
    assert not old.is_compatible(Version(*new))
    113+
    114+
    115+
    @pytest.mark.parametrize(
    116+
    "wrongtype",
    117+
    [
    118+
    "wrongtype",
    119+
    dict(a=2),
    120+
    list(),
    121+
    ]
    122+
    )
    123+
    def test_should_fail_with_incompatible_type_for_compatible_match(wrongtype):
    124+
    with pytest.raises(TypeError):
    125+
    v = Version(1, 2, 3)
    126+
    v.is_compatible(wrongtype)

    0 commit comments

    Comments
     (0)
    0