to ``colander.SchemaNode`` for roundtripping purposes. - Make it possible to pickle ``colander.null``.
13 KiB
The Null Value
colander.null is a
sentinel value which may be passed to colander.SchemaNode.serialize during serialization or
to colander.SchemaNode.deserialize during
deserialization.
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.
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.
Note that colander.null has no relationship to the built-in
Python None value.
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.nullis passed directly to theserializemethod of a node. - indirectly: because every schema node uses a
colander.nullvalue as itsdefaultattribute 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 | value | value serialized |
| <missing> | value | value serialized |
| colander.null | colander.null | null serialized |
| <missing> | colander.null | null serialized |
| value | <missing> | value serialized |
| value_a | value_b | value_a serialized |
| value | colander.null | value serialized |
| colander.null | <missing> | null serialized |
| colander.null | value | null 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 colanderr.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:
- If the schema node has an explicit
missingattribute (the node's constructor was supplied with an explicitmissingargument), themissingvalue will be returned. Note that when this happens, themissingvalue is not validated by any schema node validator: it is simply returned. - If the schema node does not have an explicitly provided
missingattribute (the node's constructor was not supplied with an explicitmissingvalue), acolander.Invalidexception 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. or
colander.nuil 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 | <missing> | Invalid exception raised |
| <missing> | <missing> | Invalid exception raised |
| colander.null | value | value used |
| <missing> | value | value used |
| <missing> | colander.null | colander.null used |
| value | <missing> | value used |
| value | colander.null | 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.