Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

do not autogenerate classes when loading namespace #555

Merged
merged 8 commits into from
Apr 9, 2021
Merged
9 changes: 2 additions & 7 deletions src/hdmf/build/classgenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,11 @@ def _get_container_type(cls, type_name, type_map):
"""Search all namespaces for the container class associated with the given data type.
Raises TypeDoesNotExistError if type is not found in any namespace.
"""
container_type = None
for val in type_map.container_types.values():
# NOTE that the type_name may appear in multiple namespaces based on how they were resolved
# but the same type_name should point to the same class
container_type = val.get(type_name)
if container_type is not None:
return container_type
container_type = type_map.get_container_cls(type_name)
if container_type is None: # pragma: no cover
# this should never happen after hdmf#322
raise TypeDoesNotExistError("Type '%s' does not exist." % type_name)
return container_type

@classmethod
def _get_type(cls, spec, type_map):
Expand Down
61 changes: 41 additions & 20 deletions src/hdmf/build/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,38 +474,56 @@ def load_namespaces(self, **kwargs):
for new_ns, ns_deps in deps.items():
for src_ns, types in ns_deps.items():
for dt in types:
container_cls = self.get_container_cls(src_ns, dt)
container_cls = self.get_container_cls(dt, src_ns, autogen=False)
if container_cls is None:
container_cls = TypeSource(src_ns, dt)
self.register_container_type(new_ns, dt, container_cls)
return deps

@docval({"name": "namespace", "type": str, "doc": "the namespace containing the data_type"},
{"name": "data_type", "type": str, "doc": "the data type to create a AbstractContainer class for"},
@docval({"name": "data_type", "type": str, "doc": "the data type to create a AbstractContainer class for"},
{"name": "namespace", "type": str, "doc": "the namespace containing the data_type", "default": None},
{"name": "autogen", "type": bool, "doc": "autogenerate class if one does not exist", "default": True},
returns='the class for the given namespace and data_type', rtype=type)
def get_container_cls(self, **kwargs):
"""Get the container class from data type specification.
If no class has been associated with the ``data_type`` from ``namespace``, a class will be dynamically
created and returned.
"""
namespace, data_type = getargs('namespace', 'data_type', kwargs)
namespace, data_type, autogen = getargs('namespace', 'data_type', 'autogen', kwargs)

# namespace is unknown, so look it up
if namespace is None:
for key, val in self.__container_types.items():
# NOTE that the type_name may appear in multiple namespaces based on how they were resolved
# but the same type_name should point to the same class
if data_type in val:
namespace = key
break

cls = self.__get_container_cls(namespace, data_type)
if cls is None: # dynamically generate a class
if cls is None and autogen: # dynamically generate a class
spec = self.__ns_catalog.get_spec(namespace, data_type)
if isinstance(spec, GroupSpec):
self.__resolve_child_container_classes(spec, namespace)
self.__check_dependent_types(spec, namespace)
parent_cls = self.__get_parent_cls(namespace, data_type, spec)
attr_names = self.__default_mapper_cls.get_attr_names(spec)
cls = self.__class_generator.generate_class(data_type, spec, parent_cls, attr_names, self)
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 __check_dependent_types(self, spec, namespace):
"""Ensure that classes for all types used by this type exist and generate them if not.
"""
if spec.data_type_inc is not None:
self.get_container_cls(spec.data_type_inc, namespace)
if isinstance(spec, GroupSpec):
for child_spec in (spec.groups + spec.datasets):
if child_spec.data_type_inc is not None:
self.get_container_cls(child_spec.data_type_inc, namespace)
if child_spec.data_type_def is not None:
self.get_container_cls(child_spec.data_type_def, namespace)
for child_spec in spec.links:
if child_spec.target_type is not None:
self.get_container_cls(child_spec.target_type, namespace)

def __get_parent_cls(self, namespace, data_type, spec):
dt_hier = self.__ns_catalog.get_hierarchy(namespace, data_type)
Expand All @@ -527,15 +545,17 @@ def __get_parent_cls(self, namespace, data_type, spec):
return parent_cls

def __get_container_cls(self, namespace, data_type):
"""Get the container class for the namespace, data_type. If the class doesn't exist yet, generate it."""
if namespace not in self.__container_types:
return None
if data_type not in self.__container_types[namespace]:
return None
ret = self.__container_types[namespace][data_type]
if isinstance(ret, TypeSource):
ret = self.__get_container_cls(ret.namespace, ret.data_type)
if ret is not None:
self.register_container_type(namespace, data_type, ret)
if isinstance(ret, TypeSource): # data_type is a dependency from ret.namespace
cls = self.get_container_cls(ret.data_type, ret.namespace) # get class / generate class
# register the same class into this namespace (replaces TypeSource)
self.register_container_type(namespace, data_type, cls)
ret = cls
return ret

@docval({'name': 'obj', 'type': (GroupBuilder, DatasetBuilder, LinkBuilder, GroupSpec, DatasetSpec),
Expand Down Expand Up @@ -591,7 +611,7 @@ def get_cls(self, **kwargs):
namespace = self.get_builder_ns(builder)
if namespace is None:
raise ValueError("No namespace found for builder %s" % builder.path)
return self.get_container_cls(namespace, data_type)
return self.get_container_cls(data_type, namespace)

@docval({'name': 'spec', 'type': (DatasetSpec, GroupSpec), 'doc': 'the parent spec to search'},
{'name': 'builder', 'type': (DatasetBuilder, GroupBuilder, LinkBuilder),
Expand Down Expand Up @@ -683,8 +703,9 @@ def register_container_type(self, **kwargs):
self.__container_types.setdefault(namespace, dict())
self.__container_types[namespace][data_type] = container_cls
self.__data_types.setdefault(container_cls, (namespace, data_type))
setattr(container_cls, spec.type_key(), data_type)
setattr(container_cls, 'namespace', namespace)
if not isinstance(container_cls, TypeSource):
setattr(container_cls, spec.type_key(), data_type)
setattr(container_cls, 'namespace', namespace)

@docval({"name": "container_cls", "type": type,
"doc": "the AbstractContainer class for which the given ObjectMapper class gets used for"},
Expand Down
20 changes: 10 additions & 10 deletions src/hdmf/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,15 @@ def available_namespaces():
raise RuntimeError("Unable to load a TypeMap - no namespace file found")


DynamicTable = __TYPE_MAP.get_container_cls(CORE_NAMESPACE, 'DynamicTable')
VectorData = __TYPE_MAP.get_container_cls(CORE_NAMESPACE, 'VectorData')
VectorIndex = __TYPE_MAP.get_container_cls(CORE_NAMESPACE, 'VectorIndex')
ElementIdentifiers = __TYPE_MAP.get_container_cls(CORE_NAMESPACE, 'ElementIdentifiers')
DynamicTableRegion = __TYPE_MAP.get_container_cls(CORE_NAMESPACE, 'DynamicTableRegion')
EnumData = __TYPE_MAP.get_container_cls(EXP_NAMESPACE, 'EnumData')
CSRMatrix = __TYPE_MAP.get_container_cls(CORE_NAMESPACE, 'CSRMatrix')
ExternalResources = __TYPE_MAP.get_container_cls(EXP_NAMESPACE, 'ExternalResources')
SimpleMultiContainer = __TYPE_MAP.get_container_cls(CORE_NAMESPACE, 'SimpleMultiContainer')
DynamicTable = __TYPE_MAP.get_container_cls('DynamicTable', CORE_NAMESPACE)
VectorData = __TYPE_MAP.get_container_cls('VectorData', CORE_NAMESPACE)
VectorIndex = __TYPE_MAP.get_container_cls('VectorIndex', CORE_NAMESPACE)
ElementIdentifiers = __TYPE_MAP.get_container_cls('ElementIdentifiers', CORE_NAMESPACE)
DynamicTableRegion = __TYPE_MAP.get_container_cls('DynamicTableRegion', CORE_NAMESPACE)
EnumData = __TYPE_MAP.get_container_cls('EnumData', EXP_NAMESPACE)
CSRMatrix = __TYPE_MAP.get_container_cls('CSRMatrix', CORE_NAMESPACE)
ExternalResources = __TYPE_MAP.get_container_cls('ExternalResources', EXP_NAMESPACE)
SimpleMultiContainer = __TYPE_MAP.get_container_cls('SimpleMultiContainer', CORE_NAMESPACE)


@docval({'name': 'extensions', 'type': (str, TypeMap, list),
Expand Down Expand Up @@ -197,7 +197,7 @@ def get_class(**kwargs):
"""Get the class object of the Container subclass corresponding to a given neurdata_type.
"""
data_type, namespace = getargs('data_type', 'namespace', kwargs)
return __TYPE_MAP.get_container_cls(namespace, data_type)
return __TYPE_MAP.get_container_cls(data_type, namespace)


@docval({'name': 'io', 'type': HDMFIO,
Expand Down
Loading