From a3c590e806b49cd15a9556dbc71f059e2c2d3962 Mon Sep 17 00:00:00 2001 From: Trevor McCasland Date: Tue, 9 Aug 2016 16:17:05 -0500 Subject: Add Range type Add a type which accepts "1-3" to produce a range. This can be used to EG find a free port in a range or validate that a user input is acceptable. It should be possible to make a ListOpt(Range) which will accept "1-3,5-6". Change-Id: I9a2727522f25535a25c53ac89cd5e0096c87fef9 Co-Authored-By: Alexis Lee --- oslo_config/tests/test_types.py | 40 +++++++++++++++++++++++++++++ oslo_config/types.py | 57 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/oslo_config/tests/test_types.py b/oslo_config/tests/test_types.py index b53817d..524aa94 100644 --- a/oslo_config/tests/test_types.py +++ b/oslo_config/tests/test_types.py @@ -16,6 +16,7 @@ import re import unittest from oslo_config import types +from six.moves import range as compat_range class ConfigTypeTests(unittest.TestCase): @@ -545,6 +546,45 @@ class ListTypeTests(TypeTestHelper, unittest.TestCase): self.assertFalse(types.List() == types.Integer()) +class RangeTypeTests(TypeTestHelper, unittest.TestCase): + type = types.Range() + + def assertRange(self, s, r1, r2, step=1): + self.assertEqual(list(compat_range(r1, r2, step)), + list(self.type_instance(s))) + + def test_range(self): + self.assertRange('0-2', 0, 3) + self.assertRange('-2-0', -2, 1) + self.assertRange('2-0', 2, -1, -1) + self.assertRange('-3--1', -3, 0) + self.assertRange('-1--3', -1, -4, -1) + self.assertRange('-1', -1, 0) + self.assertInvalid('--1') + self.assertInvalid('4-') + self.assertInvalid('--') + self.assertInvalid('1.1-1.2') + self.assertInvalid('a-b') + + def test_range_bounds(self): + self.type_instance = types.Range(1, 3) + self.assertRange('1-3', 1, 4) + self.assertRange('2-2', 2, 3) + self.assertRange('2', 2, 3) + self.assertInvalid('1-4') + self.assertInvalid('0-3') + self.assertInvalid('0-4') + + def test_range_exclusive(self): + self.type_instance = types.Range(inclusive=False) + self.assertRange('0-2', 0, 2) + self.assertRange('-2-0', -2, 0) + self.assertRange('2-0', 2, 0, -1) + self.assertRange('-3--1', -3, -1) + self.assertRange('-1--3', -1, -3, -1) + self.assertRange('-1', -1, -1) + + class DictTypeTests(TypeTestHelper, unittest.TestCase): type = types.Dict() diff --git a/oslo_config/types.py b/oslo_config/types.py index 892b589..c28f335 100644 --- a/oslo_config/types.py +++ b/oslo_config/types.py @@ -27,6 +27,7 @@ import abc import netaddr import rfc3986 import six +from six.moves import range as compat_range @six.add_metaclass(abc.ABCMeta) @@ -471,6 +472,62 @@ class List(ConfigType): return ','.join(value) +class Range(ConfigType): + + """Range type. + + Represents a range of integers. A range is identified by an integer both + sides of a '-' character. Negatives are allowed. A single number is also a + valid range. + + :param min: Optional check that lower bound is greater than or equal to + min. + :param max: Optional check that upper bound is less than or equal to max. + :param inclusive: True if the right bound is to be included in the range. + :param type_name: Type name to be used in the sample config file. + + .. versionadded:: 3.16 + """ + + def __init__(self, min=None, max=None, inclusive=True, + type_name='range value'): + super(Range, self).__init__(type_name) + self.min = min + self.max = max + self.inclusive = inclusive + + def __call__(self, value): + value = str(value) + num = "0|-?[1-9][0-9]*" + m = re.match("^(%s)(?:-(%s))?$" % (num, num), value) + if not m: + raise ValueError('Invalid Range: %s' % value) + left = int(m.group(1)) + right = int(left if m.group(2) is None else m.group(2)) + + if left < right: + left = Integer(min=self.min)(left) + right = Integer(max=self.max)(right) + step = 1 + else: + left = Integer(max=self.max)(left) + right = Integer(min=self.min)(right) + step = -1 + if self.inclusive: + right += step + return compat_range(left, right, step) + + def __eq__(self, other): + return ( + (self.__class__ == other.__class__) and + (self.min == other.min) and + (self.max == other.max) + ) + + def _formatter(self, value): + return value + + class Dict(ConfigType): """Dictionary type. -- cgit v1.2.1