Pylons / colander

A serialization/deserialization/validation library for strings, mappings and lists.
https://docs.pylonsproject.org/projects/colander/en/latest/
Other
447 stars 145 forks source link

Deferred validation of sequence raises `IndexError`. #86

Open AndreLouisCaron opened 11 years ago

AndreLouisCaron commented 11 years ago

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.

jayd3e commented 11 years ago

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.

AndreLouisCaron commented 11 years ago

@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?

jayd3e commented 11 years ago

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 commented 11 years ago

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

AndreLouisCaron commented 11 years ago

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 :-)