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

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 

5 

6 

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 

14 

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) 

21 

22 def set_written(self, builder): 

23 """ 

24 Mark this builder as written. 

25 

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 

33 

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. 

36 

37 :param builder: Builder object to get the written flag for 

38 :type builder: Builder 

39 

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) 

44 

45 

46class NamespaceToBuilderHelper(object): 

47 """Helper class used in HDF5IO (and possibly elsewhere) to convert a namespace to a builder for I/O""" 

48 

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 

76 

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] 

81 

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 

101 

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