- New interface methods required by types and schema nodes:

``pserialize`` and ``pdeserialize``.  These partially serialize or
  partially deserialize a value (the definition of "partial" is up to
  the type).

- Mapping type: missing -> partial.
This commit is contained in:
Chris McDonough
2010-04-02 05:20:45 +00:00
parent 9275184212
commit b97ef0fba2
4 changed files with 188 additions and 90 deletions

View File

@@ -14,12 +14,20 @@ Next release
- Add a ``__setitem__`` method to the ``colander.Invalid`` class.
- The ``colander.Mapping`` keyword argument ``unknown_keys`` has been
renamed to ``unknown``.
- Allow ``colander.Mapping`` type to accept a new constructor
argument: ``missing``.
argument: ``partial``.
- Allow ``colander.Mapping.serialize`` and
``colander.Mapping.deserialize`` methods to accept ``unknown`` and
``serialize`` keyword arguments.
``partial`` keyword arguments.
- New interface methods required by types and schema nodes:
``pserialize`` and ``pdeserialize``. These partially serialize or
partially deserialize a value (the definition of "partial" is up to
the type).
0.5 (2010-03-31)
----------------

View File

@@ -189,49 +189,57 @@ class OneOf(object):
raise Invalid(node, '"%s" is not one of %s' % (
value, ', '.join(['%s' % x for x in self.values])))
class Mapping(object):
class Type(object):
""" Abstract base class for types (only for convenience) """
def pserialize(self, node, value):
return self.serialize(node, value)
def pdeserialize(self, node, value):
return self.deserialize(node, value)
class Mapping(Type):
""" A type which represents a mapping of names to nodes.
The subnodes of the :class:`colander.SchemaNode` that wraps
this type imply the named keys and values in the mapping.
The constructor of this type accepts two extra optional keyword
arguments that other types do not: ``unknown`` and ``missing``.
arguments that other types do not: ``unknown`` and ``partial``.
unknown
``unknown`` controls the behavior of this type when an unknown
key is encountered in the value passed to the ``serialize`` or
``deserialize`` methods of this instance. The potential values
of ``unknown`` are:
``unknown`` controls the behavior of this type when an unknown
key is encountered in the value passed to the ``serialize`` or
``deserialize`` methods of this instance. The potential
values of ``unknown`` are:
- ``ignore`` means that keys that are not present in the schema
associated with this type will be ignored during
deserialization.
- ``ignore`` means that keys that are not present in the schema
associated with this type will be ignored during
deserialization.
- ``raise`` will cause a :exc:`colander.Invalid` exception to be
raised when unknown keys are present during deserialization.
- ``raise`` will cause a :exc:`colander.Invalid` exception to be
raised when unknown keys are present during deserialization.
- ``preserve`` will preserve the 'raw' unknown keys and values
in the returned data structure.
- ``preserve`` will preserve the 'raw' unknown keys and values
in the returned data structure.
Default: ``ignore``.
Default: ``ignore``.
missing
``missing`` controls the behavior of this type when a
partial
``partial`` controls the behavior of this type when a
schema-expected key is missing from the value passed to the
``serialize`` and ``deserialize`` methods of this instance.
During serialization and deserialization, when ``missing`` is
``raise``, a :exc:`colander.Invalid` exception will be raised
During serialization and deserialization, when ``partial`` is
``False``, a :exc:`colander.Invalid` exception will be raised
if the mapping value does not contain a key specified by the
schema node related to this mapping type. When ``missing`` is
``ignore``, no exception is raised and a partial mapping will
schema node related to this mapping type. When ``partial`` is
``True``, no exception is raised and a partial mapping will
be serialized/deserialized.
Default: ``raise``.
"""
def __init__(self, unknown='ignore', missing='raise'):
self.missing = self._check_missing(missing)
def __init__(self, unknown='ignore', partial=False):
self.partial = partial
self.unknown = self._check_unknown(unknown)
def _check_unknown(self, unknown):
@@ -241,23 +249,15 @@ class Mapping(object):
'or "preserve"')
return unknown
def _check_missing(self, missing):
if not missing in ['ignore', 'raise']:
raise ValueError(
'missing argument must be one of "ignore" or "raise"')
return missing
def _validate(self, node, value):
try:
return dict(value)
except Exception, e:
raise Invalid(node, '%r is not a mapping type: %s' % (value, e))
def _impl(self, node, value, callback, default_callback, unknown, missing):
if missing is None:
missing = self.missing
else:
missing = self._check_missing(missing)
def _impl(self, node, value, callback, default_callback, unknown, partial):
if partial is None:
partial = self.partial
if unknown is None:
unknown = self.unknown
@@ -276,7 +276,7 @@ class Mapping(object):
try:
if subval is _missing:
if subnode.required:
if missing == 'raise':
if not partial:
raise Invalid(
subnode,
'"%s" is required but missing' % subnode.name)
@@ -303,12 +303,12 @@ class Mapping(object):
return result
def deserialize(self, node, value, unknown=None, missing=None):
def deserialize(self, node, value, unknown=None, partial=None):
"""
Along with the normal ``node`` and ``value`` arguments, this
method implementation accepts two additional optional
arguments that other type implementations do not: ``missing``
and ``raise``. These arguments can be used to override the
arguments that other type implementations do not: ``unknown``
and ``partial``. These arguments can be used to override the
instance defaults of the same name for the duration of a
particular serialization or deserialization.
@@ -319,12 +319,12 @@ class Mapping(object):
this instance. It defaults to ``None``, which signifies
that the instance default should be used.
missing
If this value is provided, it must be one of ``raise`` or
``ignore``, overriding the behavior implied by the value set
by the ``missing`` argument to constructor of this instance.
It defaults to ``None``, which signifies that the instance
default should be used.
partial
If this value is provided, it must be a boolean, overriding
the behavior implied by the value set by the ``partial``
argument to constructor of this instance. It defaults to
``None``, which signifies that the instance default should
be used.
"""
def callback(subnode, subval):
@@ -332,14 +332,14 @@ class Mapping(object):
def default_callback(subnode):
return subnode.default
return self._impl(
node, value, callback, default_callback, unknown, missing)
node, value, callback, default_callback, unknown, partial)
def serialize(self, node, value, unknown=None, missing=None):
def serialize(self, node, value, unknown=None, partial=None):
"""
Along with the normal ``node`` and ``value`` arguments, this
method implementation accepts two additional optional
arguments that other type implementations do not: ``missing``
and ``raise``. These arguments can be used to override the
arguments that other type implementations do not: ``unknown``
and ``partial``. These arguments can be used to override the
instance defaults of the same name for the duration of a
particular serialization or deserialization.
@@ -350,12 +350,12 @@ class Mapping(object):
this instance. It defaults to ``None``, which signifies
that the instance default should be used.
missing
If this value is provided, it must be one of ``raise`` or
``ignore``, overriding the behavior implied by the value set
by the ``missing`` argument to constructor of this instance.
It defaults to ``None``, which signifies that the instance
default should be used.
partial
If this value is provided, it must be a boolean, overriding
the behavior implied by the value set by the ``partial``
argument to constructor of this instance. It defaults to
``None``, which signifies that the instance default should
be used.
"""
def callback(subnode, subval):
return subnode.serialize(subval)
@@ -363,7 +363,13 @@ class Mapping(object):
return subnode.serialize(subnode.default)
return self._impl(
node, value, callback, default_callback, unknown, missing)
node, value, callback, default_callback, unknown, partial)
def pserialize(self, node, value):
return self.serialize(node, value, partial=True)
def pdeserialize(self, node, value):
return self.serialize(node, value, partial=True)
class Positional(object):
"""
@@ -373,7 +379,7 @@ class Positional(object):
creating a dictionary representation of an error tree.
"""
class Tuple(Positional):
class Tuple(Type, Positional):
""" A type which represents a fixed-length sequence of nodes.
The subnodes of the :class:`colander.SchemaNode` that wraps
@@ -427,7 +433,7 @@ class Tuple(Positional):
return subnode.serialize(subval)
return self._impl(node, value, callback)
class Sequence(Positional):
class Sequence(Type, Positional):
"""
A type which represents a variable-length sequence of nodes,
all of which must be of the same type.
@@ -535,7 +541,7 @@ Seq = Sequence
default_encoding = 'utf-8'
class String(object):
class String(Type):
""" A type representing a Unicode string. This type constructor
accepts a single argument ``encoding``, representing the encoding
which should be applied to object serialization. It defaults to
@@ -568,7 +574,7 @@ class String(object):
Str = String
class Integer(object):
class Integer(Type):
""" A type representing an integer.
The subnodes of the :class:`colander.SchemaNode` that wraps
@@ -592,7 +598,7 @@ class Integer(object):
Int = Integer
class Float(object):
class Float(Type):
""" A type representing a float.
The subnodes of the :class:`colander.SchemaNode` that wraps
@@ -616,7 +622,7 @@ class Float(object):
Int = Integer
class Boolean(object):
class Boolean(Type):
""" A type representing a boolean object.
During deserialization, a value in the set (``false``, ``0``) will
@@ -649,7 +655,7 @@ class Boolean(object):
Bool = Boolean
class GlobalObject(object):
class GlobalObject(Type):
""" A type representing an importable Python object. This type
serializes 'global' Python objects (objects which can be imported)
to dotted Python names.
@@ -756,7 +762,7 @@ class GlobalObject(object):
except AttributeError:
raise Invalid(node, '%r has no __name__' % value)
class DateTime(object):
class DateTime(Type):
""" A type representing a Python ``datetime.datetime`` object.
This type serializes python ``datetime.datetime`` objects to a
@@ -822,7 +828,7 @@ class DateTime(object):
'exc':e})
return result
class Date(object):
class Date(Type):
""" A type representing a Python ``datetime.date`` object.
This type serializes python ``datetime.date`` objects to a
@@ -833,7 +839,7 @@ class Date(object):
You can adjust the error message reported by this class by
changing its ``err_template`` attribute in a subclass on an
instance of this class. By default, the ``err_tempalte``
instance of this class. By default, the ``err_template``
attribute is the string ``%(value)s cannot be parsed as an iso8601
date: %(exc)s``. This string is used as the interpolation subject
of a dictionary composed of ``value`` and ``exc``. ``value`` and
@@ -946,20 +952,35 @@ class SchemaNode(object):
return self.typ.serialize(self, self.default)
def deserialize(self, value):
""" Derialize the value based on the schema represented by this
node """
""" Deserialize the value based on the schema represented by
this node. The values passed as ``kw`` will be passed along
to the ``deserialize`` method of this node's type."""
value = self.typ.deserialize(self, value)
if self.validator is not None:
self.validator(self, value)
return value
def serialize(self, value):
""" Serialize the value based on the schema represented by this
node """
""" Serialize the value based on the schema represented by
this node. The values passed as ``kw`` will be passed along
to the ``serialize`` method of this node's type."""
return self.typ.serialize(self, value)
def pserialize(self, value):
""" Partially serialize the value based on the schema
represented by this node. The values passed as ``kw`` will be
passed along to the ``pserialize`` method of this node's type."""
return self.typ.pserialize(self, value)
def pdeserialize(self, value):
""" Partially deserialize the value based on the schema
represented by this node. The values passed as ``kw`` will be
passed along to the ``pdeserialize`` method of this node's
type."""
return self.typ.pdeserialize(self, value)
def add(self, node):
""" Add a subnode to this node """
""" Add a subnode to this node. """
self.children.append(node)
def __getitem__(self, name):

View File

@@ -34,7 +34,6 @@ class Type(object):
def deserialize(self, struct, value):
"""
Deserialze the serialization represented by ``value`` to a
data structure. The deserialization should be composed of one
or more objects which can be serialized by the
@@ -53,3 +52,28 @@ class Type(object):
raised.
"""
def pserialize(self, node, value):
""" Partially serialize a value, ignoring any missing
components.
The description of the ``node`` and ``value`` arguments are
the same as those provided to ``serialize``.
The return value and behavior of any partial serialization is
completely type-dependent. If partial serialization is not
applicable for a type, this method will usually be an alias
for that type's 'serialize' method.
"""
def pdeserialize(self, node, value):
""" Partially deserialize a value, ignoring any missing
components.
The description of the ``node`` and ``value`` arguments are
the same as those provided to ``deserialize``.
The return value and behavior of any partial deserialization
is completely type-dependent. If partial deserialization is
not applicable for a type, this method will usually be an
alias for that type's 'deserialize' method.
"""

View File

@@ -211,6 +211,23 @@ class TestOneOf(unittest.TestCase):
e = invalid_exc(validator, None, None)
self.assertEqual(e.msg, '"None" is not one of 1, 2')
class TestType(unittest.TestCase):
def _makeOne(self):
from colander import Type
return Type()
def test_pserialize(self):
typ = self._makeOne()
typ.serialize = lambda *arg: arg
result = typ.pserialize(None, None)
self.assertEqual(result, (None, None))
def test_pdeserialize(self):
typ = self._makeOne()
typ.deserialize = lambda *arg: arg
result = typ.pdeserialize(None, None)
self.assertEqual(result, (None, None))
class TestMapping(unittest.TestCase):
def _makeOne(self, *arg, **kw):
from colander import Mapping
@@ -227,16 +244,6 @@ class TestMapping(unittest.TestCase):
except ValueError, e: # pragma: no cover
raise AssertionError(e)
def test_ctor_bad_missing(self):
self.assertRaises(ValueError, self._makeOne, 'ignore', 'badarg')
def test_ctor_good_missing(self):
try:
self._makeOne(missing='ignore')
self._makeOne(missing='raise')
except ValueError, e: # pragma: no cover
raise AssertionError(e)
def test_deserialize_not_a_mapping(self):
node = DummySchemaNode(None)
typ = self._makeOne()
@@ -316,24 +323,24 @@ class TestMapping(unittest.TestCase):
e = invalid_exc(typ.deserialize, node, {'a':1})
self.assertEqual(e.children[0].msg, '"b" is required but missing')
def test_deserialize_subnode_missing_ignore(self):
def test_deserialize_subnode_partial(self):
node = DummySchemaNode(None)
node.children = [
DummySchemaNode(None, name='a'),
DummySchemaNode(None, name='b'),
]
typ = self._makeOne(missing='ignore')
typ = self._makeOne(partial=True)
result = typ.deserialize(node, {'a':1})
self.assertEqual(result, {'a':1})
def test_deserialize_subnode_missing_ignore_override(self):
def test_deserialize_subnode_partial_ignore_override(self):
node = DummySchemaNode(None)
node.children = [
DummySchemaNode(None, name='a'),
DummySchemaNode(None, name='b'),
]
typ = self._makeOne()
result = typ.deserialize(node, {'a':1}, 'ignore', 'ignore')
result = typ.deserialize(node, {'a':1}, 'ignore', True)
self.assertEqual(result, {'a':1})
def test_serialize_not_a_mapping(self):
@@ -415,24 +422,44 @@ class TestMapping(unittest.TestCase):
e = invalid_exc(typ.serialize, node, {'a':1})
self.assertEqual(e.children[0].msg, '"b" is required but missing')
def test_serialize_subnode_missing_ignore(self):
def test_serialize_subnode_partial(self):
node = DummySchemaNode(None)
node.children = [
DummySchemaNode(None, name='a'),
DummySchemaNode(None, name='b'),
]
typ = self._makeOne(missing='ignore')
typ = self._makeOne(partial=True)
result = typ.serialize(node, {'a':1})
self.assertEqual(result, {'a':1})
def test_serialize_subnode_missing_ignore_override(self):
def test_serialize_subnode_partial_ignore_override(self):
node = DummySchemaNode(None)
node.children = [
DummySchemaNode(None, name='a'),
DummySchemaNode(None, name='b'),
]
typ = self._makeOne()
result = typ.serialize(node, {'a':1}, 'ignore', 'ignore')
result = typ.serialize(node, {'a':1}, 'ignore', True)
self.assertEqual(result, {'a':1})
def test_pserialize(self):
node = DummySchemaNode(None)
node.children = [
DummySchemaNode(None, name='a'),
DummySchemaNode(None, name='b'),
]
typ = self._makeOne()
result = typ.pserialize(node, {'a':1})
self.assertEqual(result, {'a':1})
def test_pdeserialize(self):
node = DummySchemaNode(None)
node.children = [
DummySchemaNode(None, name='a'),
DummySchemaNode(None, name='b'),
]
typ = self._makeOne()
result = typ.pdeserialize(node, {'a':1})
self.assertEqual(result, {'a':1})
class TestTuple(unittest.TestCase):
@@ -1200,12 +1227,24 @@ class TestSchemaNode(unittest.TestCase):
e = invalid_exc(node.deserialize, 1)
self.assertEqual(e.msg, 'Wrong')
def test_pdeserialize(self):
typ = DummyType()
node = self._makeOne(typ)
result = node.pdeserialize(1)
self.assertEqual(result, 1)
def test_serialize(self):
typ = DummyType()
node = self._makeOne(typ)
result = node.serialize(1)
self.assertEqual(result, 1)
def test_pserialize(self):
typ = DummyType()
node = self._makeOne(typ)
result = node.pserialize(1)
self.assertEqual(result, 1)
def test_add(self):
node = self._makeOne(None)
node.add(1)
@@ -1473,4 +1512,10 @@ class DummyType(object):
def deserialize(self, node, value):
return value
def pserialize(self, node, value):
return value
def pdeserialize(self, node, value):
return value