diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 9da5eb037..db5c38e93 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -2,7 +2,9 @@ from uuid import uuid4 from six import with_metaclass from .utils import docval, getargs, ExtenderMeta +from .query import HDMFDataset from warnings import warn +from copy import deepcopy class Container(with_metaclass(ExtenderMeta, object)): @@ -126,6 +128,30 @@ def parent(self, parent_container): parent_container.__children.append(self) parent_container.set_modified() + def __deepcopy__(self, memo): + ''' Create a deep copy of this Container + Reset the root parent to None, set all container_source to None, and set new object_id value + ''' + deepcopy_method = self.__deepcopy__ + self.__deepcopy__ = None + cp = deepcopy(self, memo) + self.__deepcopy__ = deepcopy_method + + cp.__parent = None + cp.__container_source = None + cp.__object_id = str(uuid4()) + cp.__modified = True + + for child in cp.children: + child.parent = cp + + # resolve HDMFDataset after and separately because DataIO can wrap an HDMFDataset + for k, v in cp.__dict__.items(): + if isinstance(v, HDMFDataset): + setattr(cp, k, v.dataset) + + return cp + class Data(Container): diff --git a/src/hdmf/data_utils.py b/src/hdmf/data_utils.py index f2fc0b9b5..10c8b7562 100644 --- a/src/hdmf/data_utils.py +++ b/src/hdmf/data_utils.py @@ -5,9 +5,9 @@ from collections import Iterable # Python 2.7 from operator import itemgetter - import numpy as np from six import with_metaclass, text_type, binary_type +from copy import deepcopy from .container import Data, DataRegion from .utils import docval, getargs, popargs, docval_macro, get_data_shape @@ -512,6 +512,9 @@ def __next__(self): def __iter__(self): return self.data.__iter__() + def __deepcopy__(self, memo): + return deepcopy(self.data) + class RegionSlicer(with_metaclass(ABCMeta, DataRegion)): ''' diff --git a/tests/unit/test_container.py b/tests/unit/test_container.py index 8927e4b0f..7e5e3800b 100644 --- a/tests/unit/test_container.py +++ b/tests/unit/test_container.py @@ -1,6 +1,10 @@ import unittest2 as unittest +from tests.unit.test_utils import Foo from hdmf.container import Container +from hdmf.query import HDMFDataset +from hdmf.data_utils import DataIO +from copy import deepcopy class Subcontainer(Container): @@ -118,6 +122,51 @@ def test_type_hierarchy(self): self.assertEqual(Container.type_hierarchy(), (Container, object)) self.assertEqual(Subcontainer.type_hierarchy(), (Subcontainer, Container, object)) + def test_deepcopy(self): + parent_obj = Container('obj1') + parent_obj.container_source = 'a file' + parent_obj.set_modified(False) + child_obj = Container('obj2') + child_obj.parent = parent_obj + child_obj.container_source = 'a file' + child_obj.set_modified(False) + child_child_obj = Container('obj3') + child_child_obj.parent = child_obj + child_child_obj.container_source = 'a file' + + parent_copy = deepcopy(parent_obj) + self.assertEqual(parent_copy.name, 'obj1') + self.assertEqual(parent_copy.children[0].name, 'obj2') + self.assertEqual(parent_copy.children[0].children[0].name, 'obj3') + self.assertNotEqual(parent_copy.object_id, parent_obj.object_id) + self.assertNotEqual(parent_copy.children[0].object_id, child_obj.object_id) + self.assertNotEqual(parent_copy.children[0].children[0].object_id, child_child_obj.object_id) + self.assertIsNone(parent_copy.container_source) + self.assertIsNone(parent_copy.children[0].container_source) + self.assertIsNone(parent_copy.children[0].children[0].container_source) + self.assertTrue(parent_copy.modified) + self.assertTrue(parent_copy.children[0].modified) + self.assertTrue(parent_copy.children[0].children[0].modified) + + child_copy = deepcopy(child_obj) + self.assertIsNone(child_copy.parent) + + def test_deepcopy_data(self): + parent_obj = Foo('obj1', HDMFDataset([1, 2, 3, 4, 5]), 'a string', 10) + parent_obj.container_source = 'a file' + parent_obj.set_modified(False) + child_obj = Foo('obj2', DataIO(HDMFDataset([1, 2, 3, 4, 5])), 'a string2', 20) + child_obj.parent = parent_obj + child_obj.container_source = 'a file' + + parent_copy = deepcopy(parent_obj) + self.assertListEqual(parent_copy.my_data, [1, 2, 3, 4, 5]) + self.assertEqual(parent_copy.attr1, 'a string') + self.assertEqual(parent_copy.attr2, 10) + self.assertListEqual(parent_copy.children[0].my_data, [1, 2, 3, 4, 5]) + + # TODO test deepcopy with references and H5Dataset + if __name__ == '__main__': unittest.main() diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index e4cd1310c..1a4ef01ee 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -8,7 +8,7 @@ class Foo(Container): @docval({'name': 'name', 'type': str, 'doc': 'the name of this Foo'}, - {'name': 'my_data', 'type': 'array_data', 'doc': 'some data'}, + {'name': 'my_data', 'type': ('array_data', 'data'), 'doc': 'some data'}, {'name': 'attr1', 'type': str, 'doc': 'an attribute'}, {'name': 'attr2', 'type': int, 'doc': 'another attribute'}, {'name': 'attr3', 'type': float, 'doc': 'a third attribute', 'default': 3.14})