diff options
-rw-r--r-- | ipalib/parameters.py | 31 | ||||
-rw-r--r-- | tests/test_ipalib/test_parameters.py | 70 |
2 files changed, 94 insertions, 7 deletions
diff --git a/ipalib/parameters.py b/ipalib/parameters.py index fe32c92f6..e07808de1 100644 --- a/ipalib/parameters.py +++ b/ipalib/parameters.py @@ -31,6 +31,7 @@ TODO: * Add maxvalue, minvalue kwargs and rules to `Int` and `Float` """ +import re from types import NoneType from util import make_repr from request import ugettext @@ -38,6 +39,7 @@ from plugable import ReadOnly, lock, check_name from errors2 import ConversionError, RequirementError, ValidationError from constants import NULLS, TYPE_ERROR, CALLABLE_ERROR + class DefaultFrom(ReadOnly): """ Derive a default value from other supplied values. @@ -779,6 +781,7 @@ class Data(Param): ('minlength', int, None), ('maxlength', int, None), ('length', int, None), + ('pattern', (basestring,), None), ) def __init__(self, name, *rules, **kw): @@ -814,6 +817,16 @@ class Data(Param): self.nice, self.minlength) ) + def _rule_pattern(self, _, value): + """ + Check pattern (regex) contraint. + """ + assert type(value) is self.type + if self.re.match(value) is None: + return _('must match pattern "%(pattern)s"') % dict( + pattern=self.pattern, + ) + class Bytes(Data): """ @@ -830,9 +843,12 @@ class Bytes(Data): type = str type_error = _('must be binary data') - kwargs = Data.kwargs + ( - ('pattern', str, None), - ) + def __init__(self, name, *rules, **kw): + if kw.get('pattern', None) is None: + self.re = None + else: + self.re = re.compile(kw['pattern']) + super(Bytes, self).__init__(name, *rules, **kw) def _rule_minlength(self, _, value): """ @@ -880,9 +896,12 @@ class Str(Data): type = unicode type_error = _('must be Unicode text') - kwargs = Data.kwargs + ( - ('pattern', unicode, None), - ) + def __init__(self, name, *rules, **kw): + if kw.get('pattern', None) is None: + self.re = None + else: + self.re = re.compile(kw['pattern'], re.UNICODE) + super(Str, self).__init__(name, *rules, **kw) def _convert_scalar(self, value, index=None): """ diff --git a/tests/test_ipalib/test_parameters.py b/tests/test_ipalib/test_parameters.py index 70430714e..816f24c94 100644 --- a/tests/test_ipalib/test_parameters.py +++ b/tests/test_ipalib/test_parameters.py @@ -21,6 +21,7 @@ Test the `ipalib.parameters` module. """ +import re from types import NoneType from inspect import isclass from tests.util import raises, ClassChecker, read_only @@ -632,7 +633,7 @@ class test_Data(ClassChecker): assert o.minlength is None assert o.maxlength is None assert o.length is None - assert not hasattr(o, 'pattern') + assert o.pattern is None # Test mixing length with minlength or maxlength: o = self.cls('my_data', length=5) @@ -687,6 +688,7 @@ class test_Bytes(ClassChecker): assert o.maxlength is None assert o.length is None assert o.pattern is None + assert o.re is None # Test mixing length with minlength or maxlength: o = self.cls('my_bytes', length=5) @@ -804,6 +806,39 @@ class test_Bytes(ClassChecker): assert dummy.called() is True dummy.reset() + def test_rule_pattern(self): + """ + Test the `ipalib.parameters.Bytes._rule_pattern` method. + """ + # Test our assumptions about Python re module and Unicode: + pat = '\w+$' + r = re.compile(pat) + assert r.match('Hello_World') is not None + assert r.match(utf8_bytes) is None + assert r.match(binary_bytes) is None + + # Create instance: + o = self.cls('my_bytes', pattern=pat) + assert o.pattern is pat + rule = o._rule_pattern + translation = u'pattern=%(pattern)r' + dummy = dummy_ugettext(translation) + + # Test with passing values: + for value in ('HELLO', 'hello', 'Hello_World'): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in ('Hello!', 'Hello World', utf8_bytes, binary_bytes): + assert_equal( + rule(dummy, value), + translation % dict(pattern=pat), + ) + assert_equal(dummy.message, 'must match pattern "%(pattern)s"') + assert dummy.called() is True + dummy.reset() + class test_Str(ClassChecker): """ @@ -921,6 +956,39 @@ class test_Str(ClassChecker): assert dummy.called() is True dummy.reset() + def test_rule_pattern(self): + """ + Test the `ipalib.parameters.Str._rule_pattern` method. + """ + # Test our assumptions about Python re module and Unicode: + pat = '\w{5}$' + r1 = re.compile(pat) + r2 = re.compile(pat, re.UNICODE) + assert r1.match(unicode_str) is None + assert r2.match(unicode_str) is not None + + # Create instance: + o = self.cls('my_str', pattern=pat) + assert o.pattern is pat + rule = o._rule_pattern + translation = u'pattern=%(pattern)r' + dummy = dummy_ugettext(translation) + + # Test with passing values: + for value in (u'HELLO', u'hello', unicode_str): + assert rule(dummy, value) is None + assert dummy.called() is False + + # Test with failing values: + for value in (u'H LLO', u'***lo', unicode_str + unicode_str): + assert_equal( + rule(dummy, value), + translation % dict(pattern=pat), + ) + assert_equal(dummy.message, 'must match pattern "%(pattern)s"') + assert dummy.called() is True + dummy.reset() + class test_Password(ClassChecker): """ |