From 6948236ba4abdf3418fabb0bb9fa4947be1d011e Mon Sep 17 00:00:00 2001 From: Chris Rossi Date: Wed, 27 Jul 2011 11:35:08 -0400 Subject: [PATCH] Add get_value method. --- colander/__init__.py | 39 +++++++++++++++++++++++++ colander/tests.py | 68 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) diff --git a/colander/__init__.py b/colander/__init__.py index 3f669f9..247a44c 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -353,6 +353,9 @@ class SchemaType(object): def set_value(self, node, appstruct, path, value): raise AssertionError("Can't call 'set_value' on a leaf node.") + def get_value(self, node, appstruct, path): + raise AssertionError("Can't call 'set_value' on a leaf node.") + class Mapping(SchemaType): """ A type which represents a mapping of names to nodes. @@ -501,6 +504,14 @@ class Mapping(SchemaType): appstruct[path] = value return appstruct + def get_value(self, node, appstruct, path): + if '.' in path: + name, rest = path.split('.', 1) + next_node = node[name] + return next_node.typ.get_value(next_node, appstruct[name], rest) + return appstruct[path] + + class Positional(object): """ Marker abstract base class meaning 'this type has children which @@ -619,6 +630,21 @@ class Tuple(Positional, SchemaType): appstruct[index] = value return tuple(appstruct) + def get_value(self, node, appstruct, path): + if '.' in path: + name, rest = path.split('.', 1) + else: + name, rest = path, None + for index, next_node in enumerate(node.children): + if next_node.name == name: + break + else: + raise KeyError(name) + if rest is not None: + return next_node.typ.get_value(next_node, appstruct[index], rest) + return appstruct[index] + + class Sequence(Positional, SchemaType): """ A type which represents a variable-length sequence of nodes, @@ -782,6 +808,14 @@ class Sequence(Positional, SchemaType): appstruct[index] = value return appstruct + def get_value(self, node, appstruct, path): + if '.' in path: + name, rest = path.split('.', 1) + index = int(name) + next_node = node.children[0] + return next_node.typ.get_value(next_node, appstruct[index], rest) + return appstruct[int(path)] + Seq = Sequence class String(SchemaType): @@ -1510,6 +1544,11 @@ class SchemaNode(object): path. """ self.typ.set_value(self, appstruct, dotted_name, value) + def get_value(self, appstruct, dotted_name): + """ Traverses the appstruct using the schema and retrieves the value + specified by the dotted_name path.""" + return self.typ.get_value(self, appstruct, dotted_name) + 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 d11820b..444acd6 100644 --- a/colander/tests.py +++ b/colander/tests.py @@ -347,6 +347,11 @@ class TestSchemaType(unittest.TestCase): self.assertRaises( AssertionError, typ.set_value, None, None, None, None) + def test_get_value(self): + typ = self._makeOne() + self.assertRaises( + AssertionError, typ.get_value, None, None, None) + class TestMapping(unittest.TestCase): def _makeOne(self, *arg, **kw): from colander import Mapping @@ -548,6 +553,17 @@ class TestMapping(unittest.TestCase): typ.set_value(node1, appstruct, 'node2.foo', 'bar') self.assertEqual(appstruct, {'node2': {'foo': 'bar', 'baz': 'baz'}}) + def test_get_value(self): + typ = self._makeOne() + node1 = DummySchemaNode(typ, name='node1') + node2 = DummySchemaNode(typ, name='node2') + node1.children = [node2] + appstruct = {'node2': {'foo': 'bar', 'baz': 'baz'}} + self.assertEqual(typ.get_value(node1, appstruct, 'node2'), + {'foo': 'bar', 'baz': 'baz'}) + self.assertEqual(typ.get_value(node1, appstruct, 'node2.foo'), 'bar') + + class TestTuple(unittest.TestCase): def _makeOne(self): from colander import Tuple @@ -732,6 +748,35 @@ class TestTuple(unittest.TestCase): self.assertRaises( KeyError, typ.set_value, node, (1, 2), 'foobar', 34) + def test_get_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)) + self.assertEqual(typ.get_value(node, appstruct, 'foo'), (1, 2)) + self.assertEqual(typ.get_value(node, appstruct, 'foo.b'), 2) + + def test_get_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.get_value, node, (1, 2), 'foobar') + class TestSequence(unittest.TestCase): def _makeOne(self, **kw): from colander import Sequence @@ -873,6 +918,15 @@ class TestSequence(unittest.TestCase): typ.set_value(node1, appstruct, '1.0', 34) self.assertEqual(appstruct, [[1, 2], [34, 4]]) + def test_getvalue(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]] + self.assertEqual(typ.get_value(node1, appstruct, '1'), [3, 4]) + self.assertEqual(typ.get_value(node1, appstruct, '1.0'), 3) class TestString(unittest.TestCase): def _makeOne(self, encoding=None): @@ -2175,6 +2229,20 @@ class TestFunctional(object): schema.set_value(appstruct, 'seq2.1.key', 6) self.assertEqual(appstruct['seq2'][1], {'key':6, 'key2':4}) + def test_get_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() + self.assertEqual(schema.get_value(appstruct, 'seq'), + [(1, 's'),(2, 's'), (3, 's'), (4, 's')]) + self.assertEqual(schema.get_value(appstruct, 'seq2.1.key'), 3) + def test_invalid_asdict(self): expected = { 'schema.int': '20 is greater than maximum value 10',