Skip to content

Latest commit

 

History

History
212 lines (159 loc) · 7.73 KB

extending.rst

File metadata and controls

212 lines (159 loc) · 7.73 KB

Extending Colander

You can extend Colander by defining a new :term:`type` or by defining a new :term:`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 :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.

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 :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.

Implementing Type Classes

The constraints of a type class implementation are:

  • It must have both a serialize and deserialize method.
  • it must deal specially with the value :attr:`colander.null` within both serialize and deserialize.
  • its serialize method must be able to make sense of a value generated by its deserialize 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 the cstruct.

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.

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 :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

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 :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`.

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 :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`.