mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Add pattern matching to Str and Bytes
This commit is contained in:
committed by
Rob Crittenden
parent
18cecdc515
commit
b5b2e55be5
@@ -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):
|
||||
"""
|
||||
|
@@ -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):
|
||||
"""
|
||||
|
Reference in New Issue
Block a user