Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw error on export if required dependent properties are missing #667

Merged
merged 6 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion +file/fillExport.m
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@
if prop.required && not(prop.readonly) && not(isParentRequired)
dependencyCheck{end+1} = sprintf('~isempty(obj.%s) && isempty(obj.%s)', depPropname, name);
warnIfMissingRequiredDependentAttributeStr = ...
sprintf('obj.warnIfRequiredDependencyMissing(''%s'', ''%s'', fullpath)', name, depPropname);
sprintf('obj.throwErrorIfRequiredDependencyMissing(''%s'', ''%s'', fullpath)', name, depPropname);
end
end

Expand Down
12 changes: 5 additions & 7 deletions +tests/+unit/nwbExportTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,8 @@ function testExportWithMissingRequiredDependentProperty(testCase)
nwbFile = testCase.initNwbFile();
fileName = "testExportWithMissingRequiredDependentProperty";

% Should work without warning
testCase.verifyWarningFree( ...
@(nwbObj, filePath) nwbExport(nwbFile, fileName + "_1.nwb") )
% Should work without error
nwbExport(nwbFile, fileName + "_1.nwb")

% Now we add a value to the "general_source_script" property. This
% is a dataset with a required attribute called "file_name".
Expand All @@ -85,10 +84,9 @@ function testExportWithMissingRequiredDependentProperty(testCase)
% property.
nwbFile.general_source_script = '.../nwbExportTest.m';

% Verify that exporting the file issues warning that a required
% property (i.e general_source_script_file_name) is missing

testCase.verifyWarning( ...
% Verify that exporting the file throws an error, stating that a
% required property (i.e general_source_script_file_name) is missing
testCase.verifyError( ...
@(nwbObj, filePath) nwbExport(nwbFile, fileName + "_2.nwb"), ...
'NWB:DependentRequiredPropertyMissing')
end
Expand Down
2 changes: 1 addition & 1 deletion +types/+core/ImageSeries.m
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ function postset_external_file_starting_frame(obj)
obj.warnIfPropertyAttributeNotExported('external_file_starting_frame', 'external_file', fullpath)
end
if ~isempty(obj.external_file) && isempty(obj.external_file_starting_frame)
obj.warnIfRequiredDependencyMissing('external_file_starting_frame', 'external_file', fullpath)
obj.throwErrorIfRequiredDependencyMissing('external_file_starting_frame', 'external_file', fullpath)
end
if ~isempty(obj.format)
if startsWith(class(obj.format), 'types.untyped.')
Expand Down
4 changes: 2 additions & 2 deletions +types/+core/ImagingPlane.m
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ function postset_origin_coords_unit(obj)
obj.warnIfPropertyAttributeNotExported('grid_spacing_unit', 'grid_spacing', fullpath)
end
if ~isempty(obj.grid_spacing) && isempty(obj.grid_spacing_unit)
obj.warnIfRequiredDependencyMissing('grid_spacing_unit', 'grid_spacing', fullpath)
obj.throwErrorIfRequiredDependencyMissing('grid_spacing_unit', 'grid_spacing', fullpath)
end
if ~isempty(obj.imaging_rate)
if startsWith(class(obj.imaging_rate), 'types.untyped.')
Expand Down Expand Up @@ -514,7 +514,7 @@ function postset_origin_coords_unit(obj)
obj.warnIfPropertyAttributeNotExported('origin_coords_unit', 'origin_coords', fullpath)
end
if ~isempty(obj.origin_coords) && isempty(obj.origin_coords_unit)
obj.warnIfRequiredDependencyMissing('origin_coords_unit', 'origin_coords', fullpath)
obj.throwErrorIfRequiredDependencyMissing('origin_coords_unit', 'origin_coords', fullpath)
end
if ~isempty(obj.reference_frame)
if startsWith(class(obj.reference_frame), 'types.untyped.')
Expand Down
26 changes: 13 additions & 13 deletions +types/+core/ImagingRetinotopy.m
Original file line number Diff line number Diff line change
Expand Up @@ -1024,23 +1024,23 @@ function postset_vasculature_image_format(obj)
obj.warnIfPropertyAttributeNotExported('axis_1_power_map_dimension', 'axis_1_power_map', fullpath)
end
if ~isempty(obj.axis_1_power_map) && isempty(obj.axis_1_power_map_dimension)
obj.warnIfRequiredDependencyMissing('axis_1_power_map_dimension', 'axis_1_power_map', fullpath)
obj.throwErrorIfRequiredDependencyMissing('axis_1_power_map_dimension', 'axis_1_power_map', fullpath)
end
if ~isempty(obj.axis_1_power_map) && ~isa(obj.axis_1_power_map, 'types.untyped.SoftLink') && ~isa(obj.axis_1_power_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_1_power_map/field_of_view'], obj.axis_1_power_map_field_of_view, 'forceArray');
elseif isempty(obj.axis_1_power_map) && ~isempty(obj.axis_1_power_map_field_of_view)
obj.warnIfPropertyAttributeNotExported('axis_1_power_map_field_of_view', 'axis_1_power_map', fullpath)
end
if ~isempty(obj.axis_1_power_map) && isempty(obj.axis_1_power_map_field_of_view)
obj.warnIfRequiredDependencyMissing('axis_1_power_map_field_of_view', 'axis_1_power_map', fullpath)
obj.throwErrorIfRequiredDependencyMissing('axis_1_power_map_field_of_view', 'axis_1_power_map', fullpath)
end
if ~isempty(obj.axis_1_power_map) && ~isa(obj.axis_1_power_map, 'types.untyped.SoftLink') && ~isa(obj.axis_1_power_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_1_power_map/unit'], obj.axis_1_power_map_unit);
elseif isempty(obj.axis_1_power_map) && ~isempty(obj.axis_1_power_map_unit)
obj.warnIfPropertyAttributeNotExported('axis_1_power_map_unit', 'axis_1_power_map', fullpath)
end
if ~isempty(obj.axis_1_power_map) && isempty(obj.axis_1_power_map_unit)
obj.warnIfRequiredDependencyMissing('axis_1_power_map_unit', 'axis_1_power_map', fullpath)
obj.throwErrorIfRequiredDependencyMissing('axis_1_power_map_unit', 'axis_1_power_map', fullpath)
end
if startsWith(class(obj.axis_2_phase_map), 'types.untyped.')
refs = obj.axis_2_phase_map.export(fid, [fullpath '/axis_2_phase_map'], refs);
Expand Down Expand Up @@ -1075,23 +1075,23 @@ function postset_vasculature_image_format(obj)
obj.warnIfPropertyAttributeNotExported('axis_2_power_map_dimension', 'axis_2_power_map', fullpath)
end
if ~isempty(obj.axis_2_power_map) && isempty(obj.axis_2_power_map_dimension)
obj.warnIfRequiredDependencyMissing('axis_2_power_map_dimension', 'axis_2_power_map', fullpath)
obj.throwErrorIfRequiredDependencyMissing('axis_2_power_map_dimension', 'axis_2_power_map', fullpath)
end
if ~isempty(obj.axis_2_power_map) && ~isa(obj.axis_2_power_map, 'types.untyped.SoftLink') && ~isa(obj.axis_2_power_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_2_power_map/field_of_view'], obj.axis_2_power_map_field_of_view, 'forceArray');
elseif isempty(obj.axis_2_power_map) && ~isempty(obj.axis_2_power_map_field_of_view)
obj.warnIfPropertyAttributeNotExported('axis_2_power_map_field_of_view', 'axis_2_power_map', fullpath)
end
if ~isempty(obj.axis_2_power_map) && isempty(obj.axis_2_power_map_field_of_view)
obj.warnIfRequiredDependencyMissing('axis_2_power_map_field_of_view', 'axis_2_power_map', fullpath)
obj.throwErrorIfRequiredDependencyMissing('axis_2_power_map_field_of_view', 'axis_2_power_map', fullpath)
end
if ~isempty(obj.axis_2_power_map) && ~isa(obj.axis_2_power_map, 'types.untyped.SoftLink') && ~isa(obj.axis_2_power_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/axis_2_power_map/unit'], obj.axis_2_power_map_unit);
elseif isempty(obj.axis_2_power_map) && ~isempty(obj.axis_2_power_map_unit)
obj.warnIfPropertyAttributeNotExported('axis_2_power_map_unit', 'axis_2_power_map', fullpath)
end
if ~isempty(obj.axis_2_power_map) && isempty(obj.axis_2_power_map_unit)
obj.warnIfRequiredDependencyMissing('axis_2_power_map_unit', 'axis_2_power_map', fullpath)
obj.throwErrorIfRequiredDependencyMissing('axis_2_power_map_unit', 'axis_2_power_map', fullpath)
end
if startsWith(class(obj.axis_descriptions), 'types.untyped.')
refs = obj.axis_descriptions.export(fid, [fullpath '/axis_descriptions'], refs);
Expand All @@ -1111,39 +1111,39 @@ function postset_vasculature_image_format(obj)
obj.warnIfPropertyAttributeNotExported('focal_depth_image_bits_per_pixel', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && isempty(obj.focal_depth_image_bits_per_pixel)
obj.warnIfRequiredDependencyMissing('focal_depth_image_bits_per_pixel', 'focal_depth_image', fullpath)
obj.throwErrorIfRequiredDependencyMissing('focal_depth_image_bits_per_pixel', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && ~isa(obj.focal_depth_image, 'types.untyped.SoftLink') && ~isa(obj.focal_depth_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/focal_depth_image/dimension'], obj.focal_depth_image_dimension, 'forceArray');
elseif isempty(obj.focal_depth_image) && ~isempty(obj.focal_depth_image_dimension)
obj.warnIfPropertyAttributeNotExported('focal_depth_image_dimension', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && isempty(obj.focal_depth_image_dimension)
obj.warnIfRequiredDependencyMissing('focal_depth_image_dimension', 'focal_depth_image', fullpath)
obj.throwErrorIfRequiredDependencyMissing('focal_depth_image_dimension', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && ~isa(obj.focal_depth_image, 'types.untyped.SoftLink') && ~isa(obj.focal_depth_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/focal_depth_image/field_of_view'], obj.focal_depth_image_field_of_view, 'forceArray');
elseif isempty(obj.focal_depth_image) && ~isempty(obj.focal_depth_image_field_of_view)
obj.warnIfPropertyAttributeNotExported('focal_depth_image_field_of_view', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && isempty(obj.focal_depth_image_field_of_view)
obj.warnIfRequiredDependencyMissing('focal_depth_image_field_of_view', 'focal_depth_image', fullpath)
obj.throwErrorIfRequiredDependencyMissing('focal_depth_image_field_of_view', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && ~isa(obj.focal_depth_image, 'types.untyped.SoftLink') && ~isa(obj.focal_depth_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/focal_depth_image/focal_depth'], obj.focal_depth_image_focal_depth);
elseif isempty(obj.focal_depth_image) && ~isempty(obj.focal_depth_image_focal_depth)
obj.warnIfPropertyAttributeNotExported('focal_depth_image_focal_depth', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && isempty(obj.focal_depth_image_focal_depth)
obj.warnIfRequiredDependencyMissing('focal_depth_image_focal_depth', 'focal_depth_image', fullpath)
obj.throwErrorIfRequiredDependencyMissing('focal_depth_image_focal_depth', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && ~isa(obj.focal_depth_image, 'types.untyped.SoftLink') && ~isa(obj.focal_depth_image, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/focal_depth_image/format'], obj.focal_depth_image_format);
elseif isempty(obj.focal_depth_image) && ~isempty(obj.focal_depth_image_format)
obj.warnIfPropertyAttributeNotExported('focal_depth_image_format', 'focal_depth_image', fullpath)
end
if ~isempty(obj.focal_depth_image) && isempty(obj.focal_depth_image_format)
obj.warnIfRequiredDependencyMissing('focal_depth_image_format', 'focal_depth_image', fullpath)
obj.throwErrorIfRequiredDependencyMissing('focal_depth_image_format', 'focal_depth_image', fullpath)
end
if ~isempty(obj.sign_map)
if startsWith(class(obj.sign_map), 'types.untyped.')
Expand All @@ -1158,15 +1158,15 @@ function postset_vasculature_image_format(obj)
obj.warnIfPropertyAttributeNotExported('sign_map_dimension', 'sign_map', fullpath)
end
if ~isempty(obj.sign_map) && isempty(obj.sign_map_dimension)
obj.warnIfRequiredDependencyMissing('sign_map_dimension', 'sign_map', fullpath)
obj.throwErrorIfRequiredDependencyMissing('sign_map_dimension', 'sign_map', fullpath)
end
if ~isempty(obj.sign_map) && ~isa(obj.sign_map, 'types.untyped.SoftLink') && ~isa(obj.sign_map, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/sign_map/field_of_view'], obj.sign_map_field_of_view, 'forceArray');
elseif isempty(obj.sign_map) && ~isempty(obj.sign_map_field_of_view)
obj.warnIfPropertyAttributeNotExported('sign_map_field_of_view', 'sign_map', fullpath)
end
if ~isempty(obj.sign_map) && isempty(obj.sign_map_field_of_view)
obj.warnIfRequiredDependencyMissing('sign_map_field_of_view', 'sign_map', fullpath)
obj.throwErrorIfRequiredDependencyMissing('sign_map_field_of_view', 'sign_map', fullpath)
end
if startsWith(class(obj.vasculature_image), 'types.untyped.')
refs = obj.vasculature_image.export(fid, [fullpath '/vasculature_image'], refs);
Expand Down
2 changes: 1 addition & 1 deletion +types/+core/NWBFile.m
Original file line number Diff line number Diff line change
Expand Up @@ -1155,7 +1155,7 @@ function postset_general_source_script_file_name(obj)
obj.warnIfPropertyAttributeNotExported('general_source_script_file_name', 'general_source_script', fullpath)
end
if ~isempty(obj.general_source_script) && isempty(obj.general_source_script_file_name)
obj.warnIfRequiredDependencyMissing('general_source_script_file_name', 'general_source_script', fullpath)
obj.throwErrorIfRequiredDependencyMissing('general_source_script_file_name', 'general_source_script', fullpath)
end
io.writeGroup(fid, [fullpath '/general']);
if ~isempty(obj.general_stimulus)
Expand Down
2 changes: 1 addition & 1 deletion +types/+core/TimeSeries.m
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ function postset_starting_time_rate(obj)
obj.warnIfPropertyAttributeNotExported('starting_time_rate', 'starting_time', fullpath)
end
if ~isempty(obj.starting_time) && isempty(obj.starting_time_rate)
obj.warnIfRequiredDependencyMissing('starting_time_rate', 'starting_time', fullpath)
obj.throwErrorIfRequiredDependencyMissing('starting_time_rate', 'starting_time', fullpath)
end
if ~isempty(obj.starting_time) && ~isa(obj.starting_time, 'types.untyped.SoftLink') && ~isa(obj.starting_time, 'types.untyped.ExternalLink')
io.writeAttribute(fid, [fullpath '/starting_time/unit'], obj.starting_time_unit);
Expand Down
10 changes: 4 additions & 6 deletions +types/+untyped/MetaClass.m
Original file line number Diff line number Diff line change
Expand Up @@ -186,17 +186,15 @@ function displayWarningIfMissingRequiredProps(obj)
end
end

function warnIfRequiredDependencyMissing(obj, propName, depPropName, fullpath)
function throwErrorIfRequiredDependencyMissing(obj, propName, depPropName, fullpath)
if isempty(fullpath); fullpath = 'root'; end
warnState = warning('backtrace', 'off');
cleanupObj = onCleanup(@(s) warning(warnState));
warningId = 'NWB:DependentRequiredPropertyMissing';
warningMessage = sprintf( [ ...
errorId = 'NWB:DependentRequiredPropertyMissing';
errorMessage = sprintf( [ ...
'The property "%s" of type "%s" in file location "%s" is ' ...
'required when the property "%s" is set. Please add a value ' ...
'for "%s" and re-export.'], ...
propName, class(obj), fullpath, depPropName, propName);
warning(warningId, warningMessage) %#ok<SPWRN>
error(errorId, errorMessage) %#ok<SPERR>
end

function throwErrorIfMissingRequiredProps(obj, fullpath)
Expand Down