From 9075c93fd20ea8ed24c125c0dece91b76bcdda14 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Thu, 24 Aug 2023 17:30:54 +0200 Subject: [PATCH 01/19] update: add support for reading elements from file --- junifer/api/cli.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/junifer/api/cli.py b/junifer/api/cli.py index 62426da2b8..19ebbf65d4 100644 --- a/junifer/api/cli.py +++ b/junifer/api/cli.py @@ -51,8 +51,16 @@ def _parse_elements(element: str, config: Dict) -> Union[List, None]: logger.debug(f"Parsing elements: {element}") if len(element) == 0: return None - # TODO: If len == 1, check if its a file, then parse elements from file - elements = [tuple(x.split(",")) if "," in x else x for x in element] + # Check if the element is a file for single element; + # if yes, then parse elements from it + if len(element) == 1 and Path(element[0]).resolve().is_file(): + fetched_element = _parse_elements_file(Path(element[0]).resolve()) + else: + fetched_element = element + # Process multi-keyed elements + elements = [ + tuple(x.split(",")) if "," in x else x for x in fetched_element + ] logger.debug(f"Parsed elements: {elements}") if elements is not None and "elements" in config: warn_with_log( @@ -74,6 +82,27 @@ def _parse_elements(element: str, config: Dict) -> Union[List, None]: return elements +def _parse_elements_file(filepath: Path) -> List[str]: + """Parse elements from file. + + Parameters + ---------- + filepath : pathlib.Path + The path to the element file. + + Returns + ------- + list of str + The element(s) as list. + + """ + # Read file + with open(filepath) as file: + raw_elements = file.readlines() + # Strip whitespace and return + return [element.strip() for element in raw_elements] + + def _validate_verbose( ctx: click.Context, param: str, value: str ) -> Union[str, int]: From 66ea53ad775f640a55199d37c47d6f16a11dbd50 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Thu, 24 Aug 2023 17:32:38 +0200 Subject: [PATCH 02/19] update: add test for reading elements from file --- junifer/api/tests/test_cli.py | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/junifer/api/tests/test_cli.py b/junifer/api/tests/test_cli.py index aa01285e4e..7955f67b2d 100644 --- a/junifer/api/tests/test_cli.py +++ b/junifer/api/tests/test_cli.py @@ -74,6 +74,57 @@ def test_run_and_collect_commands( assert collect_result.exit_code == 0 +@pytest.mark.parametrize( + "elements", + [ + "sub-01", + "sub-01\nsub-02", + ], +) +def test_run_using_element_file(tmp_path: Path, elements: str) -> None: + """Test run command using element file. + + Parameters + ---------- + tmp_path : pathlib.Path + The path to the test directory. + elements : str + The elements to write to the element file. + + """ + # Create test file + test_file_path = tmp_path / "elements.txt" + with open(test_file_path, "w") as f: + f.write(elements) + + # Get test config + infile = Path(__file__).parent / "data" / "gmd_mean.yaml" + # Read test config + contents = yaml.load(infile) + # Working directory + workdir = tmp_path / "workdir" + contents["workdir"] = str(workdir.resolve()) + # Output directory + outdir = tmp_path / "outdir" + # Storage + contents["storage"]["uri"] = str(outdir.resolve()) + # Write new test config + outfile = tmp_path / "in.yaml" + yaml.dump(contents, stream=outfile) + # Run command arguments + run_args = [ + str(outfile.absolute()), + "--verbose", + "debug", + "--element", + str(test_file_path.resolve()), + ] + # Invoke run command + run_result = runner.invoke(run, run_args) + # Check + assert run_result.exit_code == 0 + + def test_wtf_short() -> None: """Test short version of wtf command.""" # Invoke wtf command From 517a1c5d42736415c135e4c07d5e56f476a4f42c Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Thu, 24 Aug 2023 18:57:55 +0200 Subject: [PATCH 03/19] update: add filter() method for BaseDataGrabber --- junifer/datagrabber/base.py | 45 +++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/junifer/datagrabber/base.py b/junifer/datagrabber/base.py index 37e122033b..011befe046 100644 --- a/junifer/datagrabber/base.py +++ b/junifer/datagrabber/base.py @@ -117,6 +117,51 @@ def datadir(self) -> Path: """ return self._datadir + def filter(self, selection: List[Union[str, Tuple[str]]]) -> Iterator: + """Filter elements to be grabbed. + + Parameters + ---------- + selection : list of str or tuple + The list of partial element key values to filter using. + + Yields + ------ + object + An element that can be indexed by the DataGrabber. + + """ + + def filter_func(element: Union[str, Tuple[str]]) -> bool: + """Filter element based on selection. + + Parameters + ---------- + element : str or tuple of str + The element to be filtered. + + Returns + ------- + bool + If the element passes the filter or not. + + """ + # Convert element to tuple + if not isinstance(element, tuple): + element = (element,) + # Filter based on selection kind + if isinstance(selection[0], str): + for opt in selection: + if opt in element: + return True + elif isinstance(selection[0], tuple): + for opt in selection: + if set(opt).issubset(element): + return True + return False + + yield from filter(filter_func, self.get_elements()) + @abstractmethod def get_element_keys(self) -> List[str]: """Get element keys. From 744b36a50638e9f6668336867d3a0cdf817b980c Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Thu, 24 Aug 2023 18:58:45 +0200 Subject: [PATCH 04/19] update: add tests for BaseDataGrabber.filter() --- junifer/datagrabber/tests/test_base.py | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/junifer/datagrabber/tests/test_base.py b/junifer/datagrabber/tests/test_base.py index 6769033036..dd8b75ac7c 100644 --- a/junifer/datagrabber/tests/test_base.py +++ b/junifer/datagrabber/tests/test_base.py @@ -67,3 +67,60 @@ def get_element_keys(self): with pytest.raises(NotImplementedError): dg.get_item(subject=1) # type: ignore + + +def test_BaseDataGrabber_filter_single() -> None: + """Test single-keyed element filter for BaseDataGrabber.""" + + # Create concrete class + class FilterDataGrabber(BaseDataGrabber): + def get_item(self, subject): + return {"BOLD": {}} + + def get_elements(self): + return ["sub01", "sub02", "sub03"] + + def get_element_keys(self): + return ["subject"] + + dg = FilterDataGrabber(datadir="/tmp", types=["BOLD"]) + with dg: + assert "sub01" in list(dg.filter(["sub01"])) + assert "sub02" not in list(dg.filter(["sub01"])) + + +def test_BaseDataGrabber_filter_multi() -> None: + """Test multi-keyed element filter for BaseDataGrabber.""" + + # Create concrete class + class FilterDataGrabber(BaseDataGrabber): + def get_item(self, subject): + return {"BOLD": {}} + + def get_elements(self): + return [ + ("sub01", "rest"), + ("sub01", "movie"), + ("sub02", "rest"), + ("sub02", "movie"), + ("sub03", "rest"), + ("sub03", "movie"), + ] + + def get_element_keys(self): + return ["subject", "task"] + + dg = FilterDataGrabber(datadir="/tmp", types=["BOLD"]) + with dg: + assert ("sub01", "rest") in list( + dg.filter([("sub01", "rest")]) # type: ignore + ) + assert ("sub01", "movie") not in list( + dg.filter([("sub01", "rest")]) # type: ignore + ) + assert ("sub02", "rest") not in list( + dg.filter([("sub01", "rest")]) # type: ignore + ) + assert ("sub02", "movie") not in list( + dg.filter([("sub01", "rest")]) # type: ignore + ) From 3c79b6435e4ed54723bb9188ef8277700e48470e Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Thu, 24 Aug 2023 18:59:09 +0200 Subject: [PATCH 05/19] update: adapt code to allow element filtering while fitting MarkerCollection --- junifer/api/functions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/junifer/api/functions.py b/junifer/api/functions.py index 37e94ad2d2..727d4581a7 100644 --- a/junifer/api/functions.py +++ b/junifer/api/functions.py @@ -163,7 +163,9 @@ def run( # Fit elements with datagrabber_object: if elements is not None: - for t_element in elements: + for t_element in datagrabber_object.filter( + elements # type: ignore + ): mc.fit(datagrabber_object[t_element]) else: for t_element in datagrabber_object: From 96d32a02fd83aa690af6c76cb961ed4ac00f6aab Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Thu, 24 Aug 2023 19:00:54 +0200 Subject: [PATCH 06/19] chore: improve type annotation and docstring in CLI functions --- junifer/api/cli.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/junifer/api/cli.py b/junifer/api/cli.py index 19ebbf65d4..0384c4f202 100644 --- a/junifer/api/cli.py +++ b/junifer/api/cli.py @@ -8,7 +8,7 @@ import subprocess import sys from pathlib import Path -from typing import Dict, List, Union +from typing import Dict, List, Tuple, Union import click @@ -32,20 +32,32 @@ ) -def _parse_elements(element: str, config: Dict) -> Union[List, None]: +def _parse_elements(element: Tuple[str], config: Dict) -> Union[List, None]: """Parse elements from cli. Parameters ---------- - element : str - The element to operate on. + element : tuple of str + The element(s) to operate on. config : dict The configuration to operate using. Returns ------- - list - The element(s) as list. + list or None + The element(s) as list or None. + + Raises + ------ + ValueError + If no element is found either in the command-line options or + the configuration file. + + Warns + ----- + RuntimeWarning + If elements are specified both via the command-line options and + the configuration file. """ logger.debug(f"Parsing elements: {element}") @@ -162,7 +174,9 @@ def cli() -> None: # pragma: no cover callback=_validate_verbose, default="info", ) -def run(filepath: click.Path, element: str, verbose: Union[str, int]) -> None: +def run( + filepath: click.Path, element: Tuple[str], verbose: Union[str, int] +) -> None: """Run command for CLI. \f @@ -171,8 +185,8 @@ def run(filepath: click.Path, element: str, verbose: Union[str, int]) -> None: ---------- filepath : click.Path The filepath to the configuration file. - element : str - The element to operate using. + element : tuple of str + The element to operate on. verbose : click.Choice The verbosity level: warning, info or debug (default "info"). From 9e011f2be4f82c6f0e56cfe7f41388e3358a4513 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Thu, 24 Aug 2023 19:02:01 +0200 Subject: [PATCH 07/19] chore: improve commentary in CLI functions --- junifer/api/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/junifer/api/cli.py b/junifer/api/cli.py index 0384c4f202..b17c0fcfb8 100644 --- a/junifer/api/cli.py +++ b/junifer/api/cli.py @@ -61,6 +61,7 @@ def _parse_elements(element: Tuple[str], config: Dict) -> Union[List, None]: """ logger.debug(f"Parsing elements: {element}") + # Early return None to continue with all elements if len(element) == 0: return None # Check if the element is a file for single element; From f19f1103e8a3cf70b107b87e8126510ca618fc71 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Thu, 24 Aug 2023 19:02:17 +0200 Subject: [PATCH 08/19] chore: improve log messages in CLI functions --- junifer/api/cli.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/junifer/api/cli.py b/junifer/api/cli.py index b17c0fcfb8..fc631d4d5e 100644 --- a/junifer/api/cli.py +++ b/junifer/api/cli.py @@ -82,14 +82,15 @@ def _parse_elements(element: Tuple[str], config: Dict) -> Union[List, None]: "over the configuration file. That is, the elements specified " "in the command line will be used. The elements specified in " "the configuration file will be ignored. To remove this warning, " - 'please remove the "elements" item from the configuration file.' + "please remove the `elements` item from the configuration file." ) elif elements is None: + # Check in config elements = config.get("elements", None) if elements is None: raise_error( - "The 'elements' key is set in the configuration, but its value" - " is 'None'. It is likely that there is an empty 'elements' " + "The `elements` key is set in the configuration, but its value" + " is `None`. It is likely that there is an empty `elements` " "section in the yaml configuration file." ) return elements From 4aa1d4935e64244f8b763b5fa2d6992b42f206ae Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Thu, 24 Aug 2023 19:02:36 +0200 Subject: [PATCH 09/19] chore: improve docstring in CLI function tests --- junifer/api/tests/test_cli.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/junifer/api/tests/test_cli.py b/junifer/api/tests/test_cli.py index 7955f67b2d..8929537518 100644 --- a/junifer/api/tests/test_cli.py +++ b/junifer/api/tests/test_cli.py @@ -35,7 +35,16 @@ def test_run_and_collect_commands( tmp_path: Path, elements: Tuple[str, ...] ) -> None: - """Test run and collect commands.""" + """Test run and collect commands. + + Parameters + ---------- + tmp_path : pathlib.Path + The path to the test directory. + elements : tuple of str + The elements to operate on. + + """ # Get test config infile = Path(__file__).parent / "data" / "gmd_mean.yaml" # Read test config From e823da468cbe2221f835802ade3ee537e632879a Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Thu, 24 Aug 2023 19:03:22 +0200 Subject: [PATCH 10/19] chore: improve type annotation and docstring in BaseDataGrabber --- junifer/datagrabber/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/junifer/datagrabber/base.py b/junifer/datagrabber/base.py index 011befe046..a34db6bf29 100644 --- a/junifer/datagrabber/base.py +++ b/junifer/datagrabber/base.py @@ -58,12 +58,12 @@ def __iter__(self) -> Iterator: """ yield from self.get_elements() - def __getitem__(self, element: Union[str, Tuple]) -> Dict[str, Dict]: + def __getitem__(self, element: Union[str, Tuple[str]]) -> Dict[str, Dict]: """Enable indexing support. Parameters ---------- - element : str or tuple + element : str or tuple of str The element to be indexed. Returns @@ -181,7 +181,7 @@ def get_element_keys(self) -> List[str]: ) @abstractmethod - def get_elements(self) -> List: + def get_elements(self) -> List[Union[str, Tuple[str]]]: """Get elements. Returns From 7e24f46f9069355f26eff295e12efdc045e61fdd Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 6 Sep 2023 13:22:17 +0200 Subject: [PATCH 11/19] fix: correct element for test_run_single_element_with_preprocessing() --- junifer/api/tests/test_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junifer/api/tests/test_functions.py b/junifer/api/tests/test_functions.py index 83f230b825..008843615d 100644 --- a/junifer/api/tests/test_functions.py +++ b/junifer/api/tests/test_functions.py @@ -120,7 +120,7 @@ def test_run_single_element_with_preprocessing(tmp_path: Path) -> None: preprocessor={ "kind": "fMRIPrepConfoundRemover", }, - elements=["sub-001"], + elements=["sub-01"], ) # Check files files = list(outdir.glob("*.sqlite")) From dc95cb09d241604d0097098fc0cd018398857faf Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 6 Sep 2023 13:59:16 +0200 Subject: [PATCH 12/19] docs: add info about specifying elements via a file for junifer run --- docs/using/running.rst | 50 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/using/running.rst b/docs/using/running.rst index 168f58f753..118e01a3af 100644 --- a/docs/using/running.rst +++ b/docs/using/running.rst @@ -29,20 +29,68 @@ The ``run`` command accepts the following additional arguments: * ``--element``: The *element* to run. If not specified, all elements will be run. This parameter can be specified multiple times to run multiple elements. If the *element* requires several parameters, they can be specified by - separating them with ``,``. + separating them with ``,``. It also accepts a file (e.g., ``elements.txt``) + containing complete or partial element(s). Example of running two elements: +-------------------------------- .. code-block:: bash junifer run config.yaml --element sub-01 --element sub-02 +You can also specify the elements via a text file like so: + +.. code-block:: bash + + junifer run config.yaml --element elements.txt + +And the corresponding ``elements.txt`` would be like so: + +.. code-block:: text + + sub-01 + sub-02 + Example of elements with multiple parameters and verbose output: +---------------------------------------------------------------- .. code-block:: bash junifer run --verbose info config.yaml --element sub-01,ses-01 +You can also specify the elements via a text file like so: + +.. code-block:: bash + + junifer run --verbose info config.yaml --element elements.txt + +And the corresponding ``elements.txt`` would be like so: + +.. code-block:: text + + sub-01,ses-01 + +In case you wanted to run for all possible sessions (e.g., ``ses-01``, +``ses-02``, ``ses-03``) but only for ``sub-01``, you could also do: + +.. code-block:: bash + + junifer run --verbose info config.yaml --element sub-01 + +or, + +.. code-block:: bash + + junifer run --verbose info config.yaml --element elements.txt + +and then the ``elements.txt`` would be like so: + +.. code-block:: text + + sub-01 + + .. _collect: Collecting Results From 2d24214efe37454e62d497c05d6aea2ba816778a Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Wed, 6 Sep 2023 14:04:34 +0200 Subject: [PATCH 13/19] chore: add changelog 182.enh --- docs/changes/newsfragments/182.enh | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changes/newsfragments/182.enh diff --git a/docs/changes/newsfragments/182.enh b/docs/changes/newsfragments/182.enh new file mode 100644 index 0000000000..615c16adef --- /dev/null +++ b/docs/changes/newsfragments/182.enh @@ -0,0 +1 @@ +Support element(s) to be specified via text file for ``--element`` option of ``junifer run`` by `Synchon Mandal`_ From 8c757dc726ceb3232904bbdbad1d32de1ac71ea0 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 6 Oct 2023 12:06:40 +0200 Subject: [PATCH 14/19] update: make api.cli._parse_elements_file() return list of tuple using pandas to read csv --- junifer/api/cli.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/junifer/api/cli.py b/junifer/api/cli.py index fc631d4d5e..2b2e6f9ba1 100644 --- a/junifer/api/cli.py +++ b/junifer/api/cli.py @@ -11,6 +11,7 @@ from typing import Dict, List, Tuple, Union import click +import pandas as pd from ..utils.logging import ( configure_logging, @@ -96,7 +97,7 @@ def _parse_elements(element: Tuple[str], config: Dict) -> Union[List, None]: return elements -def _parse_elements_file(filepath: Path) -> List[str]: +def _parse_elements_file(filepath: Path) -> List[Tuple[str, ...]]: """Parse elements from file. Parameters @@ -106,15 +107,19 @@ def _parse_elements_file(filepath: Path) -> List[str]: Returns ------- - list of str + list of tuple of str The element(s) as list. """ - # Read file - with open(filepath) as file: - raw_elements = file.readlines() - # Strip whitespace and return - return [element.strip() for element in raw_elements] + # Read CSV into dataframe + csv_df = pd.read_csv( + filepath, + header=None, # no header # type: ignore + index_col=False, # no index column + skipinitialspace=True, # no leading space after delimiter + ) + # Convert to list of tuple of str + return list(map(tuple, csv_df.to_numpy().astype(str))) # type: ignore def _validate_verbose( From 430ea6347d267639ada35c77157f4f01741574a4 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 6 Oct 2023 12:07:23 +0200 Subject: [PATCH 15/19] update: slight refactor in element parsing logic in api.cli._parse_elements() --- junifer/api/cli.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/junifer/api/cli.py b/junifer/api/cli.py index 2b2e6f9ba1..2d6ccf343e 100644 --- a/junifer/api/cli.py +++ b/junifer/api/cli.py @@ -68,13 +68,10 @@ def _parse_elements(element: Tuple[str], config: Dict) -> Union[List, None]: # Check if the element is a file for single element; # if yes, then parse elements from it if len(element) == 1 and Path(element[0]).resolve().is_file(): - fetched_element = _parse_elements_file(Path(element[0]).resolve()) + elements = _parse_elements_file(Path(element[0]).resolve()) else: - fetched_element = element - # Process multi-keyed elements - elements = [ - tuple(x.split(",")) if "," in x else x for x in fetched_element - ] + # Process multi-keyed elements + elements = [tuple(x.split(",")) if "," in x else x for x in element] logger.debug(f"Parsed elements: {elements}") if elements is not None and "elements" in config: warn_with_log( From 17703ff03b077ef61bd2b47bb189a76668e901e9 Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Fri, 6 Oct 2023 12:08:03 +0200 Subject: [PATCH 16/19] chore: add edge cases for test_run_using_element_file() --- junifer/api/tests/test_cli.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/junifer/api/tests/test_cli.py b/junifer/api/tests/test_cli.py index 8929537518..f537c3ebb9 100644 --- a/junifer/api/tests/test_cli.py +++ b/junifer/api/tests/test_cli.py @@ -88,6 +88,8 @@ def test_run_and_collect_commands( [ "sub-01", "sub-01\nsub-02", + " sub-01 ", + "sub-01\n sub-02", ], ) def test_run_using_element_file(tmp_path: Path, elements: str) -> None: From 569782f8d734b37a778715a52709d5ade39ad37c Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 10 Oct 2023 13:01:36 +0200 Subject: [PATCH 17/19] fix: remove trailing whitespace from cell entries in dataframe when parsing elements from file --- junifer/api/cli.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/junifer/api/cli.py b/junifer/api/cli.py index 2d6ccf343e..efa4707acb 100644 --- a/junifer/api/cli.py +++ b/junifer/api/cli.py @@ -115,8 +115,12 @@ def _parse_elements_file(filepath: Path) -> List[Tuple[str, ...]]: index_col=False, # no index column skipinitialspace=True, # no leading space after delimiter ) + # Remove trailing whitespace in cell entries + csv_df_trimmed = csv_df.apply( + lambda x: x.str.strip() if x.dtype == "object" else x + ) # Convert to list of tuple of str - return list(map(tuple, csv_df.to_numpy().astype(str))) # type: ignore + return list(map(tuple, csv_df_trimmed.to_numpy().astype(str))) def _validate_verbose( From 123d8150ebe86a711a4e570f3772d6bacd04462f Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 10 Oct 2023 13:02:18 +0200 Subject: [PATCH 18/19] update: add test for multi-element access via element file --- junifer/api/tests/test_cli.py | 49 +++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/junifer/api/tests/test_cli.py b/junifer/api/tests/test_cli.py index f537c3ebb9..d9b9a69bef 100644 --- a/junifer/api/tests/test_cli.py +++ b/junifer/api/tests/test_cli.py @@ -5,13 +5,13 @@ # License: AGPL from pathlib import Path -from typing import Tuple +from typing import List, Tuple import pytest from click.testing import CliRunner from ruamel.yaml import YAML -from junifer.api.cli import collect, run, selftest, wtf +from junifer.api.cli import _parse_elements_file, collect, run, selftest, wtf # Configure YAML class @@ -136,6 +136,51 @@ def test_run_using_element_file(tmp_path: Path, elements: str) -> None: assert run_result.exit_code == 0 +@pytest.mark.parametrize( + "elements, expected_list", + [ + ("sub-01,ses-01", [("sub-01", "ses-01")]), + ( + "sub-01,ses-01\nsub-02,ses-01", + [("sub-01", "ses-01"), ("sub-02", "ses-01")], + ), + ("sub-01, ses-01", [("sub-01", "ses-01")]), + ( + "sub-01, ses-01\nsub-02, ses-01", + [("sub-01", "ses-01"), ("sub-02", "ses-01")], + ), + (" sub-01 , ses-01 ", [("sub-01", "ses-01")]), + ( + " sub-01 , ses-01 \n sub-02, ses-01 ", + [("sub-01", "ses-01"), ("sub-02", "ses-01")], + ), + ], +) +def test_multi_element_access( + tmp_path: Path, elements: str, expected_list: List[Tuple[str, ...]] +) -> None: + """Test mulit-element parsing. + + Parameters + ---------- + tmp_path : pathlib.Path + The path to the test directory. + elements : str + The parametrized elements to write to the element file. + expected_list : list of tuple of str + The parametrized list of element tuples to expect. + + """ + # Create test file + test_file_path = tmp_path / "elements_multi.txt" + with open(test_file_path, "w") as f: + f.write(elements) + # Load element file + read_elements = _parse_elements_file(test_file_path) + # Check + assert read_elements == expected_list + + def test_wtf_short() -> None: """Test short version of wtf command.""" # Invoke wtf command From 8666562c26dfd5ec1827eee17a972d96c11f4fee Mon Sep 17 00:00:00 2001 From: Synchon Mandal Date: Tue, 10 Oct 2023 13:02:50 +0200 Subject: [PATCH 19/19] chore: update docstrings for tests in test_cli.py --- junifer/api/tests/test_cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junifer/api/tests/test_cli.py b/junifer/api/tests/test_cli.py index d9b9a69bef..7d94ca35f4 100644 --- a/junifer/api/tests/test_cli.py +++ b/junifer/api/tests/test_cli.py @@ -42,7 +42,7 @@ def test_run_and_collect_commands( tmp_path : pathlib.Path The path to the test directory. elements : tuple of str - The elements to operate on. + The parametrized elements to operate on. """ # Get test config @@ -100,7 +100,7 @@ def test_run_using_element_file(tmp_path: Path, elements: str) -> None: tmp_path : pathlib.Path The path to the test directory. elements : str - The elements to write to the element file. + The parametrized elements to write to the element file. """ # Create test file