[ENH]: Raise/warn if element is not ran #319
11 changed files with 186 additions and 65 deletions
1
docs/changes/newsfragments/319.enh
Normal file
1
docs/changes/newsfragments/319.enh
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Raise error when partial or complete element selectors are invalid when running the pipeline by `Synchon Mandal`_
|
||||||
|
|
@ -20,7 +20,13 @@ from ..pipeline import (
|
||||||
)
|
)
|
||||||
from ..preprocess import BasePreprocessor
|
from ..preprocess import BasePreprocessor
|
||||||
from ..storage import BaseFeatureStorage
|
from ..storage import BaseFeatureStorage
|
||||||
from ..typing import DataGrabberLike, MarkerLike, PreprocessorLike, StorageLike
|
from ..typing import (
|
||||||
|
DataGrabberLike,
|
||||||
|
Elements,
|
||||||
|
MarkerLike,
|
||||||
|
PreprocessorLike,
|
||||||
|
StorageLike,
|
||||||
|
)
|
||||||
from ..utils import logger, raise_error, warn_with_log, yaml
|
from ..utils import logger, raise_error, warn_with_log, yaml
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -121,7 +127,7 @@ def run(
|
||||||
markers: list[dict],
|
markers: list[dict],
|
||||||
storage: dict,
|
storage: dict,
|
||||||
preprocessors: Optional[list[dict]] = None,
|
preprocessors: Optional[list[dict]] = None,
|
||||||
elements: Optional[list[tuple[str, ...]]] = None,
|
elements: Optional[Elements] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Run the pipeline on the selected element.
|
"""Run the pipeline on the selected element.
|
||||||
|
|
||||||
|
|
@ -147,7 +153,7 @@ def run(
|
||||||
List of preprocessors to use. Each preprocessor is a dict with at
|
List of preprocessors to use. Each preprocessor is a dict with at
|
||||||
least a key ``kind`` specifying the preprocessor to use. All other keys
|
least a key ``kind`` specifying the preprocessor to use. All other keys
|
||||||
are passed to the preprocessor constructor (default None).
|
are passed to the preprocessor constructor (default None).
|
||||||
elements : list of tuple or None, optional
|
elements : list or None, optional
|
||||||
Element(s) to process. Will be used to index the DataGrabber
|
Element(s) to process. Will be used to index the DataGrabber
|
||||||
(default None).
|
(default None).
|
||||||
|
|
||||||
|
|
@ -155,6 +161,8 @@ def run(
|
||||||
------
|
------
|
||||||
ValueError
|
ValueError
|
||||||
If ``workdir.cleanup=False`` when ``len(elements) > 1``.
|
If ``workdir.cleanup=False`` when ``len(elements) > 1``.
|
||||||
|
RuntimeError
|
||||||
|
If invalid element selectors are found.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Conditional to handle workdir config
|
# Conditional to handle workdir config
|
||||||
|
|
@ -208,10 +216,22 @@ def run(
|
||||||
# Fit elements
|
# Fit elements
|
||||||
with datagrabber_object:
|
with datagrabber_object:
|
||||||
if elements is not None:
|
if elements is not None:
|
||||||
for t_element in datagrabber_object.filter(
|
# Keep track of valid selectors
|
||||||
elements # type: ignore
|
valid_elements = []
|
||||||
):
|
for t_element in datagrabber_object.filter(elements):
|
||||||
|
valid_elements.append(t_element)
|
||||||
mc.fit(datagrabber_object[t_element])
|
mc.fit(datagrabber_object[t_element])
|
||||||
|
# Compute invalid selectors
|
||||||
|
invalid_elements = set(elements) - set(valid_elements)
|
||||||
|
# Report if invalid selectors are found
|
||||||
|
if invalid_elements:
|
||||||
|
raise_error(
|
||||||
|
msg=(
|
||||||
|
"The following element selectors are invalid:\n"
|
||||||
|
f"{invalid_elements}"
|
||||||
|
),
|
||||||
|
klass=RuntimeError,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for t_element in datagrabber_object:
|
for t_element in datagrabber_object:
|
||||||
mc.fit(datagrabber_object[t_element])
|
mc.fit(datagrabber_object[t_element])
|
||||||
|
|
@ -243,7 +263,7 @@ def queue(
|
||||||
kind: str,
|
kind: str,
|
||||||
jobname: str = "junifer_job",
|
jobname: str = "junifer_job",
|
||||||
|
Let me try to make it concrete and maybe it'll be easier to understand: For this DataGrabber:
Now if I do: will run all "four" elements of the "subject". Now, if I instead do: it'd just "exit" without the changes in this PR, but give you this: with the current PR. Also, doing this: would give you: Is this what you meant or something else? Let me try to make it concrete and maybe it'll be easier to understand:
For this DataGrabber:
```yaml
...
...
...
datagrabber:
kind: DMCC13Benchmark
types:
- BOLD
...
...
...
```
`junifer list-elements <yaml>` will give you:
```console
...
...
...
sub-f9057kp,ses-wave1pro,Rest,AP,1
sub-f9057kp,ses-wave1pro,Rest,PA,2
sub-f9057kp,ses-wave1rea,Rest,AP,1
sub-f9057kp,ses-wave1rea,Rest,PA,2
```
Now if I do:
```console
junifer run <yaml> --element sub-f9057kp
```
will run all "four" elements of the "subject".
Now, if I instead do:
```console
junifer run <yaml> --element sub-f9057k
```
it'd just "exit" without the changes in this PR, but give you this:
```console
...
...
...
RuntimeError: The following element selectors are invalid:
{'sub-f9057k'}
```
with the current PR. Also, doing this:
```console
junifer run <yaml> --element sub-f9057kp,ses-wave1pr
```
would give you:
```console
...
...
...
RuntimeError: The following element selectors are invalid:
{('sub-f9057kp', 'ses-wave1pr')}
```
Is this what you meant or something else?
That's what I meant. I'm kind of rusty now, but I had the impression that the PR would not account for that logic. That's what I meant. I'm kind of rusty now, but I had the impression that the PR would not account for that logic.
Resolving it then. Resolving it then.
|
|||||||
overwrite: bool = False,
|
overwrite: bool = False,
|
||||||
elements: Optional[list[tuple[str, ...]]] = None,
|
elements: Optional[Elements] = None,
|
||||||
**kwargs: Union[str, int, bool, dict, tuple, list],
|
**kwargs: Union[str, int, bool, dict, tuple, list],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Queue a job to be executed later.
|
"""Queue a job to be executed later.
|
||||||
|
|
@ -258,7 +278,7 @@ def queue(
|
||||||
The name of the job (default "junifer_job").
|
The name of the job (default "junifer_job").
|
||||||
overwrite : bool, optional
|
overwrite : bool, optional
|
||||||
Whether to overwrite if job directory already exists (default False).
|
Whether to overwrite if job directory already exists (default False).
|
||||||
elements : list of tuple or None, optional
|
elements : list or None, optional
|
||||||
Element(s) to process. Will be used to index the DataGrabber
|
Element(s) to process. Will be used to index the DataGrabber
|
||||||
(default None).
|
(default None).
|
||||||
**kwargs : dict
|
**kwargs : dict
|
||||||
|
|
@ -341,7 +361,7 @@ def queue(
|
||||||
elements = dg.get_elements()
|
elements = dg.get_elements()
|
||||||
# Listify elements
|
# Listify elements
|
||||||
if not isinstance(elements, list):
|
if not isinstance(elements, list):
|
||||||
elements: list[Union[str, tuple]] = [elements]
|
elements: Elements = [elements]
|
||||||
|
|
||||||
# Check job queueing system
|
# Check job queueing system
|
||||||
adapter = None
|
adapter = None
|
||||||
|
|
@ -406,7 +426,7 @@ def reset(config: dict) -> None:
|
||||||
|
|
||||||
def list_elements(
|
def list_elements(
|
||||||
datagrabber: dict,
|
datagrabber: dict,
|
||||||
elements: Optional[list[tuple[str, ...]]] = None,
|
elements: Optional[Elements] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""List elements of the datagrabber filtered using `elements`.
|
"""List elements of the datagrabber filtered using `elements`.
|
||||||
|
|
||||||
|
|
@ -416,7 +436,7 @@ def list_elements(
|
||||||
DataGrabber to index. Must have a key ``kind`` with the kind of
|
DataGrabber to index. Must have a key ``kind`` with the kind of
|
||||||
DataGrabber to use. All other keys are passed to the DataGrabber
|
DataGrabber to use. All other keys are passed to the DataGrabber
|
||||||
constructor.
|
constructor.
|
||||||
elements : list of tuple or None, optional
|
elements : list or None, optional
|
||||||
Element(s) to filter using. Will be used to index the DataGrabber
|
Element(s) to filter using. Will be used to index the DataGrabber
|
||||||
(default None).
|
(default None).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@
|
||||||
import shutil
|
import shutil
|
||||||
import textwrap
|
import textwrap
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Union
|
from typing import Optional
|
||||||
|
|
||||||
|
from ...typing import Elements
|
||||||
from ...utils import logger, make_executable, raise_error, run_ext_cmd
|
from ...utils import logger, make_executable, raise_error, run_ext_cmd
|
||||||
from .queue_context_adapter import QueueContextAdapter
|
from .queue_context_adapter import QueueContextAdapter
|
||||||
|
|
||||||
|
|
@ -63,7 +64,7 @@ class GnuParallelLocalAdapter(QueueContextAdapter):
|
||||||
job_name: str,
|
job_name: str,
|
||||||
job_dir: Path,
|
job_dir: Path,
|
||||||
yaml_config_path: Path,
|
yaml_config_path: Path,
|
||||||
elements: list[Union[str, tuple]],
|
elements: Elements,
|
||||||
pre_run: Optional[str] = None,
|
pre_run: Optional[str] = None,
|
||||||
pre_collect: Optional[str] = None,
|
pre_collect: Optional[str] = None,
|
||||||
env: Optional[dict[str, str]] = None,
|
env: Optional[dict[str, str]] = None,
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@
|
||||||
import shutil
|
import shutil
|
||||||
import textwrap
|
import textwrap
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Union
|
from typing import Optional
|
||||||
|
|
||||||
|
from ...typing import Elements
|
||||||
from ...utils import logger, make_executable, raise_error, run_ext_cmd
|
from ...utils import logger, make_executable, raise_error, run_ext_cmd
|
||||||
from .queue_context_adapter import QueueContextAdapter
|
from .queue_context_adapter import QueueContextAdapter
|
||||||
|
|
||||||
|
|
@ -81,7 +82,7 @@ class HTCondorAdapter(QueueContextAdapter):
|
||||||
job_name: str,
|
job_name: str,
|
||||||
job_dir: Path,
|
job_dir: Path,
|
||||||
yaml_config_path: Path,
|
yaml_config_path: Path,
|
||||||
elements: list[Union[str, tuple]],
|
elements: Elements,
|
||||||
pre_run: Optional[str] = None,
|
pre_run: Optional[str] = None,
|
||||||
pre_collect: Optional[str] = None,
|
pre_collect: Optional[str] = None,
|
||||||
env: Optional[dict[str, str]] = None,
|
env: Optional[dict[str, str]] = None,
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,19 @@
|
||||||
# License: AGPL
|
# License: AGPL
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from contextlib import AbstractContextManager, nullcontext
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Union
|
from typing import Any, Optional, Union
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from nibabel.filebasedimages import ImageFileError
|
||||||
from ruamel.yaml import YAML
|
from ruamel.yaml import YAML
|
||||||
|
|
||||||
import junifer.testing.registry # noqa: F401
|
import junifer.testing.registry # noqa: F401
|
||||||
from junifer.api import collect, list_elements, queue, reset, run
|
from junifer.api import collect, list_elements, queue, reset, run
|
||||||
from junifer.datagrabber.base import BaseDataGrabber
|
from junifer.datagrabber.base import BaseDataGrabber
|
||||||
from junifer.pipeline import PipelineComponentRegistry
|
from junifer.pipeline import PipelineComponentRegistry
|
||||||
|
from junifer.typing import Elements
|
||||||
|
|
||||||
|
|
||||||
# Configure YAML class
|
# Configure YAML class
|
||||||
|
|
@ -25,12 +28,37 @@ yaml.allow_unicode = True
|
||||||
yaml.indent(mapping=2, sequence=4, offset=2)
|
yaml.indent(mapping=2, sequence=4, offset=2)
|
||||||
|
|
||||||
|
|
||||||
|
# Kept for parametrizing
|
||||||
|
_datagrabber = {
|
||||||
|
"kind": "PartlyCloudyTestingDataGrabber",
|
||||||
|
}
|
||||||
|
_bids_ses_datagrabber = {
|
||||||
|
"kind": "PatternDataladDataGrabber",
|
||||||
|
"uri": "https://gin.g-node.org/juaml/datalad-example-bids-ses",
|
||||||
|
"types": ["T1w", "BOLD"],
|
||||||
|
"patterns": {
|
||||||
|
"T1w": {
|
||||||
|
"pattern": (
|
||||||
|
"{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
|
||||||
|
),
|
||||||
|
"space": "MNI152NLin6Asym",
|
||||||
|
},
|
||||||
|
"BOLD": {
|
||||||
|
"pattern": (
|
||||||
|
"{subject}/{session}/func/{subject}_{session}_task-rest_bold.nii.gz"
|
||||||
|
),
|
||||||
|
"space": "MNI152NLin6Asym",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"replacements": ["subject", "session"],
|
||||||
|
"rootdir": "example_bids_ses",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def datagrabber() -> dict[str, str]:
|
def datagrabber() -> dict[str, str]:
|
||||||
"""Return a datagrabber as a dictionary."""
|
"""Return a datagrabber as a dictionary."""
|
||||||
return {
|
return _datagrabber.copy()
|
||||||
"kind": "PartlyCloudyTestingDataGrabber",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -60,11 +88,48 @@ def storage() -> dict[str, str]:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"datagrabber, element, expect",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
_datagrabber,
|
||||||
|
[("sub-01",)],
|
||||||
|
pytest.raises(RuntimeError, match="element selectors are invalid"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
_datagrabber,
|
||||||
|
["sub-01"],
|
||||||
|
nullcontext(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
_bids_ses_datagrabber,
|
||||||
|
["sub-01"],
|
||||||
|
pytest.raises(ImageFileError, match="is not a gzip file"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
_bids_ses_datagrabber,
|
||||||
|
[("sub-01", "ses-01")],
|
||||||
|
pytest.raises(ImageFileError, match="is not a gzip file"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
_bids_ses_datagrabber,
|
||||||
|
[("sub-01", "ses-100")],
|
||||||
|
pytest.raises(RuntimeError, match="element selectors are invalid"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
_bids_ses_datagrabber,
|
||||||
|
[("sub-100", "ses-01")],
|
||||||
|
pytest.raises(RuntimeError, match="element selectors are invalid"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
def test_run_single_element(
|
def test_run_single_element(
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
datagrabber: dict[str, str],
|
datagrabber: dict[str, Any],
|
||||||
markers: list[dict[str, str]],
|
markers: list[dict[str, str]],
|
||||||
storage: dict[str, str],
|
storage: dict[str, str],
|
||||||
|
element: Elements,
|
||||||
|
expect: AbstractContextManager,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test run function with single element.
|
"""Test run function with single element.
|
||||||
|
|
||||||
|
|
@ -78,17 +143,22 @@ def test_run_single_element(
|
||||||
Testing markers as list of dictionary.
|
Testing markers as list of dictionary.
|
||||||
storage : dict
|
storage : dict
|
||||||
Testing storage as dictionary.
|
Testing storage as dictionary.
|
||||||
|
element : list of str or tuple
|
||||||
|
The parametrized element.
|
||||||
|
expect : typing.ContextManager
|
||||||
|
The parametrized ContextManager object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Set storage
|
# Set storage
|
||||||
storage["uri"] = str((tmp_path / "out.sqlite").resolve())
|
storage["uri"] = str((tmp_path / "out.sqlite").resolve())
|
||||||
# Run operations
|
# Run operations
|
||||||
|
with expect:
|
||||||
run(
|
run(
|
||||||
workdir=tmp_path,
|
workdir=tmp_path,
|
||||||
datagrabber=datagrabber,
|
datagrabber=datagrabber,
|
||||||
markers=markers,
|
markers=markers,
|
||||||
storage=storage,
|
storage=storage,
|
||||||
elements=[("sub-01",)],
|
elements=element,
|
||||||
)
|
)
|
||||||
# Check files
|
# Check files
|
||||||
files = list(tmp_path.glob("*.sqlite"))
|
files = list(tmp_path.glob("*.sqlite"))
|
||||||
|
|
@ -128,18 +198,30 @@ def test_run_single_element_with_preprocessing(
|
||||||
"kind": "fMRIPrepConfoundRemover",
|
"kind": "fMRIPrepConfoundRemover",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
elements=[("sub-01",)],
|
elements=["sub-01"],
|
||||||
)
|
)
|
||||||
# Check files
|
# Check files
|
||||||
files = list(tmp_path.glob("*.sqlite"))
|
files = list(tmp_path.glob("*.sqlite"))
|
||||||
assert len(files) == 1
|
assert len(files) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"element, expect",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
[("sub-01",), ("sub-03",)],
|
||||||
|
pytest.raises(RuntimeError, match="element selectors are invalid"),
|
||||||
|
),
|
||||||
|
(["sub-01", "sub-03"], nullcontext()),
|
||||||
|
],
|
||||||
|
)
|
||||||
def test_run_multi_element_multi_output(
|
def test_run_multi_element_multi_output(
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
datagrabber: dict[str, str],
|
datagrabber: dict[str, str],
|
||||||
markers: list[dict[str, str]],
|
markers: list[dict[str, str]],
|
||||||
storage: dict[str, str],
|
storage: dict[str, str],
|
||||||
|
element: Elements,
|
||||||
|
expect: AbstractContextManager,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test run function with multi element and multi output.
|
"""Test run function with multi element and multi output.
|
||||||
|
|
||||||
|
|
@ -153,18 +235,23 @@ def test_run_multi_element_multi_output(
|
||||||
Testing markers as list of dictionary.
|
Testing markers as list of dictionary.
|
||||||
storage : dict
|
storage : dict
|
||||||
Testing storage as dictionary.
|
Testing storage as dictionary.
|
||||||
|
element : list of str or tuple
|
||||||
|
The parametrized element.
|
||||||
|
expect : typing.ContextManager
|
||||||
|
The parametrized ContextManager object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Set storage
|
# Set storage
|
||||||
storage["uri"] = str((tmp_path / "out.sqlite").resolve())
|
storage["uri"] = str((tmp_path / "out.sqlite").resolve())
|
||||||
storage["single_output"] = False # type: ignore
|
storage["single_output"] = False # type: ignore
|
||||||
# Run operations
|
# Run operations
|
||||||
|
with expect:
|
||||||
run(
|
run(
|
||||||
workdir=tmp_path,
|
workdir=tmp_path,
|
||||||
datagrabber=datagrabber,
|
datagrabber=datagrabber,
|
||||||
markers=markers,
|
markers=markers,
|
||||||
storage=storage,
|
storage=storage,
|
||||||
elements=[("sub-01",), ("sub-03",)],
|
elements=element,
|
||||||
)
|
)
|
||||||
# Check files
|
# Check files
|
||||||
files = list(tmp_path.glob("*.sqlite"))
|
files = list(tmp_path.glob("*.sqlite"))
|
||||||
|
|
@ -200,7 +287,7 @@ def test_run_multi_element_single_output(
|
||||||
datagrabber=datagrabber,
|
datagrabber=datagrabber,
|
||||||
markers=markers,
|
markers=markers,
|
||||||
storage=storage,
|
storage=storage,
|
||||||
elements=[("sub-01",), ("sub-03",)],
|
elements=["sub-01", "sub-03"],
|
||||||
)
|
)
|
||||||
# Check files
|
# Check files
|
||||||
files = list(tmp_path.glob("*.sqlite"))
|
files = list(tmp_path.glob("*.sqlite"))
|
||||||
|
|
@ -569,7 +656,7 @@ def test_reset_run(
|
||||||
datagrabber=datagrabber,
|
datagrabber=datagrabber,
|
||||||
markers=markers,
|
markers=markers,
|
||||||
storage=storage,
|
storage=storage,
|
||||||
elements=[("sub-01",)],
|
elements=["sub-01"],
|
||||||
)
|
)
|
||||||
# Reset operation
|
# Reset operation
|
||||||
reset(config={"storage": storage})
|
reset(config={"storage": storage})
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ from typing import Union
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
from ..typing import Elements
|
||||||
from ..utils import logger, raise_error, warn_with_log, yaml
|
from ..utils import logger, raise_error, warn_with_log, yaml
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -142,7 +143,7 @@ def parse_yaml(filepath: Union[str, Path]) -> dict: # noqa: C901
|
||||||
|
|
||||||
def parse_elements(
|
def parse_elements(
|
||||||
element: tuple[str, ...], config: dict
|
element: tuple[str, ...], config: dict
|
||||||
) -> Union[list[tuple[str, ...]], None]:
|
) -> Union[Elements, None]:
|
||||||
"""Parse elements from cli.
|
"""Parse elements from cli.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -203,7 +204,7 @@ def parse_elements(
|
||||||
return elements
|
return elements
|
||||||
|
|
||||||
|
|
||||||
def _parse_elements_file(filepath: Path) -> list[tuple[str, ...]]:
|
def _parse_elements_file(filepath: Path) -> Elements:
|
||||||
"""Parse elements from file.
|
"""Parse elements from file.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -213,7 +214,7 @@ def _parse_elements_file(filepath: Path) -> list[tuple[str, ...]]:
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list of tuple of str
|
list
|
||||||
The element(s) as list.
|
The element(s) as list.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -227,5 +228,8 @@ def _parse_elements_file(filepath: Path) -> list[tuple[str, ...]]:
|
||||||
)
|
)
|
||||||
# Remove trailing whitespace in cell entries
|
# Remove trailing whitespace in cell entries
|
||||||
csv_df_trimmed = csv_df.apply(lambda x: x.str.strip())
|
csv_df_trimmed = csv_df.apply(lambda x: x.str.strip())
|
||||||
# Convert to list of tuple of str
|
# Convert to list of tuple of str if more than one column else flatten
|
||||||
|
if len(csv_df_trimmed.columns) == 1:
|
||||||
|
return csv_df_trimmed.to_numpy().flatten().tolist()
|
||||||
|
else:
|
||||||
return list(map(tuple, csv_df_trimmed.to_numpy()))
|
return list(map(tuple, csv_df_trimmed.to_numpy()))
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from pathlib import Path
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from ..pipeline import UpdateMetaMixin
|
from ..pipeline import UpdateMetaMixin
|
||||||
|
from ..typing import Element, Elements
|
||||||
from ..utils import logger, raise_error
|
from ..utils import logger, raise_error
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -67,9 +68,7 @@ class BaseDataGrabber(ABC, UpdateMetaMixin):
|
||||||
"""
|
"""
|
||||||
yield from self.get_elements()
|
yield from self.get_elements()
|
||||||
|
|
||||||
def __getitem__(
|
def __getitem__(self, element: Element) -> dict[str, dict]:
|
||||||
self, element: Union[str, tuple[str, ...]]
|
|
||||||
) -> dict[str, dict]:
|
|
||||||
"""Enable indexing support.
|
"""Enable indexing support.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -137,13 +136,13 @@ class BaseDataGrabber(ABC, UpdateMetaMixin):
|
||||||
"""
|
"""
|
||||||
return self._datadir
|
return self._datadir
|
||||||
|
|
||||||
def filter(self, selection: list[Union[str, tuple[str]]]) -> Iterator:
|
def filter(self, selection: Elements) -> Iterator:
|
||||||
"""Filter elements to be grabbed.
|
"""Filter elements to be grabbed.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
selection : list of str or tuple
|
selection : list
|
||||||
The list of partial element key values to filter using.
|
The list of partial or complete element selectors to filter using.
|
||||||
|
|
||||||
Yields
|
Yields
|
||||||
------
|
------
|
||||||
|
|
@ -152,7 +151,7 @@ class BaseDataGrabber(ABC, UpdateMetaMixin):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def filter_func(element: Union[str, tuple[str]]) -> bool:
|
def filter_func(element: Element) -> bool:
|
||||||
"""Filter element based on selection.
|
"""Filter element based on selection.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -201,15 +200,14 @@ class BaseDataGrabber(ABC, UpdateMetaMixin):
|
||||||
) # pragma: no cover
|
) # pragma: no cover
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_elements(self) -> list[Union[str, tuple[str]]]:
|
def get_elements(self) -> Elements:
|
||||||
"""Get elements.
|
"""Get elements.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list
|
list
|
||||||
List of elements that can be grabbed. The elements can be strings,
|
List of elements that can be grabbed. The elements can be strings
|
||||||
tuples or any object that will be then used as a key to index the
|
or tuples of strings to index the DataGrabber.
|
||||||
DataGrabber.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise_error(
|
raise_error(
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ from datalad.support.exceptions import IncompleteResultsError
|
||||||
from datalad.support.gitrepo import GitRepo
|
from datalad.support.gitrepo import GitRepo
|
||||||
|
|
||||||
from ..pipeline import WorkDirManager
|
from ..pipeline import WorkDirManager
|
||||||
|
from ..typing import Element
|
||||||
from ..utils import config, logger, raise_error, warn_with_log
|
from ..utils import config, logger, raise_error, warn_with_log
|
||||||
from .base import BaseDataGrabber
|
from .base import BaseDataGrabber
|
||||||
|
|
||||||
|
|
@ -312,7 +313,7 @@ class DataladDataGrabber(BaseDataGrabber):
|
||||||
logger.debug(f"Dropping {f}")
|
logger.debug(f"Dropping {f}")
|
||||||
self._dataset.drop(f, result_renderer="disabled")
|
self._dataset.drop(f, result_renderer="disabled")
|
||||||
|
|
||||||
def __getitem__(self, element: Union[str, tuple]) -> dict:
|
def __getitem__(self, element: Element) -> dict:
|
||||||
"""Implement single element indexing in the Datalad database.
|
"""Implement single element indexing in the Datalad database.
|
||||||
|
|
||||||
It will first obtain the paths from the parent class and then
|
It will first obtain the paths from the parent class and then
|
||||||
|
|
@ -320,11 +321,11 @@ class DataladDataGrabber(BaseDataGrabber):
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
element : str or tuple
|
element : str or tuple of str
|
||||||
The element to be indexed. If one string is provided, it is
|
The element to be indexed. If one string is provided, it is
|
||||||
assumed to be a tuple with only one item. If a tuple is provided,
|
assumed to be a tuple with only one item. If a tuple is provided,
|
||||||
each item in the tuple is the value for the replacement string
|
each item in the tuple is the value for the replacement string
|
||||||
specified in "replacements".
|
specified in ``"replacements"``.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from typing import Optional, Union
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from ..api.decorators import register_datagrabber
|
from ..api.decorators import register_datagrabber
|
||||||
from ..typing import DataGrabberPatterns
|
from ..typing import DataGrabberPatterns, Elements
|
||||||
from ..utils import logger, raise_error
|
from ..utils import logger, raise_error
|
||||||
from .base import BaseDataGrabber
|
from .base import BaseDataGrabber
|
||||||
from .pattern_validation_mixin import PatternValidationMixin
|
from .pattern_validation_mixin import PatternValidationMixin
|
||||||
|
|
@ -367,7 +367,7 @@ class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
||||||
"""
|
"""
|
||||||
return self.replacements
|
return self.replacements
|
||||||
|
|
||||||
def get_item(self, **element: str) -> dict[str, dict]:
|
def get_item(self, **element: dict) -> dict[str, dict]:
|
||||||
"""Implement single element indexing for the datagrabber.
|
"""Implement single element indexing for the datagrabber.
|
||||||
|
|
||||||
This method constructs a real path to the requested item's data, by
|
This method constructs a real path to the requested item's data, by
|
||||||
|
|
@ -450,7 +450,7 @@ class PatternDataGrabber(BaseDataGrabber, PatternValidationMixin):
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def get_elements(self) -> list:
|
def get_elements(self) -> Elements:
|
||||||
"""Implement fetching list of elements in the dataset.
|
"""Implement fetching list of elements in the dataset.
|
||||||
|
|
||||||
It will use regex to search for "replacements" in the "patterns" and
|
It will use regex to search for "replacements" in the "patterns" and
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ __all__ = [
|
||||||
"MarkerInOutMappings",
|
"MarkerInOutMappings",
|
||||||
"DataGrabberPatterns",
|
"DataGrabberPatterns",
|
||||||
"ConfigVal",
|
"ConfigVal",
|
||||||
|
"Element",
|
||||||
|
"Elements",
|
||||||
]
|
]
|
||||||
|
|
||||||
from ._typing import (
|
from ._typing import (
|
||||||
|
|
@ -24,4 +26,6 @@ from ._typing import (
|
||||||
MarkerInOutMappings,
|
MarkerInOutMappings,
|
||||||
DataGrabberPatterns,
|
DataGrabberPatterns,
|
||||||
ConfigVal,
|
ConfigVal,
|
||||||
|
Element,
|
||||||
|
Elements,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ __all__ = [
|
||||||
"DataGrabberLike",
|
"DataGrabberLike",
|
||||||
"DataGrabberPatterns",
|
"DataGrabberPatterns",
|
||||||
"Dependencies",
|
"Dependencies",
|
||||||
|
"Element",
|
||||||
|
"Elements",
|
||||||
"ExternalDependencies",
|
"ExternalDependencies",
|
||||||
"MarkerInOutMappings",
|
"MarkerInOutMappings",
|
||||||
"MarkerLike",
|
"MarkerLike",
|
||||||
|
|
@ -62,3 +64,5 @@ DataGrabberPatterns = dict[
|
||||||
str, Union[dict[str, str], Sequence[dict[str, str]]]
|
str, Union[dict[str, str], Sequence[dict[str, str]]]
|
||||||
]
|
]
|
||||||
ConfigVal = Union[bool, int, float]
|
ConfigVal = Union[bool, int, float]
|
||||||
|
Element = Union[str, tuple[str, ...]]
|
||||||
|
Elements = Sequence[Element]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue
This logic won't work in case element is part of a tuple of elements.
The idea is that we can have subject/session/task/etc as the element but only pass the "subject" part an the filter function will return all of the elements for that subject.
I think that the logic should be to check if all items in the
elementarray yielded at least one valid element.