diff --git a/src/hdmf/build/manager.py b/src/hdmf/build/manager.py index 5ab13a874..fe0893818 100644 --- a/src/hdmf/build/manager.py +++ b/src/hdmf/build/manager.py @@ -384,7 +384,8 @@ def __get_container_type(self, container_name): container_type = val.get(container_name) if container_type is not None: return container_type - if container_type is None: + if container_type is None: # pragma: no cover + # this code should never happen after hdmf#322 raise TypeDoesNotExistError("Type '%s' does not exist." % container_name) def __get_type(self, spec): @@ -516,6 +517,9 @@ def get_container_cls(self, **kwargs): cls = self.__get_container_cls(namespace, data_type) if cls is None: spec = self.__ns_catalog.get_spec(namespace, data_type) + if isinstance(spec, GroupSpec): + self.__resolve_child_container_classes(spec, namespace) + dt_hier = self.__ns_catalog.get_hierarchy(namespace, data_type) parent_cls = None for t in dt_hier: @@ -534,7 +538,7 @@ def get_container_cls(self, **kwargs): parent_cls = bases[0] if type(parent_cls) is not ExtenderMeta: raise ValueError("parent class %s is not of type ExtenderMeta - %s" % (parent_cls, type(parent_cls))) - name = data_type + attr_names = self.__default_mapper_cls.get_attr_names(spec) fields = dict() for k, field_spec in attr_names.items(): @@ -542,15 +546,25 @@ def get_container_cls(self, **kwargs): fields[k] = field_spec try: d = self.__get_cls_dict(parent_cls, fields, spec.name, spec.default_name) - except TypeDoesNotExistError as e: - name = spec.get('data_type_def', 'Unknown') + except TypeDoesNotExistError as e: # pragma: no cover + # this error should never happen after hdmf#322 + name = spec.data_type_def + if name is None: + name = 'Unknown' raise ValueError("Cannot dynamically generate class for type '%s'. " % name + str(e) + " Please define that type before defining '%s'." % name) - cls = ExtenderMeta(str(name), bases, d) + cls = ExtenderMeta(str(data_type), bases, d) self.register_container_type(namespace, data_type, cls) return cls + def __resolve_child_container_classes(self, spec, namespace): + for child_spec in (spec.groups + spec.datasets): + if child_spec.data_type_inc is not None: + self.get_container_cls(namespace, child_spec.data_type_inc) + elif child_spec.data_type_def is not None: + self.get_container_cls(namespace, child_spec.data_type_def) + def __get_container_cls(self, namespace, data_type): if namespace not in self.__container_types: return None @@ -781,5 +795,5 @@ def get_builder_name(self, **kwargs): return obj_mapper.get_builder_name(container) -class TypeDoesNotExistError(Exception): +class TypeDoesNotExistError(Exception): # pragma: no cover pass diff --git a/tests/unit/build_tests/test_io_map.py b/tests/unit/build_tests/test_io_map.py index 13eaa6252..69a60fc3c 100644 --- a/tests/unit/build_tests/test_io_map.py +++ b/tests/unit/build_tests/test_io_map.py @@ -347,7 +347,7 @@ def test_dynamic_container_composition(self): with self.assertRaises(TypeError): Baz1('My Baz', [1, 2, 3, 4], 'string attribute', 1000, attr3=98.6, attr4=1.0, baz2=bar) - def test_dynamic_container_composition_wrong_order(self): + def test_dynamic_container_composition_reverse_order(self): baz_spec2 = GroupSpec('A composition inside', data_type_def='Baz2', data_type_inc=self.bar_spec, attributes=[ @@ -360,10 +360,25 @@ def test_dynamic_container_composition_wrong_order(self): groups=[GroupSpec('A composition inside', data_type_inc='Baz2')]) self.spec_catalog.register_spec(baz_spec1, 'extension.yaml') self.spec_catalog.register_spec(baz_spec2, 'extension.yaml') + Baz1 = self.type_map.get_container_cls(CORE_NAMESPACE, 'Baz1') + Baz2 = self.type_map.get_container_cls(CORE_NAMESPACE, 'Baz2') + Baz1('My Baz', [1, 2, 3, 4], 'string attribute', 1000, attr3=98.6, attr4=1.0, + baz2=Baz2('My Baz', [1, 2, 3, 4], 'string attribute', 1000, attr3=98.6, attr4=1.0)) + + Bar = self.type_map.get_container_cls(CORE_NAMESPACE, 'Bar') + bar = Bar('My Bar', [1, 2, 3, 4], 'string attribute', 1000) + + with self.assertRaises(TypeError): + Baz1('My Baz', [1, 2, 3, 4], 'string attribute', 1000, attr3=98.6, attr4=1.0, baz2=bar) + + def test_dynamic_container_composition_missing_type(self): + baz_spec1 = GroupSpec('A composition test outside', data_type_def='Baz1', data_type_inc=self.bar_spec, + attributes=[AttributeSpec('attr3', 'an example float attribute', 'float'), + AttributeSpec('attr4', 'another example float attribute', 'float')], + groups=[GroupSpec('A composition inside', data_type_inc='Baz2')]) + self.spec_catalog.register_spec(baz_spec1, 'extension.yaml') - # Setup all the data we need - msg = ("Cannot dynamically generate class for type 'Baz1'. Type 'Baz2' does not exist. " - "Please define that type before defining 'Baz1'.") + msg = "No specification for 'Baz2' in namespace 'test_core'" with self.assertRaisesWith(ValueError, msg): self.manager.type_map.get_container_cls(CORE_NAMESPACE, 'Baz1')