From 048b76c143d9888d0e7fae45f0f685f53e58c73b Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Thu, 11 Dec 2014 10:58:21 +0100 Subject: [PATCH 1/6] Add a UUID string validator The `uuid` validator uses a regex validator to match on all UUID strings allowed by the `uuid.UUID` class. --- colander/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/colander/__init__.py b/colander/__init__.py index be973bb..235503a 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -300,9 +300,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 +465,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'), 2) # 2 = re.IGNORECASE + + class SchemaType(object): """ Base class for all schema types """ def flatten(self, node, appstruct, prefix='', listitem=False): From 369e9dc2ef273def6a742ea35fddceedd3d29bb8 Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Thu, 11 Dec 2014 11:01:58 +0100 Subject: [PATCH 2/6] Sign the contributors agreement Signed-off-by: Jimmy Thrasibule --- CONTRIBUTORS.txt | 1 + 1 file changed, 1 insertion(+) 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 From 442014665227c736bffa8b68e40e1caadf1c4c3d Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Fri, 12 Dec 2014 22:09:14 +0100 Subject: [PATCH 3/6] Fix UUID validator implementation - Document the new `flags` parameter now accepted by the Regex validator. - Make use of the correct regex flag. --- colander/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colander/__init__.py b/colander/__init__.py index 235503a..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. @@ -467,7 +470,7 @@ 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'), 2) # 2 = re.IGNORECASE +uuid = Regex(UUID_REGEX, _('Invalid UUID string'), re.IGNORECASE) class SchemaType(object): From 3741e6296093b9ca43e6230b9a88883c5603fded Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Fri, 12 Dec 2014 22:50:04 +0100 Subject: [PATCH 4/6] Add unit tests for UUID validator --- colander/tests/test_colander.py | 51 +++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 961ff5d..cd2976f 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.assertIsNone(result) + + def test_success_with_dashes(self): + val = '123e4567-e89b-12d3-a456-426655440000' + result = self._callFUT(val) + self.assertIsNone(result) + + def test_success_upper_case(self): + val = '123E4567-E89B-12D3-A456-426655440000' + result = self._callFUT(val) + self.assertIsNone(result) + + def test_success_with_braces(self): + val = '{123e4567-e89b-12d3-a456-426655440000}' + result = self._callFUT(val) + self.assertIsNone(result) + + def test_success_with_urn_ns(self): + val = 'urn:uuid:{123e4567-e89b-12d3-a456-426655440000}' + result = self._callFUT(val) + self.assertIsNone(result) + + 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 From 6c873ba762e94c82cd8ee389e357f8d4df508633 Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Fri, 12 Dec 2014 23:17:24 +0100 Subject: [PATCH 5/6] Add the `uuid` validator to the API documentation --- docs/api.rst | 5 +++++ 1 file changed, 5 insertions(+) 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 ~~~~~ From b706ea144cab2068be2f047593cc12fb47b9d683 Mon Sep 17 00:00:00 2001 From: Jimmy Thrasibule Date: Fri, 12 Dec 2014 23:49:01 +0100 Subject: [PATCH 6/6] Fix test unit errors Replace assertIsNone by assertEqual. --- colander/tests/test_colander.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index cd2976f..7918e45 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -546,27 +546,27 @@ class TestUUID(unittest.TestCase): def test_success_hexadecimal(self): val = '123e4567e89b12d3a456426655440000' result = self._callFUT(val) - self.assertIsNone(result) + self.assertEqual(result, None) def test_success_with_dashes(self): val = '123e4567-e89b-12d3-a456-426655440000' result = self._callFUT(val) - self.assertIsNone(result) + self.assertEqual(result, None) def test_success_upper_case(self): val = '123E4567-E89B-12D3-A456-426655440000' result = self._callFUT(val) - self.assertIsNone(result) + self.assertEqual(result, None) def test_success_with_braces(self): val = '{123e4567-e89b-12d3-a456-426655440000}' result = self._callFUT(val) - self.assertIsNone(result) + self.assertEqual(result, None) def test_success_with_urn_ns(self): val = 'urn:uuid:{123e4567-e89b-12d3-a456-426655440000}' result = self._callFUT(val) - self.assertIsNone(result) + self.assertEqual(result, None) def test_failure_random_string(self): val = 'not-a-uuid'