diff --git a/CHANGES.rst b/CHANGES.rst index c7aaf66..235b99f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -21,6 +21,14 @@ Features - Add a ``missing_msg`` argument to ``SchemaNode`` that specifies the error message to be used when the node is required and missing +- ``colander.String`` schema type now supports an optional keyword argument + ``allow_empty`` which, when True, deserializes an empty string to an + empty string. When False (default), an empty string deserializes to + ``colander.null``. This allows for a node to be explicitly required, but + allow an empty ('') value to be provided. + https://github.com/Pylons/colander/issues/199 + + Bug Fixes --------- diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 81031f0..9245229 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -122,6 +122,7 @@ Contributors - Gouji Ochiai, 2014/08/21 - Tim Tisdall, 2014/09/10 - Romain Commandé, 2014/10/11 +- Lucas Taylor, 2014/11/26 - Nando Florestan, 2014/11/27 - Amos Latteier, 2014/11/30 - Jimmy Thrasibule, 2014/12/11 diff --git a/colander/__init__.py b/colander/__init__.py index 1bd8f87..949299c 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1113,7 +1113,7 @@ Seq = Sequence class String(SchemaType): """ A type representing a Unicode string. - This type constructor accepts one argument: + This type constructor accepts two arguments: ``encoding`` Represents the encoding which should be applied to value @@ -1166,11 +1166,17 @@ class String(SchemaType): encoding. If this is not true, an :exc:`colander.Invalid` error will result. + ``allow_empty`` + Boolean, if True allows deserialization of an empty string. If + False (default), empty strings will deserialize to + :attr:`colander.null` + The subnodes of the :class:`colander.SchemaNode` that wraps this type are ignored. """ - def __init__(self, encoding=None): + def __init__(self, encoding=None, allow_empty=False): self.encoding = encoding + self.allow_empty = allow_empty def serialize(self, node, appstruct): if appstruct is null: @@ -1192,6 +1198,9 @@ class String(SchemaType): mapping={'val':appstruct, 'err':e}) ) def deserialize(self, node, cstruct): + if cstruct == '' and self.allow_empty: + return text_type('') + if not cstruct: return null diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 6d05d71..943005f 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -1439,9 +1439,9 @@ class TestSequence(unittest.TestCase): self.assertEqual(result, SequenceItems(['a'])) class TestString(unittest.TestCase): - def _makeOne(self, encoding=None): + def _makeOne(self, encoding=None, allow_empty=False): from colander import String - return String(encoding) + return String(encoding, allow_empty) def test_alias(self): from colander import Str @@ -1454,6 +1454,9 @@ class TestString(unittest.TestCase): typ = self._makeOne(None) result = typ.deserialize(node, '') self.assertEqual(result, null) + typ = self._makeOne(None, allow_empty=True) + result = typ.deserialize(node, '') + self.assertEqual(result, '') def test_deserialize_uncooperative(self): val = Uncooperative()