From 68d49269c65be4a9ee2e1abec641c6dc0ae8ba71 Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 5 Apr 2010 05:50:04 +0000 Subject: [PATCH] - Add Email and Regex validators. --- CHANGES.txt | 2 ++ colander/__init__.py | 35 ++++++++++++++++++++++++++++++++++- colander/tests.py | 42 ++++++++++++++++++++++++++++++++++++++++++ docs/api.rst | 4 ++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 741dc52..81a1ef1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,8 @@ Changes Next release ------------ +- Add Email and Regex validators. + - Raise a ``colander.Invalid`` error if a ``colander.SequenceSchema`` is created with more than one member. diff --git a/colander/__init__.py b/colander/__init__.py index b02cd72..a3bb1e6 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -2,6 +2,7 @@ import datetime import itertools import iso8601 import pprint +import re class _missing(object): pass @@ -170,6 +171,39 @@ class Function(object): if isinstance(result, basestring): raise Invalid(node, result) +class Regex(object): + """ Regular expression validator. Initialize it with the string + regular expression ``regex`` that will be compiled and matched + against ``value`` when validator is called. If ``msg`` is + supplied, it will be the error message to be used; otherwise, + defaults to 'String does not match expected pattern'. + + When calling, if ``value`` matches the regular expression, + validation succeeds; otherwise, :exc:`colander.Invalid` is + raised with the ``msg`` error message. + """ + def __init__(self, regex, msg=None): + self.match_object = re.compile(regex) + if msg is None: + self.msg = "String does not match expected pattern" + else: + self.msg = msg + + def __call__(self, node, value): + if self.match_object.match(value) is None: + raise Invalid(self.msg) + +class Email(Regex): + """ Email address validator. If ``msg`` is supplied, it will be + the error message to be used when raising :exc:`colander.Invalid`; + otherwise, defaults to "Invalid email address". + """ + def __init__(self, msg=None): + if msg is None: + msg = "Invalid email address" + super(Email, self).__init__( + u'(?i)^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$', msg=msg) + class Range(object): """ Validator which succeeds if the value it is passed is greater or equal to ``min`` and less than or equal to ``max``. If ``min`` @@ -214,7 +248,6 @@ class Length(object): node, 'Longer than maximum length %s' % self.max) - class OneOf(object): """ Validator which succeeds if the value passed to it is one of a fixed set of values """ diff --git a/colander/tests.py b/colander/tests.py index 6a121b8..2ceff98 100644 --- a/colander/tests.py +++ b/colander/tests.py @@ -194,6 +194,48 @@ class TestRange(unittest.TestCase): e = invalid_exc(validator, None, 2) self.assertEqual(e.msg, '2 is greater than maximum value 1') +class TestRegex(unittest.TestCase): + def _makeOne(self, pattern): + from colander import Regex + return Regex(pattern) + + def test_valid_regex(self): + self.assertEqual(self._makeOne('a')(None, 'a'), None) + self.assertEqual(self._makeOne('[0-9]+')(None, '1111'), None) + self.assertEqual(self._makeOne('')(None, ''), None) + self.assertEqual(self._makeOne('.*')(None, ''), None) + + def test_invalid_regexs(self): + from colander import Invalid + self.assertRaises(Invalid, self._makeOne('[0-9]+'), None, 'a') + self.assertRaises(Invalid, self._makeOne('a{2,4}'), None, 'ba') + +class TestEmail(unittest.TestCase): + def _makeOne(self): + from colander import Email + return Email() + + def test_valid_emails(self): + validator = self._makeOne() + self.assertEqual(validator(None, 'me@here.com'), None) + self.assertEqual(validator(None, 'me1@here1.com'), None) + self.assertEqual(validator(None, 'name@here1.us'), None) + self.assertEqual(validator(None, 'name@here1.info'), None) + self.assertEqual(validator(None, 'foo@bar.baz.biz'), None) + + def test_empty_email(self): + validator = self._makeOne() + from colander import Invalid + self.assertRaises(Invalid, validator, None, '') + + def test_invalid_emails(self): + validator = self._makeOne() + from colander import Invalid + self.assertRaises(Invalid, validator, None, 'me@here.') + self.assertRaises(Invalid, validator, None, 'name@here.comcom') + self.assertRaises(Invalid, validator, None, '@here.us') + self.assertRaises(Invalid, validator, None, '(name)@here.info') + class TestLength(unittest.TestCase): def _makeOne(self, min=None, max=None): from colander import Length diff --git a/docs/api.rst b/docs/api.rst index 2f9c6f9..eb93379 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -22,6 +22,10 @@ Validators .. autoclass:: Function + .. autoclass:: Regex + + .. autoclass:: Email + Types ~~~~~