Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ CRITICAL: # All the fields under CRITICAL will be required for dandi validate t
- check_subject_age
- check_subject_proper_age_range
- check_session_id_no_slashes
- check_nwb_schema_version_official_release
BEST_PRACTICE_VIOLATION:
- check_data_orientation # not 100% accurate, so need to deelevate from CRITICAL to skip it in dandi validate
2 changes: 2 additions & 0 deletions src/nwbinspector/checks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
check_experimenter_form,
check_institution,
check_keywords,
check_nwb_schema_version_official_release,
check_processing_module_name,
check_session_id_no_slashes,
check_session_start_time_future_date,
Expand Down Expand Up @@ -108,6 +109,7 @@
"check_large_dataset_compression",
"check_keywords",
"check_institution",
"check_nwb_schema_version_official_release",
"check_subject_age",
"check_subject_sex",
"check_subject_exists",
Expand Down
48 changes: 48 additions & 0 deletions src/nwbinspector/checks/_nwbfile_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,54 @@
PROCESSING_MODULE_CONFIG = ["ophys", "ecephys", "icephys", "behavior", "misc", "ogen", "retinotopy"]


@register_check(importance=Importance.BEST_PRACTICE_VIOLATION, neurodata_type=NWBFile)
def check_nwb_schema_version_official_release(nwbfile: NWBFile) -> Optional[InspectorMessage]:
"""
Check if the NWB schema version used is an official release version.
Unofficial versions include development versions (e.g., '2.8.0-dev'),
pre-release versions (e.g., '2.8.0-alpha', '2.8.0-beta', '2.8.0-rc1'),
or any version containing non-standard suffixes.
Best Practice: Use only official, released versions of the NWB schema for
production data to ensure compatibility and reproducibility.
"""
try:
import pynwb

# Get the schema version from the namespace catalog
manager = pynwb.get_manager()
core_namespace = manager.namespace_catalog.get_namespace("core")
schema_version = core_namespace.version
Comment on lines +36 to +41
Copy link
Contributor

@stephprince stephprince Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check uses the loaded namespace from PyNWB to check the schema version validity and not the nwbfile argument provided. I think it would be more direct to check the nwb_version attribute of the nwbfile provided. Thoughts?


# Define pattern for official release versions (semantic versioning)
# Official versions should match pattern: MAJOR.MINOR.PATCH (e.g., "2.8.0")
official_version_pattern = r"^\d+\.\d+\.\d+$"

# Check if the version matches the official pattern
if not re.match(official_version_pattern, schema_version):
# Define common unofficial version patterns for better error messages
if re.match(r"^\d+\.\d+\.\d+-(dev|alpha|beta|rc\d*|pre).*$", schema_version):
version_type = "development or pre-release"
else:
version_type = "non-standard"

return InspectorMessage(
message=(
f"The NWB schema version '{schema_version}' appears to be a {version_type} version. "
f"For production data, it is recommended to use only official release versions "
f"(e.g., '2.8.0') to ensure compatibility and reproducibility."
)
)

except Exception as exception:
# If we can't determine the schema version, don't report an error
# This could happen with very old versions of PyNWB or in unusual setups
return None

return None


@register_check(importance=Importance.BEST_PRACTICE_SUGGESTION, neurodata_type=NWBFile)
def check_session_start_time_old_date(nwbfile: NWBFile) -> Optional[InspectorMessage]:
"""
Expand Down
1 change: 1 addition & 0 deletions tests/test_check_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def test_load_config(self):
"check_subject_age",
"check_subject_proper_age_range",
"check_session_id_no_slashes",
"check_nwb_schema_version_official_release",
],
BEST_PRACTICE_VIOLATION=[
"check_data_orientation",
Expand Down
73 changes: 73 additions & 0 deletions tests/unit_tests/test_nwbfile_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
check_experimenter_form,
check_institution,
check_keywords,
check_nwb_schema_version_official_release,
check_processing_module_name,
check_session_id_no_slashes,
check_session_start_time_future_date,
Expand Down Expand Up @@ -619,3 +620,75 @@ def test_check_subject_id_with_slashes():
object_name="subject",
location="/general/subject",
)


def test_check_nwb_schema_version_official_release_pass():
"""Test that official schema versions pass the check."""
# The current schema version (2.8.0) should be considered official
assert check_nwb_schema_version_official_release(minimal_nwbfile) is None


def test_check_nwb_schema_version_official_release_fail():
"""Test that unofficial schema versions fail the check."""
# We need to mock the schema version to test different scenarios
import unittest.mock

# Test development version
with unittest.mock.patch("pynwb.get_manager") as mock_manager:
Comment on lines +634 to +637
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comment on the check, but I think these tests could also be updated/simplified using mock nwbfiles with the nwb_version attribute manually changed instead of patching the namespace manager

mock_catalog = unittest.mock.MagicMock()
mock_namespace = unittest.mock.MagicMock()
mock_namespace.version = "2.8.0-dev"
mock_catalog.get_namespace.return_value = mock_namespace
mock_manager.return_value.namespace_catalog = mock_catalog

result = check_nwb_schema_version_official_release(minimal_nwbfile)
assert result == InspectorMessage(
message=(
"The NWB schema version '2.8.0-dev' appears to be a development or pre-release version. "
"For production data, it is recommended to use only official release versions "
"(e.g., '2.8.0') to ensure compatibility and reproducibility."
),
importance=Importance.BEST_PRACTICE_VIOLATION,
check_function_name="check_nwb_schema_version_official_release",
object_type="NWBFile",
object_name="root",
location="/",
)


def test_check_nwb_schema_version_pre_release_versions():
"""Test that various pre-release versions are detected."""
import unittest.mock

test_cases = [
("2.8.0-alpha", "development or pre-release"),
("2.8.0-beta", "development or pre-release"),
("2.8.0-rc1", "development or pre-release"),
("2.8.0-dev", "development or pre-release"),
("2.8.0-pre", "development or pre-release"),
("2.8.0.custom", "non-standard"),
("v2.8.0", "non-standard"),
]

for version, version_type in test_cases:
with unittest.mock.patch("pynwb.get_manager") as mock_manager:
mock_catalog = unittest.mock.MagicMock()
mock_namespace = unittest.mock.MagicMock()
mock_namespace.version = version
mock_catalog.get_namespace.return_value = mock_namespace
mock_manager.return_value.namespace_catalog = mock_catalog

result = check_nwb_schema_version_official_release(minimal_nwbfile)
assert result is not None
assert version in result.message
assert version_type in result.message


def test_check_nwb_schema_version_exception_handling():
"""Test that exceptions in schema version detection are handled gracefully."""
import unittest.mock

with unittest.mock.patch("pynwb.get_manager", side_effect=Exception("Mock error")):
# Should return None when unable to determine schema version
result = check_nwb_schema_version_official_release(minimal_nwbfile)
assert result is None
Loading