colander icon indicating copy to clipboard operation
colander copied to clipboard

Deferred validation of sequence raises `IndexError`.

Open AndreLouisCaron opened this issue 11 years ago • 5 comments

I'm following the RangedIntSchemaNode example under Subclassing SchemaNode and it works (provided you add a schema_type class attribute), but I can't get it to work for sequences.

For example:

import colander

class SortedSequenceSchemaNode(colander.SchemaNode):
    default = []
    title = 'Sorted sequence'
    schema_type = colander.Sequence

    @colander.deferred
    def validator(self, kw):
        sorting_key = kw.get('sorting_key', None)
        def validate_sorted_sequence(node, cstruct):
            # Check edge cases.
            if cstruct is None or cstruct is colander.null:
                return colander.null
            if not iterable(cstruct):
                raise colander.Invalid('Expecting a sequence, not %r.' % cstruct)
            # Sort items in sequence.
            return sorted(cstruct, key=sorting_key)
        return validate_sorted_sequence


class TestSchema(colander.MappingSchema):
    stuff = SortedSequenceSchemaNode(missing=[])


data = {'stuff': ['A', 'b', 'C', 'd']}
data = TestSchema().bind(sorting_key=str.lower).deserialize(data)
print data['stuff']

When I execute this, I get

IndexError: list index out of range

in colander/__init__.py, line 858:

result.append(callback(node.children[0], subval))

I'm using Colander 1.0a2.

AndreLouisCaron avatar Mar 11 '13 14:03 AndreLouisCaron

So this is about a week old, but hopefully it might still be relevant for you. The problem with what you're doing is that a sequence node always requires a single child node. By calling SortedSequenceSchemaNode(missing=[]), you are assigning the sequence 0 child nodes, hence the IndexError. The way you would accomplish this is through this code:

import colander

class SortedSequenceSchemaNode(colander.SchemaNode):
    default = []
    title = 'Sorted sequence'
    schema_type = colander.Sequence

    # children
    stuff = colander.SchemaNode(colander.String())

    @colander.deferred
    def validator(self, kw):
        sorting_key = kw.get('sorting_key', None)
        def validate_sorted_sequence(node, cstruct):
            # Check edge cases.
            if cstruct is None or cstruct is colander.null:
                return colander.null
            if not list(cstruct):
                raise colander.Invalid('Expecting a sequence, not %r.' % cstruct)
            # Sort items in sequence.
            return sorted(cstruct, key=sorting_key)
        return validate_sorted_sequence


class TestSchema(colander.MappingSchema):
    stuff = SortedSequenceSchemaNode(missing=[])


if __name__ == '__main__':
    data = {'stuff': ['A', 'b', 'C', 'd']}
    data = TestSchema().bind(sorting_key=unicode.lower).deserialize(data)
    print data['stuff']

The notable piece of this new code, is that I add a child node called 'stuff' that has type colander.String.

jayd3e avatar Mar 18 '13 06:03 jayd3e

@jayd3e Are there any perceivable effects of adding the child node? For instance, if I use this to sort numbers, will it still work as is and sort numbers or will it do a lexical sort over strings?

AndreLouisCaron avatar Mar 18 '13 12:03 AndreLouisCaron

If you are using it to sort numbers/characters, then would need to make that validator a preparer, as it wouldn't actually change key if its a validator, it would simply make sure that an exception isn't thrown. Right now, we don't support deferred preparers. I'm going to merge your pull request today that does this, so you can at least use master.

jayd3e avatar Mar 18 '13 17:03 jayd3e

But to answer your question, with a preparer you could do both numerical sort or lexical sort.

jayd3e avatar Mar 18 '13 17:03 jayd3e

If you are using it to sort numbers/characters, then would need to make that validator a preparer, as it wouldn't actually change key if its a validator, it would simply make sure that an exception isn't thrown. Right now, we don't support deferred preparers.

Yes I understand that now. That's how I ended up with the deferred preparer patch :-)

AndreLouisCaron avatar Mar 19 '13 03:03 AndreLouisCaron