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 create a bundle of default node behavior. The subclass can define the
following methods and attributes: ``preparer``, ``validator``, ``default``, following methods and attributes: ``preparer``, ``validator``, ``default``,
``missing``, ``name``, ``title``, ``description``, ``widget``, and ``missing``, ``name``, ``title``, ``description``, ``widget``, and
``after_bind``. For example, the older, more imperative style that ``after_bind``.
looked like this still works::
For example, the older, more imperative style that looked like this still
works, of course::
from colander import SchemaNode from colander import SchemaNode
@@ -45,19 +47,19 @@ Features
title='Ranged Int' title='Ranged Int'
) )
But you can alternately now do something like:: But you can alternately now do something like this::
from colander import SchemaNode from colander import SchemaNode
class RangedIntSchemaNode(SchemaNode): class RangedIntSchemaNode(SchemaNode):
validator = colander.range(0, 10) validator = colander.Range(0, 10)
default = 10 default = 10
title = 'Ranged Int' title = 'Ranged Int'
ranged_int = RangedInt() ranged_int = RangedInt()
Values that are expected to be callables can be methods of the schemanode Values that are expected to be callables can now alternately be methods of
subclass instead of plain attributes:: the schemanode subclass instead of plain attributes::
from colander import SchemaNode from colander import SchemaNode
@@ -71,22 +73,22 @@ Features
ranged_int = RangedInt() ranged_int = RangedInt()
When implementing a method value that expects ``node``, ``node`` must be Note that when implementing a method value such as ``validator`` that
provided in the call signature, even though ``node`` will almost always be expects to receive a ``node`` argument, ``node`` must be provided in the
the same as ``self``. This is because Colander simply treats the method call signature, even though ``node`` will almost always be the same as
as another kind of callable, be it a method, or a function, or an instance ``self``. This is because Colander simply treats the method as another
that has a ``__call__`` method. It doesn't care that it happens to be a kind of callable, be it a method, or a function, or an instance that has a
method of ``self``, and it needs to support callables that are not ``__call__`` method. It doesn't care that it happens to be a method of
methods, so it sends ``node`` in regardless. ``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 Normal inheritance rules apply to class attributes and methods defined in
a schemanode subclass. If your schemanode subclass inherits from another a schemanode subclass. If your schemanode subclass inherits from another
schemanode class, your schemanode subclass' methods and class attributes schemanode class, your schemanode subclass' methods and class attributes
will override the superclass' methods and class attributes. will override the superclass' methods and class attributes.
Method values that need to be deferred for binding cannot currently be You can't currently use *method* definitions as ``colander.deferred``
implemented as ``colander.deferred`` callables. For example this will callables. For example this will *not* work::
*not* work::
from colander import SchemaNode from colander import SchemaNode
@@ -110,9 +112,33 @@ Features
TypeError: avalidator() takes exactly 3 arguments (2 given) TypeError: avalidator() takes exactly 3 arguments (2 given)
Instead of trying to defer methods via a decorator, you can instead use However, if you treat the thing being decorated as a function instead of a
the ``bindings`` attribute of ``self`` to obtain access to the bind method (remove the ``self`` argument from the argument list), it will
parameters within values that are methody:: 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 from colander import SchemaNode
@@ -129,42 +155,18 @@ Features
ranged_int = RangedInt() ranged_int = RangedInt()
bound_range_int = ranged_int.bind(request=request) bound_range_int = ranged_int.bind(request=request)
You can use ``after_bind`` to set attributes of the schemanode that rely If the things you're trying to defer aren't callables like ``validator``,
on binding variables, such as ``missing`` and ``default``:: 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 from colander import SchemaNode
class RangedIntSchemaNode(SchemaNode): class UserIdSchemaNode(SchemaNode):
default = 10 title = 'User Id'
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')
def after_bind(self, node, kw): def after_bind(self, node, kw):
self.request = kw['request'] self.default = kw['request'].user.id
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()
You can override the default values of a schemanode subclass in its You can override the default values of a schemanode subclass in its
constructor:: constructor::
@@ -180,18 +182,41 @@ Features
In the above example, the validation will be done on 0-20, not 0-10. 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 If a schema node name conflicts with a schema value attribute name on the
can work around it with the ``name`` argument to the schema node:: 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 from colander import SchemaNode, Schema
class TitleNode(SchemaNode):
validator = colander.range(0, 10)
default = 10
class SomeSchema(Schema): class SomeSchema(Schema):
title = 'Some 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 Backwards Incompatibilities
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~