diff --git a/CHANGES.rst b/CHANGES.rst index 4285ab7..4b96df0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -60,6 +60,14 @@ Features - Add ``normalize`` option to ``Decimal``, stripping the rightmost trailing zeros. +- ``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 81a76cb..83b087f 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -124,6 +124,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 d641df6..d9f0617 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1159,7 +1159,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 @@ -1212,11 +1212,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: @@ -1240,6 +1246,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 e3e6410..f9e4c2e 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -1466,9 +1466,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 @@ -1481,6 +1481,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()