diff --git a/docs/extending.rst b/docs/extending.rst index be9b4a9f..3646c4b8 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -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:: @@ -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 @@ -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 @@ -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, @@ -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: @@ -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`. -