Coverage for src/hdmf/common/io/table.py: 88%

99 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-04 02:57 +0000

1from .. import register_map 

2from ..table import DynamicTable, VectorData, VectorIndex, DynamicTableRegion 

3from ...build import ObjectMapper, BuildManager, CustomClassGenerator 

4from ...spec import Spec 

5from ...utils import docval, getargs, popargs, AllowPositional 

6 

7 

8@register_map(DynamicTable) 

9class DynamicTableMap(ObjectMapper): 

10 

11 def __init__(self, spec): 

12 super().__init__(spec) 

13 vector_data_spec = spec.get_data_type('VectorData') 

14 self.map_spec('columns', vector_data_spec) 

15 

16 @ObjectMapper.object_attr('colnames') 

17 def attr_columns(self, container, manager): 

18 if all(not col for col in container.columns): 

19 return tuple() 

20 return container.colnames 

21 

22 @docval({"name": "spec", "type": Spec, "doc": "the spec to get the attribute value for"}, 

23 {"name": "container", "type": DynamicTable, "doc": "the container to get the attribute value from"}, 

24 {"name": "manager", "type": BuildManager, "doc": "the BuildManager used for managing this build"}, 

25 returns='the value of the attribute') 

26 def get_attr_value(self, **kwargs): 

27 ''' Get the value of the attribute corresponding to this spec from the given container ''' 

28 spec, container, manager = getargs('spec', 'container', 'manager', kwargs) 

29 attr_value = super().get_attr_value(spec, container, manager) 

30 if attr_value is None and spec.name in container: 

31 if spec.data_type_inc == 'VectorData': 

32 attr_value = container[spec.name] 

33 if isinstance(attr_value, VectorIndex): 

34 attr_value = attr_value.target 

35 elif spec.data_type_inc == 'DynamicTableRegion': 35 ↛ 36line 35 didn't jump to line 36, because the condition on line 35 was never true

36 attr_value = container[spec.name] 

37 if isinstance(attr_value, VectorIndex): 

38 attr_value = attr_value.target 

39 if attr_value.table is None: 

40 msg = "empty or missing table for DynamicTableRegion '%s' in DynamicTable '%s'" % \ 

41 (attr_value.name, container.name) 

42 raise ValueError(msg) 

43 elif spec.data_type_inc == 'VectorIndex': 43 ↛ 45line 43 didn't jump to line 45, because the condition on line 43 was never false

44 attr_value = container[spec.name] 

45 return attr_value 

46 

47 

48class DynamicTableGenerator(CustomClassGenerator): 

49 

50 @classmethod 

51 def apply_generator_to_field(cls, field_spec, bases, type_map): 

52 """Return True if this is a DynamicTable and the field spec is a column.""" 

53 for b in bases: 53 ↛ 57line 53 didn't jump to line 57, because the loop on line 53 didn't complete

54 if issubclass(b, DynamicTable): 54 ↛ 53line 54 didn't jump to line 53, because the condition on line 54 was never false

55 break 

56 else: # return False if no base is a subclass of DynamicTable 

57 return False 

58 dtype = cls._get_type(field_spec, type_map) 

59 return isinstance(dtype, type) and issubclass(dtype, VectorData) 

60 

61 @classmethod 

62 def process_field_spec(cls, classdict, docval_args, parent_cls, attr_name, not_inherited_fields, type_map, spec): 

63 """Add __columns__ to the classdict and update the docval args for the field spec with the given attribute name. 

64 :param classdict: The dict to update with __columns__. 

65 :param docval_args: The list of docval arguments. 

66 :param parent_cls: The parent class. 

67 :param attr_name: The attribute name of the field spec for the container class to generate. 

68 :param not_inherited_fields: Dictionary of fields not inherited from the parent class. 

69 :param type_map: The type map to use. 

70 :param spec: The spec for the container class to generate. 

71 """ 

72 if attr_name.endswith('_index'): # do not add index columns to __columns__ 

73 return 

74 field_spec = not_inherited_fields[attr_name] 

75 column_conf = dict( 

76 name=attr_name, 

77 description=field_spec['doc'], 

78 required=field_spec.required 

79 ) 

80 dtype = cls._get_type(field_spec, type_map) 

81 if issubclass(dtype, DynamicTableRegion): 

82 # the spec does not know which table this DTR points to 

83 # the user must specify the table attribute on the DTR after it is generated 

84 column_conf['table'] = True 

85 else: 

86 column_conf['class'] = dtype 

87 

88 index_counter = 0 

89 index_name = attr_name 

90 while '{}_index'.format(index_name) in not_inherited_fields: # an index column exists for this column 

91 index_name = '{}_index'.format(index_name) 

92 index_counter += 1 

93 if index_counter == 1: 

94 column_conf['index'] = True 

95 elif index_counter > 1: 95 ↛ 96line 95 didn't jump to line 96, because the condition on line 95 was never true

96 column_conf['index'] = index_counter 

97 

98 classdict.setdefault('__columns__', list()).append(column_conf) 

99 

100 # do not add DynamicTable columns to init docval 

101 

102 @classmethod 

103 def post_process(cls, classdict, bases, docval_args, spec): 

104 """Convert classdict['__columns__'] to tuple. 

105 :param classdict: The class dictionary. 

106 :param bases: The list of base classes. 

107 :param docval_args: The dict of docval arguments. 

108 :param spec: The spec for the container class to generate. 

109 """ 

110 # convert classdict['__columns__'] from list to tuple if present 

111 columns = classdict.get('__columns__') 

112 if columns is not None: 112 ↛ exitline 112 didn't return from function 'post_process', because the condition on line 112 was never false

113 classdict['__columns__'] = tuple(columns) 

114 

115 @classmethod 

116 def set_init(cls, classdict, bases, docval_args, not_inherited_fields, name): 

117 if '__columns__' not in classdict: 117 ↛ 118line 117 didn't jump to line 118, because the condition on line 117 was never true

118 return 

119 

120 base_init = classdict.get('__init__') 

121 if base_init is None: # pragma: no cover 

122 raise ValueError("Generated class dictionary is missing base __init__ method.") 

123 

124 # add a specialized docval arg for __init__ for specifying targets for DTRs 

125 docval_args_local = docval_args.copy() 

126 target_tables_dvarg = dict( 

127 name='target_tables', 

128 doc=('dict mapping DynamicTableRegion column name to the table that the DTR points to. The column is ' 

129 'added to the table if it is not already present (i.e., when it is optional).'), 

130 type=dict, 

131 default=None 

132 ) 

133 cls._add_to_docval_args(docval_args_local, target_tables_dvarg, err_if_present=True) 

134 

135 @docval(*docval_args_local, allow_positional=AllowPositional.WARNING) 

136 def __init__(self, **kwargs): 

137 target_tables = popargs('target_tables', kwargs) 

138 base_init(self, **kwargs) 

139 

140 # set target attribute on DTR 

141 if target_tables: 

142 for colname, table in target_tables.items(): 

143 if colname not in self: # column has not yet been added (it is optional) 

144 column_conf = None 

145 for conf in self.__columns__: 

146 if conf['name'] == colname: 

147 column_conf = conf 

148 break 

149 if column_conf is None: 

150 raise ValueError("'%s' is not the name of a predefined column of table %s." 

151 % (colname, self)) 

152 if not column_conf.get('table', False): 

153 raise ValueError("Column '%s' must be a DynamicTableRegion to have a target table." 

154 % colname) 

155 self.add_column(name=column_conf['name'], 

156 description=column_conf['description'], 

157 index=column_conf.get('index', False), 

158 table=True) 

159 if isinstance(self[colname], VectorIndex): 

160 col = self[colname].target 

161 else: 

162 col = self[colname] 

163 col.table = table 

164 

165 classdict['__init__'] = __init__