colander
colander copied to clipboard
Deferred validation of sequence raises `IndexError`.
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.
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 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?
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.
But to answer your question, with a preparer you could do both numerical sort or lexical sort.
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 :-)