Merge pull request #10 from cjw296/prepare
The "prepare" stuff myself and mcdonc discussed.
This commit is contained in:
@@ -14,6 +14,9 @@ Next release
|
|||||||
``colander.DateTime``. See
|
``colander.DateTime``. See
|
||||||
https://github.com/Pylons/colander/issues#issue/6.
|
https://github.com/Pylons/colander/issues#issue/6.
|
||||||
|
|
||||||
|
- Add the ability to insert a :class:`~colander.interfaces.Preparer`
|
||||||
|
between deserialization and validation. See the :ref:`preparing`.
|
||||||
|
|
||||||
0.9.2 (2011-03-28)
|
0.9.2 (2011-03-28)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@@ -102,4 +102,4 @@ Contributors
|
|||||||
------------
|
------------
|
||||||
|
|
||||||
- Chris McDonough, 2011/02/16
|
- Chris McDonough, 2011/02/16
|
||||||
|
- Chris Withers, 2011/05/45
|
||||||
|
@@ -1231,6 +1231,10 @@ class SchemaNode(object):
|
|||||||
'required'. When ``missing`` is :attr:`colander.required`, the
|
'required'. When ``missing`` is :attr:`colander.required`, the
|
||||||
``required`` computed attribute will be ``True``.
|
``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
|
- ``validator``: Optional validator for this node. It should be
|
||||||
an object that implements the
|
an object that implements the
|
||||||
:class:`colander.interfaces.Validator` interface.
|
:class:`colander.interfaces.Validator` interface.
|
||||||
@@ -1275,6 +1279,7 @@ class SchemaNode(object):
|
|||||||
|
|
||||||
def __init__(self, typ, *children, **kw):
|
def __init__(self, typ, *children, **kw):
|
||||||
self.typ = typ
|
self.typ = typ
|
||||||
|
self.preparer = kw.pop('preparer', None)
|
||||||
self.validator = kw.pop('validator', None)
|
self.validator = kw.pop('validator', None)
|
||||||
self.default = kw.pop('default', null)
|
self.default = kw.pop('default', null)
|
||||||
self.missing = kw.pop('missing', required)
|
self.missing = kw.pop('missing', required)
|
||||||
@@ -1338,13 +1343,14 @@ class SchemaNode(object):
|
|||||||
|
|
||||||
def deserialize(self, cstruct=null):
|
def deserialize(self, cstruct=null):
|
||||||
""" Deserialize the :term:`cstruct` into an :term:`appstruct` based
|
""" Deserialize the :term:`cstruct` into an :term:`appstruct` based
|
||||||
on the schema, and return the deserialized, then validate the
|
on the schema, run this :term:`appstruct` through the
|
||||||
resulting appstruct. The ``cstruct`` value is deserialized into an
|
preparer, if one is present, then validate the
|
||||||
|
prepared appstruct. The ``cstruct`` value is deserialized into an
|
||||||
``appstruct`` unconditionally.
|
``appstruct`` unconditionally.
|
||||||
|
|
||||||
If ``appstruct`` returned by type deserialization is the value
|
If ``appstruct`` returned by type deserialization and
|
||||||
:attr:`colander.null`, do something special before attempting
|
preparation is the value :attr:`colander.null`, do something
|
||||||
validation:
|
special before attempting validation:
|
||||||
|
|
||||||
- If the ``missing`` attribute of this node has been set explicitly,
|
- If the ``missing`` attribute of this node has been set explicitly,
|
||||||
return its value. No validation of this value is performed; it is
|
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)
|
appstruct = self.typ.deserialize(self, cstruct)
|
||||||
|
|
||||||
|
if self.preparer is not None:
|
||||||
|
appstruct = self.preparer(appstruct)
|
||||||
|
|
||||||
if appstruct is null:
|
if appstruct is null:
|
||||||
appstruct = self.missing
|
appstruct = self.missing
|
||||||
if appstruct is required:
|
if appstruct is required:
|
||||||
|
@@ -1,6 +1,19 @@
|
|||||||
|
def Preparer(value):
|
||||||
|
"""
|
||||||
|
A preparer is called after deserialization of a value but before
|
||||||
|
that value is validated.
|
||||||
|
|
||||||
|
Any modifications to ``value`` required should be made by
|
||||||
|
returning the modified value rather than modifying in-place.
|
||||||
|
|
||||||
|
If no modification is required, then ``value`` should be returned
|
||||||
|
as-is.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def Validator(node, value):
|
def Validator(node, value):
|
||||||
"""
|
"""
|
||||||
A validator is called after deserialization of a value.
|
A validator is called after preparation of the deserialized value.
|
||||||
|
|
||||||
If ``value`` is not valid, raise a :class:`colander.Invalid`
|
If ``value`` is not valid, raise a :class:`colander.Invalid`
|
||||||
instance as an exception after.
|
instance as an exception after.
|
||||||
|
@@ -1463,6 +1463,14 @@ class TestSchemaNode(unittest.TestCase):
|
|||||||
node = self._makeOne(None, 0, widget='abc')
|
node = self._makeOne(None, 0, widget='abc')
|
||||||
self.assertEqual(node.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):
|
def test_ctor_with_unknown_kwarg(self):
|
||||||
node = self._makeOne(None, 0, foo=1)
|
node = self._makeOne(None, 0, foo=1)
|
||||||
self.assertEqual(node.foo, 1)
|
self.assertEqual(node.foo, 1)
|
||||||
@@ -1486,6 +1494,29 @@ class TestSchemaNode(unittest.TestCase):
|
|||||||
result = node.deserialize(1)
|
result = node.deserialize(1)
|
||||||
self.assertEqual(result, 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):
|
def test_deserialize_with_validator(self):
|
||||||
typ = DummyType()
|
typ = DummyType()
|
||||||
validator = DummyValidator(msg='Wrong')
|
validator = DummyValidator(msg='Wrong')
|
||||||
|
@@ -89,20 +89,29 @@ Schema Node Objects
|
|||||||
|
|
||||||
A schema is composed of one or more *schema node* objects, each typically of
|
A schema is composed of one or more *schema node* objects, each typically of
|
||||||
the class :class:`colander.SchemaNode`, usually in a nested arrangement.
|
the class :class:`colander.SchemaNode`, usually in a nested arrangement.
|
||||||
Each schema node object has a required *type*, an optional deserialization
|
Each schema node object has a required *type*, an optional *preparer*
|
||||||
*validator*, an optional *default*, an optional *missing*, an optional
|
for adjusting data after deserialization, an optional
|
||||||
*title*, an optional *description*, and a slightly less optional *name*. It
|
*validator* for deserialized prepared data, an optional *default*, an
|
||||||
also accepts *arbitrary* keyword arguments, which are attached directly as
|
optional *missing*, an optional *title*, an optional *description*,
|
||||||
attributes to the node instance.
|
and a slightly less optional *name*. It also accepts *arbitrary*
|
||||||
|
keyword arguments, which are attached directly as attributes to the
|
||||||
|
node instance.
|
||||||
|
|
||||||
The *type* of a schema node indicates its data type (such as
|
The *type* of a schema node indicates its data type (such as
|
||||||
:class:`colander.Int` or :class:`colander.String`).
|
:class:`colander.Int` or :class:`colander.String`).
|
||||||
|
|
||||||
The *validator* of a schema node is called after deserialization; it
|
The *preparer* of a schema node is called after
|
||||||
makes sure the deserialized value matches a constraint. An example of
|
deserialization but before validation; it prepares a deserialized
|
||||||
|
value for validation. Examples would be to prepend schemes that may be
|
||||||
|
missing on url values or to filter html provided by a rich text
|
||||||
|
editor. A preparer is not called during serialization, only during
|
||||||
|
deserialization.
|
||||||
|
|
||||||
|
The *validator* of a schema node is called after deserialization and
|
||||||
|
preparation ; it makes sure the value matches a constraint. An example of
|
||||||
such a validator is provided in the schema above:
|
such a validator is provided in the schema above:
|
||||||
``validator=colander.Range(0, 200)``. A validator is not called after
|
``validator=colander.Range(0, 200)``. A validator is not called after
|
||||||
serialization, only after deserialization.
|
schema node serialization, only after node deserialization.
|
||||||
|
|
||||||
The *default* of a schema node indicates the value to be serialized if
|
The *default* of a schema node indicates the value to be serialized if
|
||||||
a value for the schema node is not found in the input data during
|
a value for the schema node is not found in the input data during
|
||||||
@@ -392,6 +401,40 @@ which the exception is related.
|
|||||||
See the :class:`colander.Invalid` API documentation for more
|
See the :class:`colander.Invalid` API documentation for more
|
||||||
information.
|
information.
|
||||||
|
|
||||||
|
.. _preparing:
|
||||||
|
|
||||||
|
Preparing deserialized data for validation
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In certain circumstances, it is necessary to modify the deserialized
|
||||||
|
value before validating it.
|
||||||
|
|
||||||
|
For example, a :class:`~colander.String` node may be required to
|
||||||
|
contain content, but that content may come from a rich text
|
||||||
|
editor. Such an editor may return ``<b></b>`` which may appear to be
|
||||||
|
valid but doesn't contain content, or
|
||||||
|
``<a href="javascript:alert('evil'')">good</a>`` which is valid, but
|
||||||
|
only after some processing.
|
||||||
|
|
||||||
|
The following schema uses `htmllaundry`__ and a
|
||||||
|
:class:`~colander.interfaces.Preparer` to do the correct thing in both
|
||||||
|
cases:
|
||||||
|
|
||||||
|
__ http://pypi.python.org/pypi/htmllaundry/
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
import colander
|
||||||
|
import htmlaundry
|
||||||
|
|
||||||
|
class Page(colander.MappingSchema):
|
||||||
|
title = colander.SchemaNode(colander.String())
|
||||||
|
content = colander.SchemaNode(colander.String(),
|
||||||
|
preparer=htmllaundry.sanitize,
|
||||||
|
validator=colander.Length(1))
|
||||||
|
|
||||||
|
|
||||||
Serialization
|
Serialization
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@@ -3,6 +3,8 @@ Interfaces
|
|||||||
|
|
||||||
.. automodule:: colander.interfaces
|
.. automodule:: colander.interfaces
|
||||||
|
|
||||||
|
.. autofunction:: Preparer
|
||||||
|
|
||||||
.. autofunction:: Validator
|
.. autofunction:: Validator
|
||||||
|
|
||||||
.. autoclass:: Type
|
.. autoclass:: Type
|
||||||
|
Reference in New Issue
Block a user