Skip to content

Commit ca97bc0

Browse files
Merge branch 'dev' into document_formatter
2 parents 165b1d2 + 71f27ae commit ca97bc0

File tree

13 files changed

+138
-118
lines changed

13 files changed

+138
-118
lines changed

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,25 @@ body:
6363
- 3.9
6464
validations:
6565
required: true
66+
- type: dropdown
67+
id: usage
68+
attributes:
69+
label: Usage
70+
options:
71+
- Command Line Interface
72+
- Library (Python code)
73+
validations:
74+
required: true
75+
- type: dropdown
76+
id: streaming
77+
attributes:
78+
label: Were you streaming with ROS3?
79+
options:
80+
- 'Yes'
81+
- 'No'
82+
- I'm not sure what that is
83+
validations:
84+
required: true
6685
- type: textarea
6786
id: package_versions
6887
attributes:

docs/_static/css/custom.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,12 @@ dl.class em:not([class]):last-of-type::after {
1414
dl.class > dt:first-of-type {
1515
display: block !important;
1616
}
17+
18+
button.copybtn {
19+
height:25px;
20+
width:25px;
21+
opacity: 0.5;
22+
padding: 0;
23+
border: none;
24+
background: none;
25+
}

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"sphinx.ext.intersphinx",
1919
"myst_parser",
2020
"sphinx.ext.extlinks",
21+
'sphinx_copybutton',
2122
]
2223
templates_path = ["_templates"]
2324
master_doc = "index"

docs/user_guide/advanced_usage.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ S3 path on the DANDI archive. Resolution of these paths can be performed via the
7070
with DandiAPIClient() as client:
7171
dandiset = client.get_dandiset(dandiset_id, dandiset_type)
7272
for asset in dandiset.get_assets():
73-
asset = dandiset.get_asset_by_path(asset.path)
7473
s3_url = asset.get_content_url(follow_redirects=1, strip_query=True)
7574
messages.extend(list(inspect_nwb(nwbfile_path=s3_url, driver="ros3")))
7675

nwbinspector/checks/image_series.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,31 @@
99

1010
@register_check(importance=Importance.CRITICAL, neurodata_type=ImageSeries)
1111
def check_image_series_external_file_valid(image_series: ImageSeries):
12-
"""Check if the external_file specified by an ImageSeries actually exists at the relative location."""
12+
"""Check if the external_file specified by an ImageSeries actually exists."""
1313
if image_series.external_file is None:
1414
return
1515
nwbfile_path = Path(get_nwbfile_path_from_internal_object(obj=image_series))
16+
print(nwbfile_path)
1617
for file_path in image_series.external_file:
17-
if not Path(file_path).is_absolute() and not (nwbfile_path / file_path).exists():
18+
if not Path(file_path).is_absolute() and not (nwbfile_path.parent / file_path).exists():
1819
yield InspectorMessage(
1920
message=(
2021
f"The external file '{file_path}' does not exist. Please confirm the relative location to the"
2122
" NWBFile."
2223
)
2324
)
25+
26+
27+
@register_check(importance=Importance.BEST_PRACTICE_VIOLATION, neurodata_type=ImageSeries)
28+
def check_image_series_external_file_relative(image_series: ImageSeries):
29+
"""Check if the external_file specified by an ImageSeries, if it exists, is relative."""
30+
if image_series.external_file is None:
31+
return
32+
for file_path in image_series.external_file:
33+
if Path(file_path).is_absolute():
34+
yield InspectorMessage(
35+
message=(
36+
f"The external file '{file_path}' is not a relative path. "
37+
"Please adjust the absolute path to be relative to the location of the NWBFile."
38+
)
39+
)

nwbinspector/checks/nwbfile_metadata.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,20 @@ def check_subject_exists(nwbfile: NWBFile):
7676
def check_doi_publications(nwbfile: NWBFile):
7777
"""Check if related_publications has been properly added as 'doi: ###' or an external 'doi' link."""
7878
valid_starts = ["doi:", "http://dx.doi.org/", "https://doi.org/"]
79-
if nwbfile.related_publications:
80-
for publication in nwbfile.related_publications:
81-
if not any((publication.startswith(valid_start) for valid_start in valid_starts)):
82-
yield InspectorMessage(
83-
message=(
84-
f"Metadata /general/related_publications '{publication}' does not start with 'doi: ###' or is "
85-
"not an external 'doi' link."
86-
)
79+
80+
if not nwbfile.related_publications:
81+
return
82+
83+
for publication in nwbfile.related_publications:
84+
if isinstance(publication, bytes):
85+
publication = publication.decode()
86+
if not any((publication.startswith(valid_start) for valid_start in valid_starts)):
87+
yield InspectorMessage(
88+
message=(
89+
f"Metadata /general/related_publications '{publication}' does not start with 'doi: ###' and is "
90+
"not an external 'doi' link."
8791
)
92+
)
8893

8994

9095
@register_check(importance=Importance.BEST_PRACTICE_SUGGESTION, neurodata_type=Subject)
@@ -95,8 +100,8 @@ def check_subject_age(subject: Subject):
95100
return InspectorMessage(message="Subject is missing age and date_of_birth.")
96101
elif not re.fullmatch(duration_regex, subject.age):
97102
return InspectorMessage(
98-
message="Subject age does not follow ISO 8601 duration format, e.g. 'P2Y' for 2 years or 'P23W' for 23 "
99-
"weeks."
103+
message=f"Subject age, '{subject.age}', does not follow ISO 8601 duration format, e.g. 'P2Y' for 2 years "
104+
f"or 'P23W' for 23 weeks."
100105
)
101106

102107

nwbinspector/inspector_tools.py

Lines changed: 1 addition & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -269,74 +269,8 @@ def format_messages(
269269
return formatted_messages
270270

271271

272-
def supports_color(): # pragma: no cover
273-
"""
274-
Return True if the running system's terminal supports color, and False otherwise.
275-
276-
From https://github.com/django/django/blob/main/django/core/management/color.py
277-
"""
278-
279-
def vt_codes_enabled_in_windows_registry():
280-
"""Check the Windows Registry to see if VT code handling has been enabled by default."""
281-
try:
282-
# winreg is only available on Windows.
283-
import winreg
284-
except ImportError:
285-
return False
286-
else:
287-
reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Console")
288-
try:
289-
reg_key_value, _ = winreg.QueryValueEx(reg_key, "VirtualTerminalLevel")
290-
except FileNotFoundError:
291-
return False
292-
else:
293-
return reg_key_value == 1
294-
295-
# isatty is not always implemented, #6223.
296-
is_a_tty = hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
297-
298-
return is_a_tty and (
299-
sys.platform != "win32"
300-
or "ANSICON" in os.environ
301-
or "WT_SESSION" in os.environ # Windows Terminal supports VT codes.
302-
or os.environ.get("TERM_PROGRAM") == "vscode" # Microsoft Visual Studio Code's built-in terminal.
303-
or vt_codes_enabled_in_windows_registry()
304-
)
305-
306-
307-
def wrap_color(formatted_messages: List[str], no_color: bool = False): # pragma: no cover
308-
"""Wrap the file output with colors for console output."""
309-
if not supports_color():
310-
return formatted_messages
311-
reset_color = "\x1b[0m"
312-
color_map = {
313-
"CRITICAL IMPORTANCE": "\x1b[31m",
314-
"BEST PRACTICE VIOLATION": "\x1b[33m",
315-
"BEST PRACTICE SUGGESTION": reset_color,
316-
"NWBFile": reset_color,
317-
}
318-
319-
color_shift_points = dict()
320-
for line_index, line in enumerate(formatted_messages):
321-
for color_trigger in color_map:
322-
if color_trigger in line:
323-
color_shift_points.update(
324-
{line_index: color_map[color_trigger], line_index + 1: color_map[color_trigger]}
325-
)
326-
colored_output = list()
327-
current_color = None
328-
for line in formatted_messages:
329-
transition_point = line_index in color_shift_points
330-
if transition_point:
331-
current_color = color_shift_points[line_index]
332-
colored_output.append(f"{current_color}{line}{reset_color}")
333-
if current_color is not None and not transition_point:
334-
colored_output.append(f"{current_color}{line[:6]}{reset_color}{line[6:]}")
335-
336-
337-
def print_to_console(formatted_messages: List[str], no_color: bool = False):
272+
def print_to_console(formatted_messages: List[str]):
338273
"""Print report file contents to console."""
339-
wrap_color(formatted_messages=formatted_messages, no_color=no_color)
340274
sys.stdout.write(os.linesep * 2)
341275
for line in formatted_messages:
342276
sys.stdout.write(line + "\n")

nwbinspector/nwbinspector.py

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -151,32 +151,28 @@ def configure_checks(
151151

152152
@click.command()
153153
@click.argument("path")
154-
@click.option("-m", "--modules", help="Modules to import prior to reading the file(s).")
155-
@click.option("--no-color", help="Disable coloration for console display of output.", is_flag=True)
154+
@click.option("--modules", help="Modules to import prior to reading the file(s).")
156155
@click.option(
157156
"--report-file-path",
158157
default=None,
159158
help="Save path for the report file.",
160159
type=click.Path(writable=True),
161160
)
162-
@click.option("-o", "--overwrite", help="Overwrite an existing report file at the location.", is_flag=True)
161+
@click.option("--overwrite", help="Overwrite an existing report file at the location.", is_flag=True)
163162
@click.option("--levels", help="Comma-separated names of InspectorMessage attributes to organize by.")
164163
@click.option(
165164
"--reverse", help="Comma-separated booleans corresponding to reversing the order for each value of 'levels'."
166165
)
167-
@click.option("-i", "--ignore", help="Comma-separated names of checks to skip.")
168-
@click.option("-s", "--select", help="Comma-separated names of checks to run.")
166+
@click.option("--ignore", help="Comma-separated names of checks to skip.")
167+
@click.option("--select", help="Comma-separated names of checks to run.")
169168
@click.option(
170-
"-t",
171169
"--threshold",
172170
default="BEST_PRACTICE_SUGGESTION",
173171
type=click.Choice(["CRITICAL", "BEST_PRACTICE_VIOLATION", "BEST_PRACTICE_SUGGESTION"]),
174172
help="Ignores tests with an assigned importance below this threshold.",
175173
)
176-
@click.option(
177-
"-c", "--config", help="Name of config or path of config .yaml file that overwrites importance of checks."
178-
)
179-
@click.option("-j", "--json-file-path", help="Write json output to this location.")
174+
@click.option("--config", help="Name of config or path of config .yaml file that overwrites importance of checks.")
175+
@click.option("--json-file-path", help="Write json output to this location.")
180176
@click.option("--n-jobs", help="Number of jobs to use in parallel.", default=1)
181177
@click.option("--skip-validate", help="Skip the PyNWB validation step.", is_flag=True)
182178
@click.option(
@@ -206,7 +202,6 @@ def configure_checks(
206202
def inspect_all_cli(
207203
path: str,
208204
modules: Optional[str] = None,
209-
no_color: bool = False,
210205
report_file_path: str = None,
211206
levels: str = None,
212207
reverse: Optional[str] = None,
@@ -270,12 +265,11 @@ def inspect_all_cli(
270265
json_report = dict(header=get_report_header(), messages=messages)
271266
json.dump(obj=json_report, fp=fp, cls=InspectorOutputJSONEncoder)
272267
print(f"{os.linesep*2}Report saved to {str(Path(json_file_path).absolute())}!{os.linesep}")
273-
if len(messages):
274-
formatted_messages = format_messages(messages=messages, levels=levels, reverse=reverse, detailed=detailed)
275-
print_to_console(formatted_messages=formatted_messages, no_color=no_color)
276-
if report_file_path is not None:
277-
save_report(report_file_path=report_file_path, formatted_messages=formatted_messages, overwrite=overwrite)
278-
print(f"{os.linesep*2}Report saved to {str(Path(report_file_path).absolute())}!{os.linesep}")
268+
formatted_messages = format_messages(messages=messages, levels=levels, reverse=reverse, detailed=detailed)
269+
print_to_console(formatted_messages=formatted_messages)
270+
if report_file_path is not None:
271+
save_report(report_file_path=report_file_path, formatted_messages=formatted_messages, overwrite=overwrite)
272+
print(f"{os.linesep*2}Report saved to {str(Path(report_file_path).absolute())}!{os.linesep}")
279273

280274

281275
def inspect_all(

requirements-rtd.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ Jinja2<3.1
55
sphinx==3.5.1
66
sphinx_rtd_theme==0.5.1
77
readthedocs-sphinx-search==0.1.0rc3
8+
sphinx-copybutton==0.5.0

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
long_description = f.read()
66
setup(
77
name="nwbinspector",
8-
version="0.3.16",
8+
version="0.3.18",
99
description="Tool to inspect NWB files for best practices compliance.",
1010
long_description=long_description,
1111
long_description_content_type="text/markdown",

0 commit comments

Comments
 (0)