You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by em...@apache.org on 2017/04/14 22:01:19 UTC
incubator-ariatosca git commit: ARIA-140 Version utils
Repository: incubator-ariatosca
Updated Branches:
refs/heads/ARIA-140-version-utils [created] 3f11fd718
ARIA-140 Version utils
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/3f11fd71
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/3f11fd71
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/3f11fd71
Branch: refs/heads/ARIA-140-version-utils
Commit: 3f11fd718c9ee7d96a332ef23c2806086aacc384
Parents: a7e7826
Author: Tal Liron <ta...@gmail.com>
Authored: Fri Apr 14 13:39:02 2017 -0500
Committer: Tal Liron <ta...@gmail.com>
Committed: Fri Apr 14 17:00:58 2017 -0500
----------------------------------------------------------------------
aria/modeling/service_template.py | 10 +-
aria/utils/versions.py | 161 +++++++++++++++++++++++++++++++++
tests/utils/test_versions.py | 76 ++++++++++++++++
3 files changed, 243 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/3f11fd71/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
index 51fea2f..a0a1124 100644
--- a/aria/modeling/service_template.py
+++ b/aria/modeling/service_template.py
@@ -33,7 +33,8 @@ from sqlalchemy.ext.associationproxy import association_proxy
from ..parser import validation
from ..parser.consumption import ConsumptionContext
from ..parser.reading import deepcopy_with_locators
-from ..utils import collections, formatting, console
+from ..utils import (collections, formatting, console)
+from ..utils.versions import VersionString
from .mixins import TemplateModelMixin
from . import (
relationship,
@@ -2135,12 +2136,13 @@ class PluginSpecificationBase(TemplateModelMixin):
matching_plugins = []
if plugins:
for plugin in plugins:
- # TODO: we need to use a version comparator
if (plugin.name == self.name) and \
- ((self.version is None) or (plugin.package_version >= self.version)):
+ ((self.version is None) or \
+ (VersionString(plugin.package_version) >= self.version)):
matching_plugins.append(plugin)
self.plugin = None
if matching_plugins:
# Return highest version of plugin
- self.plugin = sorted(matching_plugins, key=lambda plugin: plugin.package_version)[-1]
+ key = lambda plugin: VersionString(plugin.package_version).key
+ self.plugin = sorted(matching_plugins, key=key)[-1]
return self.plugin is not None
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/3f11fd71/aria/utils/versions.py
----------------------------------------------------------------------
diff --git a/aria/utils/versions.py b/aria/utils/versions.py
new file mode 100644
index 0000000..cfa92cb
--- /dev/null
+++ b/aria/utils/versions.py
@@ -0,0 +1,161 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+General-purpose version string handling
+"""
+
+import re
+
+
+_INF = float('inf')
+
+_NULL = (), _INF
+
+_DIGITS_RE = re.compile(r'^\d+$')
+
+_PREFIXES = {
+ 'dev': 0.0001,
+ 'alpha': 0.001,
+ 'beta': 0.01,
+ 'rc': 0.1,
+}
+
+
+class VersionString(unicode):
+ """
+ Version string that can be compared, sorted, made unique in a set, and used as q unique dict
+ key.
+
+ The primary part of the string is one or more dot-separated integers. Trailing zeroes are
+ treated as redundant, e.g. "1.0.0" == "1.0" == "1".
+
+ An optional qualifier can be added after a "-". The qualifier can be an integer or a specially
+ treated prefixed integer, e.g. "1.1-beta1" > "1.1-alpha2". The case of the prefix is ignored.
+
+ Integer qualifiers will always be greater than prefixed integer qualifiers, e.g. "1.1-1" >
+ "1.1-beta1".
+
+ Versions without a qualifier will always be greater than their equivalents with a qualifier,
+ e.g. e.g. "1.1" > "1.1-1".
+
+ Any value that does not conform to this format will be treated as a zero version, which would
+ be lesser than any non-zero version.
+
+ For efficient list sorts use the ``key`` property, e.g.:
+ ``sorted(versions, key=lambda x: x.key)``
+ """
+
+ NULL = None # initialized below
+
+ def __init__(self, value=None):
+ if value is not None:
+ super(VersionString, self).__init__(value)
+ self.key = parse_version_string(self)
+
+ def __eq__(self, version):
+ if not isinstance(version, VersionString):
+ version = VersionString(version)
+ return self.key == version.key
+
+ def __lt__(self, version):
+ if not isinstance(version, VersionString):
+ version = VersionString(version)
+ return self.key < version.key
+
+ def __hash__(self):
+ return self.key.__hash__()
+
+
+def parse_version_string(version): # pylint: disable=too-many-branches
+ """
+ Parses a version string.
+
+ :param version: The version string
+ :returns: The primary tuple and qualifier float
+ :rtype: ((int), float)
+ """
+
+ if version is None:
+ return _NULL
+ version = unicode(version)
+
+ # Split to primary and qualifier on '-'
+ split = version.split('-', 2)
+ if len(split) == 2:
+ primary, qualifier = split
+ else:
+ primary = split[0]
+ qualifier = None
+
+ # Parse primary
+ split = primary.split('.')
+ primary = []
+ for element in split:
+ if _DIGITS_RE.match(element) is None:
+ # Invalid version string
+ return _NULL
+ try:
+ element = int(element)
+ except ValueError:
+ # Invalid version string
+ return _NULL
+ primary.append(element)
+
+ # Remove redundant zeros
+ for element in reversed(primary):
+ if element == 0:
+ primary.pop()
+ else:
+ break
+ primary = tuple(primary)
+
+ # Parse qualifier
+ if qualifier is not None:
+ if _DIGITS_RE.match(qualifier) is not None:
+ # Integer qualifer
+ try:
+ qualifier = float(int(qualifier))
+ except ValueError:
+ # Invalid version string
+ return _NULL
+ else:
+ # Prefixed integer qualifier
+ value = None
+ qualifier = qualifier.lower()
+ for prefix, factor in _PREFIXES.iteritems():
+ if qualifier.startswith(prefix):
+ value = qualifier[len(prefix):]
+ if _DIGITS_RE.match(value) is None:
+ # Invalid version string
+ return _NULL
+ try:
+ value = float(int(value)) * factor
+ except ValueError:
+ # Invalid version string
+ return _NULL
+ break
+ if value is None:
+ # Invalid version string
+ return _NULL
+ qualifier = value
+ else:
+ # Version strings with no qualifiers are higher
+ qualifier = _INF
+
+ return primary, qualifier
+
+
+VersionString.NULL = VersionString()
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/3f11fd71/tests/utils/test_versions.py
----------------------------------------------------------------------
diff --git a/tests/utils/test_versions.py b/tests/utils/test_versions.py
new file mode 100644
index 0000000..94f632b
--- /dev/null
+++ b/tests/utils/test_versions.py
@@ -0,0 +1,76 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from aria.utils.versions import VersionString
+
+
+def test_versions():
+ # No qualifiers
+ assert VersionString('20') == VersionString('20')
+ assert VersionString('20') == VersionString('20.0')
+ assert VersionString('20') == VersionString('20.0.0')
+ assert VersionString('20') < VersionString('20.0.1')
+
+ # With numeric qualifiers
+ assert VersionString('20.0.1-1') < VersionString('20.0.1-2')
+ assert VersionString('20.0.1-0') < VersionString('20.0.1')
+ assert VersionString('20.0.1-1') < VersionString('20.0.1')
+
+ # With prefixed qualifiers
+ assert VersionString('20.0.1-beta1') < VersionString('20.0.1-beta2')
+ assert VersionString('20.0.1-beta1') < VersionString('20.0.1-1')
+ assert VersionString('20.0.1-beta1') < VersionString('20.0.1')
+ assert VersionString('20.0.1-beta2') < VersionString('20.0.1-rc2')
+ assert VersionString('20.0.1-alpha2') < VersionString('20.0.1-beta1')
+ assert VersionString('20.0.1-dev2') < VersionString('20.0.1-alpha1')
+ assert VersionString('20.0.1-DEV2') < VersionString('20.0.1-ALPHA1')
+
+ # Coercive comparisons
+ assert VersionString('20.0.0') == VersionString(10 * 2)
+ assert VersionString('20.0.0') == VersionString(20.0)
+
+ # Non-VersionString comparisons
+ assert VersionString('20.0.0') == 20
+ assert VersionString('20.0.0') < '20.0.1'
+
+ # Nulls
+ assert VersionString() == VersionString()
+ assert VersionString() == VersionString.NULL
+ assert VersionString(None) == VersionString.NULL
+ assert VersionString.NULL == None # pylint: disable=singleton-comparison
+ assert VersionString.NULL == 0
+
+ # Invalid version strings
+ assert VersionString('maxim is maxim') == VersionString.NULL
+ assert VersionString('20.maxim.0') == VersionString.NULL
+ assert VersionString('20.0.0-maxim1') == VersionString.NULL
+ assert VersionString('20.0.1-1.1') == VersionString.NULL
+
+ # Sorts
+ v1 = VersionString('20.0.0')
+ v2 = VersionString('20.0.1-beta1')
+ v3 = VersionString('20.0.1')
+ v4 = VersionString('20.0.2')
+ assert [v1, v2, v3, v4] == sorted([v4, v3, v2, v1], key=lambda v: v.key)
+
+ # Sets
+ v1 = VersionString('20.0.0')
+ v2 = VersionString('20.0')
+ v3 = VersionString('20')
+ assert set([v1]) == set([v1, v2, v3])
+
+ # Dicts
+ the_dict = {v1: 'test'}
+ assert the_dict.get(v2) == 'test'