diff --git a/.appveyor.yml b/.appveyor.yml index 14a6aa98f..a33a9acae 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -11,6 +11,7 @@ branches: only: - develop - master + - /lts-.*/ cache: # Cache downloaded pip packages and built wheels. diff --git a/CHANGELOG.md b/CHANGELOG.md index 99fdc1cf1..6255b5b16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Fixed - Fix non-deterministic behavior [#817](https://github.com/snipsco/snips-nlu/pull/817) +- Removed dependency on `semantic_version` to accept `"subpatches"` number [#825](https://github.com/snipsco/snips-nlu/pull/825) ## [0.19.7] ### Changed diff --git a/setup.py b/setup.py index 4412dc33f..bd9258e4f 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,6 @@ "scikit-learn>=0.20,<0.21; python_version<'3.5'", "scikit-learn>=0.21.1,<0.22; python_version>='3.5'", "scipy>=1.0,<2.0", - "semantic_version>=2.6,<3.0", "sklearn-crfsuite>=0.3.6,<0.4", "snips-nlu-parsers>=0.2,<0.3", "snips-nlu-utils>=0.8,<0.9", diff --git a/snips_nlu/cli/utils.py b/snips_nlu/cli/utils.py index 7c26b5933..fb225adb3 100644 --- a/snips_nlu/cli/utils.py +++ b/snips_nlu/cli/utils.py @@ -7,10 +7,10 @@ from enum import Enum, unique import requests -from semantic_version import Version import snips_nlu from snips_nlu import __about__ +from snips_nlu.common.utils import parse_version @unique @@ -72,8 +72,9 @@ def get_json(url, desc): def get_compatibility(): version = __about__.__version__ - semver_version = Version(version) - minor_version = "%d.%d" % (semver_version.major, semver_version.minor) + parsed_version = parse_version(version) + minor_version = "%s.%s" % ( + parsed_version["major"], parsed_version["minor"]) table = get_json(__about__.__compatibility__, "Compatibility table") nlu_table = table["snips-nlu"] compatibility = nlu_table.get(version, nlu_table.get(minor_version)) diff --git a/snips_nlu/common/utils.py b/snips_nlu/common/utils.py index 382432176..b58bf2d49 100644 --- a/snips_nlu/common/utils.py +++ b/snips_nlu/common/utils.py @@ -3,6 +3,7 @@ import importlib import json import numbers +import re from builtins import bytes as newbytes, str as newstr from datetime import datetime from functools import wraps @@ -12,8 +13,8 @@ import pkg_resources from future.utils import text_type -from snips_nlu.constants import ( - END, START, RES_MATCH_RANGE, ENTITY_KIND, RES_VALUE) +from snips_nlu.constants import (END, ENTITY_KIND, RES_MATCH_RANGE, RES_VALUE, + START) from snips_nlu.exceptions import NotTrained, PersistingError REGEX_PUNCT = {'\\', '.', '+', '*', '?', '(', ')', '|', '[', ']', '{', '}', @@ -215,3 +216,24 @@ def sort_key_fn(entity): entities, overlap, sort_key_fn) return sorted(deduplicated_entities, key=lambda entity: entity[RES_MATCH_RANGE][START]) + + +SEMVER_PATTERN = r"^(?P0|[1-9]\d*)" \ + r".(?P0|[1-9]\d*)" \ + r".(?P0|[1-9]\d*)" \ + r"(?:.(?P0|[1-9]\d*))?" \ + r"(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-]" \ + r"[0-9a-zA-Z-]*)" \ + r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" \ + r"(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)" \ + r")?$" +SEMVER_REGEX = re.compile(SEMVER_PATTERN) + + +def parse_version(string_version): + match = SEMVER_REGEX.match(string_version) + if match is None: + msg = "Invalid version: %s. Accepted versions must match the" \ + " following regex pattern: %s" % (string_version, SEMVER_PATTERN) + raise ValueError(msg) + return match.groupdict() diff --git a/snips_nlu/tests/test_utils.py b/snips_nlu/tests/test_utils.py index 6f495395d..d4903fc49 100644 --- a/snips_nlu/tests/test_utils.py +++ b/snips_nlu/tests/test_utils.py @@ -6,13 +6,14 @@ from future.utils import iteritems from mock import MagicMock -from snips_nlu.constants import START, END +from snips_nlu import __model_version__, __version__ +from snips_nlu.common.dict_utils import LimitedSizeDict +from snips_nlu.common.log_utils import DifferedLoggingMessage +from snips_nlu.common.utils import ( + ranges_overlap, replace_entities_with_placeholders, parse_version) +from snips_nlu.constants import END, START from snips_nlu.preprocessing import tokenize_light from snips_nlu.tests.utils import SnipsTest -from snips_nlu.common.utils import ( - ranges_overlap, replace_entities_with_placeholders) -from snips_nlu.common.log_utils import DifferedLoggingMessage -from snips_nlu.common.dict_utils import LimitedSizeDict class TestLimitedSizeDict(SnipsTest): @@ -159,3 +160,139 @@ def placeholder_fn(x): self.assertDictEqual(expected_mapping, range_mapping) self.assertEqual(expected_processed_text, processed_text) + + +class TestVersion(SnipsTest): + def test_should_parse_valid_versions(self): + # Given + valid_versions = [ + "0.0.4.18", # Version with subpatch number + "1.0.4.18", # Version with subpatch number + "0.0.4", + "0.0.4.0", + "1.2.3", + "10.20.30", + "1.1.2-prerelease+meta", + "1.1.2+meta", + "1.1.2+meta-valid", + "1.0.0-alpha", + "1.0.0-beta", + "1.0.0-alpha.beta", + "1.0.0-alpha.beta.1", + "1.0.0-alpha.1", + "1.0.0-alpha0.valid", + "1.0.0-alpha.0valid", + "1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay", + "1.0.0-rc.1+build.1", + "2.0.0-rc.1+build.123", + "1.2.3-beta", + "10.2.3-DEV-SNAPSHOT", + "1.2.3-SNAPSHOT-123", + "1.0.0", + "2.0.0", + "1.1.7", + "2.0.0+build.1848", + "2.0.1-alpha.1227", + "1.0.0-alpha+beta", + "1.2.3----RC-SNAPSHOT.12.9.1--.12+788", + "1.2.3----R-S.12.9.1--.12+meta", + "1.2.3----RC-SNAPSHOT.12.9.1--.12", + "1.0.0+0.build.1-rc.10000aaa-kk-0.1", + "99999999999999999999999.999999999999999999.99999999999999999", + "1.0.0-0A.is.legal", + ] + + for v in valid_versions: + msg = "Failed to parser valid version '%s'" % v + with self.fail_if_exception(msg): + # When / Then + parse_version(v) + + def test_should_raise_on_invalid_versions(self): + # Given + invalid_versions = [ + "1", + "1.2", + "1.2.3-0123", + "1.2.3-0123.0123", + "1.1.2+.123", + "+invalid", + "-invalid", + "-invalid+invalid", + "-invalid.01", + "alpha", + "alpha.beta", + "alpha.beta.1", + "alpha.1", + "alpha+beta", + "alpha_beta", + "alpha.", + "alpha..", + "beta", + "1.0.0-alpha_beta", + "-alpha.", + "1.0.0-alpha..", + "1.0.0-alpha..1", + "1.0.0-alpha...1", + "1.0.0-alpha....1", + "1.0.0-alpha.....1", + "1.0.0-alpha......1", + "1.0.0-alpha.......1", + "01.1.1", + "1.01.1", + "1.1.01", + "1.2", + "1.2.3.DEV", + "1.2-SNAPSHOT", + "1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788", + "1.2-RC-SNAPSHOT", + "-1.0.3-gamma+b7718", + "+justmeta", + "9.8.7+meta+meta", + "9.8.7-whatever+meta+meta", + "99999999999999999999999.999999999999999999.99999999999999999" + "----RC-SNAPSHOT.12.09.1--------------------------------..12", + ] + + for v in invalid_versions: + with self.assertRaises(ValueError) as exc: + # When + parse_version(v) + # Then + self.assertTrue(str(exc).startswith("Invalid version:")) + + def test_should_get_version_attributes(self): + # Given + v = "0.19.0.1-alpha1+meta" + + # When + parsed_version = parse_version(v) + + # Then + expected_parsed_version = { + "major": "0", + "minor": "19", + "patch": "0", + "subpatch": "1", + "prerelease": "alpha1", + "buildmetadata": "meta", + } + self.assertDictEqual(parsed_version, expected_parsed_version) + + def test_version_should_be_semantic(self): + # Given + v = __version__ + + # When/Then + msg = "Version number '%s' is not semantically valid" % v + with self.fail_if_exception(msg): + parse_version(v) + + def test_model_version_should_be_semantic(self): + # Given + model_version = __model_version__ + + # When/Then + msg = "Version number '%s' is not semantically valid" % model_version + with self.fail_if_exception(msg): + parse_version(model_version) diff --git a/snips_nlu/tests/test_version.py b/snips_nlu/tests/test_version.py deleted file mode 100644 index fac64ac31..000000000 --- a/snips_nlu/tests/test_version.py +++ /dev/null @@ -1,38 +0,0 @@ -from semantic_version import Version - -from snips_nlu import __model_version__, __version__ -from snips_nlu.tests.utils import SnipsTest - - -class TestVersion(SnipsTest): - def test_version_should_be_semantic(self): - # Given - version = __version__ - - # When - valid = False - try: - Version(version) - valid = True - except ValueError: - pass - - # Then - self.assertTrue(valid, "Version number '%s' is not semantically valid" - % version) - - def test_model_version_should_be_semantic(self): - # Given - model_version = __model_version__ - - # When - valid = False - try: - Version(model_version) - valid = True - except ValueError: - pass - - # Then - self.assertTrue(valid, "Version number '%s' is not semantically valid" - % model_version)