Skip to content

Commit

Permalink
Register new child types before new parent type for dynamic cls… (#322)
Browse files Browse the repository at this point in the history
* Register new child types before new parent type for dynamic cls gen

* Remove breakpoint

* Add another test for missing type

* Add pragma: no cover to lines that are not expected to run
  • Loading branch information
rly authored Mar 26, 2020
1 parent 9da9162 commit d09e43e
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 10 deletions.
26 changes: 20 additions & 6 deletions src/hdmf/build/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand All @@ -534,23 +538,33 @@ 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():
if not spec.is_inherited_spec(field_spec):
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
Expand Down Expand Up @@ -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
23 changes: 19 additions & 4 deletions tests/unit/build_tests/test_io_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=[
Expand All @@ -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')

Expand Down

0 comments on commit d09e43e

Please sign in to comment.