Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix docs for custom types #242

Merged
merged 4 commits into from
Jan 16, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 80 additions & 74 deletions docs/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@ 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.
A type is a class that inherits from ``colander.SchemaType`` and implements
these methods:

- ``serialize``: converts a Python data structure (:term:`appstruct`)
into a serialization (:term:`cstruct`).
- ``deserialize``: converts a serialized value (:term:`cstruct`) into a
Python data structure (:term:`appstruct`).
- If it contains child nodes, it must also implement ``cstruct_children``,
``flatten``, ``unflatten``, ``set_value`` and ``get_value`` methods. It
may inherit from ``Mapping``, ``Tuple``, ``Set``, ``List`` or ``Sequence``
to obtain these methods, but only if the expected behavior is the same.

.. note::

See also: :class:`colander.interfaces.Type`.

.. note::

Expand All @@ -25,18 +33,17 @@ 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
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.
``"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
from colander import SchemaType, Invalid, null

class Boolean(object):
class Boolean(SchemaType):
def serialize(self, node, appstruct):
if appstruct is null:
return null
Expand All @@ -54,9 +61,6 @@ serializes a boolean to the string ``true`` or ``false`` or the special
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
Expand All @@ -71,59 +75,78 @@ 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
~~~~~~~~~~~~~~~~~~~~~~~~~
Method Specifications
~~~~~~~~~~~~~~~~~~~~~

``serialize``
^^^^^^^^^^^^^

Arguments:

- ``node``: the ``SchemaNode`` associated with this type
- ``appstruct``: the :term:`appstruct` value that needs to be serialized

If ``appstruct`` is invalid, it should raise :exc:`colander.Invalid`,
passing ``node`` as the first constructor argument.

It must deal specially with the value :attr:`colander.null`.

It must be able to make sense of any value generated by ``deserialize``.

``deserialize``
^^^^^^^^^^^^^^^

The constraints of a type class implementation are:
Arguments:

- It must have both a ``serialize`` and ``deserialize`` method.
- ``node``: the ``SchemaNode`` associated with this type
- ``cstruct``: the :term:`cstruct` value that needs to be deserialized

- it must deal specially with the value :attr:`colander.null` within both
``serialize`` and ``deserialize``.
If ``cstruct`` is invalid, it should raise :exc:`colander.Invalid`,
passing ``node`` as the first constructor argument.

- its ``serialize`` method must be able to make sense of a value generated by
its ``deserialize`` method and vice versa.
It must deal specially with the value :attr:`colander.null`.

- 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``.
It must be able to make sense of any value generated by ``serialize``.

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

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

- ``node``: the ``SchemaNode`` associated with this type
- ``cstruct``: the :term:`cstruct` that the caller wants to obtain child values
for

You only need to define this method for complex types that have child nodes,
such as mappings and sequences.

``cstruct_children`` should return a value based on ``cstruct`` for
each child node in ``node`` (or an empty list if ``node`` has no children). If
``cstruct`` does not contain a value for a particular child, that child should
be replaced with the ``colander.null`` value in the returned list.

``cstruct_children`` should *never* raise an exception, even if it is passed a
nonsensical ``cstruct`` argument. In that case, it should return a sequence of
as many ``colander.null`` values as there are child nodes.


Constructor (``__init__``)
^^^^^^^^^^^^^^^^^^^^^^^^^^

`SchemaType` does not define a constructor, and user code (not Colander)
instantiates type objects, so custom types may define this method and use it
for their own purposes.

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.
Both the ``serialize`` and ``deserialize`` methods must be able to
receive :attr:`colander.null` values and handle them intelligently. This
will happen whenever the data structure being serialized or deserialized
does not provide a value for this node. In many cases, ``serialize`` or
``deserialize`` should just return :attr:`colander.null` when passed
:attr:`colander.null`.

A type might also choose to return :attr:`colander.null` if the value it
receives is *logically* (but not literally) null. For example,
Expand All @@ -137,22 +160,6 @@ within its ``deserialize`` method.
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:

Expand Down Expand Up @@ -210,4 +217,3 @@ constructor if one needs to be raised.

For a more formal definition of a the interface of a validator, see
:class:`colander.interfaces.Validator`.