Skip to content

Commit cb7eae5

Browse files
authored
Merge branch 'main' into update-intro-tutorial
2 parents b12b6a1 + f73c347 commit cb7eae5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1721
-324
lines changed

+file/fillValidators.m

Lines changed: 84 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
nm = propnames{i};
55
prop = props(nm);
66

7-
if (isa(prop, 'file.Attribute') || isa(prop, 'file.Dataset')) ...
8-
&& prop.readonly && ~isempty(prop.value)
7+
if file.internal.isPropertyReadonly(prop) && ~isempty(prop.value)
98
% Need to add a validator for inherited and readonly properties. In
109
% the superclass these properties might not be read-only and due to
1110
% inheritance rules in MATLAB it is not possible to change property
@@ -16,14 +15,10 @@
1615
else
1716
continue
1817
end
19-
elseif isa(prop, 'file.Link') && ~prop.isConstrainedSet
20-
validationBody = fillLinkValidation(nm, prop, namespaceReg);
21-
else
22-
if startsWith(class(prop), 'file.')
23-
validationBody = fillUnitValidation(nm, prop, namespaceReg);
24-
else % primitive type
25-
validationBody = fillDtypeValidation(nm, prop, namespaceReg);
26-
end
18+
elseif startsWith(class(prop), 'file.') % HDMF primitive type
19+
validationBody = fillUnitValidation(nm, prop, namespaceReg);
20+
else % MATLAB primitive type
21+
validationBody = fillDtypeValidation(nm, prop, namespaceReg);
2722
end
2823

2924
headerStr = ['function val = validate_' nm '(obj, val)'];
@@ -33,7 +28,7 @@
3328
functionStr = strjoin({headerStr ...
3429
file.addSpaces(strtrim(validationBody), 4) 'end'}, newline);
3530
end
36-
validationStr = [validationStr newline functionStr];
31+
validationStr = strcat(validationStr, [newline, functionStr]);
3732
end
3833
end
3934

@@ -61,9 +56,10 @@
6156
fillDtypeValidation(name, prop.dtype, namespaceReg)...
6257
fillDimensionValidation(name, prop.shape)...
6358
}, newline);
64-
else % Link
65-
fullTypeName = namespaceReg.getFullClassName(prop.type);
66-
unitValidationStr = fillDtypeValidation(name, fullTypeName, namespaceReg);
59+
elseif isa(prop, 'file.Link') % Link
60+
unitValidationStr = fillLinkValidation(name, prop, namespaceReg);
61+
else
62+
error('NWB:InternalError', 'Unexpected property specification')
6763
end
6864
end
6965

@@ -77,13 +73,18 @@
7773
namedprops = struct();
7874
constraints = {};
7975
if isempty(prop.type)
76+
warning('NWB:FillValidators:ValidationNotImplemented', ...
77+
['Detected a group-based data type (''%s'') with an untyped ', ...
78+
'subgroup. Validation logic for checking shape or quantity ', ...
79+
'of nested elements is not implemented '], prop.name)
80+
8081
%% process datasets
8182
% if type, check if constrained
8283
% if constrained, add to constr
8384
% otherwise, check type once
8485
% otherwise, check dtype
8586
for iDataset = 1:length(prop.datasets)
86-
dataset = p.datasets(iDataset);
87+
dataset = prop.datasets(iDataset);
8788

8889
if isempty(dataset.type)
8990
namedprops.(dataset.name) = dataset.dtype;
@@ -104,8 +105,8 @@
104105
% otherwise, error. This shouldn't happen.
105106
for iSubGroup = 1:length(prop.subgroups)
106107
subGroup = prop.subgroups(iSubGroup);
108+
assert(~isempty(subGroup.type), 'Unsupported case with two nested untyped groups');
107109
subGroupFullName = namespaceReg.getFullClassName(subGroup.type);
108-
assert(~isempty(subGroup.type), 'Weird case with two untyped groups');
109110

110111
if isempty(subGroup.name)
111112
constraints{end+1} = subGroupFullName;
@@ -124,7 +125,7 @@
124125
for iLink = 1:length(prop.links)
125126
Link = prop.links(iLink);
126127
namespace = namespaceReg.getNamespace(Link.type);
127-
namedprops.(Link.name) = ['types.', namespace, '.', Link.type];
128+
namedprops.(Link.name) = char(matnwb.common.composeFullClassName(namespace.name, Link.type));
128129
end
129130
else
130131
constraints{end+1} = namespaceReg.getFullClassName(prop.type);
@@ -152,29 +153,52 @@
152153
end
153154

154155
function unitValidationStr = fillDatasetValidation(name, prop, namespaceReg)
156+
155157
unitValidationStr = '';
156-
if isempty(prop.type)
157-
unitValidationStr = strjoin({unitValidationStr...
158-
fillDtypeValidation(name, prop.dtype, namespaceReg)...
159-
fillDimensionValidation(name, prop.shape)...
160-
}, newline);
161-
elseif prop.isConstrainedSet
158+
validationLines = {};
159+
160+
if prop.isConstrainedSet
162161
fullname = getFullClassName(namespaceReg, prop.type, name);
163162
if isempty(fullname)
164163
return
165164
end
166165

167-
unitValidationStr = strjoin({unitValidationStr...
168-
['constrained = { ''' fullname ''' };']...
169-
['types.util.checkSet(''' name ''', struct(), constrained, val);']...
170-
}, newline);
166+
validationLines = [validationLines ...
167+
{['constrained = { ''' fullname ''' };']} ...
168+
{['types.util.checkSet(''' name ''', struct(), constrained, val);']} ...
169+
];
171170
else
172-
fullname = getFullClassName(namespaceReg, prop.type, name);
173-
if isempty(fullname)
174-
return
171+
if ~isempty(prop.type) % Dataset class like e.g. VectorData
172+
fullname = getFullClassName(namespaceReg, prop.type, name);
173+
if isempty(fullname)
174+
return
175+
end
176+
177+
validationLines = [validationLines ...
178+
{fillTypeValidation(name, fullname)}];
179+
180+
datasetValidationLines = [...
181+
{fillDtypeValidation(name, prop.dtype, namespaceReg)}, ...
182+
{fillDimensionValidation(name, prop.shape)} ];
183+
184+
datasetValidationLines(cellfun('isempty', datasetValidationLines)) = [];
185+
datasetValidationLines = indentLines(datasetValidationLines, 4);
186+
187+
if ~isempty(datasetValidationLines)
188+
validationLines{end+1} = 'if ~isempty(val)';
189+
validationLines{end+1} = ' [val, originalVal] = types.util.unwrapValue(val);';
190+
validationLines = [validationLines, datasetValidationLines];
191+
validationLines{end+1} = ' val = types.util.rewrapValue(val, originalVal);';
192+
validationLines{end+1} = 'end';
193+
end
194+
else
195+
validationLines = [validationLines ...
196+
{fillDtypeValidation(name, prop.dtype, namespaceReg)} ...
197+
{fillDimensionValidation(name, prop.shape)} ];
175198
end
176-
unitValidationStr = [unitValidationStr newline fillDtypeValidation(name, fullname, namespaceReg)];
177199
end
200+
validationLines(cellfun('isempty', validationLines)) = [];
201+
unitValidationStr = strjoin(validationLines, newline);
178202
end
179203

180204
function validationStr = fillLinkValidation(name, prop, namespaceReg)
@@ -186,6 +210,10 @@
186210
end
187211

188212
function fdvstr = fillDimensionValidation(name, shape)
213+
214+
if isnumeric(shape) && isnan(shape) % Any shape is allowed
215+
fdvstr = ''; return
216+
end
189217

190218
if isnumeric(shape) && isnan(shape) % Any shape is allowed
191219
fdvstr = ''; return
@@ -213,18 +241,19 @@
213241
fdvstr = sprintf('types.util.validateShape(''%s'', %s, val)', name, validShapeStr);
214242
end
215243

244+
function validationStr = fillTypeValidation(name, type)
245+
validationStr = ['types.util.checkType(''' name ''', ''' type ''', val);'];
246+
end
247+
216248
%NOTE: can return empty strings
217249
function fdvstr = fillDtypeValidation(name, type, namespaceReg)
218250
if isstruct(type)
219251
fnames = fieldnames(type);
220252
fdvstr = strjoin({...
221-
'if isempty(val) || isa(val, ''types.untyped.DataStub'')'...
222-
' return;'...
223-
'end'...
224-
'if ~istable(val) && ~isstruct(val) && ~isa(val, ''containers.Map'')'...
225-
[' error(''NWB:Type:InvalidPropertyType'', ''Property `' name '` must be a table, struct, or containers.Map.'');']...
226-
'end'...
227-
'vprops = struct();'...
253+
'if isempty(val)'...
254+
' % skip validation for empty values'...
255+
'else'...
256+
' vprops = struct();'...
228257
}, newline);
229258
vprops = cell(length(fnames),1);
230259
for i=1:length(fnames)
@@ -234,10 +263,11 @@
234263
else
235264
typeval = type.(nm);
236265
end
237-
vprops{i} = ['vprops.' nm ' = ''' typeval ''';'];
266+
vprops{i} = [' vprops.' nm ' = ''' typeval ''';'];
238267
end
239268
fdvstr = [fdvstr, newline, strjoin(vprops, newline), newline, ...
240-
'val = types.util.checkDtype(''' name ''', vprops, val);'];
269+
' val = types.util.checkDtype(''' name ''', vprops, val);' ...
270+
newline, 'end'];
241271
elseif isReferenceType(type)
242272
fdvstr = fillReferenceTypeValidation(name, type, namespaceReg);
243273
else
@@ -337,3 +367,17 @@
337367
end
338368
fullReferenceClassName = ['types.untyped.' referenceClassName];
339369
end
370+
371+
function indentedLines = indentLines(lines, numIndents)
372+
if iscell(lines)
373+
indentedLines = cellfun(@(c) indentSingle(c, numIndents), lines);
374+
else
375+
indentedLines = indentSingle(lines, numIndents);
376+
end
377+
378+
function indentedLine = indentSingle(line, numIndents)
379+
splitLine = split(line, newline);
380+
splitLine = strcat({repmat(' ', 1, numIndents)}, splitLine);
381+
indentedLine = join(splitLine, newline);
382+
end
383+
end

+file/processClass.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
if ~isKey(pregen, nodename)
1818

1919
spec.internal.resolveInheritedFields(node, branch(iAncestor+1:end))
20+
spec.internal.expandFieldsInheritedByInclusion(node)
2021

2122
switch node('class_type')
2223
case 'groups'

+io/getNeurodataTypeInfo.m

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
function typeInfo = getNeurodataTypeInfo(attributeInfo)
2+
% getNeurodataTypeInfo - Get neurodata type info from attribute info structure
3+
%
4+
% Syntax:
5+
% typeInfo = io.getNeurodataTypeInfo(attributeInfo)
6+
%
7+
% Input Arguments:
8+
% attributeInfo - A struct containing attributes information from which to
9+
% extract neurodata type information.
10+
%
11+
% Output Arguments:
12+
% typeInfo - A struct containing 'namespace', 'name', and 'typename'
13+
% fields describing the neurodata type. Fields are empty
14+
% character vectors if neurodata type info is not present.
15+
16+
% Todo: return empty structure instead. Requires updating functions that use output
17+
typeInfo = struct('namespace', '', 'name', '', 'typename', '');
18+
19+
if isempty(attributeInfo)
20+
return
21+
end
22+
23+
names = {attributeInfo.Name};
24+
25+
% Get neurodata_type if present
26+
typeDefMask = strcmp(names, 'neurodata_type');
27+
hasTypeDef = any(typeDefMask);
28+
if hasTypeDef
29+
typeDef = attributeInfo(typeDefMask).Value;
30+
if iscellstr(typeDef) %#ok<ISCLSTR>
31+
typeDef = typeDef{1};
32+
end
33+
typeInfo.name = typeDef;
34+
end
35+
36+
% Get namespace if present
37+
namespaceMask = strcmp(names, 'namespace');
38+
hasNamespace = any(namespaceMask);
39+
if hasNamespace
40+
namespace = attributeInfo(namespaceMask).Value;
41+
if iscellstr(namespace) %#ok<ISCLSTR>
42+
namespace = namespace{1};
43+
end
44+
typeInfo.namespace = namespace;
45+
end
46+
47+
% Get full classname given a namespace and a neurodata type
48+
if hasTypeDef && hasNamespace
49+
typeInfo.typename = matnwb.common.composeFullClassName(...
50+
typeInfo.namespace, typeInfo.name);
51+
end
52+
end

+io/parseAttributes.m

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,39 @@
11
function [args, type] = parseAttributes(filename, attributes, context, Blacklist)
2-
%typename is the type of name if it exists. Empty string otherwise
3-
%args is a containers.Map of all valid attributes
2+
% parseAttributes - Parse an attribute info structure
3+
%
4+
% Syntax:
5+
% [args, type] = io.parseAttributes(filename, attributes, context, Blacklist)
6+
% This function parses a given attribute info structure and returns a
7+
% containers.Map of valid attributes along with neurodata type info if it
8+
% exists.
9+
%
10+
% Input Arguments:
11+
% filename - The name of the file containing attributes.
12+
% attributes - The attributes to be parsed.
13+
% context - The context (h5 location) in which the attributes are located.
14+
% Blacklist - A list of attributes to be excluded from the parsing.
15+
%
16+
% Output Arguments:
17+
% args - A containers.Map of all valid attributes.
18+
% type - A structure with type information (see io.getNeurodataTypeInfo)
19+
%
20+
% See also: io.getNeurodataTypeInfo
21+
422
args = containers.Map;
5-
type = struct('namespace', '', 'name', '', 'typename', '');
23+
type = io.getNeurodataTypeInfo(attributes);
24+
625
if isempty(attributes)
726
return;
827
end
9-
names = {attributes.Name};
10-
11-
typeDefMask = strcmp(names, 'neurodata_type');
12-
hasTypeDef = any(typeDefMask);
13-
if hasTypeDef
14-
typeDef = attributes(typeDefMask).Value;
15-
if iscellstr(typeDef)
16-
typeDef = typeDef{1};
17-
end
18-
type.name = typeDef;
19-
end
2028

21-
namespaceMask = strcmp(names, 'namespace');
22-
hasNamespace = any(namespaceMask);
23-
if hasNamespace
24-
namespace = attributes(namespaceMask).Value;
25-
if iscellstr(namespace)
26-
namespace = namespace{1};
27-
end
28-
type.namespace = namespace;
29-
end
29+
names = {attributes.Name};
3030

31-
if hasTypeDef && hasNamespace
32-
validNamespace = misc.str2validName(type.namespace);
33-
validName = misc.str2validName(type.name);
34-
type.typename = ['types.' validNamespace '.' validName];
35-
end
31+
% We already got type information (if present), so we add type-specific
32+
% attributes to the blacklist before parsing the rest of the attribute list
33+
Blacklist.attributes = [Blacklist.attributes, {'neurodata_type', 'namespace'}];
3634

3735
blacklistMask = ismember(names, Blacklist.attributes);
38-
deleteMask = typeDefMask | namespaceMask | blacklistMask;
39-
attributes(deleteMask) = [];
36+
attributes(blacklistMask) = [];
4037
for i=1:length(attributes)
4138
attr = attributes(i);
4239

+io/parseGroup.m

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,9 @@
5353
S = h5info(filename, link.Value{1});
5454
% Suggested improvement: Use info structure if Link is located in the group (or
5555
% subgroup) which is currently being parsed.
56-
if ismember('neurodata_type', {S.Attributes.Name})
57-
namespace = h5readatt(filename, link.Value{1}, 'namespace');
58-
neurodataType = h5readatt(filename, link.Value{1}, 'neurodata_type');
59-
fullTargetTypeName = matnwb.common.composeFullClassName(namespace, neurodataType);
60-
else
61-
fullTargetTypeName = '';
62-
end
56+
57+
typeInfo = io.getNeurodataTypeInfo(S.Attributes);
58+
fullTargetTypeName = typeInfo.typename;
6359

6460
lnk = types.untyped.SoftLink(link.Value{1}, fullTargetTypeName);
6561
case 'external link'

+matnwb/+common/composeFullClassName.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
namespaceName = strrep(namespaceName, '-', '_');
1010
end
1111

12+
for i = 1:numel(namespaceName)
13+
namespaceName{i} = misc.str2validName(namespaceName{i});
14+
neurodataType{i} = misc.str2validName(neurodataType{i});
15+
end
16+
1217
fullClassName = compose("types.%s.%s", namespaceName, neurodataType);
1318
fullClassName = transpose(fullClassName); % Return as row vector
1419
end

0 commit comments

Comments
 (0)