- 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:
12
CHANGES.txt
12
CHANGES.txt
@@ -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)
|
||||
----------------
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user