diff --git a/colander/__init__.py b/colander/__init__.py index a10bac6..c579c89 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -1231,6 +1231,10 @@ class SchemaNode(object): 'required'. When ``missing`` is :attr:`colander.required`, the ``required`` computed attribute will be ``True``. + - ``preparer``: Optional preparer for this node. It should be + an object that implements the + :class:`colander.interfaces.Preparer` interface. + - ``validator``: Optional validator for this node. It should be an object that implements the :class:`colander.interfaces.Validator` interface. @@ -1275,6 +1279,7 @@ class SchemaNode(object): def __init__(self, typ, *children, **kw): self.typ = typ + self.preparer = kw.pop('preparer', None) self.validator = kw.pop('validator', None) self.default = kw.pop('default', null) self.missing = kw.pop('missing', required) @@ -1338,13 +1343,14 @@ class SchemaNode(object): def deserialize(self, cstruct=null): """ Deserialize the :term:`cstruct` into an :term:`appstruct` based - on the schema, and return the deserialized, then validate the - resulting appstruct. The ``cstruct`` value is deserialized into an + on the schema, run this :term:`appstruct` through the + preparer, if one is present, then validate the + prepared appstruct. The ``cstruct`` value is deserialized into an ``appstruct`` unconditionally. - If ``appstruct`` returned by type deserialization is the value - :attr:`colander.null`, do something special before attempting - validation: + If ``appstruct`` returned by type deserialization and + preparation is the value :attr:`colander.null`, do something + special before attempting validation: - If the ``missing`` attribute of this node has been set explicitly, return its value. No validation of this value is performed; it is @@ -1361,6 +1367,9 @@ class SchemaNode(object): """ appstruct = self.typ.deserialize(self, cstruct) + if self.preparer is not None: + appstruct = self.preparer(appstruct) + if appstruct is null: appstruct = self.missing if appstruct is required: diff --git a/colander/tests.py b/colander/tests.py index f747190..f3c457e 100644 --- a/colander/tests.py +++ b/colander/tests.py @@ -1463,6 +1463,14 @@ class TestSchemaNode(unittest.TestCase): node = self._makeOne(None, 0, widget='abc') self.assertEqual(node.widget, 'abc') + def test_ctor_with_preparer(self): + node = self._makeOne(None, 0, preparer='abc') + self.assertEqual(node.preparer, 'abc') + + def test_ctor_without_preparer(self): + node = self._makeOne(None, 0) + self.assertEqual(node.preparer, None) + def test_ctor_with_unknown_kwarg(self): node = self._makeOne(None, 0, foo=1) self.assertEqual(node.foo, 1) @@ -1486,6 +1494,29 @@ class TestSchemaNode(unittest.TestCase): result = node.deserialize(1) self.assertEqual(result, 1) + def test_deserialize_with_preparer(self): + from colander import Invalid + typ = DummyType() + def preparer(value): + return 'prepared_'+value + def validator(node, value): + if not value.startswith('prepared'): + raise Invalid(node, 'not prepared') + node = self._makeOne(typ, + preparer=preparer, + validator=validator) + self.assertEqual(node.deserialize('value'), + 'prepared_value') + + def test_deserialize_preparer_before_missing_check(self): + from colander import null + typ = DummyType() + def preparer(value): + return null + node = self._makeOne(typ,preparer=preparer) + e = invalid_exc(node.deserialize, 1) + self.assertEqual(e.msg, 'Required') + def test_deserialize_with_validator(self): typ = DummyType() validator = DummyValidator(msg='Wrong')