From 38d953a9e1091142712e18f6ec672f8a43264b0f Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Wed, 27 Jul 2011 08:20:57 -0400 Subject: [PATCH] Add set_value method. --- colander/__init__.py | 49 +++++++++++++++++++++++++++++++++ colander/tests.py | 65 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/colander/__init__.py b/colander/__init__.py index a03980f..3f669f9 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -350,6 +350,9 @@ class SchemaType(object): assert paths == [name], "paths should be [name] for leaf nodes." return fstruct[name] + def set_value(self, node, appstruct, path, value): + raise AssertionError("Can't call 'set_value' on a leaf node.") + class Mapping(SchemaType): """ A type which represents a mapping of names to nodes. @@ -487,6 +490,16 @@ class Mapping(SchemaType): def unflatten(self, node, paths, fstruct): return _unflatten_mapping(node, paths, fstruct) + def set_value(self, node, appstruct, path, value): + if '.' in path: + next_name, rest = path.split('.', 1) + next_node = node[next_name] + next_appstruct = appstruct[next_name] + appstruct[next_name] = next_node.typ.set_value( + next_node, next_appstruct, rest, value) + else: + appstruct[path] = value + return appstruct class Positional(object): """ @@ -587,6 +600,25 @@ class Tuple(Positional, SchemaType): appstruct.append(mapstruct[subnode.name]) return tuple(appstruct) + def set_value(self, node, appstruct, path, value): + appstruct = list(appstruct) + if '.' in path: + next_name, rest = path.split('.', 1) + else: + next_name, rest = path, None + for index, next_node in enumerate(node.children): + if next_node.name == next_name: + break + else: + raise KeyError(next_name) + if rest is not None: + next_appstruct = appstruct[index] + appstruct[index] = next_node.typ.set_value( + next_node, next_appstruct, rest, value) + else: + appstruct[index] = value + return tuple(appstruct) + class Sequence(Positional, SchemaType): """ A type which represents a variable-length sequence of nodes, @@ -737,6 +769,18 @@ class Sequence(Positional, SchemaType): get_child, rewrite_subpath) return [mapstruct[str(index)] for index in xrange(len(mapstruct))] + def set_value(self, node, appstruct, path, value): + if '.' in path: + next_name, rest = path.split('.', 1) + index = int(next_name) + next_node = node.children[0] + next_appstruct = appstruct[index] + appstruct[index] = next_node.typ.set_value( + next_node, next_appstruct, rest, value) + else: + index = int(path) + appstruct[index] = value + return appstruct Seq = Sequence @@ -1461,6 +1505,11 @@ class SchemaNode(object): paths = sorted(fstruct.keys()) return self.typ.unflatten(self, paths, fstruct) + def set_value(self, appstruct, dotted_name, value): + """ Uses the schema to set a value in an appstruct from a dotted_name + path. """ + self.typ.set_value(self, appstruct, dotted_name, value) + def deserialize(self, cstruct=null): """ Deserialize the :term:`cstruct` into an :term:`appstruct` based on the schema, run this :term:`appstruct` through the diff --git a/colander/tests.py b/colander/tests.py index ebd7011..d11820b 100644 --- a/colander/tests.py +++ b/colander/tests.py @@ -342,6 +342,11 @@ class TestSchemaType(unittest.TestCase): result = typ.unflatten(node, ['node'], {'node': 'appstruct'}) self.assertEqual(result, 'appstruct') + def test_set_value(self): + typ = self._makeOne() + self.assertRaises( + AssertionError, typ.set_value, None, None, None, None) + class TestMapping(unittest.TestCase): def _makeOne(self, *arg, **kw): from colander import Mapping @@ -534,6 +539,14 @@ class TestMapping(unittest.TestCase): self.assertEqual(result, { 'one': {'a': 1, 'b': 2}, 'two': {'c': 3, 'd': 4}}) + def test_set_value(self): + typ = self._makeOne() + node1 = DummySchemaNode(typ, name='node1') + node2 = DummySchemaNode(typ, name='node2') + node1.children = [node2] + appstruct = {'node2': {'foo': 'foo', 'baz': 'baz'}} + typ.set_value(node1, appstruct, 'node2.foo', 'bar') + self.assertEqual(appstruct, {'node2': {'foo': 'bar', 'baz': 'baz'}}) class TestTuple(unittest.TestCase): def _makeOne(self): @@ -690,6 +703,35 @@ class TestTuple(unittest.TestCase): {'node': (1, 2), 'node.a': 1, 'node.b': 2}) self.assertEqual(result, (1, 2)) + def test_set_value(self): + typ = self._makeOne() + node = DummySchemaNode(typ, name='node') + node.children = [ + DummySchemaNode(typ, name='foo'), + DummySchemaNode(typ, name='bar') + ] + node['foo'].children = [ + DummySchemaNode(None, name='a'), + DummySchemaNode(None, name='b'), + ] + node['bar'].children = [ + DummySchemaNode(None, name='c'), + DummySchemaNode(None, name='d'), + ] + appstruct = ((1, 2), (3, 4)) + result = typ.set_value(node, appstruct, 'bar.c', 34) + self.assertEqual(result, ((1, 2), (34, 4))) + + def test_set_value_bad_path(self): + typ = self._makeOne() + node = DummySchemaNode(typ, name='node') + node.children = [ + DummySchemaNode(None, name='foo'), + DummySchemaNode(None, name='bar') + ] + self.assertRaises( + KeyError, typ.set_value, node, (1, 2), 'foobar', 34) + class TestSequence(unittest.TestCase): def _makeOne(self, **kw): from colander import Sequence @@ -821,6 +863,16 @@ class TestSequence(unittest.TestCase): {'node.0': 'a', 'node.1': 'b'}) self.assertEqual(result, ['a', 'b']) + def test_setvalue(self): + typ = self._makeOne() + node1 = DummySchemaNode(typ, name='seq1') + node2 = DummySchemaNode(typ, name='seq2') + node1.children = [node2] + node2.children = DummySchemaNode(None, name='items') + appstruct = [[1, 2], [3, 4]] + typ.set_value(node1, appstruct, '1.0', 34) + self.assertEqual(appstruct, [[1, 2], [34, 4]]) + class TestString(unittest.TestCase): def _makeOne(self, encoding=None): @@ -2110,6 +2162,19 @@ class TestFunctional(object): schema.unflatten(schema.flatten(appstruct)), appstruct) + def test_set_value(self): + import colander + appstruct = { + 'int':10, + 'ob':colander.tests, + 'seq':[(1, 's'),(2, 's'), (3, 's'), (4, 's')], + 'seq2':[{'key':1, 'key2':2}, {'key':3, 'key2':4}], + 'tup':(1, 's'), + } + schema = self._makeSchema() + schema.set_value(appstruct, 'seq2.1.key', 6) + self.assertEqual(appstruct['seq2'][1], {'key':6, 'key2':4}) + def test_invalid_asdict(self): expected = { 'schema.int': '20 is greater than maximum value 10',