Add regex validation to String type

This lets you specify a regex that string options must match
(unanchored).

Change-Id: I0be613ee38a4752b135de5ee7f189621670d1071
This commit is contained in:
Chris St. Pierre 2015-07-14 10:47:09 -05:00
parent 9a1ba80ce2
commit 3f7b642c60
2 changed files with 53 additions and 5 deletions
oslo_config

@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import re
import unittest
from oslo_config import types
@ -98,6 +98,34 @@ class StringTypeTests(TypeTestHelper, unittest.TestCase):
def test_not_equal_to_other_class(self):
self.assertFalse(types.String() == types.Integer())
def test_regex_matches(self):
self.type_instance = types.String(regex=re.compile("^[A-Z]"))
self.assertConvertedValue("Foo", "Foo")
def test_regex_matches_uncompiled(self):
self.type_instance = types.String(regex="^[A-Z]")
self.assertConvertedValue("Foo", "Foo")
def test_regex_fails(self):
self.type_instance = types.String(regex=re.compile("^[A-Z]"))
self.assertInvalid("foo")
def test_regex_and_choices_raises(self):
self.assertRaises(ValueError,
types.String,
regex=re.compile("^[A-Z]"),
choices=["Foo", "Bar", "baz"])
def test_equal_with_same_regex(self):
t1 = types.String(regex=re.compile("^[A-Z]"))
t2 = types.String(regex=re.compile("^[A-Z]"))
self.assertTrue(t1 == t2)
def test_not_equal_with_different_regex(self):
t1 = types.String(regex=re.compile("^[A-Z]"))
t2 = types.String(regex=re.compile("^[a-z]"))
self.assertFalse(t1 == t2)
class BooleanTypeTests(TypeTestHelper, unittest.TestCase):
type = types.Boolean()

@ -18,6 +18,8 @@ Use these classes as values for the `type` argument to
:class:`oslo_config.cfg.Opt` and its subclasses.
"""
import re
import netaddr
import six
@ -36,20 +38,28 @@ class String(ConfigType):
String values do not get transformed and are returned as str objects.
:param choices: Optional sequence of valid values.
:param choices: Optional sequence of valid values. Mutually
exclusive with 'regex'.
:param quotes: If True and string is enclosed with single or double
quotes, will strip those quotes. Will signal error if
string have quote at the beginning and no quote at
the end. Turned off by default. Useful if used with
container types like List.
:param regex: Optional regular expression (string or compiled
regex) that the value must match on an unanchored
search. Mutually exclusive with 'choices'.
"""
BASE_TYPES = six.string_types
def __init__(self, choices=None, quotes=False):
def __init__(self, choices=None, quotes=False, regex=None):
super(String, self).__init__()
if choices and regex:
raise ValueError("'choices' and 'regex' cannot both be specified")
self.choices = choices
self.quotes = quotes
self.regex = re.compile(regex) if regex is not None else None
def __call__(self, value):
value = str(value)
@ -59,6 +69,10 @@ class String(ConfigType):
raise ValueError('Non-closed quote: %s' % value)
value = value[1:-1]
if self.regex and not self.regex.search(value):
raise ValueError("Value %r doesn't match regex %r" %
(value, self.regex.pattern))
if self.choices is None or value in self.choices:
return value
@ -68,15 +82,21 @@ class String(ConfigType):
repr(value)))
def __repr__(self):
details = []
if self.choices:
return 'String(choices=%s)' % repr(self.choices)
details.append("choices=%r" % self.choices)
if self.regex:
details.append("regex=%r" % self.regex.pattern)
if details:
return "String(%s)" % ",".join(details)
return 'String'
def __eq__(self, other):
return (
(self.__class__ == other.__class__) and
(self.choices == other.choices) and
(self.quotes == other.quotes)
(self.quotes == other.quotes) and
(self.regex == other.regex)
)