Extending Colander
==================

You can extend Colander by defining a new :term:`type` or by defining
a new :term:`validator`.

.. _defining_a_new_type:

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.

.. code-block::  python
   :linenos:

   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)
           value = cstruct.lower()
           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:

.. code-block:: python
   :linenos:

   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.

.. code-block:: python
   :linenos:

    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:

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.

.. code-block:: python
   :linenos:

   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:

.. code-block:: python
   :linenos:

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

