junifer 0.0.7.dev332__py3-none-any.whl → 0.0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- junifer/_version.py +9 -19
- junifer/api/__init__.pyi +17 -2
- junifer/api/functions.py +222 -39
- junifer/api/queue_context/__init__.pyi +11 -3
- junifer/api/queue_context/gnu_parallel_local_adapter.py +81 -120
- junifer/api/queue_context/htcondor_adapter.py +118 -181
- junifer/api/queue_context/queue_context_adapter.py +51 -7
- junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +10 -35
- junifer/api/queue_context/tests/test_htcondor_adapter.py +13 -50
- junifer/api/res/fsl/run_fsl_docker.sh +4 -0
- junifer/api/tests/test_functions.py +863 -36
- junifer/cli/__init__.py +1 -7
- junifer/cli/cli.py +41 -22
- junifer/cli/parser.py +23 -147
- junifer/cli/tests/data/gmd_mean.yaml +6 -6
- junifer/cli/tests/data/gmd_mean_htcondor.yaml +6 -5
- junifer/cli/tests/data/partly_cloudy_agg_mean_tian.yml +1 -0
- junifer/cli/tests/test_cli.py +120 -4
- junifer/cli/tests/test_cli_utils.py +23 -6
- junifer/cli/utils.py +14 -9
- junifer/configs/__init__.py +5 -0
- junifer/configs/__init__.pyi +0 -0
- junifer/configs/juseless/datagrabbers/__init__.pyi +4 -2
- junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +18 -24
- junifer/configs/juseless/datagrabbers/camcan_vbm.py +20 -27
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +44 -52
- junifer/configs/juseless/datagrabbers/tests/test_ixi_vbm.py +0 -7
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -19
- junifer/configs/juseless/datagrabbers/ucla.py +111 -104
- junifer/configs/juseless/datagrabbers/ukb_vbm.py +19 -25
- junifer/configs/py.typed +0 -0
- junifer/conftest.py +63 -24
- junifer/data/_dispatch.py +9 -19
- junifer/data/coordinates/_ants_coordinates_warper.py +4 -1
- junifer/data/coordinates/_coordinates.py +51 -6
- junifer/data/coordinates/_fsl_coordinates_warper.py +4 -1
- junifer/data/coordinates/tests/test_coordinates.py +15 -3
- junifer/data/coordinates/tests/test_coordinates_names.py +37 -0
- junifer/data/maps/_ants_maps_warper.py +18 -12
- junifer/data/maps/_fsl_maps_warper.py +7 -3
- junifer/data/maps/_maps.py +12 -8
- junifer/data/maps/tests/test_maps.py +13 -21
- junifer/data/masks/_ants_mask_warper.py +18 -12
- junifer/data/masks/_fsl_mask_warper.py +7 -3
- junifer/data/masks/_masks.py +15 -12
- junifer/data/masks/tests/test_masks.py +20 -17
- junifer/data/parcellations/_ants_parcellation_warper.py +18 -12
- junifer/data/parcellations/_fsl_parcellation_warper.py +7 -5
- junifer/data/parcellations/_parcellations.py +125 -91
- junifer/data/parcellations/tests/test_parcellations.py +122 -1
- junifer/data/template_spaces.py +117 -50
- junifer/data/tests/test_template_spaces.py +10 -7
- junifer/data/utils.py +10 -8
- junifer/datagrabber/__init__.pyi +38 -5
- junifer/datagrabber/aomic/__init__.pyi +8 -1
- junifer/datagrabber/aomic/_types.py +22 -0
- junifer/datagrabber/aomic/id1000.py +193 -183
- junifer/datagrabber/aomic/piop1.py +215 -259
- junifer/datagrabber/aomic/piop2.py +211 -244
- junifer/datagrabber/aomic/tests/test_id1000.py +70 -53
- junifer/datagrabber/aomic/tests/test_piop1.py +98 -102
- junifer/datagrabber/aomic/tests/test_piop2.py +88 -85
- junifer/datagrabber/base.py +253 -79
- junifer/datagrabber/datalad_base.py +127 -76
- junifer/datagrabber/dmcc13_benchmark.py +204 -289
- junifer/datagrabber/hcp1200/__init__.pyi +7 -2
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +61 -40
- junifer/datagrabber/hcp1200/hcp1200.py +151 -164
- junifer/datagrabber/hcp1200/tests/test_hcp1200.py +116 -128
- junifer/datagrabber/multiple.py +183 -63
- junifer/datagrabber/pattern.py +358 -227
- junifer/datagrabber/pattern_datalad.py +84 -124
- junifer/datagrabber/pattern_validation_mixin.py +22 -50
- junifer/datagrabber/tests/test_base.py +17 -4
- junifer/datagrabber/tests/test_datalad_base.py +20 -27
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +110 -122
- junifer/datagrabber/tests/test_multiple.py +293 -31
- junifer/datagrabber/tests/test_pattern.py +577 -18
- junifer/datagrabber/tests/test_pattern_datalad.py +177 -107
- junifer/datagrabber/tests/test_pattern_validation_mixin.py +9 -51
- junifer/datareader/default.py +8 -5
- junifer/datareader/tests/test_default_reader.py +1 -1
- junifer/external/BrainPrint/.codecov.yml +2 -0
- junifer/external/BrainPrint/.codespellignore +2 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/bug-report.md +45 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/documentation.md +12 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/feature-request.md +24 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/questions-help-support.md +28 -0
- junifer/external/BrainPrint/.github/dependabot.yml +6 -0
- junifer/external/BrainPrint/.github/workflows/build.yml +53 -0
- junifer/external/BrainPrint/.github/workflows/code-style.yml +44 -0
- junifer/external/BrainPrint/.github/workflows/doc.yml +65 -0
- junifer/external/BrainPrint/.github/workflows/publish.yml +31 -0
- junifer/external/BrainPrint/.github/workflows/pytest.yml +54 -0
- junifer/external/BrainPrint/.gitignore +123 -0
- junifer/external/BrainPrint/LICENSE +21 -0
- junifer/external/BrainPrint/README.md +108 -0
- junifer/external/BrainPrint/brainprint/cli/__init__.py +11 -0
- junifer/external/BrainPrint/brainprint/cli/help_text.py +169 -0
- junifer/external/BrainPrint/brainprint/cli/parser.py +150 -0
- junifer/external/BrainPrint/brainprint/cli/utils.py +14 -0
- junifer/external/BrainPrint/brainprint/commands/__init__.py +0 -0
- junifer/external/BrainPrint/brainprint/commands/sys_info.py +18 -0
- junifer/external/BrainPrint/brainprint/utils/tests/__init__.py +0 -0
- junifer/external/BrainPrint/brainprint/utils/tests/test_config.py +33 -0
- junifer/external/BrainPrint/doc/Makefile +20 -0
- junifer/external/BrainPrint/doc/_static/.gitkeep +0 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/class.rst +10 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/function.rst +8 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/module.rst +6 -0
- junifer/external/BrainPrint/doc/api/index.rst +11 -0
- junifer/external/BrainPrint/doc/conf.py +272 -0
- junifer/external/BrainPrint/doc/index.rst +140 -0
- junifer/external/BrainPrint/doc/links.inc +36 -0
- junifer/external/BrainPrint/doc/make.bat +35 -0
- junifer/external/BrainPrint/doc/references.bib +23 -0
- junifer/external/BrainPrint/pyproject.toml +170 -0
- junifer/external/BrainPrint/setup.py +3 -0
- junifer/external/__init__.py +5 -0
- junifer/external/__init__.pyi +0 -0
- junifer/external/nilearn/junifer_connectivity_measure.py +95 -39
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +226 -67
- junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +39 -27
- junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +231 -21
- junifer/external/py.typed +0 -0
- junifer/markers/__init__.pyi +6 -3
- junifer/markers/base.py +126 -76
- junifer/markers/brainprint.py +30 -39
- junifer/markers/complexity/__init__.py +20 -3
- junifer/markers/complexity/complexity_base.py +40 -41
- junifer/markers/complexity/hurst_exponent.py +22 -35
- junifer/markers/complexity/multiscale_entropy_auc.py +53 -41
- junifer/markers/complexity/perm_entropy.py +22 -36
- junifer/markers/complexity/range_entropy.py +22 -35
- junifer/markers/complexity/range_entropy_auc.py +27 -36
- junifer/markers/complexity/sample_entropy.py +22 -36
- junifer/markers/complexity/tests/test_complexity_registry.py +48 -0
- junifer/markers/complexity/tests/test_hurst_exponent.py +47 -36
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +50 -36
- junifer/markers/complexity/tests/test_perm_entropy.py +48 -36
- junifer/markers/complexity/tests/test_range_entropy.py +47 -36
- junifer/markers/complexity/tests/test_range_entropy_auc.py +47 -36
- junifer/markers/complexity/tests/test_sample_entropy.py +48 -36
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +50 -36
- junifer/markers/complexity/weighted_perm_entropy.py +22 -36
- junifer/markers/ets_rss.py +35 -31
- junifer/markers/falff/__init__.pyi +7 -1
- junifer/markers/falff/_afni_falff.py +22 -9
- junifer/markers/falff/_junifer_falff.py +47 -31
- junifer/markers/falff/falff_base.py +80 -60
- junifer/markers/falff/falff_maps.py +17 -41
- junifer/markers/falff/falff_parcels.py +26 -51
- junifer/markers/falff/falff_spheres.py +34 -61
- junifer/markers/falff/tests/test_falff_maps.py +20 -23
- junifer/markers/falff/tests/test_falff_mask.py +99 -0
- junifer/markers/falff/tests/test_falff_parcels.py +14 -10
- junifer/markers/falff/tests/test_falff_spheres.py +14 -10
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +32 -34
- junifer/markers/functional_connectivity/edge_functional_connectivity_maps.py +12 -25
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +19 -34
- junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +24 -44
- junifer/markers/functional_connectivity/functional_connectivity_base.py +50 -40
- junifer/markers/functional_connectivity/functional_connectivity_maps.py +12 -25
- junifer/markers/functional_connectivity/functional_connectivity_parcels.py +19 -34
- junifer/markers/functional_connectivity/functional_connectivity_spheres.py +24 -45
- junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +21 -22
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_maps.py +8 -7
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +8 -4
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +8 -4
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_maps.py +8 -7
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +9 -5
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +8 -12
- junifer/markers/maps_aggregation.py +77 -55
- junifer/markers/parcel_aggregation.py +91 -70
- junifer/markers/reho/__init__.pyi +7 -1
- junifer/markers/reho/_afni_reho.py +23 -14
- junifer/markers/reho/_junifer_reho.py +112 -123
- junifer/markers/reho/reho_base.py +115 -37
- junifer/markers/reho/reho_maps.py +18 -32
- junifer/markers/reho/reho_parcels.py +26 -41
- junifer/markers/reho/reho_spheres.py +33 -54
- junifer/markers/reho/tests/test_junifer_reho.py +178 -0
- junifer/markers/reho/tests/test_reho_maps.py +27 -25
- junifer/markers/reho/tests/test_reho_parcels.py +28 -22
- junifer/markers/reho/tests/test_reho_spheres.py +16 -12
- junifer/markers/sphere_aggregation.py +88 -72
- junifer/markers/temporal_snr/temporal_snr_base.py +32 -30
- junifer/markers/temporal_snr/temporal_snr_maps.py +9 -18
- junifer/markers/temporal_snr/temporal_snr_parcels.py +19 -27
- junifer/markers/temporal_snr/temporal_snr_spheres.py +27 -41
- junifer/markers/temporal_snr/tests/test_temporal_snr_base.py +2 -2
- junifer/markers/temporal_snr/tests/test_temporal_snr_maps.py +17 -22
- junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +11 -3
- junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +9 -9
- junifer/markers/tests/test_brainprint.py +10 -9
- junifer/markers/tests/test_ets_rss.py +7 -3
- junifer/markers/tests/test_lost_regions.py +279 -0
- junifer/markers/tests/test_maps_aggregation.py +67 -114
- junifer/markers/tests/test_marker_utils.py +40 -1
- junifer/markers/tests/test_markers_base.py +275 -6
- junifer/markers/tests/test_parcel_aggregation.py +91 -53
- junifer/markers/tests/test_sphere_aggregation.py +53 -47
- junifer/markers/utils.py +267 -5
- junifer/onthefly/__init__.py +2 -3
- junifer/onthefly/__init__.pyi +4 -0
- junifer/onthefly/_brainprint.py +3 -5
- junifer/onthefly/py.typed +0 -0
- junifer/onthefly/read_transform.py +11 -9
- junifer/onthefly/tests/test_read_transform.py +7 -7
- junifer/pipeline/__init__.pyi +4 -2
- junifer/pipeline/marker_collection.py +14 -10
- junifer/pipeline/pipeline_component_registry.py +25 -6
- junifer/pipeline/pipeline_step_mixin.py +5 -12
- junifer/pipeline/tests/test_data_object_dumper.py +10 -10
- junifer/pipeline/tests/test_marker_collection.py +145 -168
- junifer/pipeline/tests/test_pipeline_component_registry.py +52 -8
- junifer/pipeline/tests/test_pipeline_step_mixin.py +12 -26
- junifer/pipeline/tests/test_update_meta_mixin.py +4 -5
- junifer/pipeline/tests/test_workdir_manager.py +39 -0
- junifer/pipeline/update_meta_mixin.py +13 -10
- junifer/pipeline/utils.py +22 -17
- junifer/pipeline/workdir_manager.py +41 -32
- junifer/preprocess/__init__.pyi +9 -4
- junifer/preprocess/_temporal_filter.py +34 -33
- junifer/preprocess/_temporal_slicer.py +17 -30
- junifer/preprocess/base.py +99 -55
- junifer/preprocess/confounds/__init__.pyi +10 -2
- junifer/preprocess/confounds/fmriprep_confound_remover.py +87 -109
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +64 -45
- junifer/preprocess/smoothing/__init__.pyi +2 -2
- junifer/preprocess/smoothing/_afni_smoothing.py +8 -7
- junifer/preprocess/smoothing/_fsl_smoothing.py +6 -4
- junifer/preprocess/smoothing/_nilearn_smoothing.py +7 -5
- junifer/preprocess/smoothing/smoothing.py +59 -40
- junifer/preprocess/smoothing/tests/test_smoothing.py +28 -8
- junifer/preprocess/tests/test_preprocess_base.py +17 -9
- junifer/preprocess/tests/test_temporal_filter.py +37 -16
- junifer/preprocess/tests/test_temporal_slicer.py +12 -12
- junifer/preprocess/warping/__init__.pyi +2 -2
- junifer/preprocess/warping/_ants_warper.py +56 -40
- junifer/preprocess/warping/_fsl_warper.py +49 -36
- junifer/preprocess/warping/_utils.py +106 -0
- junifer/preprocess/warping/space_warper.py +99 -98
- junifer/preprocess/warping/tests/test_space_warper.py +46 -41
- junifer/preprocess/warping/tests/test_warping_utils.py +163 -0
- junifer/stats.py +46 -34
- junifer/storage/__init__.pyi +8 -4
- junifer/storage/base.py +129 -64
- junifer/storage/hdf5.py +307 -199
- junifer/storage/pandas_base.py +46 -24
- junifer/storage/sqlite.py +99 -62
- junifer/storage/tests/data/collected_h5io_fork.hdf5 +0 -0
- junifer/storage/tests/test_hdf5.py +147 -12
- junifer/storage/tests/test_repeated_storage.py +293 -0
- junifer/storage/tests/test_sqlite.py +9 -20
- junifer/storage/tests/test_storage_base.py +18 -21
- junifer/storage/tests/test_utils.py +83 -22
- junifer/storage/utils.py +58 -38
- junifer/testing/__init__.pyi +2 -2
- junifer/testing/datagrabbers.py +480 -44
- junifer/testing/registry.py +12 -0
- junifer/testing/tests/test_adhdtesting_datagrabber.py +24 -0
- junifer/testing/tests/test_oasisvmbtesting_datagrabber.py +5 -0
- junifer/testing/tests/test_partlycloudytesting_datagrabber.py +52 -0
- junifer/testing/tests/test_spmauditory_datagrabber.py +16 -0
- junifer/testing/tests/test_testing_registry.py +31 -0
- junifer/testing/tests/test_testing_utils.py +35 -0
- junifer/testing/tests/test_uclacnpvbmtesting_datagrabber.py +81 -0
- junifer/testing/utils.py +33 -1
- junifer/tests/test_stats.py +1 -5
- junifer/typing/__init__.pyi +2 -0
- junifer/typing/_typing.py +34 -19
- junifer/typing/py.typed +0 -0
- junifer/utils/__init__.pyi +10 -1
- junifer/utils/_config.py +1 -2
- junifer/utils/helpers.py +68 -2
- junifer/utils/logging.py +31 -7
- junifer/utils/tests/test_helpers.py +36 -1
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/METADATA +43 -35
- junifer-0.0.8.dist-info/RECORD +402 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/WHEEL +1 -1
- junifer/cli/tests/test_parser.py +0 -280
- junifer/external/h5io/h5io/__init__.py +0 -15
- junifer/external/h5io/h5io/_h5io.py +0 -767
- junifer/external/h5io/h5io/_version.py +0 -1
- junifer/external/h5io/h5io/chunked_array.py +0 -94
- junifer/external/h5io/h5io/chunked_list.py +0 -65
- junifer/markers/complexity/__init__.pyi +0 -17
- junifer-0.0.7.dev332.dist-info/RECORD +0 -353
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/AUTHORS.rst +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/LICENSE.md +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/top_level.txt +0 -0
junifer/datagrabber/multiple.py
CHANGED
|
@@ -5,23 +5,73 @@
|
|
|
5
5
|
# Synchon Mandal <s.mandal@fz-juelich.de>
|
|
6
6
|
# License: AGPL
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
from pydantic import BeforeValidator, ConfigDict
|
|
9
13
|
|
|
10
14
|
from ..api.decorators import register_datagrabber
|
|
11
|
-
from ..typing import DataGrabberLike
|
|
12
|
-
from ..utils import deep_update, raise_error
|
|
13
|
-
from .base import BaseDataGrabber
|
|
15
|
+
from ..typing import DataGrabberLike, Element
|
|
16
|
+
from ..utils import deep_update, ensure_list, raise_error
|
|
17
|
+
from .base import BaseDataGrabber, DataType
|
|
18
|
+
from .pattern import PatternDataGrabber, _join_elements
|
|
19
|
+
from .pattern_datalad import PatternDataladDataGrabber
|
|
14
20
|
|
|
15
21
|
|
|
16
22
|
__all__ = ["MultipleDataGrabber"]
|
|
17
23
|
|
|
18
24
|
|
|
25
|
+
def _nested_types_only(
|
|
26
|
+
datagrabber: DataGrabberLike, data_type: str
|
|
27
|
+
) -> list[str] | None:
|
|
28
|
+
"""Get the nested data types grabbed without the main file of a data type.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
datagrabber : DataGrabber-like object
|
|
33
|
+
The DataGrabber.
|
|
34
|
+
data_type : str
|
|
35
|
+
The data type.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
list of str or None
|
|
40
|
+
The nested data types (e.g., ``["confounds"]``) if the DataGrabber
|
|
41
|
+
only grabs them, without the main file of the data type, or None if
|
|
42
|
+
it grabs the main file.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
patterns = getattr(datagrabber, "patterns", None)
|
|
46
|
+
if patterns is None:
|
|
47
|
+
return None
|
|
48
|
+
dtype_val = patterns.get(data_type)
|
|
49
|
+
# Data types with a list of patterns (e.g., Warp) have their main files
|
|
50
|
+
if dtype_val is None or isinstance(dtype_val, list):
|
|
51
|
+
return None
|
|
52
|
+
if "pattern" in dtype_val:
|
|
53
|
+
return None
|
|
54
|
+
return [k for k, v in dtype_val.items() if isinstance(v, dict)]
|
|
55
|
+
|
|
56
|
+
|
|
19
57
|
@register_datagrabber
|
|
20
58
|
class MultipleDataGrabber(BaseDataGrabber):
|
|
21
59
|
"""Concrete implementation for multi sourced data fetching.
|
|
22
60
|
|
|
23
61
|
Implements a DataGrabber which can be used to fetch data from multiple
|
|
24
|
-
DataGrabbers.
|
|
62
|
+
DataGrabbers, e.g., from different datasets:
|
|
63
|
+
|
|
64
|
+
* Each data type is grabbed by one DataGrabber (set with their ``types``),
|
|
65
|
+
except for the DataGrabbers that only grab the nested data types of a
|
|
66
|
+
data type (without its main file, e.g., only the ``confounds`` of
|
|
67
|
+
``BOLD``), which replace the nested data types of the other one.
|
|
68
|
+
* The element keys are the ones of the first DataGrabber, followed by the
|
|
69
|
+
other keys of the other DataGrabbers, which can have different keys
|
|
70
|
+
(e.g., ``subject`` and ``task`` for ``BOLD``, and only ``subject`` for
|
|
71
|
+
``VBM_GM``). The keys with the same name must have the same values in
|
|
72
|
+
all the DataGrabbers.
|
|
73
|
+
* The elements are the ones available in all the DataGrabbers, joined on
|
|
74
|
+
their common keys, and each DataGrabber grabs an element with its keys.
|
|
25
75
|
|
|
26
76
|
Parameters
|
|
27
77
|
----------
|
|
@@ -33,62 +83,66 @@ class MultipleDataGrabber(BaseDataGrabber):
|
|
|
33
83
|
Raises
|
|
34
84
|
------
|
|
35
85
|
RuntimeError
|
|
36
|
-
If
|
|
37
|
-
|
|
86
|
+
If more than one DataGrabber grabs the main file of a data type or
|
|
87
|
+
the same nested data type without the main file.
|
|
38
88
|
|
|
39
89
|
"""
|
|
40
90
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
91
|
+
model_config = ConfigDict(extra="allow")
|
|
92
|
+
|
|
93
|
+
datagrabbers: list[
|
|
94
|
+
DataGrabberLike | PatternDataGrabber | PatternDataladDataGrabber
|
|
95
|
+
]
|
|
96
|
+
types: Annotated[
|
|
97
|
+
DataType | list[DataType], BeforeValidator(ensure_list)
|
|
98
|
+
] = [] # noqa: RUF012
|
|
99
|
+
datadir: Path = Path(".")
|
|
100
|
+
|
|
101
|
+
def validate_datagrabber_params(self) -> None:
|
|
102
|
+
"""Run extra logical validation for datagrabber."""
|
|
103
|
+
# Each data type is grabbed by one DataGrabber, except for the nested
|
|
104
|
+
# data types grabbed without the main file, which can only be
|
|
105
|
+
# grabbed by one of these DataGrabbers
|
|
106
|
+
main = defaultdict(list)
|
|
107
|
+
nested = defaultdict(list)
|
|
108
|
+
for i, dg in enumerate(self.datagrabbers):
|
|
109
|
+
for data_type in dg.get_types():
|
|
110
|
+
nested_types = _nested_types_only(dg, data_type)
|
|
111
|
+
if nested_types is None:
|
|
112
|
+
main[data_type].append(i)
|
|
113
|
+
else:
|
|
114
|
+
for nested_type in nested_types:
|
|
115
|
+
nested[(data_type, nested_type)].append(i)
|
|
116
|
+
for data_type, dgs in main.items():
|
|
117
|
+
if len(dgs) > 1:
|
|
47
118
|
raise_error(
|
|
48
|
-
msg=
|
|
119
|
+
msg=(
|
|
120
|
+
f"Data type `{data_type}` is grabbed by more than one "
|
|
121
|
+
f"DataGrabber ({dgs}), it must be removed from the "
|
|
122
|
+
"`types` of all but one of them"
|
|
123
|
+
),
|
|
49
124
|
klass=RuntimeError,
|
|
50
125
|
)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if len(types) != len(set(types)):
|
|
54
|
-
if all(hasattr(dg, "patterns") for dg in datagrabbers):
|
|
55
|
-
first_patterns = datagrabbers[0].patterns
|
|
56
|
-
for dg in datagrabbers[1:]:
|
|
57
|
-
for data_type in set(types):
|
|
58
|
-
dtype_pattern = dg.patterns.get(data_type)
|
|
59
|
-
if dtype_pattern is None:
|
|
60
|
-
continue
|
|
61
|
-
# Check if first-level keys of data type are same
|
|
62
|
-
if (
|
|
63
|
-
dtype_pattern.keys()
|
|
64
|
-
== first_patterns[data_type].keys()
|
|
65
|
-
):
|
|
66
|
-
raise_error(
|
|
67
|
-
msg=(
|
|
68
|
-
"DataGrabbers have overlapping mandatory "
|
|
69
|
-
"and / or optional key(s) for data type: "
|
|
70
|
-
f"`{data_type}`"
|
|
71
|
-
),
|
|
72
|
-
klass=RuntimeError,
|
|
73
|
-
)
|
|
74
|
-
else:
|
|
75
|
-
# Can't check further
|
|
126
|
+
for (data_type, nested_type), dgs in nested.items():
|
|
127
|
+
if len(dgs) > 1:
|
|
76
128
|
raise_error(
|
|
77
|
-
msg=
|
|
129
|
+
msg=(
|
|
130
|
+
f"Nested data type `{data_type}.{nested_type}` is "
|
|
131
|
+
f"grabbed by more than one DataGrabber ({dgs})"
|
|
132
|
+
),
|
|
78
133
|
klass=RuntimeError,
|
|
79
134
|
)
|
|
80
|
-
self._datagrabbers = datagrabbers
|
|
81
135
|
|
|
82
|
-
def __getitem__(self, element:
|
|
136
|
+
def __getitem__(self, element: Element) -> dict:
|
|
83
137
|
"""Implement indexing.
|
|
84
138
|
|
|
85
139
|
Parameters
|
|
86
140
|
----------
|
|
87
|
-
element :
|
|
141
|
+
element : `Element`
|
|
88
142
|
The element to be indexed. If one string is provided, it is
|
|
89
143
|
assumed to be a tuple with only one item. If a tuple is provided,
|
|
90
|
-
each item in the tuple is the value for the
|
|
91
|
-
|
|
144
|
+
each item in the tuple is the value for the element key (see
|
|
145
|
+
:meth:`.get_element_keys`).
|
|
92
146
|
|
|
93
147
|
Returns
|
|
94
148
|
-------
|
|
@@ -97,57 +151,104 @@ class MultipleDataGrabber(BaseDataGrabber):
|
|
|
97
151
|
specified element.
|
|
98
152
|
|
|
99
153
|
"""
|
|
100
|
-
|
|
101
|
-
|
|
154
|
+
if not isinstance(element, tuple):
|
|
155
|
+
element = (element,)
|
|
156
|
+
named_element = dict(
|
|
157
|
+
zip(self.get_element_keys(), element, strict=True)
|
|
158
|
+
)
|
|
159
|
+
# Grab the element with the keys of each DataGrabber
|
|
160
|
+
outs = []
|
|
102
161
|
metas = []
|
|
103
|
-
for dg in self.
|
|
104
|
-
|
|
105
|
-
|
|
162
|
+
for dg in self.datagrabbers:
|
|
163
|
+
t_element = tuple(named_element[k] for k in dg.get_element_keys())
|
|
164
|
+
outs.append((dg, dg[t_element]))
|
|
106
165
|
# Now get the meta for this datagrabber
|
|
107
166
|
t_meta = {}
|
|
108
167
|
dg.update_meta(t_meta, "datagrabber")
|
|
109
168
|
# Store all the sub-datagrabbers meta
|
|
110
169
|
metas.append(t_meta["meta"]["datagrabber"])
|
|
111
170
|
|
|
171
|
+
# Add the data types with their main file first, and then the nested
|
|
172
|
+
# data types grabbed without the main file, which replace the ones
|
|
173
|
+
# grabbed with the main file
|
|
174
|
+
out = {}
|
|
175
|
+
for nested_only in (False, True):
|
|
176
|
+
for dg, t_out in outs:
|
|
177
|
+
for data_type, value in t_out.items():
|
|
178
|
+
t_nested_only = (
|
|
179
|
+
_nested_types_only(dg, data_type) is not None
|
|
180
|
+
)
|
|
181
|
+
if t_nested_only != nested_only:
|
|
182
|
+
continue
|
|
183
|
+
if data_type in out:
|
|
184
|
+
deep_update(out[data_type], value)
|
|
185
|
+
else:
|
|
186
|
+
out[data_type] = value
|
|
187
|
+
|
|
112
188
|
# Update all the metas again
|
|
113
189
|
for kind in out:
|
|
114
190
|
to_update = out[kind]
|
|
115
191
|
if not isinstance(to_update, list):
|
|
116
192
|
to_update = [to_update]
|
|
193
|
+
element_keys = self.get_type_element_keys(kind)
|
|
117
194
|
for t_kind in to_update:
|
|
118
195
|
self.update_meta(t_kind, "datagrabber")
|
|
119
196
|
t_kind["meta"]["datagrabber"]["datagrabbers"] = metas
|
|
197
|
+
t_kind["meta"]["element"] = named_element
|
|
198
|
+
t_kind["meta"]["_element_keys"] = element_keys
|
|
120
199
|
return out
|
|
121
200
|
|
|
201
|
+
def get_type_element_keys(self, data_type: str) -> list[str]:
|
|
202
|
+
"""Get the element keys that the data of a data type depends on.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
data_type : str
|
|
207
|
+
The data type.
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
list of str
|
|
212
|
+
The element keys that the data of the data type depends on in all
|
|
213
|
+
the DataGrabbers grabbing it (e.g., the one grabbing the main file
|
|
214
|
+
and the one grabbing only its nested data types).
|
|
215
|
+
|
|
216
|
+
"""
|
|
217
|
+
keys = set()
|
|
218
|
+
for dg in self.datagrabbers:
|
|
219
|
+
if data_type in dg.get_types():
|
|
220
|
+
keys.update(dg.get_type_element_keys(data_type))
|
|
221
|
+
return [k for k in self.get_element_keys() if k in keys]
|
|
222
|
+
|
|
122
223
|
def __enter__(self) -> "MultipleDataGrabber":
|
|
123
224
|
"""Implement context entry."""
|
|
124
|
-
for dg in self.
|
|
225
|
+
for dg in self.datagrabbers:
|
|
125
226
|
dg.__enter__()
|
|
126
227
|
return self
|
|
127
228
|
|
|
128
229
|
def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
|
|
129
230
|
"""Implement context exit."""
|
|
130
|
-
for dg in self.
|
|
231
|
+
for dg in self.datagrabbers:
|
|
131
232
|
dg.__exit__(exc_type, exc_value, exc_traceback)
|
|
132
233
|
|
|
133
|
-
# TODO: return type should be List[List[str]], but base type is List[str]
|
|
134
234
|
def get_types(self) -> list[str]:
|
|
135
235
|
"""Get types.
|
|
136
236
|
|
|
137
237
|
Returns
|
|
138
238
|
-------
|
|
139
|
-
list of
|
|
239
|
+
list of str
|
|
140
240
|
The types of data to be grabbed.
|
|
141
241
|
|
|
142
242
|
"""
|
|
143
|
-
types = [x for dg in self.
|
|
144
|
-
return types
|
|
243
|
+
types = [x for dg in self.datagrabbers for x in dg.get_types()]
|
|
244
|
+
return list(dict.fromkeys(types))
|
|
145
245
|
|
|
146
246
|
def get_element_keys(self) -> list[str]:
|
|
147
247
|
"""Get element keys.
|
|
148
248
|
|
|
149
249
|
For each item in the ``element`` tuple passed to ``__getitem__()``,
|
|
150
|
-
this method returns the corresponding key(s)
|
|
250
|
+
this method returns the corresponding key(s): the keys of the first
|
|
251
|
+
DataGrabber, followed by the other keys of the other DataGrabbers.
|
|
151
252
|
|
|
152
253
|
Returns
|
|
153
254
|
-------
|
|
@@ -155,11 +256,16 @@ class MultipleDataGrabber(BaseDataGrabber):
|
|
|
155
256
|
The element keys.
|
|
156
257
|
|
|
157
258
|
"""
|
|
158
|
-
|
|
259
|
+
keys = [k for dg in self.datagrabbers for k in dg.get_element_keys()]
|
|
260
|
+
return list(dict.fromkeys(keys))
|
|
159
261
|
|
|
160
262
|
def get_elements(self) -> list:
|
|
161
263
|
"""Get elements.
|
|
162
264
|
|
|
265
|
+
The elements of the DataGrabbers are joined on their common keys, as
|
|
266
|
+
the elements of the data types of a :class:`.PatternDataGrabber`, so
|
|
267
|
+
the elements are the ones available in all the DataGrabbers.
|
|
268
|
+
|
|
163
269
|
Returns
|
|
164
270
|
-------
|
|
165
271
|
list
|
|
@@ -169,11 +275,25 @@ class MultipleDataGrabber(BaseDataGrabber):
|
|
|
169
275
|
related DataGrabbers.
|
|
170
276
|
|
|
171
277
|
"""
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
for
|
|
175
|
-
|
|
176
|
-
|
|
278
|
+
elements = None
|
|
279
|
+
keys = []
|
|
280
|
+
for dg in self.datagrabbers:
|
|
281
|
+
t_keys = dg.get_element_keys()
|
|
282
|
+
t_elements = {
|
|
283
|
+
e if isinstance(e, tuple) else (e,) for e in dg.get_elements()
|
|
284
|
+
}
|
|
285
|
+
if elements is None:
|
|
286
|
+
elements, keys = t_elements, t_keys
|
|
287
|
+
else:
|
|
288
|
+
elements, keys = _join_elements(
|
|
289
|
+
elements, keys, t_elements, t_keys
|
|
290
|
+
)
|
|
291
|
+
if elements is None:
|
|
292
|
+
return []
|
|
293
|
+
out = sorted(elements)
|
|
294
|
+
if len(keys) == 1:
|
|
295
|
+
out = [element[0] for element in out]
|
|
296
|
+
return out
|
|
177
297
|
|
|
178
298
|
def get_item(self, **_: dict) -> dict[str, dict]:
|
|
179
299
|
"""Get the specified item from the dataset.
|