Skip to content

Commit d98fcc5

Browse files
committed
implemented as functional
1 parent a4fa77e commit d98fcc5

File tree

3 files changed

+52
-61
lines changed

3 files changed

+52
-61
lines changed

nwbinspector/nwbinspector.py

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
)
3030
from .register_checks import InspectorMessage, Importance
3131
from .tools import get_s3_urls_and_dandi_paths
32-
from .utils import FilePathType, PathType, OptionalListOfStrings
32+
from .utils import FilePathType, PathType, OptionalListOfStrings, robust_s3_read
3333

3434
INTERNAL_CONFIGS = dict(dandi=Path(__file__).parent / "internal_configs" / "dandi.inspector_config.yaml")
3535

@@ -462,51 +462,30 @@ def inspect_nwb(
462462
filterwarnings(action="ignore", message="No cached namespaces found in .*")
463463
filterwarnings(action="ignore", message="Ignoring cached namespace .*")
464464

465-
def _collect_all_messages(
466-
nwbfile_path: FilePathType, checks: list, driver: Optional[str] = None, skip_validate: bool = False
467-
):
468-
with pynwb.NWBHDF5IO(path=nwbfile_path, mode="r", load_namespaces=True, driver=driver) as io:
469-
if not skip_validate:
470-
validation_errors = pynwb.validate(io=io)
471-
for validation_error in validation_errors:
472-
yield InspectorMessage(
473-
message=validation_error.reason,
474-
importance=Importance.PYNWB_VALIDATION,
475-
check_function_name=validation_error.name,
476-
location=validation_error.location,
477-
file_path=nwbfile_path,
478-
)
479-
480-
try:
481-
nwbfile = io.read()
482-
for inspector_message in run_checks(nwbfile=nwbfile, checks=checks):
483-
inspector_message.file_path = nwbfile_path
484-
yield inspector_message
485-
except OSError as ex:
486-
raise ex # propagate outside private
487-
except Exception as ex:
465+
with pynwb.NWBHDF5IO(path=nwbfile_path, mode="r", load_namespaces=True, driver=driver) as io:
466+
if not skip_validate:
467+
validation_errors = pynwb.validate(io=io)
468+
for validation_error in validation_errors:
488469
yield InspectorMessage(
489-
message=traceback.format_exc(),
490-
importance=Importance.ERROR,
491-
check_function_name=f"During io.read() - {type(ex)}: {str(ex)}",
470+
message=validation_error.reason,
471+
importance=Importance.PYNWB_VALIDATION,
472+
check_function_name=validation_error.name,
473+
location=validation_error.location,
492474
file_path=nwbfile_path,
493475
)
494476

495-
if driver != "ros3":
496-
return _collect_all_messages(
497-
nwbfile_path=nwbfile_path, checks=checks, driver=driver, skip_validate=skip_validate
498-
)
499-
else:
500-
retries = 0
501-
502-
while retries < max_retries:
503-
try:
504-
retries += 1
505-
return _collect_all_messages(
506-
nwbfile_path=nwbfile_path, checks=checks, driver=driver, skip_validate=skip_validate
507-
)
508-
except OSError: # Cannot curl request
509-
sleep(0.1 * 2**retries)
477+
try:
478+
nwbfile = robust_s3_read(command=io.read, max_retries=max_retries)
479+
for inspector_message in run_checks(nwbfile=nwbfile, checks=checks):
480+
inspector_message.file_path = nwbfile_path
481+
yield inspector_message
482+
except Exception as ex:
483+
yield InspectorMessage(
484+
message=traceback.format_exc(),
485+
importance=Importance.ERROR,
486+
check_function_name=f"During io.read() - {type(ex)}: {str(ex)}",
487+
file_path=nwbfile_path,
488+
)
510489

511490

512491
def run_checks(nwbfile: pynwb.NWBFile, checks: list):
@@ -522,7 +501,7 @@ def run_checks(nwbfile: pynwb.NWBFile, checks: list):
522501
for nwbfile_object in nwbfile.objects.values():
523502
if check_function.neurodata_type is None or issubclass(type(nwbfile_object), check_function.neurodata_type):
524503
try:
525-
output = check_function(nwbfile_object)
504+
output = robust_s3_read(command=check_function, command_args=[nwbfile_object])
526505
# if an individual check fails, include it in the report and continue with the inspection
527506
except Exception:
528507
output = InspectorMessage(

nwbinspector/utils.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import re
33
import json
44
import numpy as np
5-
from typing import TypeVar, Optional, List
5+
from typing import TypeVar, Optional, List, Dict, Callable
66
from pathlib import Path
77
from importlib import import_module
88
from packaging import version
9+
from time import sleep
910

1011
PathType = TypeVar("PathType", str, Path) # For types that can be either files or folders
1112
FilePathType = TypeVar("FilePathType", str, Path)
@@ -113,3 +114,19 @@ def get_package_version(name: str) -> version.Version:
113114

114115
package_version = get_distribution(name).version
115116
return version.parse(package_version)
117+
118+
119+
def robust_s3_read(
120+
command: Callable, max_retries: int = 10, command_args: Optional[list] = None, command_kwargs: Optional[Dict] = None
121+
):
122+
"""Attempt the command (usually acting on an S3 IO) up to the number of max_retries using exponential backoff."""
123+
command_args = command_args or []
124+
command_kwargs = command_kwargs or dict()
125+
for retry in range(max_retries):
126+
try:
127+
return command(*command_args, **command_kwargs)
128+
except OSError: # cannot curl request
129+
sleep(0.1 * 2**retry)
130+
except Exception as exc:
131+
raise exc
132+
raise TimeoutError(f"Unable to complete the command ({command.__name__}) after {max_retries} attempts!")

tests/unit_tests/test_time_series.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
check_missing_unit,
1616
check_resolution,
1717
)
18-
from nwbinspector.utils import get_package_version
18+
from nwbinspector.utils import get_package_version, robust_s3_read
1919

2020
try:
2121
# Test ros3 on sub-YutaMouse54/sub-YutaMouse54_ses-YutaMouse54-160630_behavior+ecephys.nwb from #3
@@ -190,22 +190,17 @@ def test_check_none_matnwb_resolution_pass():
190190
191191
produced with MatNWB, when read with PyNWB~=2.0.1 and HDMF<=3.2.1 contains a resolution value of None.
192192
"""
193-
max_retries = 10
194-
retries = 0
195-
196-
while retries < max_retries:
197-
try:
198-
retries += 1
199-
with pynwb.NWBHDF5IO(
200-
path="https://dandiarchive.s3.amazonaws.com/blobs/da5/107/da510761-653e-4b81-a330-9cdae4838180",
201-
mode="r",
202-
load_namespaces=True,
203-
driver="ros3",
204-
) as io:
205-
nwbfile = io.read()
206-
time_series = nwbfile.processing["video_files"]["video"].time_series["20170203_KIB_01_s1.1.h264"]
207-
except OSError: # Cannot curl request
208-
sleep(0.1 * 2**retries)
193+
with pynwb.NWBHDF5IO(
194+
path="https://dandiarchive.s3.amazonaws.com/blobs/da5/107/da510761-653e-4b81-a330-9cdae4838180",
195+
mode="r",
196+
load_namespaces=True,
197+
driver="ros3",
198+
) as io:
199+
nwbfile = robust_s3_read(command=io.read)
200+
time_series = robust_s3_read(
201+
"20170203_KIB_01_s1.1.h264",
202+
command=nwbfile.processing["video_files"]["video"].time_series.get,
203+
)
209204
assert check_resolution(time_series) is None
210205

211206

0 commit comments

Comments
 (0)