The Null and Drop Values¶
colander.null
is a sentinel value which may be passed to
colander.SchemaNode.serialize()
during serialization or to
colander.SchemaNode.deserialize()
during deserialization.
colander.drop
is a sentinel value which controls the
behavior of collection-like colander.SchemaNode
subclasses.
During serialization, the use of colander.null
indicates that
the appstruct value corresponding to the node it's passed to
is missing and the value of the default
attribute of the
corresponding node should be used instead. If the node's default
attribute is colander.drop
, the serializer will skip the node.
During deserialization, the use of colander.null
indicates
that the 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. If the node's missing
attribute is colander.drop
, the deserializer will skip the node.
Note that 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 The Null Value¶
A node will attempt to serialize its default value during
colander.SchemaNode.serialize()
if the value it is passed as an
appstruct
argument is the colander.null
sentinel value.
The default value of a node is specified during schema creation as
its default
attribute / argument. For example, the hair_color
node below has a default value of brown
:
import colander
class Person(colander.MappingSchema):
name = colander.SchemaNode(colander.String())
age = colander.SchemaNode(colander.Int(),
validator=colander.Range(0, 200))
hair_color = colander.SchemaNode(colander.String(), default='brown')
Because the hair_color
node is passed a default
value, if the
above schema is used to serialize a mapping that does not have a
hair_color
key, the default will be serialized:
schema = Person()
serialized = schema.serialize({'name':'Fred', 'age':20})
Even though we did not include the hair_color
attribute in the
appstruct we fed to serialize
, the value of serialized
above
will be {'name':'Fred, 'age':'20', 'hair_color':'brown'}
. This is
because a default
value of brown
was provided during schema
node construction for hair_color
.
The same outcome would have been true had we fed the schema a mapping
for serialization which had the colander.null
sentinel as the
hair_color
value:
import colander
schema = Person()
serialized = schema.serialize({'name':'Fred', 'age':20,
'hair_color':colander.null})
When the above is run, the value of serialized
will be
{'name':'Fred, 'age':'20', 'hair_color':'brown'}
just as it was in
the example where hair_color
was not present in the mapping.
As we can see, serializations may be done of partial data structures;
the colander.null
value is inserted into the serialization
whenever a corresponding value in the data structure being serialized
is missing.
Note
The injection of the colander.null
value into a
serialization when a default doesn't exist for the corresponding
node is not a behavior shared during both serialization and
deserialization. While a serialization can be performed against
a partial data structure without corresponding node defaults, a
deserialization cannot be done to partial data without
corresponding node missing
values. When a value is missing
from a data structure being deserialized, and no missing
value
exists for the node corresponding to the missing item in the data
structure, a colander.Invalid
exception will be the
result.
If, during serialization, a value for the node is missing from the
cstruct and the node does not possess an explicit default value, the
colander.null
sentinel value is passed to the type's
serialize
method directly, instructing the type to serialize a
type-specific null value.
Serialization of a null value is completely type-specific, meaning
each type is free to serialize colander.null
to a value that
makes sense for that particular type. For example, the null
serialization value of a colander.String
type is the empty
string.
For example:
import colander
class Person(colander.MappingSchema):
name = colander.SchemaNode(colander.String())
age = colander.SchemaNode(colander.Int(),
validator=colander.Range(0, 200))
hair_color = colander.SchemaNode(colander.String())
schema = Person()
serialized = schema.serialize({'name':'Fred', 'age':20})
In the above example, the hair_color
value is missing and the
schema does not name a default
value for hair_color
.
However, when we attempt to serialize the data structure, an error is
not raised. Instead, the value for serialized
above will be
{'name':'Fred, 'age':'20', 'hair_color':colander.null}
.
Because we did not include the hair_color
attribute in the data we
fed to serialize
, and there was no default
value associated
with hair_color
to fall back to, the colander.null
value
is passed as the appstruct
value to the serialize
method of
the underlying type (colander.String
). The return value of
that type's serialize
method when colander.null
is passed
as the appstruct
is placed into the serialization.
colander.String
happens to return colander.null
when it is passed colander.null
as its appstruct argument, so
this is what winds up in the resulting cstruct.
The colander.null
value will be passed to a type either
directly or indirectly:
directly: because
colander.null
is passed directly to theserialize
method of a node.indirectly: because every schema node uses a
colander.null
value as itsdefault
attribute when no explicit default is provided.
When a particular type cannot serialize the null value to anything
sensible, that type's serialize
method must return the null object
itself as a serialization. For example, when the
colander.Boolean
type is asked to serialize the
colander.null
value, its serialize
method simply returns
the colander.null
value (because null is conceptually neither
true nor false).
Therefore, when colander.null
is used as input to
serialization, or as the default value of a schema node, it is
possible that the colander.null
value will placed into the
serialized data structure. The consumer of the serialization must
anticipate this and deal with the special colander.null
value
in the output however it sees fit.
Serialization Combinations¶
Within this table, the Value
column represents the value passed to
the colander.SchemaNode.serialize()
method of a particular
schema node, the Default
column represents the default
value
of that schema node, and the Result
column is a description of the
result of invoking the colander.SchemaNode.serialize()
method of
the schema node with the effective value.
Value |
Default |
Result |
---|---|---|
colander.null |
colander.null |
null serialized |
colander.null |
<missing> |
null serialized |
colander.null |
value |
value serialized |
<missing> |
colander.null |
null serialized |
<missing> |
<missing> |
null serialized |
<missing> |
value |
value serialized |
value |
colander.null |
value serialized |
value |
<missing> |
value serialized |
value_a |
value_b |
value_a serialized |
Note
<missing>
in the above table represents the circumstance in which a
key present in a colander.MappingSchema
is not present in a
mapping passed to its colander.SchemaNode.serialize()
method. In
reality, <missing>
means exactly the same thing as
colander.null
, because the colander.Mapping
type does
the equivalent of mapping.get(keyname, colander.null)
to find a
subvalue during serialization.
Deserializing The Null Value¶
The data structure passed to colander.SchemaNode.deserialize()
may contain one or more colander.null
sentinel markers.
When a colander.null
sentinel marker is passed to the
colander.SchemaNode.deserialize()
method of a particular node in
a schema, the node will take the following steps:
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 tocolander.SchemaNode.deserialize()
in subsequent operations. Most types, when they receive thenull
value will simply return it, however.If the appstruct value computed by the type's
deserialize
method iscolander.null
and the schema node has an explicitmissing
attribute (the node's constructor was supplied with an explicitmissing
argument), themissing
value will be returned. Note that when this happens, themissing
value is not validated by any schema node validator: it is simply returned.If the appstruct value computed by the type's
deserialize
method iscolander.null
and the schema node does not have an explicitly providedmissing
attribute (the node's constructor was not supplied with an explicitmissing
value), acolander.Invalid
exception will be raised with a message indicating that the field is required.
Note
There are differences between serialization and deserialization involving
the colander.null
value. During serialization, if an
colander.null
value is encountered, and no valid default
attribute exists on the node related to the value the null value for
that node is returned. Deserialization, however, doesn't use the
default
attribute of the node to find a default deserialization value
in the same circumstance; instead it uses the missing
attribute
instead. Also, if, during deserialization, an colander.null
value
is encountered as the value passed to the deserialize method, and no
explicit missing
value exists for the node, a colander.Invalid
exception is raised (colander.null
is not returned, as it is
during serialization).
Here's an example of a deserialization which uses a missing
value
in the schema as a deserialization default value:
import colander
class Person(colander.MappingSchema):
name = colander.SchemaNode(colander.String())
age = colander.SchemaNode(colander.Int(), missing=None)
schema = Person()
deserialized = schema.deserialize({'name':'Fred', 'age':colander.null})
The value for deserialized
above will be {'name':'Fred,
'age':None}
.
Because the age
schema node is provided a missing
value of
None
, if that schema is used to deserialize a mapping that has an
an age
key of colander.null
, the missing
value of
None
is serialized into the appstruct output for age
.
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
. The empty string can
also be used as the missing
value if that is helpful.
The colander.null
value is also the default, so it needn't be
specified in the cstruct. Therefore, the deserialized
value of
the below is equivalent to the above's:
import colander
class Person(colander.MappingSchema):
name = colander.SchemaNode(colander.String())
age = colander.SchemaNode(colander.Int(), missing=None)
schema = Person()
deserialized = schema.deserialize({'name':'Fred'})
Deserialization Combinations¶
Within this table, the Value
column represents the value passed to
the colander.SchemaNode.deserialize()
method of a particular
schema node, the Missing
column represents the missing
value
of that schema node, and the Result
column is a description of the
result of invoking the colander.SchemaNode.deserialize()
method
of the schema node with the effective value.
Value |
Missing |
Result |
---|---|---|
colander.null |
colander.null |
colander.null used |
colander.null |
<missing> |
Invalid exception raised |
colander.null |
value |
value used |
<missing> |
colander.null |
colander.null used |
<missing> |
<missing> |
Invalid exception raised |
<missing> |
value |
value used |
value |
colander.null |
value used |
value |
<missing> |
value used |
value_a |
value_b |
value_a used |
Note
<missing>
in the above table represents the circumstance in which a
key present in a colander.MappingSchema
is not present in a
mapping passed to its colander.SchemaNode.deserialize()
method. In
reality, <missing>
means exactly the same thing as
colander.null
, because the colander.Mapping
type does the
equivalent of mapping.get(keyname, colander.null)
to find a subvalue
during deserialization.