Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
99332ad
Merge pull request #161 from con/reduce_codecov_spam
CodyCBakerPhD Oct 24, 2025
f05d58d
chore: add back blurb for consistency
CodyCBakerPhD Oct 26, 2025
c15fb79
Merge branch 'main' into add_config
CodyCBakerPhD Oct 26, 2025
882dc59
Apply suggestion from @CodyCBakerPhD
CodyCBakerPhD Oct 27, 2025
a64f7ad
Merge branch 'main' into add_config
CodyCBakerPhD Oct 29, 2025
8aa0692
chore: fix bad merges
CodyCBakerPhD Oct 29, 2025
4028e5d
chore: fix bad merge
CodyCBakerPhD Oct 29, 2025
8b49a36
feat: redefine private attributes as properties and adjust their type…
candleindark Nov 4, 2025
0091975
feat: default `run_id` through default factory and remove `None` type…
candleindark Nov 4, 2025
649415e
feat: default `cache_directory` through default factory and remove `N…
candleindark Nov 4, 2025
60fa15d
feat: default `bids_directory` through default factory and remove `No…
candleindark Nov 4, 2025
7db118e
feat: default `file_mode` through default factory and remove its `"au…
candleindark Nov 4, 2025
74b2be2
feat: modify `_validate_existing_directory_as_bids` into a field vali…
candleindark Nov 4, 2025
27f6ad8
feat: filter values of CLI arguments to set `RunConfig`
candleindark Nov 4, 2025
4e58190
fix: ensure `dataset_description.json` is a file
candleindark Nov 4, 2025
7d1c0ce
fix: handle situation that `dataset_description.json` is not a valid …
candleindark Nov 4, 2025
ca5ff77
style: add parentheses to make intent explicit
candleindark Nov 4, 2025
e74ecf1
Merge pull request #175 from candleindark/add_config_revise_config_model
CodyCBakerPhD Nov 5, 2025
10492ef
Merge branch 'main' into add_config
CodyCBakerPhD Nov 5, 2025
6f3d586
feat: make RunConfig immutable; restore previous style; move helpers …
CodyCBakerPhD Nov 5, 2025
8f1928b
Merge branch 'add_config' of https://github.com/con/nwb2bids into add…
CodyCBakerPhD Nov 5, 2025
38185c7
Merge branch 'main' into add_config
CodyCBakerPhD Nov 6, 2025
3656ebf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 6, 2025
83c7b85
Merge branch 'main' into add_config
CodyCBakerPhD Nov 7, 2025
f32822c
fix: some pr suggestions
CodyCBakerPhD Nov 7, 2025
fa0d615
Update src/nwb2bids/_core/_validate_existing_bids.py
CodyCBakerPhD Nov 7, 2025
2702a2a
fix: some pr suggestions
CodyCBakerPhD Nov 7, 2025
47d3030
Merge branch 'add_config' of https://github.com/con/nwb2bids into add…
CodyCBakerPhD Nov 7, 2025
1f88ecd
Update src/nwb2bids/_converters/_dataset_converter.py
CodyCBakerPhD Nov 7, 2025
1b9bf4c
fix: update datalad tests
CodyCBakerPhD Nov 7, 2025
ea37a3b
Merge branch 'add_config' of https://github.com/con/nwb2bids into add…
CodyCBakerPhD Nov 7, 2025
9a568df
fix: update datalad tests
CodyCBakerPhD Nov 7, 2025
f0e4eba
fix: update datalad tests
CodyCBakerPhD Nov 7, 2025
0dbead2
fix: update datalad tests
CodyCBakerPhD Nov 7, 2025
365f1ef
Simplify notification check in CLI
CodyCBakerPhD Nov 8, 2025
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
9 changes: 4 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@

#### 🐛 Bug Fix

- Remove list of required status checks [#159](https://github.com/con/nwb2bids/pull/159) ([@candleindark](https://github.com/candleindark))
- Provide list of required status checks to `auto` [#158](https://github.com/con/nwb2bids/pull/158) ([@candleindark](https://github.com/candleindark) [@CodyCBakerPhD](https://github.com/CodyCBakerPhD))
- Testing auto release (again) [#156](https://github.com/con/nwb2bids/pull/156) ([@CodyCBakerPhD](https://github.com/CodyCBakerPhD))
- Testing auto release [#152](https://github.com/con/nwb2bids/pull/152) ([@CodyCBakerPhD](https://github.com/CodyCBakerPhD))
- Resolved protected branch problem in execution of the "Release with Auto" GH workflow [#151](https://github.com/con/nwb2bids/pull/151) ([@candleindark](https://github.com/candleindark))
- Add ability to read from raw dandiset metadata when invalid [#107](https://github.com/con/nwb2bids/pull/107) ([@CodyCBakerPhD](https://github.com/CodyCBakerPhD))
Expand All @@ -19,6 +18,7 @@

#### 🏠 Internal

- Automatic release [#155](https://github.com/con/nwb2bids/pull/155) ([@github-actions[bot]](https://github.com/github-actions[bot]) [@CodyCBakerPhD](https://github.com/CodyCBakerPhD))
- Use two GitHub tokens in Release with Auto workflow [#154](https://github.com/con/nwb2bids/pull/154) ([@candleindark](https://github.com/candleindark) [@CodyCBakerPhD](https://github.com/CodyCBakerPhD))
- Improved and consolidated release and deploy workflow [#138](https://github.com/con/nwb2bids/pull/138) ([@candleindark](https://github.com/candleindark))
- Provided fix for Setup Release Labels workflow [#137](https://github.com/con/nwb2bids/pull/137) ([@candleindark](https://github.com/candleindark))
Expand Down Expand Up @@ -49,15 +49,14 @@
- Blacklisted `h5py` version dependency for macOS platform [#136](https://github.com/con/nwb2bids/pull/136) ([@CodyCBakerPhD](https://github.com/CodyCBakerPhD))
- [pre-commit.ci] pre-commit autoupdate [#122](https://github.com/con/nwb2bids/pull/122) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))

#### Authors: 4
#### Authors: 5

- [@github-actions[bot]](https://github.com/github-actions[bot])
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- Austin Macdonald ([@asmacdo](https://github.com/asmacdo))
- Cody Baker ([@CodyCBakerPhD](https://github.com/CodyCBakerPhD))
- Isaac To ([@candleindark](https://github.com/candleindark))

---


# v0.5.1

Expand Down
6 changes: 4 additions & 2 deletions src/nwb2bids/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@
from ._core._convert_nwb_dataset import convert_nwb_dataset
from ._converters._dataset_converter import DatasetConverter
from ._converters._session_converter import SessionConverter
from ._converters._run_config import RunConfig
from ._inspection._inspection_result import InspectionResult, Severity, Category, DataStandard

__all__ = [
# Public methods and classes
"convert_nwb_dataset",
"Category",
"RunConfig",
"DatasetConverter",
"SessionConverter",
"Severity",
"DataStandard",
"InspectionResult",
"Category",
"Severity",
# Public submodules
"bids_models",
"testing",
Expand Down
61 changes: 51 additions & 10 deletions src/nwb2bids/_command_line_interface/_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import rich_click

from .._converters._run_config import RunConfig
from .._core._convert_nwb_dataset import convert_nwb_dataset
from .._tools._pluralize import _pluralize

Expand Down Expand Up @@ -36,6 +37,17 @@ def _nwb2bids_cli():
type=rich_click.Choice(["copy", "move", "symlink", "auto"], case_sensitive=False),
default="auto",
)
@rich_click.option(
"--cache-directory",
"cache_directory",
help=(
"The directory where run specific files (e.g., notifications, sanitization reports) will be stored. "
"Defaults to `~/.nwb2bids`."
),
required=False,
type=rich_click.Path(exists=True, dir_okay=False, readable=True),
default=None,
)
@rich_click.option(
"--additional-metadata-file-path",
"additional_metadata_file_path",
Expand All @@ -48,11 +60,25 @@ def _nwb2bids_cli():
default=None,
)
@rich_click.option("--silent", "-s", is_flag=True, help="Suppress all console output.", default=False)
@rich_click.option(
"--run-id",
help=(
"On each unique run of nwb2bids, a run ID is generated. "
"Set this option to override this to any identifying string. "
"This ID is used in the naming of the notification and sanitization reports saved to your cache directory. "
'The default ID uses runtime timestamp information of the form "date-%Y%m%d_time-%H%M%S."'
),
required=False,
type=str,
default=None,
)
def _run_convert_nwb_dataset(
nwb_paths: tuple[str, ...],
bids_directory: str | None = None,
file_mode: typing.Literal["copy", "move", "symlink", "auto"] = "auto",
additional_metadata_file_path: str | None = None,
file_mode: typing.Literal["copy", "move", "symlink", "auto"] = "auto",
cache_directory: str | None = None,
run_id: str | None = None,
silent: bool = False,
) -> None:
"""
Expand All @@ -66,17 +92,32 @@ def _run_convert_nwb_dataset(
raise ValueError(message)
handled_nwb_paths = [pathlib.Path(nwb_path) for nwb_path in nwb_paths]

notifications = convert_nwb_dataset(
nwb_paths=handled_nwb_paths,
bids_directory=bids_directory,
file_mode=file_mode,
additional_metadata_file_path=additional_metadata_file_path,
)
run_config_kwargs = {
"bids_directory": bids_directory,
"additional_metadata_file_path": additional_metadata_file_path,
"file_mode": file_mode,
"cache_directory": cache_directory,
"run_id": run_id,
}

# Filter out values that indicate absence of direct user input or signal to use default
non_missing_run_config_kwargs = {
key: value
for key, value in run_config_kwargs.items()
if (key != "file_mode" and value is not None) or (key == "file_mode" and value != "auto")
}
run_config = RunConfig(**non_missing_run_config_kwargs)

if notifications and not silent:
text = (
converter = convert_nwb_dataset(nwb_paths=handled_nwb_paths, run_config=run_config)

notifications = converter.messages
console_notification = ""
if notifications:
notification_text = (
f'\n{(n:=len(notifications))} {_pluralize(n=n, word="suggestion")} for improvement '
f'{_pluralize(n=n, word="was", plural="were")} found during conversion.'
)
console_notification = rich_click.style(text=text, fg="yellow")
console_notification += rich_click.style(text=notification_text, fg="yellow")

if console_notification != "" and not silent:
rich_click.echo(message=console_notification)
90 changes: 6 additions & 84 deletions src/nwb2bids/_converters/_base_converter.py
Original file line number Diff line number Diff line change
@@ -1,97 +1,19 @@
import abc
import json
import pathlib
import tempfile
import typing

import pydantic

from ._run_config import RunConfig


class BaseConverter(pydantic.BaseModel, abc.ABC):
run_config: RunConfig = pydantic.Field(
description="The configuration for this conversion run.", default_factory=RunConfig
)

@abc.abstractmethod
def extract_metadata(self) -> None:
"""
Extract essential metadata used by the BIDS standard from the source NWB files.
"""
message = f"The `extract_metadata` method has not been implemented by the `{self.__class__.__name__}` class."
raise NotImplementedError(message)

def _handle_bids_directory(self, bids_directory: str | pathlib.Path | None = None) -> pathlib.Path:
"""
Handle the BIDS directory path.

If the directory does not exist, create it.
If it exists, validate that it is a valid BIDS dataset.
"""
if bids_directory is None:
bids_directory = pathlib.Path.cwd()
bids_directory = pathlib.Path(bids_directory)

if bids_directory.exists():
self._validate_existing_directory_as_bids(bids_directory=bids_directory)
else:
bids_directory.mkdir(exist_ok=True)
self.extract_metadata()

return bids_directory

@staticmethod
def _validate_existing_directory_as_bids(bids_directory: pathlib.Path) -> None:
"""
Validate that the existing directory is a valid BIDS dataset.

Parameters
----------
bids_directory : pathlib.Path
The path to the directory to validate.
"""
dataset_description_file_path = bids_directory / "dataset_description.json"

current_directory_contents = {
path.stem for path in bids_directory.iterdir() if not path.name.startswith(".")
} - {"README", "CHANGES", "derivatives", "dandiset"}
if len(current_directory_contents) == 0:
default_dataset_description = {"BIDSVersion": "1.10"}
with dataset_description_file_path.open(mode="w") as file_stream:
json.dump(obj=default_dataset_description, fp=file_stream, indent=4)
return

if not dataset_description_file_path.exists():
message = (
f"The directory ({bids_directory}) exists and is not empty, but is not a valid BIDS dataset: "
"missing 'dataset_description.json'."
)
raise ValueError(message)

with dataset_description_file_path.open(mode="r") as file_stream:
dataset_description = json.load(fp=file_stream)
if dataset_description.get("BIDSVersion", None) is None:
message = (
f"The directory ({bids_directory}) exists but is not a valid BIDS dataset: "
"missing 'BIDSVersion' in 'dataset_description.json'."
)
raise ValueError(message)

@staticmethod
def _handle_file_mode(
file_mode: typing.Literal["move", "copy", "symlink", "auto"] = "auto",
) -> typing.Literal["move", "copy", "symlink"]:
if file_mode != "auto":
return file_mode

with tempfile.TemporaryDirectory(prefix="nwb2bids-") as temp_dir_str:
temp_dir_path = pathlib.Path(temp_dir_str)

# Create a test file
test_file_path = temp_dir_path / "test_file.txt"
test_file_path.touch()

try:
# Create a symlink to the test file
(temp_dir_path / "test_symlink.txt").symlink_to(target=test_file_path)
except (OSError, PermissionError, NotImplementedError): # Windows can sometimes have trouble with symlinks
# TODO: log a INFO message here when logging is set up
return "copy"
else:
# If symlink creation was successful, return "symlink"
return "symlink"
Loading
Loading