From ed5b611199b0873f8d88e13e4b47f07c9ccee6ef Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Tue, 29 Mar 2011 15:59:41 -0400 Subject: [PATCH] - Fix documentation: 0.9.2 requires ``deserialize`` of types to explicitly deal with the potential to receive ``colander.null``. --- CHANGES.txt | 6 +++ docs/basics.rst | 32 +++++++-------- docs/binding.rst | 14 ++++--- docs/extending.rst | 99 ++++++++++++++++++++++++++++------------------ docs/null.rst | 34 ++++++++++------ 5 files changed, 114 insertions(+), 71 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 49d56aa..6a7c148 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,12 @@ Changes ======= +Next release +------------ + +- Fix documentation: 0.9.2 requires ``deserialize`` of types to explicitly + deal with the potential to receive ``colander.null``. + 0.9.2 (2011-03-28) ------------------ diff --git a/docs/basics.rst b/docs/basics.rst index 178ba66..ad15ba0 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -87,13 +87,12 @@ of our definitions, a ``Person`` represents: Schema Node Objects ~~~~~~~~~~~~~~~~~~~ -A schema is composed of one or more *schema node* objects, each -typically of the class :class:`colander.SchemaNode`, usually in a -nested arrangement. Each schema node object has a required *type*, an -optional deserialization *validator*, an optional *default*, an -optional *missing*, an optional *title*, an optional *description*, an -optional *widget*, and a slightly less optional *name*. It also -accepts *arbitrary* keyword arguments, which are attached directly as +A schema is composed of one or more *schema node* objects, each typically of +the class :class:`colander.SchemaNode`, usually in a nested arrangement. +Each schema node object has a required *type*, an optional deserialization +*validator*, an optional *default*, an optional *missing*, an optional +*title*, an optional *description*, and a slightly less optional *name*. It +also accepts *arbitrary* keyword arguments, which are attached directly as attributes to the node instance. The *type* of a schema node indicates its data type (such as @@ -126,19 +125,20 @@ of the *name*. The *description* of a schema node is metadata about a schema node that can be used by higher-level systems. By default, it is empty. -The *widget* of a schema node is a concept used only by higher level -systems (such as form systems). By default it is ``None``. It won't -be discussed any further in the Colander documentation; it will -instead be explained in the context of the documentation of systems -which make use of it. - Any other keyword arguments to a schema node constructor will be attached to the node unmolested (e.g. when ``foo=1`` is passed, the resulting schema node will have an attribute named ``foo`` with the value ``1``). -.. note:: Abitrary keyword arguments are allowed to a schema node - constructor in Colander 0.9+. Prior version disallow them. +.. note:: You may see some higher-level systems (such as Deform) pass a + ``widget`` argument to a SchemaNode constructor. Such systems make use of + the fact that a SchemaNode can be passed arbitrary keyword arguments for + extension purposes. ``widget`` and other keyword arguments not enumerated + here but which are passed during schema node construction by someone + constructing a schema for a particular purpose are not used internally by + Colander; they are instead only meaningful to higher-level systems which + consume Colander schemas. Abitrary keyword arguments are allowed to a + schema node constructor in Colander 0.9+. Prior version disallow them. The name of a schema node that is introduced as a class-level attribute of a :class:`colander.MappingSchema`, @@ -193,7 +193,7 @@ Earlier we defined a schema: class Friend(colander.TupleSchema): rank = colander.SchemaNode(colander.Int(), - validator=colander.Range(0, 9999)) + validator=colander.Range(0, 9999)) name = colander.SchemaNode(colander.String()) class Phone(colander.MappingSchema): diff --git a/docs/binding.rst b/docs/binding.rst index b0c8e37..8bea5e2 100644 --- a/docs/binding.rst +++ b/docs/binding.rst @@ -27,13 +27,15 @@ scope. What Is Schema Binding? ----------------------- -- Values passed to a SchemaNode (e.g. ``description``, ``missing``, - etc.) may be an instance of the ``colander.deferred`` class. - Instances of the ``colander.deferred`` class are callables which - accept two positional arguments: a ``node`` and a ``kw`` dictionary. +- Any values passed as a keyword argument to a SchemaNode + (e.g. ``description``, ``missing``, etc.) may be an instance of the + ``colander.deferred`` class. Instances of the ``colander.deferred`` class + are callables which accept two positional arguments: a ``node`` and a + ``kw`` dictionary. -- When a schema node is bound, it is cloned, and any - ``colander.deferred`` values it has as attributes will be resolved. +- When a schema node is bound, it is cloned, and any ``colander.deferred`` + values it has as attributes will be resolved by invoking the callable + represented by the deferred value. - A ``colander.deferred`` value is a callable that accepts two positional arguments: the schema node being bound and a set of diff --git a/docs/extending.rst b/docs/extending.rst index 116ea04..5ad0d58 100644 --- a/docs/extending.rst +++ b/docs/extending.rst @@ -9,18 +9,19 @@ a new :term:`validator`. Defining a New Type ------------------- -A new type is a class with two methods:: ``serialize`` and -``deserialize``. ``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`). +A new type is a class with two methods:: ``serialize`` and ``deserialize``. +``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`). -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. +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: @@ -36,6 +37,8 @@ boolean value. 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() @@ -43,13 +46,6 @@ boolean value. return True return False -Note that the ``deserialize`` method of a type does not need to -explicitly deserialize the :attr:`colander.null` value. -Deserialization of the null value is dealt with at a higher level -(with the :meth:`colander.SchemaNode.deserialize` method); a type will -never receive an :attr:`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: .. code-block:: python @@ -61,34 +57,61 @@ Here's how you would use the resulting class as part of a schema: 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. +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: +Implementing Type Classes +~~~~~~~~~~~~~~~~~~~~~~~~~ -- it must deal specially with the value :attr:`colander.null` within - ``serialize``, translating it to a type-specific null value. +The constraints of a type class implementation are: -- its ``serialize`` method must be able to make sense of a value - generated by its ``deserialize`` method and vice versa, except that - the ``deserialize`` method needn't deal with the - :attr:`colander.null` value specially even if the ``serialize`` - method returns it. +- 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. 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 :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. +``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. It 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. +``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. +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 diff --git a/docs/null.rst b/docs/null.rst index 1cd476c..0b493af 100644 --- a/docs/null.rst +++ b/docs/null.rst @@ -17,8 +17,11 @@ that the :term:`cstruct` value corresponding to the node it's passed to is missing, and if possible, the value of the ``missing`` attribute of the corresponding node should be used instead. -Note that :attr:`colander.null` has no relationship to the built-in -Python ``None`` value. +Note that :attr:`colander.null` has no relationship to the built-in Python +``None`` value. ``colander.null`` is used instead of ``None`` because +``None`` is a potentially valid value for some serializations and +deserializations, and using it as a sentinel would prevent ``None`` from +being used in this way. .. _serializing_null: @@ -206,15 +209,24 @@ When a :attr:`colander.null` sentinel marker is passed to the :meth:`colander.SchemaNode.deserialize` method of a particular node in a schema, the node will take the following steps: -- If the schema node has an explicit ``missing`` attribute (the node's - constructor was supplied with an explicit ``missing`` argument), the - ``missing`` value will be returned. Note that when this happens, - the ``missing`` value is not validated by any schema node validator: - it is simply returned. +- The *type* object's ``deserialize`` method will be called with the null + value to allow the type to convert the null value to a type-specific + default. The resulting "appstruct" is used instead of the value passed + directly to :meth:`colander.SchemaNode.deserialize` in subsequent + operations. Most types, when they receive the ``null`` value will simply + return it, however. -- If the schema node does *not* have an explicitly provided - ``missing`` attribute (the node's constructor was not supplied with - an explicit ``missing`` value), a :exc:`colander.Invalid` exception +- If the appstruct value computed by the type's ``deserialize`` method is + ``colander.null`` and the schema node has an explicit ``missing`` attribute + (the node's constructor was supplied with an explicit ``missing`` + argument), the ``missing`` value will be returned. Note that when this + happens, the ``missing`` value is not validated by any schema node + validator: it is simply returned. + +- If the appstruct value computed by the type's ``deserialize`` method is + ``colander.null`` and the schema node does *not* have an explicitly + provided ``missing`` attribute (the node's constructor was not supplied + with an explicit ``missing`` value), a :exc:`colander.Invalid` exception will be raised with a message indicating that the field is required. .. note:: There are differences between serialization and @@ -255,7 +267,7 @@ an ``age`` key of :attr:`colander.null`, the ``missing`` value of .. note:: Note that ``None`` can be used for the ``missing`` schema node value as required, as in the above example. It's no different - than any other value used as ``missing``. or ``colander.nuil`` can + than any other value used as ``missing``. The empty string can also be used as the ``missing`` value if that is helpful. The :attr:`colander.null` value is also the default, so it needn't be