7.8 KiB
Extending Colander
You can extend Colander by defining a new type
or by defining a new
validator
.
Defining a New Type
A new type is a class with three methods:: serialize
,
deserialize
, and cstruct_children
.
serialize
converts a Python data structure (an appstruct
) into a
serialization (a cstruct
). deserialize
converts a
serialized value (a cstruct
) into a Python data structure (a appstruct
).
cstruct_children
picks apart a 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.
An Example
Here's a type which implements boolean serialization and
deserialization. It serializes a boolean to the string true
or false
or the special 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 Invalid
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' % appstruct)
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)
= cstruct.lower()
value if value in ('true', 'yes', 'y', 'on', 't', '1'):
return True
return False
def cstruct_children(self, node, cstruct):
return []
Here's how you would use the resulting class as part of a schema:
import colander
class Schema(colander.MappingSchema):
= colander.SchemaNode(Boolean()) interested
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.
Implementing Type Classes
The constraints of a type class implementation are:
- It must have both a
serialize
anddeserialize
method. - it must deal specially with the value
colander.null
within bothserialize
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 colander.Invalid
error, which expects a schema node as
its first constructor argument. appstruct
will be the 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 colander.Invalid
error, which expects a schema node as
its first constructor argument. cstruct
will be the 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 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.
Null Values
The framework requires that both the serialize
method
and the deserialize
method of a type explicitly deal with
the potential to receive a colander.null
value. 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 colander.null
value, it
should just return colander.null
to its caller.
A type might also choose to return colander.null
if the value it receives is
logically (but not literally) null. For example, colander.String
type
converts the empty string to colander.null
within its
deserialize
method.
def deserialize(self, node, cstruct):
if not cstruct:
return null
Type Constructors
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 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 colander.interfaces.Type
.
Defining a New Validator
A validator is a callable which accepts two positional arguments:
node
and value
. It returns None
if the value is valid. It raises a 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
= len(value)
num_digits = num_digits & 1
oddeven
for count in range(0, num_digits):
= int(value[count])
digit
if not (( count & 1 ) ^ oddeven ):
= digit * 2
digit if digit > 9:
= digit - 9
digit
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):
= colander.SchemaNode(colander.String(), validator=lunhnok) cc_number
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 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 colander.Invalid
exception constructor if one needs to
be raised.
For a more formal definition of a the interface of a validator, see
colander.interfaces.Validator
.