diff --git a/CHANGES.txt b/CHANGES.txt index 01036d7..f411559 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,10 +1,19 @@ Unreleased ---------- +Bug Fixes +~~~~~~~~~ + - ``iso8601.py``: Convert ``ValueError`` (raised by ``datetime``) into ``ParseErrorr`` in ``parse_date``, so that the validation machinery upstream handles it properly. +Features +~~~~~~~~ + +- Add ``colander.List`` type, modeled on ``deform.List``: this type + preserves ordering, and allows duplicates. + 1.0a5 (2013-05-31) ------------------ diff --git a/colander/__init__.py b/colander/__init__.py index ae28ea0..513ddfd 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -808,6 +808,37 @@ class Set(SchemaType): return set(cstruct) +class List(SchemaType): + """ Type representing an ordered sequence of items. + + Desrializes an iterable to a ``list`` object. + + If the :attr:`colander.null` value is passed to the serialize + method of this class, the :attr:`colander.null` value will be + returned. + + .. versionadded: 1.0a6 + """ + + def serialize(self, node, appstruct): + if appstruct is null: + return null + + return appstruct + + def deserialize(self, node, cstruct): + if cstruct is null: + return null + + if not is_nonstr_iter(cstruct): + raise Invalid( + node, + _('${cstruct} is not iterable', mapping={'cstruct': cstruct}) + ) + + return list(cstruct) + + class SequenceItems(list): """ List marker subclass for use by Sequence.cstruct_children, which indicates diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index d5f123a..8a2d795 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -1059,6 +1059,58 @@ class TestSet(unittest.TestCase): result = typ.deserialize(node, set()) self.assertEqual(result, set()) + +class TestList(unittest.TestCase): + def _makeOne(self, **kw): + from colander import List + return List(**kw) + + def test_serialize(self): + typ = self._makeOne() + node = DummySchemaNode(typ) + provided = [] + result = typ.serialize(node, provided) + self.assertTrue(result is provided) + + def test_serialize_null(self): + from colander import null + typ = self._makeOne() + node = DummySchemaNode(typ) + result = typ.serialize(node, null) + self.assertTrue(result is null) + + def test_deserialize_no_iter(self): + typ = self._makeOne() + node = DummySchemaNode(typ) + e = invalid_exc(typ.deserialize, node, 1) + self.assertEqual(e.msg, '${cstruct} is not iterable') + + def test_deserialize_str_no_iter(self): + typ = self._makeOne() + node = DummySchemaNode(typ) + e = invalid_exc(typ.deserialize, node, "foo") + self.assertEqual(e.msg, '${cstruct} is not iterable') + + def test_deserialize_null(self): + from colander import null + typ = self._makeOne() + node = DummySchemaNode(typ) + result = typ.deserialize(node, null) + self.assertEqual(result, null) + + def test_deserialize_valid(self): + typ = self._makeOne() + node = DummySchemaNode(typ) + result = typ.deserialize(node, ('a', 'z', 'b')) + self.assertEqual(result, ['a', 'z', 'b']) + + def test_deserialize_empty_set(self): + import colander + typ = self._makeOne() + node = DummySchemaNode(typ) + result = typ.deserialize(node, ()) + self.assertEqual(result, []) + class TestSequence(unittest.TestCase): def _makeOne(self, **kw): from colander import Sequence