diff --git a/dandi/files/bids.py b/dandi/files/bids.py index a441e5999..a6be86b45 100644 --- a/dandi/files/bids.py +++ b/dandi/files/bids.py @@ -13,10 +13,15 @@ from .bases import GenericAsset, LocalFileAsset, NWBAsset from .zarr import ZarrAsset -from ..consts import ZARR_MIME_TYPE +from ..consts import ZARR_MIME_TYPE, dandiset_metadata_file from ..metadata.core import add_common_metadata, prepare_metadata from ..misctypes import Digest -from ..validate_types import ValidationResult +from ..validate_types import ( + ORIGIN_VALIDATION_DANDI_LAYOUT, + Scope, + Severity, + ValidationResult, +) BIDS_ASSET_ERRORS = ("BIDS.NON_BIDS_PATH_PLACEHOLDER",) BIDS_DATASET_ERRORS = ("BIDS.MANDATORY_FILE_MISSING_PLACEHOLDER",) @@ -114,6 +119,39 @@ def _validate(self) -> None: # deno-compiled BIDS validator self._dataset_errors = bids_validate(self.bids_root) + # Add HINT for dandiset.yaml errors + # If any error is about the dandiset metadata file, add a HINT + # suggesting to add it to .bidsignore + additional_hints = [] + for result in self._dataset_errors: + is_dandiset_yaml_error = ( + result.path is not None + and result.path.relative_to(self.bids_root).as_posix() + == dandiset_metadata_file + ) + if is_dandiset_yaml_error: + hint = ValidationResult( + id="DANDI.BIDSIGNORE_DANDISET_YAML", + origin=ORIGIN_VALIDATION_DANDI_LAYOUT, + scope=Scope.DATASET, + origin_result=result, + severity=Severity.HINT, + dandiset_path=result.dandiset_path, + dataset_path=result.dataset_path, + path=result.path, + message=( + f"Consider creating or updating a `.bidsignore` file " + f"in the root of your BIDS dataset to ignore " + f"`{dandiset_metadata_file}`. " + f"Add the following line to `.bidsignore`:\n" + f"{dandiset_metadata_file}" + ), + ) + additional_hints.append(hint) + + # Add hints to the dataset errors + self._dataset_errors.extend(additional_hints) + # Categorized validation results related to individual assets by the # path of the asset in the BIDS dataset self._asset_errors = defaultdict(list) diff --git a/dandi/tests/test_validate.py b/dandi/tests/test_validate.py index 5b240eff8..703de8730 100644 --- a/dandi/tests/test_validate.py +++ b/dandi/tests/test_validate.py @@ -114,11 +114,30 @@ def mock_bids_validate(*args: Any, **kwargs: Any) -> list[ValidationResult]: assert err.dataset_path is not None assert err.path.relative_to(err.dataset_path).as_posix() == dandiset_metadata_file + # The error message should be the original BIDS error, not modified assert err.message is not None - assert err.message.startswith( - f"The dandiset metadata file, `{dandiset_metadata_file}`, is not a part of " - f"BIDS specification." + # We just check that it's about dandiset.yaml, not checking exact message + # since it comes from BIDS validator + + # Check that there is also a HINT about .bidsignore + validation_hints = [ + r + for r in validation_results + if r.severity is not None and r.severity == Severity.HINT + ] + + # Assert that there is at least one hint + assert len(validation_hints) >= 1 + + # Find the hint about .bidsignore for dandiset.yaml + bidsignore_hint = next( + (h for h in validation_hints if h.id == "DANDI.BIDSIGNORE_DANDISET_YAML"), + None, ) + assert bidsignore_hint is not None + assert bidsignore_hint.message is not None + assert ".bidsignore" in bidsignore_hint.message + assert dandiset_metadata_file in bidsignore_hint.message def test_validate_bids_onefile(bids_error_examples: Path, tmp_path: Path) -> None: diff --git a/dandi/validate.py b/dandi/validate.py index e67dfb383..3b4dae26f 100644 --- a/dandi/validate.py +++ b/dandi/validate.py @@ -186,22 +186,6 @@ def validate( ): r_id = id(r) if r_id not in df_result_ids: - # If the error is about the dandiset metadata file, modify - # the message in the validation to give the context of DANDI - if ( - r.path is not None - and r.dataset_path is not None - and r.path.relative_to(r.dataset_path).as_posix() - == dandiset_metadata_file - ): - r.message = ( - f"The dandiset metadata file, `{dandiset_metadata_file}`, " - f"is not a part of BIDS specification. Please include a " - f"`.bidsignore` file with specification to ignore the " - f"metadata file in your dataset. For more details, see " - f"https://github.com/bids-standard/bids-specification/" - f"issues/131#issuecomment-461060166." - ) df_results.append(r) df_result_ids.add(r_id) yield r