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
« 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
8@register_map(DynamicTable)
9class DynamicTableMap(ObjectMapper):
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)
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
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
48class DynamicTableGenerator(CustomClassGenerator):
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)
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
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
98 classdict.setdefault('__columns__', list()).append(column_conf)
100 # do not add DynamicTable columns to init docval
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)
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
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.")
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)
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)
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
165 classdict['__init__'] = __init__