diff --git a/CHANGES.txt b/CHANGES.txt index be1e1bb..721cd6e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -11,6 +11,8 @@ Bug Fixes Features ~~~~~~~~ +- Add ``colander.Set`` type, ported from ``deform.Set`` + - Add Python 3.3 to tox configuration and use newer tox testing regime (setup.py dev). diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 62357ae..35c6e72 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -107,4 +107,4 @@ Contributors - Atsushi Odagiri, 2012/02/04 - Daniel Nouri, 2012/03/28 - Gary van der Merwe, 2012/04/05 -- Domen Kožar, 2012-04-16 +- Domen Kožar, 2012/04/16 diff --git a/colander/__init__.py b/colander/__init__.py index 20521c3..9424ebd 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -733,6 +733,40 @@ class Tuple(Positional, SchemaType): return appstruct[index] +class Set(SchemaType): + """ A type representing a non-overlapping set of items. + Deserializes an iterable to a ``set`` 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: 0.9.9.1 + + """ + + 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 hasattr(cstruct, '__iter__'): + raise Invalid( + node, + _('${cstruct} is not iterable', mapping={'cstruct': cstruct}) + ) + + cstruct = set(cstruct) + if not cstruct: + return null + return 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 cc159e8..ccf5968 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -949,6 +949,52 @@ class TestTuple(unittest.TestCase): result = typ.cstruct_children(node1, ['one', 'two']) self.assertEqual(result, ['one', 'two']) + +class TestSet(unittest.TestCase): + def _makeOne(self, **kw): + from colander import Set + return Set(**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_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',)) + self.assertEqual(result, set(('a',))) + + def test_deserialize_empty_set(self): + import colander + typ = self._makeOne() + node = DummySchemaNode(typ) + result = typ.deserialize(node, set()) + self.assertEqual(result, colander.null) + class TestSequence(unittest.TestCase): def _makeOne(self, **kw): from colander import Sequence diff --git a/docs/api.rst b/docs/api.rst index c05dea3..c8471ae 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -74,6 +74,8 @@ Types .. autoclass:: Tuple + .. autoclass:: Set + .. autoclass:: Sequence .. autoclass:: Seq