Coverage for src/hdmf/backends/utils.py: 98%
63 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-07-25 05:02 +0000
« prev ^ index » next coverage.py v7.2.5, created at 2023-07-25 05:02 +0000
1"""Module with utility functions and classes used for implementation of I/O backends"""
2import os
3from ..spec import NamespaceCatalog, GroupSpec, NamespaceBuilder
4from ..utils import docval, popargs
7class WriteStatusTracker(dict):
8 """
9 Helper class used for tracking the write status of builders. I.e., to track whether a
10 builder has been written or not.
11 """
12 def __init__(self):
13 pass
15 def __builderhash(self, builder):
16 """Return the ID of a builder for use as a unique hash."""
17 # NOTE: id may not be sufficient if builders are created inline in the function call, in which
18 # case the id is the id of the functions parameter, so it can be the same for different
19 # builders. This should typically only happen in unit testing, but just to be safe.
20 return str(id(builder)) + "_" + str(builder.name)
22 def set_written(self, builder):
23 """
24 Mark this builder as written.
26 :param builder: Builder object to be marked as written
27 :type builder: Builder
28 """
29 # currently all values in self._written_builders are True, so this could be a set but is a dict for
30 # future flexibility
31 builder_id = self.__builderhash(builder)
32 self[builder_id] = True
34 def get_written(self, builder):
35 """Return True if this builder has been written to (or read from) disk by this IO object, False otherwise.
37 :param builder: Builder object to get the written flag for
38 :type builder: Builder
40 :return: True if the builder is found in self._written_builders using the builder ID, False otherwise
41 """
42 builder_id = self.__builderhash(builder)
43 return self.get(builder_id, False)
46class NamespaceToBuilderHelper(object):
47 """Helper class used in HDF5IO (and possibly elsewhere) to convert a namespace to a builder for I/O"""
49 @classmethod
50 @docval({'name': 'ns_catalog', 'type': NamespaceCatalog, 'doc': 'the namespace catalog with the specs'},
51 {'name': 'namespace', 'type': str, 'doc': 'the name of the namespace to be converted to a builder'},
52 rtype=NamespaceBuilder)
53 def convert_namespace(cls, **kwargs):
54 """Convert a namespace to a builder"""
55 ns_catalog, namespace = popargs('ns_catalog', 'namespace', kwargs)
56 ns = ns_catalog.get_namespace(namespace)
57 builder = NamespaceBuilder(ns.doc, ns.name,
58 full_name=ns.full_name,
59 version=ns.version,
60 author=ns.author,
61 contact=ns.contact)
62 for elem in ns.schema:
63 if 'namespace' in elem:
64 inc_ns = elem['namespace']
65 builder.include_namespace(inc_ns)
66 else:
67 source = elem['source']
68 for dt in ns_catalog.get_types(source):
69 spec = ns_catalog.get_spec(namespace, dt)
70 if spec.parent is not None: 70 ↛ 71line 70 didn't jump to line 71, because the condition on line 70 was never true
71 continue
72 h5_source = cls.get_source_name(source)
73 spec = cls.__copy_spec(spec)
74 builder.add_spec(h5_source, spec)
75 return builder
77 @classmethod
78 @docval({'name': 'source', 'type': str, 'doc': "source path"})
79 def get_source_name(self, source):
80 return os.path.splitext(source)[0]
82 @classmethod
83 def __copy_spec(cls, spec):
84 kwargs = dict()
85 kwargs['attributes'] = cls.__get_new_specs(spec.attributes, spec)
86 to_copy = ['doc', 'name', 'default_name', 'linkable', 'quantity', spec.inc_key(), spec.def_key()]
87 if isinstance(spec, GroupSpec):
88 kwargs['datasets'] = cls.__get_new_specs(spec.datasets, spec)
89 kwargs['groups'] = cls.__get_new_specs(spec.groups, spec)
90 kwargs['links'] = cls.__get_new_specs(spec.links, spec)
91 else:
92 to_copy.append('dtype')
93 to_copy.append('shape')
94 to_copy.append('dims')
95 for key in to_copy:
96 val = getattr(spec, key)
97 if val is not None:
98 kwargs[key] = val
99 ret = spec.build_spec(kwargs)
100 return ret
102 @classmethod
103 def __get_new_specs(cls, subspecs, spec):
104 ret = list()
105 for subspec in subspecs:
106 if not spec.is_inherited_spec(subspec) or spec.is_overridden_spec(subspec):
107 ret.append(subspec)
108 return ret