diff --git a/CHANGES.txt b/CHANGES.txt index c2a682f..f0e1006 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~