You can extend Colander by defining a new :term:`type` or by defining a new :term:`validator`.
A new type is a class with three methods:: serialize
, deserialize
,
and cstruct_children
. serialize
converts a Python data structure (an
:term:`appstruct`) into a serialization (a :term:`cstruct`). deserialize
converts a serialized value (a :term:`cstruct`) into a Python data structure
(a :term:`appstruct`). cstruct_children
picks apart a :term:`cstruct`
it's passed and attempts to returns its child values in a list, based on the
children defined in the node it's passed.
Note
The cstruct_children
method became required in Colander 0.9.9.
Here's a type which implements boolean serialization and deserialization. It
serializes a boolean to the string true
or false
or the special
:attr:`colander.null` sentinel; it then deserializes a string (presumably
true
or false
, but allows some wiggle room for t
, on
,
yes
, y
, and 1
) to a boolean value.
from colander import null
class Boolean(object):
def serialize(self, node, appstruct):
if appstruct is null:
return null
if not isinstance(appstruct, bool):
raise Invalid(node, '%r is not a boolean')
return appstruct and 'true' or 'false'
def deserialize(self, node, cstruct):
if cstruct is null:
return null
if not isinstance(cstruct, basestring):
raise Invalid(node, '%r is not a string' % cstruct)
value = cstruct.lower()
if value in ('true', 'yes', 'y', 'on', 't', '1'):
return True
return False
def cstruct_children(self):
return []
Here's how you would use the resulting class as part of a schema:
import colander
class Schema(colander.MappingSchema):
interested = colander.SchemaNode(Boolean())
The above schema has a member named interested
which will now be
serialized and deserialized as a boolean, according to the logic defined in
the Boolean
type class.
The constraints of a type class implementation are:
- It must have both a
serialize
anddeserialize
method. - it must deal specially with the value :attr:`colander.null` within both
serialize
anddeserialize
. - its
serialize
method must be able to make sense of a value generated by itsdeserialize
method and vice versa. - its
cstruct_children
method must return an empty list if the node it's passed has no children, or a value for each child node in the node it's passed based on thecstruct
.
The serialize
method of a type accepts two values: node
, and
appstruct
. node
will be the schema node associated with this type.
The node is used when the type must raise a :exc:`colander.Invalid` error,
which expects a schema node as its first constructor argument. appstruct
will be the :term:`appstruct` value that needs to be serialized.
The deserialize and method of a type accept two values: node
, and
cstruct
. node
will be the schema node associated with this type.
The node is used when the type must raise a :exc:`colander.Invalid` error,
which expects a schema node as its first constructor argument. cstruct
will be the :term:`cstruct` value that needs to be deserialized.
The cstruct_children
method accepts two values: node
and cstruct
.
node
will be the schema node associated with this type. cstruct
will
be the :term:`cstruct` that the caller wants to obtain child values for. The
cstruct_children
method should never raise an exception, even if it
passed a nonsensical value. If it is passed a nonsensical value, it should
return a sequence of colander.null
values; the sequence should contain as
many nulls as there are node children. If the cstruct
passed does not
contain a value for a particular child, that child should be replaced with
the colander.null
value in the returned list. Generally, if the type
you're defining is not expected to have children, it's fine to return an
empty list from cstruct_children
. It's only useful for complex types
such as mappings and sequences, usually.
The framework requires that both the serialize
method and the
deserialize
method of a type explicitly deal with the potential to
receive a :attr:`colander.null` value. :attr:`colander.null` will be sent to
the type during serialization and deserialization in circumstances where a
value has not been provided by the data structure being serialized or
deserialized. In the common case, when the serialize
or deserialize
method of type receives the :attr:`colander.null` value, it should just
return :attr:`colander.null` to its caller.
A type might also choose to return :attr:`colander.null` if the value it
receives is logically (but not literally) null. For example,
:class:`colander.String` type converts the empty string to colander.null
within its deserialize
method.
def deserialize(self, node, cstruct):
if not cstruct:
return null
A type class does not need to implement a constructor (__init__
),
but it isn't prevented from doing so if it needs to accept arguments;
Colander itself doesn't construct any types, only users of Colander
schemas do, so how types are constructed is beyond the scope of
Colander itself.
The :exc:`colander.Invalid` exception may be raised during serialization or deserialization as necessary for whatever reason the type feels appropriate (the inability to serialize or deserialize a value being the most common case).
For a more formal definition of a the interface of a type, see :class:`colander.interfaces.Type`.
A validator is a callable which accepts two positional arguments:
node
and value
. It returns None
if the value is valid.
It raises a :class:`colander.Invalid` exception if the value is not
valid. Here's a validator that checks if the value is a valid credit
card number.
def luhnok(node, value):
""" checks to make sure that the value passes a luhn mod-10 checksum """
sum = 0
num_digits = len(value)
oddeven = num_digits & 1
for count in range(0, num_digits):
digit = int(value[count])
if not (( count & 1 ) ^ oddeven ):
digit = digit * 2
if digit > 9:
digit = digit - 9
sum = sum + digit
if not (sum % 10) == 0:
raise Invalid(node,
'%r is not a valid credit card number' % value)
Here's how the resulting luhnok
validator might be used in a
schema:
import colander
class Schema(colander.MappingSchema):
cc_number = colander.SchemaNode(colander.String(), validator=lunhnok)
Note that the validator doesn't need to check if the value
is a
string: this has already been done as the result of the type of the
cc_number
schema node being :class:`colander.String`. Validators
are always passed the deserialized value when they are invoked.
The node
value passed to the validator is a schema node object; it
must in turn be passed to the :exc:`colander.Invalid` exception
constructor if one needs to be raised.
For a more formal definition of a the interface of a validator, see :class:`colander.interfaces.Validator`.