5.7 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 two methods:: serialize and
deserialize. 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).
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 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 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 FalseNote that the deserialize method of a type does not need
to explicitly deserialize the colander.null value. Deserialization of the null
value is dealt with at a higher level (with the colander.SchemaNode.deserialize method); a type will
never receive an colander.null value as a cstruct
argument to its deserialize method.
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.
Note that the only two real constraints of a type class are:
- it must deal specially with the value
colander.nullwithinserialize, translating it to a type-specific null value. - its
serializemethod must be able to make sense of a value generated by itsdeserializemethod and vice versa, except that thedeserializemethod needn't deal with thecolander.nullvalue specially even if theserializemethod returns it.
The serialize method of a type accepts two values:
node, and appstruct. node will be
the schema node associated with this type. It 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. It 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.
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
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 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.