From d728a64dd93a796b4520badad6fc3b4f6ad304c2 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 26 Jan 2022 09:29:21 +0100 Subject: [PATCH] get version comparison from semver since Python is removing version string parsing from the standard library moves Julia-specific semver utilities to top-level --- repo2docker/buildpacks/_r_base.py | 2 +- repo2docker/buildpacks/julia/julia_project.py | 5 +++- repo2docker/buildpacks/julia/julia_require.py | 2 +- repo2docker/buildpacks/r.py | 2 +- repo2docker/{buildpacks/julia => }/semver.py | 23 +++++++++++++++++++ tests/unit/test_semver.py | 20 +++++++++++++++- 6 files changed, 49 insertions(+), 5 deletions(-) rename repo2docker/{buildpacks/julia => }/semver.py (86%) diff --git a/repo2docker/buildpacks/_r_base.py b/repo2docker/buildpacks/_r_base.py index 4c80f5b4..5e14f3c9 100644 --- a/repo2docker/buildpacks/_r_base.py +++ b/repo2docker/buildpacks/_r_base.py @@ -3,7 +3,7 @@ Base information for using R in BuildPacks. Keeping this in r.py would lead to cyclic imports. """ -from distutils.version import LooseVersion as V +from ..semver import parse_version as V def rstudio_base_scripts(r_version): diff --git a/repo2docker/buildpacks/julia/julia_project.py b/repo2docker/buildpacks/julia/julia_project.py index b340acbd..9a569e51 100644 --- a/repo2docker/buildpacks/julia/julia_project.py +++ b/repo2docker/buildpacks/julia/julia_project.py @@ -1,10 +1,13 @@ """Generates a Dockerfile based on an input matrix for Julia""" import functools import os + import requests +import semver import toml + from ..python import PythonBuildPack -from .semver import find_semver_match, semver +from ...semver import find_semver_match class JuliaProjectTomlBuildPack(PythonBuildPack): diff --git a/repo2docker/buildpacks/julia/julia_require.py b/repo2docker/buildpacks/julia/julia_require.py index b4e4f35e..075b928e 100644 --- a/repo2docker/buildpacks/julia/julia_require.py +++ b/repo2docker/buildpacks/julia/julia_require.py @@ -1,9 +1,9 @@ """Generates a Dockerfile based on an input matrix with REQUIRE for legacy Julia""" -from distutils.version import LooseVersion as V import os from ..python import PythonBuildPack +from ...semver import parse_version as V class JuliaRequireBuildPack(PythonBuildPack): diff --git a/repo2docker/buildpacks/r.py b/repo2docker/buildpacks/r.py index 8420481f..9a734a47 100644 --- a/repo2docker/buildpacks/r.py +++ b/repo2docker/buildpacks/r.py @@ -3,8 +3,8 @@ import os import datetime import requests -from distutils.version import LooseVersion as V +from ..semver import parse_version as V from .python import PythonBuildPack from ._r_base import rstudio_base_scripts diff --git a/repo2docker/buildpacks/julia/semver.py b/repo2docker/semver.py similarity index 86% rename from repo2docker/buildpacks/julia/semver.py rename to repo2docker/semver.py index da9bf7c7..d0a2a5b8 100644 --- a/repo2docker/buildpacks/julia/semver.py +++ b/repo2docker/semver.py @@ -12,11 +12,13 @@ constraints. import re +from functools import lru_cache import semver def find_semver_match(constraint, versions_list): + """Find first version in a list of versions that matches a constraint""" matcher = create_semver_matcher(constraint) for vstr in reversed(versions_list): if matcher.match(str_to_version(vstr)): @@ -25,9 +27,29 @@ def find_semver_match(constraint, versions_list): def str_to_version(vstr): + """Convert a simple x[.y[.z]] version string to a tuple of ints""" return tuple([int(n) for n in vstr.split(".")]) +@lru_cache() +def parse_version(vstr): + """Convert a simple 'x[.y[.z]]' version string to a comparable VersionInfo + + Wraps semver.VersionInfo.parse with zero-padding, + so it can accept '1.0', where upstream only accepts exactly 3 version fields. + """ + try: + return semver.VersionInfo.parse(vstr) + except ValueError: + # may fail for e.g. short 1.0 versions + n_fields = vstr.count(".") + if n_fields < 2: + vstr = vstr + (".0" * (2 - n_fields)) + return semver.VersionInfo.parse(vstr) + else: + raise + + # Helpers def major(v): return v[0] @@ -41,6 +63,7 @@ def patch(v): return v[2] if len(v) >= 3 else 0 +@lru_cache() def create_semver_matcher(constraint_str): """Create a matcher that can be used to match version tuples diff --git a/tests/unit/test_semver.py b/tests/unit/test_semver.py index a9102463..bbbf8ca5 100644 --- a/tests/unit/test_semver.py +++ b/tests/unit/test_semver.py @@ -1,5 +1,6 @@ import pytest -from repo2docker.buildpacks.julia import semver +from semver import VersionInfo +from repo2docker import semver @pytest.mark.parametrize("test_input, expected", [("1.5.2", (1, 5, 2)), ("1", (1,))]) @@ -145,3 +146,20 @@ def test_largerthan_equal(): semver.create_semver_matcher(">=1.2.3").match(semver.str_to_version("1.2.2")) == False ) + + +@pytest.mark.parametrize( + "vstr, expected", + [ + ("1.2.3", "1.2.3"), + ("1.2", "1.2.0"), + ("1", "1.0.0"), + ], +) +def test_parse_version(vstr, expected): + version_info = semver.parse_version(vstr) + assert isinstance(version_info, semver.semver.VersionInfo) + assert str(version_info) == expected + # satisfies itself, since this is how we use it + assert semver.parse_version(expected) <= version_info + assert semver.parse_version(expected) >= version_info