This commit is contained in:
Chris McDonough
2012-10-08 13:33:47 -04:00
parent 84004d0286
commit c4c78362d4

View File

@@ -34,8 +34,10 @@ Features
create a bundle of default node behavior. The subclass can define the
following methods and attributes: ``preparer``, ``validator``, ``default``,
``missing``, ``name``, ``title``, ``description``, ``widget``, and
``after_bind``. For example, the older, more imperative style that
looked like this still works::
``after_bind``.
For example, the older, more imperative style that looked like this still
works, of course::
from colander import SchemaNode
@@ -45,19 +47,19 @@ Features
title='Ranged Int'
)
But you can alternately now do something like::
But you can alternately now do something like this::
from colander import SchemaNode
class RangedIntSchemaNode(SchemaNode):
validator = colander.range(0, 10)
validator = colander.Range(0, 10)
default = 10
title = 'Ranged Int'
ranged_int = RangedInt()
Values that are expected to be callables can be methods of the schemanode
subclass instead of plain attributes::
Values that are expected to be callables can now alternately be methods of
the schemanode subclass instead of plain attributes::
from colander import SchemaNode
@@ -71,22 +73,22 @@ Features
ranged_int = RangedInt()
When implementing a method value that expects ``node``, ``node`` must be
provided in the call signature, even though ``node`` will almost always be
the same as ``self``. This is because Colander simply treats the method
as another kind of callable, be it a method, or a function, or an instance
that has a ``__call__`` method. It doesn't care that it happens to be a
method of ``self``, and it needs to support callables that are not
methods, so it sends ``node`` in regardless.
Note that when implementing a method value such as ``validator`` that
expects to receive a ``node`` argument, ``node`` must be provided in the
call signature, even though ``node`` will almost always be the same as
``self``. This is because Colander simply treats the method as another
kind of callable, be it a method, or a function, or an instance that has a
``__call__`` method. It doesn't care that it happens to be a method of
``self``, and it needs to support callables that are not methods, so it
sends ``node`` in regardless.
Normal inheritance rules apply to class attributes and methods defined in
a schemanode subclass. If your schemanode subclass inherits from another
schemanode class, your schemanode subclass' methods and class attributes
will override the superclass' methods and class attributes.
Method values that need to be deferred for binding cannot currently be
implemented as ``colander.deferred`` callables. For example this will
*not* work::
You can't currently use *method* definitions as ``colander.deferred``
callables. For example this will *not* work::
from colander import SchemaNode
@@ -110,9 +112,33 @@ Features
TypeError: avalidator() takes exactly 3 arguments (2 given)
Instead of trying to defer methods via a decorator, you can instead use
the ``bindings`` attribute of ``self`` to obtain access to the bind
parameters within values that are methody::
However, if you treat the thing being decorated as a function instead of a
method (remove the ``self`` argument from the argument list), it will
indeed work)::
from colander import SchemaNode
class RangedIntSchemaNode(SchemaNode):
default = 10
title = 'Ranged Int'
@colander.deferred
def validator(node, kw):
request = kw['request']
def avalidator(node, cstruct):
if not 0 < cstruct < 10:
if request.user != 'admin':
raise colander.Invalid(node, 'Must be between 0 and 10')
return avalidator
ranged_int = RangedInt()
bound_ranged_int = ranged_int.bind(request=request)
In previous releases of Colander, the only way to defer the computation of
values was via the ``colander.deferred`` decorator. In this release,
however, you can instead use the ``bindings`` attribute of ``self`` to
obtain access to the bind parameters within values that are plain old
methods::
from colander import SchemaNode
@@ -129,42 +155,18 @@ Features
ranged_int = RangedInt()
bound_range_int = ranged_int.bind(request=request)
You can use ``after_bind`` to set attributes of the schemanode that rely
on binding variables, such as ``missing`` and ``default``::
If the things you're trying to defer aren't callables like ``validator``,
but they're instead just plain attributes like ``missing`` or ``default``,
instead of using a ``colander.deferred``, you can use ``after_bind`` to
set attributes of the schemanode that rely on binding variables::
from colander import SchemaNode
class RangedIntSchemaNode(SchemaNode):
default = 10
title = 'Ranged Int'
def validator(self, node, cstruct):
request = self.bindings['request']
if not 0 < cstruct < 10:
if request.user != 'admin':
raise colander.Invalid(node, 'Must be between 0 and 10')
class UserIdSchemaNode(SchemaNode):
title = 'User Id'
def after_bind(self, node, kw):
self.request = kw['request']
self.default = self.request.user.id
Non-method values can still be implemented as ``colander.deferred``
however::
from colander import SchemaNode
def _missing(node, kw):
request = kw['request']
if request.user.name == 'admin':
return 10
return 20
class RangedIntSchemaNode(SchemaNode):
default = 10
title = 'Ranged Int'
missing = colander.deferred(_missing)
ranged_int = RangedInt()
self.default = kw['request'].user.id
You can override the default values of a schemanode subclass in its
constructor::
@@ -180,18 +182,41 @@ Features
In the above example, the validation will be done on 0-20, not 0-10.
If your schema node names conflict with schema value attribute names, you
can work around it with the ``name`` argument to the schema node::
If a schema node name conflicts with a schema value attribute name on the
same class, you can work around it by giving the schema node a bogus name
in the class definition but providing a correct ``name`` argument to the
schema node constructor::
from colander import SchemaNode, Schema
class TitleNode(SchemaNode):
validator = colander.range(0, 10)
default = 10
class SomeSchema(Schema):
title = 'Some Schema'
thisnamewontmatter = TitleNode(name='title')
thisnamewillbeignored = colander.SchemaNode(
colander.String(),
name='title'
)
Note that such a workaround is only required if the conflicting names are
attached to the *exact same* class definition. Colander scrapes off schema
node definitions at each class' construction time, so it's not an issue for
inherited values. For example::
from colander import SchemaNode, Schema
class SomeSchema(Schema):
title = colander.SchemaNode(colander.String())
class AnotherSchema(SomeSchema):
title = 'Some Schema'
schema = AnotherSchema()
In the above example, even though the ``title = 'Some Schema'`` appears to
override the superclass' ``title`` SchemaNode, a ``title`` SchemaNode will
indeed be present in the child list of the ``schema`` instance
(``schema['title']`` will return the title SchemaNode) and the schema's
``title`` attribute will be ``Some Schema`` (schema.title will return
``Some Schema``).
Backwards Incompatibilities
~~~~~~~~~~~~~~~~~~~~~~~~~~~