Author: ianb
Date: 2008-09-16 13:47:05 -0600 (Tue, 16 Sep 2008)
New Revision: 3590
Modified:
FormEncode/trunk/formencode/htmlfill.py
FormEncode/trunk/formencode/validators.py
FormEncode/trunk/tests/test_validators.py
Log:
typo in 'ignore'
Modified: FormEncode/trunk/formencode/htmlfill.py
===================================================================
--- FormEncode/trunk/formencode/htmlfill.py 2008-09-16 18:53:55 UTC (rev
3589)
+++ FormEncode/trunk/formencode/htmlfill.py 2008-09-16 19:47:05 UTC (rev
3590)
@@ -502,5 +502,5 @@
'none': none_formatter,
'escape': escape_formatter,
'escapenl': escapenl_formatter,
- 'ingore': ignore_formatter,
+ 'ignore': ignore_formatter,
}
Modified: FormEncode/trunk/formencode/validators.py
===================================================================
--- FormEncode/trunk/formencode/validators.py 2008-09-16 18:53:55 UTC (rev
3589)
+++ FormEncode/trunk/formencode/validators.py 2008-09-16 19:47:05 UTC (rev
3590)
@@ -1515,6 +1515,214 @@
self.message('status', state, status=res.status),
state, url)
+
+class XRI(FancyValidator):
+ r"""
+ Validator for XRIs.
+
+ It supports both i-names and i-numbers, of the first version of the XRI
+ standard.
+
+ ::
+
+ >>> inames = XRI(xri_type="i-name")
+ >>> inames.to_python(" =John.Smith ")
+ '=John.Smith'
+ >>> inames.to_python("@Free.Software.Foundation")
+ '@Free.Software.Foundation'
+ >>> inames.to_python("Python.Software.Foundation")
+ Traceback (most recent call last):
+ ...
+ Invalid: The type of i-name is not defined; it may be either
individual or organizational
+ >>> inames.to_python("http://example.org")
+ Traceback (most recent call last):
+ ...
+ Invalid: The type of i-name is not defined; it may be either
individual or organizational
+ >>> inames.to_python("=!2C43.1A9F.B6F6.E8E6")
+ Traceback (most recent call last):
+ ...
+ Invalid: "!2C43.1A9F.B6F6.E8E6" is an invalid i-name
+ >>> iname_with_schema = XRI(True, xri_type="i-name")
+ >>> iname_with_schema.to_python("=Richard.Stallman")
+ 'xri://=Richard.Stallman'
+ >>> inames.to_python("=John Smith")
+ Traceback (most recent call last):
+ ...
+ formencode.api.Invalid: "John Smith" is an invalid i-name
+ >>> inumbers = XRI(xri_type="i-number")
+ >>> inumbers.to_python("!!1000!de21.4536.2cb2.8074")
+ '!!1000!de21.4536.2cb2.8074'
+ >>> inumbers.to_python("@!1000.9554.fabd.129c!2847.df3c")
+ '@!1000.9554.fabd.129c!2847.df3c'
+
+ """
+
+ iname_valid_pattern = re.compile(r"""
+ ^
+ [\w]+ # A global alphanumeric i-name
+ (\.[\w]+)* # An i-name with dots
+ (\*[\w]+(\.[\w]+)*)* # A community i-name
+ $
+ """, re.VERBOSE|re.UNICODE)
+
+
+ iname_invalid_start = re.compile(r"^[\d\.-]", re.UNICODE)
+ """@cvar: These characters must not be at the beggining of the i-name"""
+
+ inumber_pattern = re.compile(r"""
+ ^
+ (
+ [EMAIL PROTECTED] # It's a personal or organization i-number
+ |
+ !! # It's a network i-number
+ )
+ [\dA-F]{1,4}(\.[\dA-F]{1,4}){0,3} # A global i-number
+ (![\dA-F]{1,4}(\.[\dA-F]{1,4}){0,3})* # Zero or more sub i-numbers
+ $
+ """, re.VERBOSE|re.IGNORECASE)
+
+ messages = {
+ 'noType': _("The type of i-name is not defined; it may be either
individual or organizational"),
+ 'repeatedChar': _("Dots and dashes may not be repeated consecutively"),
+ 'badIname': _('"%(iname)s" is an invalid i-name'),
+ 'badInameStart': _("i-names may not start with numbers nor punctuation
"
+ "marks"),
+ 'badInumber': _('"%(inumber)s" is an invalid i-number'),
+ 'badType': _("The XRI must be a string (not a %(type)s: %(value)r)"),
+ 'badXri': _('"%(xri_type)s" is not a valid type of XRI')
+ }
+
+ def __init__(self, add_xri=False, xri_type="i-name", **kwargs):
+ """Create an XRI validator.
+
+ @param add_xri: Should the schema be added if not present? Officially
+ it's optional.
+ @type add_xri: C{bool}
+ @param xri_type: What type of XRI should be validated? Possible values:
+ C{i-name} or C{i-number}.
+ @type xri_type: C{str}
+
+ """
+ self.add_xri = add_xri
+ assert xri_type in ('i-name', 'i-number'), \
+ ('xri_type must be "i-name" or "i-number"')
+ self.xri_type = xri_type
+ super(XRI, self).__init__(**kwargs)
+
+ def _to_python(self, value, state):
+ """Prepend the 'xri://' schema if necessary and then remove trailing
+ spaces"""
+ value = value.strip()
+ if self.add_xri and not value.startswith("xri://"):
+ value = "xri://" + value
+ return value
+
+ def validate_python(self, value, state=None):
+ """Validate an XRI
+
+ @raise Invalid: If at least one of the following conditions in met:
+ - C{value} is not a string.
+ - The XRI is not a personal, organizational or network one.
+ - The relevant validator (i-name or i-number) considers the XRI
+ is not valid.
+
+ """
+ if not (isinstance(value, str) or isinstance(value, unicode)):
+ raise Invalid(self.message("badType", state, type=str(type(value)),
+ value=value),
+ value, state)
+
+ # Let's remove the schema, if any
+ if value.startswith("xri://"):
+ value = value[6:]
+
+ if not value[0] in ('@', '=') and not (self.xri_type == "i-number" \
+ and value[0] == '!'):
+ raise Invalid(self.message("noType", state), value, state)
+
+ if self.xri_type == "i-name":
+ self._validate_iname(value, state)
+ else:
+ self._validate_inumber(value, state)
+
+ def _validate_iname(self, iname, state):
+ """Validate an i-name"""
+ # The type is not required here:
+ iname = iname[1:]
+ if ".." in iname or "--" in iname:
+ raise Invalid(self.message("repeatedChar", state), iname, state)
+ if self.iname_invalid_start.match(iname):
+ raise Invalid(self.message("badInameStart", state), iname, state)
+ if not self.iname_valid_pattern.match(iname) or "_" in iname:
+ raise Invalid(self.message("badIname", state, iname=iname), iname,
+ state)
+
+ def _validate_inumber(self, inumber, state):
+ """Validate an i-number"""
+ if not self.__class__.inumber_pattern.match(inumber):
+ raise Invalid(self.message("badInumber", state, inumber=inumber,
+ value=inumber),
+ inumber, state)
+
+
+class OpenId(FancyValidator):
+ r"""
+ OpenId validator.
+
+ ::
+ >>> v = OpenId(add_schema=True)
+ >>> v.to_python(' example.net ')
+ 'http://example.net'
+ >>> v.to_python('@TurboGears')
+ 'xri://@TurboGears'
+ >>> w = OpenId(add_schema=False)
+ >>> w.to_python(' example.net ')
+ Traceback (most recent call last):
+ ...
+ Invalid: "example.net" is not a valid OpenId (it is neither an URL nor
an XRI)
+ >>> w.to_python('!!1000')
+ '!!1000'
+ >>> w.to_python('[EMAIL PROTECTED]')
+ Traceback (most recent call last):
+ ...
+ Invalid: "[EMAIL PROTECTED]" is not a valid OpenId (it is neither an
URL nor an XRI)
+
+ """
+
+ messages = {
+ 'badId': _('"%(id)s" is not a valid OpenId (it is neither an URL nor
an XRI)')
+ }
+
+ def __init__(self, add_schema=False, **kwargs):
+ """Create an OpenId validator.
+
+ @param add_schema: Should the schema be added if not present?
+ @type add_schema: C{bool}
+
+ """
+ self.url_validator = URL(add_http=add_schema)
+ self.iname_validator = XRI(add_schema, xri_type="i-name")
+ self.inumber_validator = XRI(add_schema, xri_type="i-number")
+
+ def _to_python(self, value, state):
+ value = value.strip()
+ try:
+ return self.url_validator.to_python(value, state)
+ except Invalid:
+ try:
+ return self.iname_validator.to_python(value, state)
+ except Invalid:
+ try:
+ return self.inumber_validator.to_python(value, state)
+ except Invalid:
+ pass
+ # It's not an OpenId!
+ raise Invalid(self.message("badId", state, id=value), value, state)
+
+ def validate_python(self, value, state):
+ self._to_python(value, state)
+
+
def StateProvince(*kw, **kwargs):
warnings.warn("please use formencode.national.USStateProvince",
DeprecationWarning, stacklevel=2)
from formencode.national import USStateProvince
Modified: FormEncode/trunk/tests/test_validators.py
===================================================================
--- FormEncode/trunk/tests/test_validators.py 2008-09-16 18:53:55 UTC (rev
3589)
+++ FormEncode/trunk/tests/test_validators.py 2008-09-16 19:47:05 UTC (rev
3590)
@@ -1,4 +1,8 @@
-from formencode.validators import String, UnicodeString, Invalid, Int
+# -*- coding: utf-8 -*-
+import unittest
+
+from formencode.validators import String, UnicodeString, Invalid, Int, XRI, \
+ OpenId
from formencode.validators import DateConverter
from formencode.variabledecode import NestedVariables
from formencode.schema import Schema
@@ -127,3 +131,171 @@
else:
raise Exception("Shouldn't be valid data", values, start_values)
+
+
+
+
+class TestXRIValidator(unittest.TestCase):
+ """Generic tests for the XRI validator"""
+
+ def test_creation_valid_params(self):
+ """The creation of an XRI validator with valid parameters must
+ succeed"""
+ XRI()
+ XRI(True, "i-name")
+ XRI(True, "i-number")
+ XRI(False, "i-name")
+ XRI(False, "i-number")
+
+ def test_creation_invalid_xri(self):
+ """Only "i-name" and "i-number" are valid XRIs"""
+ self.assertRaises(AssertionError, XRI, True, 'i-something')
+
+ def test_valid_simple_individual_iname_without_type(self):
+ """XRIs must start with either an equals sign or an at sign"""
+ validator = XRI(True, "i-name")
+ self.assertRaises(Invalid, validator.validate_python, 'Gustavo')
+
+ def test_valid_iname_with_schema(self):
+ """XRIs may have their schema in the beggining"""
+ validator = XRI()
+ self.assertEqual(validator.to_python('xri://=Gustavo'),
+ 'xri://=Gustavo')
+
+ def test_schema_is_added_if_asked(self):
+ """The schema must be added to an XRI if explicitly asked"""
+ validator = XRI(True)
+ self.assertEqual(validator.to_python('=Gustavo'),
+ 'xri://=Gustavo')
+
+ def test_schema_not_added_if_not_asked(self):
+ """The schema must not be added to an XRI unless explicitly asked"""
+ validator = XRI()
+ self.assertEqual(validator.to_python('=Gustavo'), '=Gustavo')
+
+ def test_spaces_are_trimmed(self):
+ """Spaces at the beggining or end of the XRI are removed"""
+ validator = XRI()
+ self.assertEqual(validator.to_python(' =Gustavo '), '=Gustavo')
+
+
+class TestINameValidator(unittest.TestCase):
+ """Tests for the XRI i-names validator"""
+
+ def setUp(self):
+ self.validator = XRI(xri_type="i-name")
+
+ def test_valid_global_individual_iname(self):
+ """Global & valid individual i-names must pass validation"""
+ self.validator.validate_python('=Gustavo')
+
+ def test_valid_global_organizational_iname(self):
+ """Global & valid organizational i-names must pass validation"""
+ self.validator.validate_python('@Canonical')
+
+ def test_invalid_iname(self):
+ """Non-string i-names are rejected"""
+ self.assertRaises(Invalid, self.validator.validate_python, None)
+
+ def test_exclamation_in_inames(self):
+ """Exclamation marks at the beggining of XRIs is something specific
+ to i-numbers and must be rejected in i-names"""
+ self.assertRaises(Invalid, self.validator.validate_python,
+ "!!1000!de21.4536.2cb2.8074")
+
+ def test_repeated_characters(self):
+ """Dots and dashes must not be consecutively repeated in i-names"""
+ self.assertRaises(Invalid, self.validator.validate_python,
+ "=Gustavo--Narea")
+ self.assertRaises(Invalid, self.validator.validate_python,
+ "=Gustavo..Narea")
+
+ def test_punctuation_marks_at_beggining(self):
+ """Punctuation marks at the beggining of i-names are forbidden"""
+ self.assertRaises(Invalid, self.validator.validate_python,
+ "=.Gustavo")
+ self.assertRaises(Invalid, self.validator.validate_python,
+ "=-Gustavo.Narea")
+
+ def test_numerals_at_beggining(self):
+ """Numerals at the beggining of i-names are forbidden"""
+ self.assertRaises(Invalid, self.validator.validate_python,
+ "=1Gustavo")
+
+ def test_non_english_inames(self):
+ """i-names with non-English characters are valid"""
+ self.validator.validate_python(u'=Gustavo.Narea.García')
+ self.validator.validate_python(u'@名前.例')
+
+ def test_inames_plus_paths(self):
+ """i-names with paths are valid but not supported"""
+ self.assertRaises(Invalid, self.validator.validate_python,
+ "=Gustavo/(+email)")
+
+ def test_communities(self):
+ """i-names may have so-called 'communities'"""
+ self.validator.validate_python(u'=María*Yolanda*Liliana*Gustavo')
+ self.validator.validate_python(u'=Gustavo*Andreina')
+ self.validator.validate_python(u'@IBM*Lenovo')
+
+
+class TestINumberValidator(unittest.TestCase):
+ """Tests for the XRI i-number validator"""
+
+ def setUp(self):
+ self.validator = XRI(xri_type="i-number")
+
+ def test_valid_global_personal_inumber(self):
+ """Global & valid personal i-numbers must pass validation"""
+ self.validator.validate_python('=!1000.a1b2.93d2.8c73')
+
+ def test_valid_global_organizational_inumber(self):
+ """Global & valid organizational i-numbers must pass validation"""
+ self.validator.validate_python('@!1000.a1b2.93d2.8c73')
+
+ def test_valid_global_network_inumber(self):
+ """Global & valid network i-numbers must pass validation"""
+ self.validator.validate_python('!!1000')
+
+ def test_valid_community_personal_inumbers(self):
+ """Community & valid personal i-numbers must pass validation"""
+ self.validator.validate_python('=!1000.a1b2.93d2.8c73!3ae2!1490')
+
+ def test_valid_community_organizational_inumber(self):
+ """Community & valid organizational i-numbers must pass validation"""
+ self.validator.validate_python('@!1000.9554.fabd.129c!2847.df3c')
+
+ def test_valid_community_network_inumber(self):
+ """Community & valid network i-numbers must pass validation"""
+ self.validator.validate_python('!!1000!de21.4536.2cb2.8074')
+
+
+class TestOpenIdValidator(unittest.TestCase):
+ """Tests for the OpenId validator"""
+
+ def setUp(self):
+ self.validator = OpenId(add_schema=False)
+
+ def test_url(self):
+ self.assertEqual(self.validator.to_python('http://example.org'),
+ 'http://example.org')
+
+ def test_iname(self):
+ self.assertEqual(self.validator.to_python('=Gustavo'), '=Gustavo')
+
+ def test_inumber(self):
+ self.assertEqual(self.validator.to_python('!!1000'), '!!1000')
+
+ def test_email(self):
+ """Email addresses are not valid OpenIds!"""
+ self.assertRaises(Invalid, self.validator.to_python,
+ "[EMAIL PROTECTED]")
+
+ def test_prepending_schema(self):
+ validator = OpenId(add_schema=True)
+ self.assertEqual(validator.to_python("example.org"),
+ "http://example.org")
+ self.assertEqual(validator.to_python("=Gustavo"),
+ "xri://=Gustavo")
+ self.assertEqual(validator.to_python("!!1000"),
+ "xri://!!1000")
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
FormEncode-CVS mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/formencode-cvs