diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index a2709d0..d208a1d 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -122,3 +122,4 @@ Contributors - Gouji Ochiai, 2014/08/21 - Tim Tisdall, 2014/09/10 - Amos Latteier, 2014/11/30 +- Jimmy Thrasibule, 2014/12/11 diff --git a/colander/__init__.py b/colander/__init__.py index be973bb..d0cd3fa 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -293,6 +293,9 @@ class Regex(object): error message to be used; otherwise, defaults to 'String does not match expected pattern'. + The ``regex`` expression behaviour can be modified by specifying + any ``flags`` value taken by ``re.compile``. + The ``regex`` argument may also be a pattern object (the result of ``re.compile``) instead of a string. @@ -300,9 +303,9 @@ class Regex(object): validation succeeds; otherwise, :exc:`colander.Invalid` is raised with the ``msg`` error message. """ - def __init__(self, regex, msg=None): + def __init__(self, regex, msg=None, flags=0): if isinstance(regex, string_types): - self.match_object = re.compile(regex) + self.match_object = re.compile(regex, flags) else: self.match_object = regex if msg is None: @@ -465,6 +468,11 @@ URL_REGEX = r"""(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9 url = Regex(URL_REGEX, _('Must be a URL')) + +UUID_REGEX = r"""^(?:urn:uuid:)?\{?[a-f0-9]{8}(?:-?[a-f0-9]{4}){3}-?[a-f0-9]{12}\}?$""" +uuid = Regex(UUID_REGEX, _('Invalid UUID string'), re.IGNORECASE) + + class SchemaType(object): """ Base class for all schema types """ def flatten(self, node, appstruct, prefix='', listitem=False): diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 961ff5d..7918e45 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -538,6 +538,57 @@ class Test_url_validator(unittest.TestCase): from colander import Invalid self.assertRaises(Invalid, self._callFUT, val) +class TestUUID(unittest.TestCase): + def _callFUT(self, val): + from colander import uuid + return uuid(None, val) + + def test_success_hexadecimal(self): + val = '123e4567e89b12d3a456426655440000' + result = self._callFUT(val) + self.assertEqual(result, None) + + def test_success_with_dashes(self): + val = '123e4567-e89b-12d3-a456-426655440000' + result = self._callFUT(val) + self.assertEqual(result, None) + + def test_success_upper_case(self): + val = '123E4567-E89B-12D3-A456-426655440000' + result = self._callFUT(val) + self.assertEqual(result, None) + + def test_success_with_braces(self): + val = '{123e4567-e89b-12d3-a456-426655440000}' + result = self._callFUT(val) + self.assertEqual(result, None) + + def test_success_with_urn_ns(self): + val = 'urn:uuid:{123e4567-e89b-12d3-a456-426655440000}' + result = self._callFUT(val) + self.assertEqual(result, None) + + def test_failure_random_string(self): + val = 'not-a-uuid' + from colander import Invalid + self.assertRaises(Invalid, self._callFUT, val) + + def test_failure_not_hexadecimal(self): + val = '123zzzzz-uuuu-zzzz-uuuu-42665544zzzz' + from colander import Invalid + self.assertRaises(Invalid, self._callFUT, val) + + def test_failure_invalid_length(self): + # Correct UUID: 8-4-4-4-12 + val = '88888888-333-4444-333-cccccccccccc' + from colander import Invalid + self.assertRaises(Invalid, self._callFUT, val) + + def test_failure_with_invalid_urn_ns(self): + val = 'urn:abcd:{123e4567-e89b-12d3-a456-426655440000}' + from colander import Invalid + self.assertRaises(Invalid, self._callFUT, val) + class TestSchemaType(unittest.TestCase): def _makeOne(self, *arg, **kw): from colander import SchemaType diff --git a/docs/api.rst b/docs/api.rst index 51b6d75..15439f5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -78,6 +78,11 @@ Validators A validator which ensures the value is a URL (via regex). + .. attribute:: uuid + + A UUID hexadecimal string validator via regular expression + using :class:`colander.Regex`. + Types ~~~~~