# -*- coding: utf-8 -*-
#
# Licensed 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.
"""
Stuff to handle version ranges.
"""
import re
TD_VERSION = re.compile(r'^\d+t$')
[docs]def split_version(version_string):
# We split on '.' and '-' because ancient tagged versions had the tag
# delimited by a '-'
return re.split('\.|-', version_string.strip())
[docs]def get_int_or_t(x):
try:
return int(x)
except ValueError as e:
if x is 't':
return x
if x[-1] is 't':
int(x[:-1])
return x
raise e
[docs]def is_int_or_t(x):
try:
get_int_or_t(x)
return True
except ValueError:
return False
[docs]def strip_tag(version):
"""
Strip any parts of the version that are not numeric components or t's
We leave the 't' on numeric components if it's present.
['1', '2', 'THREE'] -> (1, 2)
['1', 'TWO', '3'] -> (1, 3)
['0', '115t', 'SNAPSHOT'] -> (0, '115t')
['ZERO', '123t'] -> (123t)
['0', '148', 't'] => (0, 148, 't')
['0', '148', 't', 0, 1] => (0, 148, 't', 0, 1)
['0', '148', 't', 0, 1, 'SNAPSHOT'] => (0, 148, 't', 0, 1)
['0', '162', 'SNAPSHOT', 't', 'SNAPSHOT'] => (0, 162, 't')
This checks the components of the version from least to most significant.
:param version: something that can be sliced
:return: a tuple containing only integer components or the letter t
"""
result = list(version[:])
result = [get_int_or_t(x) for x in result if is_int_or_t(x)]
return tuple(result)
[docs]class VersionRange(object):
"""
Represents a range of version numbers [min_version, max_version).
The interval is right-open so that you can construct a numerically
continuous list of versions like so:
l = [VersionRange((0, 0), (0, 5)), VersionRange((0, 5), (1, 0))]
and for all versions v where 0.0 <= v < 1.0 is contained in exactly one
VersionRange in l.
Continuity between version ranges can be checked using is_continuous.
VersionRanges understand how to check if a Teradata version is contained
in a VersionRange, but do no special handling to accomodate Teradata
versions in their internal min_version and max_version members. I.e.,
creating a VersionRange with a Teradata version will work, but __contains__
will not work correctly. We don't currently need this, and hope not to.
Note that the right-open interval representation of a version range does
NOT allow the creation of a VersionRange that contains exactly one version.
Note that empty intervals cannot be constructed as the serve no useful
purpose. Specifically, we assert that min_version < max_version in the
constructor.
"""
def __init__(self, min_version, max_version, versioned_thing=None):
# not pythonic, but bare ints screw things up.
assert isinstance(min_version, tuple)
assert isinstance(max_version, tuple)
l = max(len(min_version), len(max_version))
min_pad = VersionRange.pad_tuple(min_version, l, 0)
max_pad = VersionRange.pad_tuple(max_version, l, 0)
assert min_pad < max_pad
self.min_version = min_version
self.max_version = max_version
self.versioned_thing = versioned_thing
def __str__(self):
return '[%s, %s) -> %s' % (
'.'.join([str(c) for c in self.min_version]),
'.'.join([str(c) for c in self.max_version]),
self.versioned_thing)
@staticmethod
[docs] def strip_td_suffix(version):
new_version = ()
for component in version:
if TD_VERSION.match(str(component)):
new_last = component[:-1]
new_version += (int(new_last),)
elif component is not 't':
new_version += (int(component),)
return new_version
@staticmethod
[docs] def pad_tuple(tup, length, pad):
assert len(tup) <= length
result = list(tup)
while len(result) < length:
result.append(pad)
return tuple(result)
[docs] def zero_pad(self, other):
"""
Pad out min_version, max_version, and other with zeroes to the length
of the longest of the three. This allows subsequent comparisons to work
as expected when tuples are of unequal length.
Returns a tuple of tuples padded out to the same length
"""
l = max(len(self.min_version), len(self.max_version), len(other))
return (self.pad_tuple(self.min_version, l, 0),
self.pad_tuple(self.max_version, l, 0),
self.pad_tuple(other, l, 0))
def __contains__(self, other):
other = self.strip_td_suffix(other)
other = tuple([int(component) for component in other])
min_pad, max_pad, o_pad = self.zero_pad(other)
return min_pad <= o_pad and o_pad < max_pad
[docs] def is_continuous(self, next):
min_pad, max_pad, next_min_pad = self.zero_pad(next.min_version)
return max_pad == next_min_pad
[docs]class VersionRangeList(object):
"""
A VersionRangeList is a list of continuous, non-overlapping VersionRanges.
This is guaranteed by calling VersionRange.is_continuous on all pairs of
VersionRanges vr[i], vr[i + 1] in the list, which ensures that the list is
both sorted in order of ascending version and that the interval
[vr[0].min_version, vr[n].max_version) has no discontinuities.
"""
def __init__(self, *range_list):
if len(range_list) >= 2:
for i in range(0, len(range_list) - 1):
assert range_list[i].is_continuous(range_list[i + 1])
self.range_list = range_list
def __str__(self):
return '\n'.join([str(vr) for vr in self.range_list])
[docs] def for_version(self, version):
for range in self.range_list:
if version in range:
return range.versioned_thing
raise KeyError(version)