Repository: PennLINC/CuBIDS

Issue:
Remove all container support

Discussion:
tsalo: #396 will fix the lingering issue.

Patch:
diff --git a/cubids/__init__.py b/cubids/__init__.py
index 92fffc3..2ac2cab 100644
--- a/cubids/__init__.py
+++ b/cubids/__init__.py
@@ -33,16 +33,7 @@ __credits__ : str
     The credits for the CuBIDS package.
 """
 
-from cubids import (
-    cli,
-    config,
-    constants,
-    cubids,
-    metadata_merge,
-    utils,
-    validator,
-    workflows,
-)
+from cubids import cli, config, constants, cubids, metadata_merge, validator, workflows
 from cubids.__about__ import __copyright__, __credits__, __packagename__, __version__
 
 __all__ = [
@@ -55,7 +46,6 @@ __all__ = [
     "constants",
     "cubids",
     "metadata_merge",
-    "utils",
     "validator",
     "workflows",
 ]
diff --git a/cubids/cli.py b/cubids/cli.py
index 846b401..43cecb6 100644
--- a/cubids/cli.py
+++ b/cubids/cli.py
@@ -151,12 +151,6 @@ def _parse_validate():
         help="Run the BIDS validator sequentially on each subject.",
         required=False,
     )
-    parser.add_argument(
-        "--container",
-        action="store",
-        help="Docker image tag or Singularity image file.",
-        default=None,
-    )
     parser.add_argument(
         "--ignore-nifti-headers",
         action="store_true",
@@ -442,11 +436,6 @@ def _parse_group():
             "then output files will go to the specified location."
         ),
     )
-    parser.add_argument(
-        "--container",
-        action="store",
-        help="Docker image tag or Singularity image file.",
-    )
     parser.add_argument(
         "--acq-group-level",
         default="subject",
@@ -595,11 +584,6 @@ def _parse_apply():
         default=False,
         help="ensure that there are no untracked changes before finding groups",
     )
-    parser.add_argument(
-        "--container",
-        action="store",
-        help="Docker image tag or Singularity image file.",
-    )
     parser.add_argument(
         "--acq-group-level",
         default="subject",
@@ -692,11 +676,6 @@ def _parse_datalad_save():
         action="store",
         help="message for this commit",
     )
-    parser.add_argument(
-        "--container",
-        action="store",
-        help="Docker image tag or Singularity image file.",
-    )
 
     return parser
 
@@ -775,11 +754,6 @@ def _parse_undo():
             "sub-X directories and dataset_description.json"
         ),
     )
-    parser.add_argument(
-        "--container",
-        action="store",
-        help="Docker image tag or Singularity image file.",
-    )
 
     return parser
 
@@ -923,11 +897,6 @@ def _parse_copy_exemplars():
     #                     help='only include an exemplar subject from these '
     #                     'listed Acquisition Groups in the exemplar dataset ',
     #                     required=False)
-    parser.add_argument(
-        "--container",
-        action="store",
-        help="Docker image tag or Singularity image file.",
-    )
     parser.add_argument(
         "--force-unlock",
         action="store_true",
@@ -1021,11 +990,6 @@ def _parse_add_nifti_info():
         default=False,
         help="unlock dataset before adding nifti info ",
     )
-    parser.add_argument(
-        "--container",
-        action="store",
-        help="Docker image tag or Singularity image file.",
-    )
     return parser
 
 
@@ -1122,11 +1086,6 @@ def _parse_purge():
         default=False,
         help="ensure that there are no untracked changes before finding groups",
     )
-    parser.add_argument(
-        "--container",
-        action="store",
-        help="Docker image tag or Singularity image file.",
-    )
     return parser
 
 
@@ -1212,11 +1171,6 @@ def _parse_remove_metadata_fields():
         default=[],
         help="space-separated list of metadata fields to remove.",
     )
-    parser.add_argument(
-        "--container",
-        action="store",
-        help="Docker image tag or Singularity image file.",
-    )
 
     return parser
 
@@ -1298,11 +1252,6 @@ def _parse_print_metadata_fields():
             "sub-X directories and dataset_description.json"
         ),
     )
-    parser.add_argument(
-        "--container",
-        action="store",
-        help="Docker image tag or Singularity image file.",
-    )
 
     return parser
 
diff --git a/cubids/config.py b/cubids/config.py
index 6c9b678..45747d0 100644
--- a/cubids/config.py
+++ b/cubids/config.py
@@ -1,7 +1,8 @@
 """Functions for configuring CuBIDS."""
 
-from pathlib import Path
 import importlib.resources
+from pathlib import Path
+
 import yaml
 
 
diff --git a/cubids/utils.py b/cubids/utils.py
deleted file mode 100644
index c4cdb45..0000000
--- a/cubids/utils.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""Miscellaneous utility functions for CuBIDS.
-
-This module provides various utility functions used throughout the CuBIDS package.
-"""
-
-import re
-from pathlib import Path
-
-
-def _get_container_type(image_name):
-    """Get and return the container type.
-
-    Parameters
-    ----------
-    image_name : :obj:`str`
-        The name of the container image.
-
-    Returns
-    -------
-    :obj:`str`
-        The container type, either "docker" or "singularity".
-
-    Raises
-    ------
-    :obj:`Exception`
-        If the container type cannot be determined.
-    """
-    # If it's a file on disk, it must be a singularity image
-    if Path(image_name).exists():
-        return "singularity"
-
-    # It needs to match a docker tag pattern to be docker
-    if re.match(r"(?:.+\/)?([^:]+)(?::.+)?", image_name):
-        return "docker"
-
-    raise Exception("Unable to determine the container type of " + image_name)
diff --git a/cubids/workflows.py b/cubids/workflows.py
index 5f419ed..c85dca2 100644
--- a/cubids/workflows.py
+++ b/cubids/workflows.py
@@ -15,7 +15,6 @@ import tqdm
 
 from cubids.cubids import CuBIDS
 from cubids.metadata_merge import merge_json_into_json
-from cubids.utils import _get_container_type
 from cubids.validator import (
     bids_validator_version,
     build_first_subject_path,
@@ -36,7 +35,6 @@ logging.getLogger("datalad").setLevel(logging.ERROR)
 def validate(
     bids_dir,
     output_prefix,
-    container,
     sequential,
     sequential_subjects,
     local_validator,
@@ -50,8 +48,6 @@ def validate(
         Path to the BIDS directory.
     output_prefix : :obj:`pathlib.Path`
         Output filename prefix.
-    container : :obj:`str`
-        Container in which to run the workflow.
     sequential : :obj:`bool`
         Run the validator sequentially.
     sequential_subjects : :obj:`list` of :obj:`str`
@@ -73,195 +69,132 @@ def validate(
             subprocess.run(["mkdir", str(bids_dir / "code" / "CuBIDS")])
 
     # Run directly from python using subprocess
-    if container is None:
-        if not sequential:
-            # run on full dataset
-            call = build_validator_call(
-                str(bids_dir),
-                local_validator,
-                ignore_nifti_headers,
-            )
-            ret = run_validator(call)
+    if not sequential:
+        # run on full dataset
+        call = build_validator_call(
+            str(bids_dir),
+            local_validator,
+            ignore_nifti_headers,
+        )
+        ret = run_validator(call)
 
-            # parse the string output
-            parsed = parse_validator_output(ret.stdout.decode("UTF-8"))
-            if parsed.shape[1] < 1:
-                logger.info("No issues/warnings parsed, your dataset is BIDS valid.")
-                sys.exit(0)
-            else:
-                logger.info("BIDS issues/warnings found in the dataset")
+        # parse the string output
+        parsed = parse_validator_output(ret.stdout.decode("UTF-8"))
+        if parsed.shape[1] < 1:
+            logger.info("No issues/warnings parsed, your dataset is BIDS valid.")
+            sys.exit(0)
+        else:
+            logger.info("BIDS issues/warnings found in the dataset")
 
-                if output_prefix:
-                    # check if absolute or relative path
-                    if abs_path_output:
-                        # normally, write dataframe to file in CLI
-                        val_tsv = str(output_prefix) + "_validation.tsv"
+            if output_prefix:
+                # check if absolute or relative path
+                if abs_path_output:
+                    # normally, write dataframe to file in CLI
+                    val_tsv = str(output_prefix) + "_validation.tsv"
 
-                    else:
-                        val_tsv = (
-                            str(bids_dir)
-                            + "/code/CuBIDS/"
-                            + str(output_prefix)
-                            + "_validation.tsv"
-                        )
-
-                    parsed.to_csv(val_tsv, sep="\t", index=False)
-
-                    # build validation data dictionary json sidecar
-                    val_dict = get_val_dictionary()
-                    val_json = val_tsv.replace("tsv", "json")
-                    with open(val_json, "w") as outfile:
-                        json.dump(val_dict, outfile, indent=4)
-
-                    logger.info("Writing issues out to %s", val_tsv)
-                    sys.exit(0)
                 else:
-                    # user may be in python session, return dataframe
-                    return parsed
-        else:
-            # logger.info("Prepping sequential validator run...")
-
-            # build a dictionary with {SubjectLabel: [List of files]}
-            subjects_dict = build_subject_paths(bids_dir)
-
-            # logger.info("Running validator sequentially...")
-            # iterate over the dictionary
-
-            parsed = []
-
-            if sequential_subjects:
-                subjects_dict = {
-                    k: v for k, v in subjects_dict.items() if k in sequential_subjects
-                }
-            assert len(list(subjects_dict.keys())) > 1, "No subjects found in filter"
-            for subject, files_list in tqdm.tqdm(subjects_dict.items()):
-                # logger.info(" ".join(["Processing subject:", subject]))
-                # create a temporary directory and symlink the data
-                with tempfile.TemporaryDirectory() as tmpdirname:
-                    for fi in files_list:
-                        # cut the path down to the subject label
-                        bids_start = fi.find(subject)
-
-                        # maybe it's a single file
-                        if bids_start < 1:
-                            bids_folder = tmpdirname
-                            fi_tmpdir = tmpdirname
-
-                        else:
-                            bids_folder = Path(fi[bids_start:]).parent
-                            fi_tmpdir = tmpdirname + "/" + str(bids_folder)
-
-                        if not os.path.exists(fi_tmpdir):
-                            os.makedirs(fi_tmpdir)
-                        output = fi_tmpdir + "/" + str(Path(fi).name)
-                        shutil.copy2(fi, output)
-
-                    # run the validator
-                    nifti_head = ignore_nifti_headers
-                    call = build_validator_call(tmpdirname, local_validator, nifti_head)
-                    ret = run_validator(call)
-                    # parse output
-                    if ret.returncode != 0:
-                        logger.error("Errors returned from validator run, parsing now")
-
-                    # parse the output and add to list if it returns a df
-                    decoded = ret.stdout.decode("UTF-8")
-                    tmp_parse = parse_validator_output(decoded)
-                    if tmp_parse.shape[1] > 1:
-                        tmp_parse["subject"] = subject
-                        parsed.append(tmp_parse)
-
-            # concatenate the parsed data and exit
-            if len(parsed) < 1:
-                logger.info("No issues/warnings parsed, your dataset is BIDS valid.")
-                sys.exit(0)
+                    val_tsv = (
+                        str(bids_dir) + "/code/CuBIDS/" + str(output_prefix) + "_validation.tsv"
+                    )
 
-            else:
-                parsed = pd.concat(parsed, axis=0)
-                subset = parsed.columns.difference(["subject"])
-                parsed = parsed.drop_duplicates(subset=subset)
+                parsed.to_csv(val_tsv, sep="\t", index=False)
 
-                logger.info("BIDS issues/warnings found in the dataset")
+                # build validation data dictionary json sidecar
+                val_dict = get_val_dictionary()
+                val_json = val_tsv.replace("tsv", "json")
+                with open(val_json, "w") as outfile:
+                    json.dump(val_dict, outfile, indent=4)
+
+                logger.info("Writing issues out to %s", val_tsv)
+                sys.exit(0)
+            else:
+                # user may be in python session, return dataframe
+                return parsed
+    else:
+        # logger.info("Prepping sequential validator run...")
+
+        # build a dictionary with {SubjectLabel: [List of files]}
+        subjects_dict = build_subject_paths(bids_dir)
+
+        # logger.info("Running validator sequentially...")
+        # iterate over the dictionary
+
+        parsed = []
+
+        if sequential_subjects:
+            subjects_dict = {k: v for k, v in subjects_dict.items() if k in sequential_subjects}
+        assert len(list(subjects_dict.keys())) > 1, "No subjects found in filter"
+        for subject, files_list in tqdm.tqdm(subjects_dict.items()):
+            # logger.info(" ".join(["Processing subject:", subject]))
+            # create a temporary directory and symlink the data
+            with tempfile.TemporaryDirectory() as tmpdirname:
+                for fi in files_list:
+                    # cut the path down to the subject label
+                    bids_start = fi.find(subject)
+
+                    # maybe it's a single file
+                    if bids_start < 1:
+                        bids_folder = tmpdirname
+                        fi_tmpdir = tmpdirname
 
-                if output_prefix:
-                    # normally, write dataframe to file in CLI
-                    if abs_path_output:
-                        val_tsv = str(output_prefix) + "_validation.tsv"
                     else:
-                        val_tsv = (
-                            str(bids_dir)
-                            + "/code/CuBIDS/"
-                            + str(output_prefix)
-                            + "_validation.tsv"
-                        )
-
-                    parsed.to_csv(val_tsv, sep="\t", index=False)
-
-                    # build validation data dictionary json sidecar
-                    val_dict = get_val_dictionary()
-                    val_json = val_tsv.replace("tsv", "json")
-                    with open(val_json, "w") as outfile:
-                        json.dump(val_dict, outfile, indent=4)
-
-                    logger.info("Writing issues out to file %s", val_tsv)
-                    sys.exit(0)
+                        bids_folder = Path(fi[bids_start:]).parent
+                        fi_tmpdir = tmpdirname + "/" + str(bids_folder)
+
+                    if not os.path.exists(fi_tmpdir):
+                        os.makedirs(fi_tmpdir)
+                    output = fi_tmpdir + "/" + str(Path(fi).name)
+                    shutil.copy2(fi, output)
+
+                # run the validator
+                nifti_head = ignore_nifti_headers
+                call = build_validator_call(tmpdirname, local_validator, nifti_head)
+                ret = run_validator(call)
+                # parse output
+                if ret.returncode != 0:
+                    logger.error("Errors returned from validator run, parsing now")
+
+                # parse the output and add to list if it returns a df
+                decoded = ret.stdout.decode("UTF-8")
+                tmp_parse = parse_validator_output(decoded)
+                if tmp_parse.shape[1] > 1:
+                    tmp_parse["subject"] = subject
+                    parsed.append(tmp_parse)
+
+        # concatenate the parsed data and exit
+        if len(parsed) < 1:
+            logger.info("No issues/warnings parsed, your dataset is BIDS valid.")
+            sys.exit(0)
+
+        else:
+            parsed = pd.concat(parsed, axis=0, ignore_index=True)
+            subset = parsed.columns.difference(["subject"])
+            parsed = parsed.drop_duplicates(subset=subset)
+
+            logger.info("BIDS issues/warnings found in the dataset")
+
+            if output_prefix:
+                # normally, write dataframe to file in CLI
+                if abs_path_output:
+                    val_tsv = str(output_prefix) + "_validation.tsv"
                 else:
-                    # user may be in python session, return dataframe
-                    return parsed
-
-    # Run it through a container
-    container_type = _get_container_type(container)
-    bids_dir_link = str(bids_dir.absolute()) + ":/bids:ro"
-    output_dir_link_t = str(output_prefix.parent.absolute()) + ":/tsv:rw"
-    output_dir_link_j = str(output_prefix.parent.absolute()) + ":/json:rw"
-    linked_output_prefix_t = "/tsv/" + output_prefix.name
-    if container_type == "docker":
-        cmd = [
-            "docker",
-            "run",
-            "--rm",
-            "-v",
-            bids_dir_link,
-            "-v",
-            GIT_CONFIG + ":/root/.gitconfig",
-            "-v",
-            output_dir_link_t,
-            "-v",
-            output_dir_link_j,
-            "--entrypoint",
-            "cubids-validate",
-            container,
-            "/bids",
-            linked_output_prefix_t,
-        ]
-        if ignore_nifti_headers:
-            cmd.append("--ignore-nifti-headers")
-
-    elif container_type == "singularity":
-        cmd = [
-            "singularity",
-            "exec",
-            "--cleanenv",
-            "-B",
-            bids_dir_link,
-            "-B",
-            output_dir_link_t,
-            "-B",
-            output_dir_link_j,
-            container,
-            "cubids-validate",
-            "/bids",
-            linked_output_prefix_t,
-        ]
-        if ignore_nifti_headers:
-            cmd.append("--ignore-nifti-headers")
+                    val_tsv = (
+                        str(bids_dir) + "/code/CuBIDS/" + str(output_prefix) + "_validation.tsv"
+                    )
+
+                parsed.to_csv(val_tsv, sep="\t", index=False)
 
-        if sequential:
-            cmd.append("--sequential")
+                # build validation data dictionary json sidecar
+                val_dict = get_val_dictionary()
+                val_json = val_tsv.replace("tsv", "json")
+                with open(val_json, "w") as outfile:
+                    json.dump(val_dict, outfile, indent=4)
 
-    logger.info("RUNNING: " + " ".join(cmd))
-    proc = subprocess.run(cmd)
-    sys.exit(proc.returncode)
+                logger.info("Writing issues out to file %s", val_tsv)
+                sys.exit(0)
+            else:
+                # user may be in python session, return dataframe
+                return parsed
 
 
 def bids_version(bids_dir, write=False):
@@ -334,15 +267,13 @@ def bids_sidecar_merge(from_json, to_json):
     sys.exit(merge_status)
 
 
-def group(bids_dir, container, acq_group_level, config, output_prefix):
+def group(bids_dir, acq_group_level, config, output_prefix):
     """Find key and param groups.
 
     Parameters
     ----------
     bids_dir : :obj:`pathlib.Path`
         Path to the BIDS directory.
-    container : :obj:`str`
-        Container in which to run the workflow.
     acq_group_level : {"subject", "session"}
         Level at which acquisition groups are created.
     config : :obj:`pathlib.Path`
@@ -351,76 +282,14 @@ def group(bids_dir, container, acq_group_level, config, output_prefix):
         Output filename prefix.
     """
     # Run directly from python using
-    if container is None:
-        bod = CuBIDS(
-            data_root=str(bids_dir),
-            acq_group_level=acq_group_level,
-            grouping_config=config,
-        )
-        bod.get_tsvs(
-            str(output_prefix),
-        )
-        sys.exit(0)
-
-    # Run it through a container
-    container_type = _get_container_type(container)
-    bids_dir_link = str(bids_dir.absolute()) + ":/bids"
-    output_dir_link = str(output_prefix.parent.absolute()) + ":/tsv:rw"
-
-    apply_config = config is not None
-    if apply_config:
-        input_config_dir_link = str(config.parent.absolute()) + ":/in_config:ro"
-        linked_input_config = "/in_config/" + config.name
-
-    linked_output_prefix = "/tsv/" + output_prefix.name
-    if container_type == "docker":
-        cmd = [
-            "docker",
-            "run",
-            "--rm",
-            "-v",
-            bids_dir_link,
-            "-v",
-            GIT_CONFIG + ":/root/.gitconfig",
-            "-v",
-            output_dir_link,
-            "--entrypoint",
-            "cubids-group",
-            container,
-            "/bids",
-            linked_output_prefix,
-        ]
-        if apply_config:
-            cmd.insert(3, "-v")
-            cmd.insert(4, input_config_dir_link)
-            cmd += ["--config", linked_input_config]
-
-    elif container_type == "singularity":
-        cmd = [
-            "singularity",
-            "exec",
-            "--cleanenv",
-            "-B",
-            bids_dir_link,
-            "-B",
-            output_dir_link,
-            container,
-            "cubids-group",
-            "/bids",
-            linked_output_prefix,
-        ]
-        if apply_config:
-            cmd.insert(3, "-B")
-            cmd.insert(4, input_config_dir_link)
-            cmd += ["--config", linked_input_config]
-
-    if acq_group_level:
-        cmd.append("--acq-group-level")
-        cmd.append(str(acq_group_level))
-
-    logger.info("RUNNING: " + " ".join(cmd))
-    proc = subprocess.run(cmd)
-    sys.exit(proc.returncode)
+    bod = CuBIDS(
+        data_root=str(bids_dir),
+        acq_group_level=acq_group_level,
+        grouping_config=config,
+    )
+    bod.get_tsvs(
+        str(output_prefix),
+    )
 
 
 def apply(
@@ -431,7 +300,6 @@ def apply(
     edited_summary_tsv,
     files_tsv,
     new_tsv_prefix,
-    container,
 ):
     """Apply the tsv changes.
 
@@ -451,219 +319,55 @@ def apply(
         Path to the files tsv.
     new_tsv_prefix : :obj:`pathlib.Path`
         Path to the new tsv prefix.
-    container : :obj:`str`
-        Container in which to run the workflow.
     """
     # Run directly from python using
-    if container is None:
-        bod = CuBIDS(
-            data_root=str(bids_dir),
-            use_datalad=use_datalad,
-            acq_group_level=acq_group_level,
-            grouping_config=config,
-        )
-        if use_datalad:
-            if not bod.is_datalad_clean():
-                raise Exception("Untracked change in " + str(bids_dir))
-        bod.apply_tsv_changes(
-            str(edited_summary_tsv),
-            str(files_tsv),
-            str(new_tsv_prefix),
-            raise_on_error=False,
-        )
-        return
-
-    # Run it through a container
-    container_type = _get_container_type(container)
-    bids_dir_link = str(bids_dir.absolute()) + ":/bids"
-    input_summary_tsv_dir_link = str(edited_summary_tsv.parent.absolute()) + ":/in_summary_tsv:ro"
-    input_files_tsv_dir_link = str(edited_summary_tsv.parent.absolute()) + ":/in_files_tsv:ro"
-    output_tsv_dir_link = str(new_tsv_prefix.parent.absolute()) + ":/out_tsv:rw"
-
-    # FROM BOND-GROUP
-    apply_config = config is not None
-    if apply_config:
-        input_config_dir_link = str(config.parent.absolute()) + ":/in_config:ro"
-        linked_input_config = "/in_config/" + config.name
-
-    linked_output_prefix = "/tsv/" + new_tsv_prefix.name
-
-    ####
-    linked_input_summary_tsv = "/in_summary_tsv/" + edited_summary_tsv.name
-    linked_input_files_tsv = "/in_files_tsv/" + files_tsv.name
-    linked_output_prefix = "/out_tsv/" + new_tsv_prefix.name
-    if container_type == "docker":
-        cmd = [
-            "docker",
-            "run",
-            "--rm",
-            "-v",
-            bids_dir_link,
-            "-v",
-            GIT_CONFIG + ":/root/.gitconfig",
-            "-v",
-            input_summary_tsv_dir_link,
-            "-v",
-            input_files_tsv_dir_link,
-            "-v",
-            output_tsv_dir_link,
-            "--entrypoint",
-            "cubids-apply",
-            container,
-            "/bids",
-            linked_input_summary_tsv,
-            linked_input_files_tsv,
-            linked_output_prefix,
-        ]
-        if apply_config:
-            cmd.insert(3, "-v")
-            cmd.insert(4, input_config_dir_link)
-            cmd += ["--config", linked_input_config]
-
-    elif container_type == "singularity":
-        cmd = [
-            "singularity",
-            "exec",
-            "--cleanenv",
-            "-B",
-            bids_dir_link,
-            "-B",
-            input_summary_tsv_dir_link,
-            "-B",
-            input_files_tsv_dir_link,
-            "-B",
-            output_tsv_dir_link,
-            container,
-            "cubids-apply",
-            "/bids",
-            linked_input_summary_tsv,
-            linked_input_files_tsv,
-            linked_output_prefix,
-        ]
-        if apply_config:
-            cmd.insert(3, "-B")
-            cmd.insert(4, input_config_dir_link)
-            cmd += ["--config", linked_input_config]
-
+    bod = CuBIDS(
+        data_root=str(bids_dir),
+        use_datalad=use_datalad,
+        acq_group_level=acq_group_level,
+        grouping_config=config,
+    )
     if use_datalad:
-        cmd.append("--use-datalad")
+        if not bod.is_datalad_clean():
+            raise Exception("Untracked change in " + str(bids_dir))
+    bod.apply_tsv_changes(
+        str(edited_summary_tsv),
+        str(files_tsv),
+        str(new_tsv_prefix),
+        raise_on_error=False,
+    )
 
-    if acq_group_level:
-        cmd.append("--acq-group-level")
-        cmd.append(str(acq_group_level))
 
-    logger.info("RUNNING: " + " ".join(cmd))
-    proc = subprocess.run(cmd)
-    sys.exit(proc.returncode)
-
-
-def datalad_save(bids_dir, container, m):
+def datalad_save(bids_dir, m):
     """Perform datalad save.
 
     Parameters
     ----------
     bids_dir : :obj:`pathlib.Path`
         Path to the BIDS directory.
-    container : :obj:`str`
-        Container in which to run the workflow.
     m : :obj:`str`
         Commit message.
     """
     # Run directly from python using
-    if container is None:
-        bod = CuBIDS(data_root=str(bids_dir), use_datalad=True)
-        bod.datalad_save(message=m)
-        sys.exit(0)
-
-    # Run it through a container
-    container_type = _get_container_type(container)
-    bids_dir_link = str(bids_dir.absolute()) + ":/bids"
-    if container_type == "docker":
-        cmd = [
-            "docker",
-            "run",
-            "--rm",
-            "-v",
-            bids_dir_link,
-            "-v",
-            GIT_CONFIG + ":/root/.gitconfig",
-            "--entrypoint",
-            "cubids-datalad-save",
-            container,
-            "/bids",
-            "-m",
-            m,
-        ]
-    elif container_type == "singularity":
-        cmd = [
-            "singularity",
-            "exec",
-            "--cleanenv",
-            "-B",
-            bids_dir_link,
-            container,
-            "cubids-datalad-save",
-            "/bids",
-            "-m",
-            m,
-        ]
-    logger.info("RUNNING: " + " ".join(cmd))
-    proc = subprocess.run(cmd)
-    sys.exit(proc.returncode)
+    bod = CuBIDS(data_root=str(bids_dir), use_datalad=True)
+    bod.datalad_save(message=m)
 
 
-def undo(bids_dir, container):
+def undo(bids_dir):
     """Revert the most recent commit.
 
     Parameters
     ----------
     bids_dir : :obj:`pathlib.Path`
         Path to the BIDS directory.
-    container : :obj:`str`
-        Container in which to run the workflow.
     """
     # Run directly from python using
-    if container is None:
-        bod = CuBIDS(data_root=str(bids_dir), use_datalad=True)
-        bod.datalad_undo_last_commit()
-        sys.exit(0)
-
-    # Run it through a container
-    container_type = _get_container_type(container)
-    bids_dir_link = str(bids_dir.absolute()) + ":/bids"
-    if container_type == "docker":
-        cmd = [
-            "docker",
-            "run",
-            "--rm",
-            "-v",
-            bids_dir_link,
-            "-v",
-            GIT_CONFIG + ":/root/.gitconfig",
-            "--entrypoint",
-            "cubids-undo",
-            container,
-            "/bids",
-        ]
-    elif container_type == "singularity":
-        cmd = [
-            "singularity",
-            "exec",
-            "--cleanenv",
-            "-B",
-            bids_dir_link,
-            container,
-            "cubids-undo",
-            "/bids",
-        ]
-    logger.info("RUNNING: " + " ".join(cmd))
-    proc = subprocess.run(cmd)
-    sys.exit(proc.returncode)
+    bod = CuBIDS(data_root=str(bids_dir), use_datalad=True)
+    bod.datalad_undo_last_commit()
 
 
 def copy_exemplars(
     bids_dir,
-    container,
     use_datalad,
     exemplars_dir,
     exemplars_tsv,
@@ -676,8 +380,6 @@ def copy_exemplars(
     ----------
     bids_dir : :obj:`pathlib.Path`
         Path to the BIDS directory.
-    container : :obj:`str`
-        Container in which to run the workflow.
     use_datalad : :obj:`bool`
         Use datalad to track changes.
     exemplars_dir : :obj:`pathlib.Path`
@@ -690,268 +392,81 @@ def copy_exemplars(
         Force unlock the dataset.
     """
     # Run directly from python using
-    if container is None:
-        bod = CuBIDS(data_root=str(bids_dir), use_datalad=use_datalad, force_unlock=force_unlock)
-        if use_datalad:
-            if not bod.is_datalad_clean():
-                raise Exception(
-                    "Untracked changes. Need to save "
-                    + str(bids_dir)
-                    + " before coyping exemplars"
-                )
-        bod.copy_exemplars(
-            str(exemplars_dir),
-            str(exemplars_tsv),
-            min_group_size=min_group_size,
-        )
-        sys.exit(0)
-
-    # Run it through a container
-    container_type = _get_container_type(container)
-    bids_dir_link = str(bids_dir.absolute()) + ":/bids:ro"
-    exemplars_dir_link = str(exemplars_dir.absolute()) + ":/exemplars:ro"
-    exemplars_tsv_link = str(exemplars_tsv.absolute()) + ":/in_tsv:ro"
-    if container_type == "docker":
-        cmd = [
-            "docker",
-            "run",
-            "--rm",
-            "-v",
-            bids_dir_link,
-            "-v",
-            exemplars_dir_link,
-            "-v",
-            GIT_CONFIG + ":/root/.gitconfig",
-            "-v",
-            exemplars_tsv_link,
-            "--entrypoint",
-            "cubids-copy-exemplars",
-            container,
-            "/bids",
-            "/exemplars",
-            "/in_tsv",
-        ]
-
-        if force_unlock:
-            cmd.append("--force-unlock")
-
-        if min_group_size:
-            cmd.append("--min-group-size")
-
-    elif container_type == "singularity":
-        cmd = [
-            "singularity",
-            "exec",
-            "--cleanenv",
-            "-B",
-            bids_dir_link,
-            "-B",
-            exemplars_dir_link,
-            "-B",
-            exemplars_tsv_link,
-            container,
-            "cubids-copy-exemplars",
-            "/bids",
-            "/exemplars",
-            "/in_tsv",
-        ]
-        if force_unlock:
-            cmd.append("--force-unlock")
-
-        if min_group_size:
-            cmd.append("--min-group-size")
-
-    logger.info("RUNNING: " + " ".join(cmd))
-    proc = subprocess.run(cmd)
-    sys.exit(proc.returncode)
+    bod = CuBIDS(data_root=str(bids_dir), use_datalad=use_datalad, force_unlock=force_unlock)
+    if use_datalad:
+        if not bod.is_datalad_clean():
+            raise Exception(
+                "Untracked changes. Need to save " + str(bids_dir) + " before coyping exemplars"
+            )
+    bod.copy_exemplars(
+        str(exemplars_dir),
+        str(exemplars_tsv),
+        min_group_size=min_group_size,
+    )
 
 
-def add_nifti_info(bids_dir, container, use_datalad, force_unlock):
+def add_nifti_info(bids_dir, use_datalad, force_unlock):
     """Add information from nifti files to the dataset's sidecars.
 
     Parameters
     ----------
     bids_dir : :obj:`pathlib.Path`
         Path to the BIDS directory.
-    container : :obj:`str`
-        Container in which to run the workflow.
     use_datalad : :obj:`bool`
         Use datalad to track changes.
     force_unlock : :obj:`bool`
         Force unlock the dataset.
     """
     # Run directly from python using
-    if container is None:
-        bod = CuBIDS(
-            data_root=str(bids_dir),
-            use_datalad=use_datalad,
-            force_unlock=force_unlock,
-        )
-        if use_datalad:
-            if not bod.is_datalad_clean():
-                raise Exception("Untracked change in " + str(bids_dir))
-            # if bod.is_datalad_clean() and not force_unlock:
-            #     raise Exception("Need to unlock " + str(bids_dir))
-        bod.add_nifti_info()
-        sys.exit(0)
-
-    # Run it through a container
-    container_type = _get_container_type(container)
-    bids_dir_link = str(bids_dir.absolute()) + ":/bids:ro"
-    if container_type == "docker":
-        cmd = [
-            "docker",
-            "run",
-            "--rm",
-            "-v",
-            bids_dir_link,
-            "-v",
-            GIT_CONFIG + ":/root/.gitconfig",
-            "--entrypoint",
-            "cubids-add-nifti-info",
-            container,
-            "/bids",
-        ]
-
-        if force_unlock:
-            cmd.append("--force-unlock")
-    elif container_type == "singularity":
-        cmd = [
-            "singularity",
-            "exec",
-            "--cleanenv",
-            "-B",
-            bids_dir_link,
-            container,
-            "cubids-add-nifti-info",
-            "/bids",
-        ]
-        if force_unlock:
-            cmd.append("--force-unlock")
-
-    logger.info("RUNNING: " + " ".join(cmd))
-    proc = subprocess.run(cmd)
-    sys.exit(proc.returncode)
+    bod = CuBIDS(
+        data_root=str(bids_dir),
+        use_datalad=use_datalad,
+        force_unlock=force_unlock,
+    )
+    if use_datalad:
+        if not bod.is_datalad_clean():
+            raise Exception("Untracked change in " + str(bids_dir))
+        # if bod.is_datalad_clean() and not force_unlock:
+        #     raise Exception("Need to unlock " + str(bids_dir))
+    bod.add_nifti_info()
 
 
-def purge(bids_dir, container, use_datalad, scans):
+def purge(bids_dir, use_datalad, scans):
     """Purge scan associations.
 
     Parameters
     ----------
     bids_dir : :obj:`pathlib.Path`
         Path to the BIDS directory.
-    container : :obj:`str`
-        Container in which to run the workflow.
     use_datalad : :obj:`bool`
         Use datalad to track changes.
     scans : :obj:`pathlib.Path`
         Path to the scans tsv.
     """
     # Run directly from python using
-    if container is None:
-        bod = CuBIDS(data_root=str(bids_dir), use_datalad=use_datalad)
-        if use_datalad:
-            if not bod.is_datalad_clean():
-                raise Exception("Untracked change in " + str(bids_dir))
-        bod.purge(str(scans))
-        sys.exit(0)
-
-    # Run it through a container
-    container_type = _get_container_type(container)
-    bids_dir_link = str(bids_dir.absolute()) + ":/bids"
-    input_scans_link = str(scans.parent.absolute()) + ":/in_scans:ro"
-    if container_type == "docker":
-        cmd = [
-            "docker",
-            "run",
-            "--rm",
-            "-v",
-            bids_dir_link,
-            "-v",
-            GIT_CONFIG + ":/root/.gitconfig",
-            "-v",
-            input_scans_link,
-            "--entrypoint",
-            "cubids-purge",
-            container,
-            "/bids",
-            input_scans_link,
-        ]
-
-    elif container_type == "singularity":
-        cmd = [
-            "singularity",
-            "exec",
-            "--cleanenv",
-            "-B",
-            bids_dir_link,
-            "-B",
-            input_scans_link,
-            container,
-            "cubids-purge",
-            "/bids",
-            input_scans_link,
-        ]
-    logger.info("RUNNING: " + " ".join(cmd))
+    bod = CuBIDS(data_root=str(bids_dir), use_datalad=use_datalad)
     if use_datalad:
-        cmd.append("--use-datalad")
-    proc = subprocess.run(cmd)
-    sys.exit(proc.returncode)
+        if not bod.is_datalad_clean():
+            raise Exception("Untracked change in " + str(bids_dir))
+    bod.purge(str(scans))
 
 
-def remove_metadata_fields(bids_dir, container, fields):
+def remove_metadata_fields(bids_dir, fields):
     """Delete fields from metadata.
 
     Parameters
     ----------
     bids_dir : :obj:`pathlib.Path`
         Path to the BIDS directory.
-    container : :obj:`str`
-        Container in which to run the workflow.
     fields : :obj:`list` of :obj:`str`
         List of fields to remove.
     """
     # Run directly from python
-    if container is None:
-        bod = CuBIDS(data_root=str(bids_dir), use_datalad=False)
-        bod.remove_metadata_fields(fields)
-        sys.exit(0)
-
-    # Run it through a container
-    container_type = _get_container_type(container)
-    bids_dir_link = str(bids_dir.absolute()) + ":/bids:rw"
-    if container_type == "docker":
-        cmd = [
-            "docker",
-            "run",
-            "--rm",
-            "-v",
-            bids_dir_link,
-            "--entrypoint",
-            "cubids-remove-metadata-fields",
-            container,
-            "/bids",
-            "--fields",
-        ] + fields
-    elif container_type == "singularity":
-        cmd = [
-            "singularity",
-            "exec",
-            "--cleanenv",
-            "-B",
-            bids_dir_link,
-            container,
-            "cubids-remove-metadata-fields",
-            "/bids",
-            "--fields",
-        ] + fields
-    logger.info("RUNNING: " + " ".join(cmd))
-    proc = subprocess.run(cmd)
-    sys.exit(proc.returncode)
-
-
-def print_metadata_fields(bids_dir, container):
+    bod = CuBIDS(data_root=str(bids_dir), use_datalad=False)
+    bod.remove_metadata_fields(fields)
+
+
+def print_metadata_fields(bids_dir):
     """Print unique metadata fields from a BIDS dataset.
 
     This function identifies and prints all unique metadata fields from
@@ -963,17 +478,12 @@ def print_metadata_fields(bids_dir, container):
     ----------
     bids_dir : :obj:`pathlib.Path`
         Path to the BIDS directory containing the `dataset_description.json` file.
-    container : :obj:`str`
-        Name of the container (e.g., Docker, Singularity) to use for running the
-        `cubids print-metadata-fields` command. If `None`, the operation is performed
-        directly in Python without a container.
 
     Raises
     ------
     SystemExit
         Raised in the following cases:
         - The `dataset_description.json` file is not found in the BIDS directory.
-        - The subprocess returns a non-zero exit code when executed in a container.
 
     """
     # Check if dataset_description.json exists
@@ -983,39 +493,7 @@ def print_metadata_fields(bids_dir, container):
         sys.exit(1)
 
     # Run directly from python
-    if container is None:
-        bod = CuBIDS(data_root=str(bids_dir), use_datalad=False)
-        fields = bod.get_all_metadata_fields()
-        print("\n".join(fields))  # logger not printing
-        # logger.info("\n".join(fields))
-        sys.exit(0)
-
-    # Run it through a container
-    container_type = _get_container_type(container)
-    bids_dir_link = str(bids_dir.absolute()) + ":/bids:ro"
-    if container_type == "docker":
-        cmd = [
-            "docker",
-            "run",
-            "--rm",
-            "-v",
-            bids_dir_link,
-            "--entrypoint",
-            "cubids-print-metadata-fields",
-            container,
-            "/bids",
-        ]
-    elif container_type == "singularity":
-        cmd = [
-            "singularity",
-            "exec",
-            "--cleanenv",
-            "-B",
-            bids_dir_link,
-            container,
-            "cubids-print-metadata-fields",
-            "/bids",
-        ]
-    logger.info("RUNNING: " + " ".join(cmd))
-    proc = subprocess.run(cmd)
-    sys.exit(proc.returncode)
+    bod = CuBIDS(data_root=str(bids_dir), use_datalad=False)
+    fields = bod.get_all_metadata_fields()
+    print("\n".join(fields))  # logger not printing
+    # logger.info("\n".join(fields))
diff --git a/pyproject.toml b/pyproject.toml
index 1337663..f10f484 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -159,7 +159,6 @@ exclude = [
     "cubids/config.py",
     "cubids/data/",
     "cubids/tests/",
-    "cubids/utils/sentry.py",
 ]
 ignore = ["D107", "E203", "E402", "E722", "W503", "N803", "N806", "N815"]
 per-file-ignores = [


Test patch:
diff --git a/cubids/tests/test_apply.py b/cubids/tests/test_apply.py
index 51afa64..0ff36ce 100644
--- a/cubids/tests/test_apply.py
+++ b/cubids/tests/test_apply.py
@@ -340,7 +340,6 @@ def test_cubids_apply_intendedfor(
             edited_summary_tsv=summary_tsv,
             files_tsv=files_tsv,
             new_tsv_prefix=None,
-            container=None,
         )
 
         with open(fmap_json) as f:
@@ -357,5 +356,4 @@ def test_cubids_apply_intendedfor(
                 edited_summary_tsv=summary_tsv,
                 files_tsv=files_tsv,
                 new_tsv_prefix=None,
-                container=None,
             )
diff --git a/cubids/tests/test_bond.py b/cubids/tests/test_bond.py
index 9f7f5b3..7b2d60a 100644
--- a/cubids/tests/test_bond.py
+++ b/cubids/tests/test_bond.py
@@ -24,13 +24,13 @@ from cubids.tests.utils import (
     get_data,
 )
 from cubids.validator import (
+    bids_validator_version,
     build_validator_call,
+    extract_summary_info,
+    get_bids_validator_version,
     parse_validator_output,
     run_validator,
-    get_bids_validator_version,
-    extract_summary_info,
     update_dataset_description,
-    bids_validator_version,
 )
 
 COMPLETE_KEY_GROUPS = [
diff --git a/cubids/tests/utils.py b/cubids/tests/utils.py
index fd7e2ab..a51d107 100644
--- a/cubids/tests/utils.py
+++ b/cubids/tests/utils.py
@@ -1,6 +1,7 @@
 """Utility functions for CuBIDS' tests."""
 
 import hashlib
+import importlib.resources
 import json
 import os
 import shutil
@@ -10,7 +11,6 @@ from pathlib import Path
 import nibabel as nb
 import numpy as np
 import pandas as pd
-import importlib.resources
 
 TEST_DATA = importlib.resources.files("cubids") / "tests/data"