From e7dc277fba30e3fa7ad120cc3ccd92721d937821 Mon Sep 17 00:00:00 2001 From: Andrew Tritt Date: Mon, 30 Jul 2018 11:33:56 -0700 Subject: [PATCH 1/6] recurse on Proxy objects --- src/pynwb/form/build/map.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/pynwb/form/build/map.py b/src/pynwb/form/build/map.py index 5efe6b8ef..ee12b75c8 100644 --- a/src/pynwb/form/build/map.py +++ b/src/pynwb/form/build/map.py @@ -15,6 +15,16 @@ class Proxy(object): + """ + A temporary object to represent a Container. This gets used when resolving the true location of a + Container's parent. + + Proxy objects allow simple bookeeping of all potential parents a Container may have. + + This object is used by providing all the necessary information for describing the object. This object + gets passed around and candidates are accumulated. Upon calling resolve, all saved candidates are matched + against the information (provided to the constructor). The candidate that has an exact match is returned. + """ def __init__(self, manager, source, location, namespace, data_type): self.__source = source @@ -26,22 +36,27 @@ def __init__(self, manager, source, location, namespace, data_type): @property def candidates(self): + """Potential matches to this Proxy object""" return self.__candidates @property def source(self): + """The source of the object e.g. file source""" return self.__source @property def location(self): + """The location of the object. This can be thought of as a unique path""" return self.__location @property def namespace(self): + """The namespace from which the data_type of this Proxy came from""" return self.__namespace @property def data_type(self): + """The data_type of Container that should match this Proxy""" return self.__data_type @docval({"name": "object", "type": (BaseBuilder, Container), "doc": "the container or builder to get a proxy for"}) @@ -115,8 +130,12 @@ def __get_proxy_container(self, container): stack = list() tmp = container while tmp is not None: - stack.append(tmp.name) - tmp = tmp.parent + if isinstance(tmp, Proxy): + stack.append(tmp.location) + break + else: + stack.append(tmp.name) + tmp = tmp.parent loc = "/".join(reversed(stack)) return Proxy(self, container.container_source, loc, ns, dt) @@ -523,6 +542,9 @@ def get_attr_value(self, **kwargs): return None attr_val = self.__get_override_attr(attr_name, container, manager) if attr_val is None: + if not hasattr(container, attr_name): + msg = "Container '%s' (%s) does not have attribute '%s'" % (container.name, type(container), attr_name) + #warnings.warn(msg) attr_val = getattr(container, attr_name, None) if attr_val is not None: attr_val = self.__convert_value(attr_val, spec) From f9f6cf40cdd117ca1bb6d9e659f997dc5bed152b Mon Sep 17 00:00:00 2001 From: Andrew Tritt Date: Mon, 30 Jul 2018 13:07:21 -0700 Subject: [PATCH 2/6] satisfy flake8 --- src/pynwb/form/build/map.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pynwb/form/build/map.py b/src/pynwb/form/build/map.py index ee12b75c8..f4d3f6e7d 100644 --- a/src/pynwb/form/build/map.py +++ b/src/pynwb/form/build/map.py @@ -542,9 +542,13 @@ def get_attr_value(self, **kwargs): return None attr_val = self.__get_override_attr(attr_name, container, manager) if attr_val is None: - if not hasattr(container, attr_name): - msg = "Container '%s' (%s) does not have attribute '%s'" % (container.name, type(container), attr_name) - #warnings.warn(msg) + # TODO: A message like this should be used to warn users when an expected attribute + # does not exist on a Container object + # + # if not hasattr(container, attr_name): + # msg = "Container '%s' (%s) does not have attribute '%s'" \ + # % (container.name, type(container), attr_name) + # #warnings.warn(msg) attr_val = getattr(container, attr_name, None) if attr_val is not None: attr_val = self.__convert_value(attr_val, spec) From d621dd7acd6f438c46d3c22dbcc146f7cb4ec186 Mon Sep 17 00:00:00 2001 From: Andrew Tritt Date: Mon, 30 Jul 2018 16:11:03 -0700 Subject: [PATCH 3/6] removed unused property --- src/pynwb/form/build/map.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pynwb/form/build/map.py b/src/pynwb/form/build/map.py index f4d3f6e7d..3de933238 100644 --- a/src/pynwb/form/build/map.py +++ b/src/pynwb/form/build/map.py @@ -34,11 +34,6 @@ def __init__(self, manager, source, location, namespace, data_type): self.__manager = manager self.__candidates = list() - @property - def candidates(self): - """Potential matches to this Proxy object""" - return self.__candidates - @property def source(self): """The source of the object e.g. file source""" From 4aa1d8995b96063b64aa2693c38501a5cde1a112 Mon Sep 17 00:00:00 2001 From: Andrew Tritt Date: Mon, 30 Jul 2018 16:11:13 -0700 Subject: [PATCH 4/6] add test for resolving links --- tests/unit/form_tests/test_io_hdf5_h5tools.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/unit/form_tests/test_io_hdf5_h5tools.py b/tests/unit/form_tests/test_io_hdf5_h5tools.py index d927f00ba..d952c5ba1 100644 --- a/tests/unit/form_tests/test_io_hdf5_h5tools.py +++ b/tests/unit/form_tests/test_io_hdf5_h5tools.py @@ -12,6 +12,8 @@ from pynwb import NWBHDF5IO from pynwb.spec import NWBNamespace, NWBGroupSpec, NWBDatasetSpec +from pynwb.ecephys import ElectricalSeries + import tempfile import warnings @@ -399,6 +401,44 @@ def __get_types(self, catalog): types.update(catalog.get_types(source['source'])) return types +class TestLinkResolution(unittest.TestCase): + + def test_link_resolve(self): + print("TEST_LINK_RESOLVE") + + nwbfile = NWBFile("source", "a file with header data", "NB123A", '2018-06-01T00:00:00') + device = nwbfile.create_device('device_name', 'source') + electrode_group = nwbfile.create_electrode_group( + name='electrode_group_name', + source='source', + description='desc', + device=device, + location='unknown') + nwbfile.add_electrode(0, + 1.0, 2.0, 3.0, # position? + imp=2.718, + location='unknown', + filtering='unknown', + description='desc', + group=electrode_group) + etr = nwbfile.create_electrode_table_region([0], 'etr_name') + for passband in ('theta', 'gamma'): + electrical_series = ElectricalSeries(name=passband + '_phase', + source='ephys_analysis', + data=[1., 2., 3.], + rate=0.0, + electrodes=etr) + with NWBHDF5IO(self.path, 'w') as io: + io.write(nwbfile) + with NWBHDF5IO(self.path, 'r') as io: + io.read() + + def setUp(self): + self.path = "test_link_resolve.nwb" + + def tearDown(self): + if os.path.exists(self.path): + os.remove(self.path) class NWBHDF5IOMultiFileTest(unittest.TestCase): """Tests for h5tools IO tools""" From af450b69726f33a20e9147169f54f1cea4b33927 Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Mon, 30 Jul 2018 20:46:23 -0400 Subject: [PATCH 5/6] flake8 --- tests/unit/form_tests/test_io_hdf5_h5tools.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/form_tests/test_io_hdf5_h5tools.py b/tests/unit/form_tests/test_io_hdf5_h5tools.py index d952c5ba1..d31d9c6d8 100644 --- a/tests/unit/form_tests/test_io_hdf5_h5tools.py +++ b/tests/unit/form_tests/test_io_hdf5_h5tools.py @@ -401,6 +401,7 @@ def __get_types(self, catalog): types.update(catalog.get_types(source['source'])) return types + class TestLinkResolution(unittest.TestCase): def test_link_resolve(self): @@ -428,6 +429,7 @@ def test_link_resolve(self): data=[1., 2., 3.], rate=0.0, electrodes=etr) + nwbfile.add_acquisition(electrical_series) with NWBHDF5IO(self.path, 'w') as io: io.write(nwbfile) with NWBHDF5IO(self.path, 'r') as io: @@ -436,6 +438,7 @@ def test_link_resolve(self): def setUp(self): self.path = "test_link_resolve.nwb" + def tearDown(self): if os.path.exists(self.path): os.remove(self.path) From 3b70c95836ef15cf902cf678a271512db480ec98 Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Mon, 30 Jul 2018 20:50:54 -0400 Subject: [PATCH 6/6] flake8 --- tests/unit/form_tests/test_io_hdf5_h5tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/form_tests/test_io_hdf5_h5tools.py b/tests/unit/form_tests/test_io_hdf5_h5tools.py index d31d9c6d8..4e1264662 100644 --- a/tests/unit/form_tests/test_io_hdf5_h5tools.py +++ b/tests/unit/form_tests/test_io_hdf5_h5tools.py @@ -438,11 +438,11 @@ def test_link_resolve(self): def setUp(self): self.path = "test_link_resolve.nwb" - def tearDown(self): if os.path.exists(self.path): os.remove(self.path) + class NWBHDF5IOMultiFileTest(unittest.TestCase): """Tests for h5tools IO tools"""