diff --git a/CHANGES.txt b/CHANGES.txt index 7d07728..97c0a89 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,6 +4,8 @@ Changes Next release ------------ +- Add a ``clone`` method to ``colander.SchemaNode`` objects. + - Add a ``__str__`` method to the ``colander.Invalid`` exception that prints an error summary. diff --git a/colander/__init__.py b/colander/__init__.py index e8cc4e4..03a9ec9 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -664,6 +664,15 @@ class SchemaNode(object): return node raise KeyError(name) + def clone(self): + """ Clone the schema node and return the clone. All subnodes + are also cloned recursively. Attributes present in node + dictionaries are preserved.""" + cloned = self.__class__(self.typ) + cloned.__dict__.update(self.__dict__) + cloned.nodes = [ node.clone() for node in self.nodes ] + return cloned + class _SchemaMeta(type): def __init__(cls, name, bases, clsattrs): nodes = [] diff --git a/colander/tests.py b/colander/tests.py index 168254d..e84f559 100644 --- a/colander/tests.py +++ b/colander/tests.py @@ -980,6 +980,26 @@ class TestSchemaNode(unittest.TestCase): node = self._makeOne(None) self.assertRaises(KeyError, node.__getitem__, 'another') + def test_clone(self): + inner_typ = DummyType() + outer_typ = DummyType() + outer_node = self._makeOne(outer_typ, name='outer') + inner_node = self._makeOne(inner_typ, name='inner') + outer_node.foo = 1 + inner_node.foo = 2 + outer_node.nodes = [inner_node] + outer_clone = outer_node.clone() + self.failIf(outer_clone is outer_node) + self.assertEqual(outer_clone.typ, outer_typ) + self.assertEqual(outer_clone.name, 'outer') + self.assertEqual(outer_node.foo, 1) + self.assertEqual(len(outer_clone.nodes), 1) + inner_clone = outer_clone.nodes[0] + self.failIf(inner_clone is inner_node) + self.assertEqual(inner_clone.typ, inner_typ) + self.assertEqual(inner_clone.name, 'inner') + self.assertEqual(inner_clone.foo, 2) + class TestSchema(unittest.TestCase): def test_alias(self): from colander import Schema