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.
Files changed (293) hide show
  1. junifer/_version.py +9 -19
  2. junifer/api/__init__.pyi +17 -2
  3. junifer/api/functions.py +222 -39
  4. junifer/api/queue_context/__init__.pyi +11 -3
  5. junifer/api/queue_context/gnu_parallel_local_adapter.py +81 -120
  6. junifer/api/queue_context/htcondor_adapter.py +118 -181
  7. junifer/api/queue_context/queue_context_adapter.py +51 -7
  8. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +10 -35
  9. junifer/api/queue_context/tests/test_htcondor_adapter.py +13 -50
  10. junifer/api/res/fsl/run_fsl_docker.sh +4 -0
  11. junifer/api/tests/test_functions.py +863 -36
  12. junifer/cli/__init__.py +1 -7
  13. junifer/cli/cli.py +41 -22
  14. junifer/cli/parser.py +23 -147
  15. junifer/cli/tests/data/gmd_mean.yaml +6 -6
  16. junifer/cli/tests/data/gmd_mean_htcondor.yaml +6 -5
  17. junifer/cli/tests/data/partly_cloudy_agg_mean_tian.yml +1 -0
  18. junifer/cli/tests/test_cli.py +120 -4
  19. junifer/cli/tests/test_cli_utils.py +23 -6
  20. junifer/cli/utils.py +14 -9
  21. junifer/configs/__init__.py +5 -0
  22. junifer/configs/__init__.pyi +0 -0
  23. junifer/configs/juseless/datagrabbers/__init__.pyi +4 -2
  24. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +18 -24
  25. junifer/configs/juseless/datagrabbers/camcan_vbm.py +20 -27
  26. junifer/configs/juseless/datagrabbers/ixi_vbm.py +44 -52
  27. junifer/configs/juseless/datagrabbers/tests/test_ixi_vbm.py +0 -7
  28. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -19
  29. junifer/configs/juseless/datagrabbers/ucla.py +111 -104
  30. junifer/configs/juseless/datagrabbers/ukb_vbm.py +19 -25
  31. junifer/configs/py.typed +0 -0
  32. junifer/conftest.py +63 -24
  33. junifer/data/_dispatch.py +9 -19
  34. junifer/data/coordinates/_ants_coordinates_warper.py +4 -1
  35. junifer/data/coordinates/_coordinates.py +51 -6
  36. junifer/data/coordinates/_fsl_coordinates_warper.py +4 -1
  37. junifer/data/coordinates/tests/test_coordinates.py +15 -3
  38. junifer/data/coordinates/tests/test_coordinates_names.py +37 -0
  39. junifer/data/maps/_ants_maps_warper.py +18 -12
  40. junifer/data/maps/_fsl_maps_warper.py +7 -3
  41. junifer/data/maps/_maps.py +12 -8
  42. junifer/data/maps/tests/test_maps.py +13 -21
  43. junifer/data/masks/_ants_mask_warper.py +18 -12
  44. junifer/data/masks/_fsl_mask_warper.py +7 -3
  45. junifer/data/masks/_masks.py +15 -12
  46. junifer/data/masks/tests/test_masks.py +20 -17
  47. junifer/data/parcellations/_ants_parcellation_warper.py +18 -12
  48. junifer/data/parcellations/_fsl_parcellation_warper.py +7 -5
  49. junifer/data/parcellations/_parcellations.py +125 -91
  50. junifer/data/parcellations/tests/test_parcellations.py +122 -1
  51. junifer/data/template_spaces.py +117 -50
  52. junifer/data/tests/test_template_spaces.py +10 -7
  53. junifer/data/utils.py +10 -8
  54. junifer/datagrabber/__init__.pyi +38 -5
  55. junifer/datagrabber/aomic/__init__.pyi +8 -1
  56. junifer/datagrabber/aomic/_types.py +22 -0
  57. junifer/datagrabber/aomic/id1000.py +193 -183
  58. junifer/datagrabber/aomic/piop1.py +215 -259
  59. junifer/datagrabber/aomic/piop2.py +211 -244
  60. junifer/datagrabber/aomic/tests/test_id1000.py +70 -53
  61. junifer/datagrabber/aomic/tests/test_piop1.py +98 -102
  62. junifer/datagrabber/aomic/tests/test_piop2.py +88 -85
  63. junifer/datagrabber/base.py +253 -79
  64. junifer/datagrabber/datalad_base.py +127 -76
  65. junifer/datagrabber/dmcc13_benchmark.py +204 -289
  66. junifer/datagrabber/hcp1200/__init__.pyi +7 -2
  67. junifer/datagrabber/hcp1200/datalad_hcp1200.py +61 -40
  68. junifer/datagrabber/hcp1200/hcp1200.py +151 -164
  69. junifer/datagrabber/hcp1200/tests/test_hcp1200.py +116 -128
  70. junifer/datagrabber/multiple.py +183 -63
  71. junifer/datagrabber/pattern.py +358 -227
  72. junifer/datagrabber/pattern_datalad.py +84 -124
  73. junifer/datagrabber/pattern_validation_mixin.py +22 -50
  74. junifer/datagrabber/tests/test_base.py +17 -4
  75. junifer/datagrabber/tests/test_datalad_base.py +20 -27
  76. junifer/datagrabber/tests/test_dmcc13_benchmark.py +110 -122
  77. junifer/datagrabber/tests/test_multiple.py +293 -31
  78. junifer/datagrabber/tests/test_pattern.py +577 -18
  79. junifer/datagrabber/tests/test_pattern_datalad.py +177 -107
  80. junifer/datagrabber/tests/test_pattern_validation_mixin.py +9 -51
  81. junifer/datareader/default.py +8 -5
  82. junifer/datareader/tests/test_default_reader.py +1 -1
  83. junifer/external/BrainPrint/.codecov.yml +2 -0
  84. junifer/external/BrainPrint/.codespellignore +2 -0
  85. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/bug-report.md +45 -0
  86. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/documentation.md +12 -0
  87. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/feature-request.md +24 -0
  88. junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/questions-help-support.md +28 -0
  89. junifer/external/BrainPrint/.github/dependabot.yml +6 -0
  90. junifer/external/BrainPrint/.github/workflows/build.yml +53 -0
  91. junifer/external/BrainPrint/.github/workflows/code-style.yml +44 -0
  92. junifer/external/BrainPrint/.github/workflows/doc.yml +65 -0
  93. junifer/external/BrainPrint/.github/workflows/publish.yml +31 -0
  94. junifer/external/BrainPrint/.github/workflows/pytest.yml +54 -0
  95. junifer/external/BrainPrint/.gitignore +123 -0
  96. junifer/external/BrainPrint/LICENSE +21 -0
  97. junifer/external/BrainPrint/README.md +108 -0
  98. junifer/external/BrainPrint/brainprint/cli/__init__.py +11 -0
  99. junifer/external/BrainPrint/brainprint/cli/help_text.py +169 -0
  100. junifer/external/BrainPrint/brainprint/cli/parser.py +150 -0
  101. junifer/external/BrainPrint/brainprint/cli/utils.py +14 -0
  102. junifer/external/BrainPrint/brainprint/commands/__init__.py +0 -0
  103. junifer/external/BrainPrint/brainprint/commands/sys_info.py +18 -0
  104. junifer/external/BrainPrint/brainprint/utils/tests/__init__.py +0 -0
  105. junifer/external/BrainPrint/brainprint/utils/tests/test_config.py +33 -0
  106. junifer/external/BrainPrint/doc/Makefile +20 -0
  107. junifer/external/BrainPrint/doc/_static/.gitkeep +0 -0
  108. junifer/external/BrainPrint/doc/_templates/autosummary/class.rst +10 -0
  109. junifer/external/BrainPrint/doc/_templates/autosummary/function.rst +8 -0
  110. junifer/external/BrainPrint/doc/_templates/autosummary/module.rst +6 -0
  111. junifer/external/BrainPrint/doc/api/index.rst +11 -0
  112. junifer/external/BrainPrint/doc/conf.py +272 -0
  113. junifer/external/BrainPrint/doc/index.rst +140 -0
  114. junifer/external/BrainPrint/doc/links.inc +36 -0
  115. junifer/external/BrainPrint/doc/make.bat +35 -0
  116. junifer/external/BrainPrint/doc/references.bib +23 -0
  117. junifer/external/BrainPrint/pyproject.toml +170 -0
  118. junifer/external/BrainPrint/setup.py +3 -0
  119. junifer/external/__init__.py +5 -0
  120. junifer/external/__init__.pyi +0 -0
  121. junifer/external/nilearn/junifer_connectivity_measure.py +95 -39
  122. junifer/external/nilearn/junifer_nifti_spheres_masker.py +226 -67
  123. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +39 -27
  124. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +231 -21
  125. junifer/external/py.typed +0 -0
  126. junifer/markers/__init__.pyi +6 -3
  127. junifer/markers/base.py +126 -76
  128. junifer/markers/brainprint.py +30 -39
  129. junifer/markers/complexity/__init__.py +20 -3
  130. junifer/markers/complexity/complexity_base.py +40 -41
  131. junifer/markers/complexity/hurst_exponent.py +22 -35
  132. junifer/markers/complexity/multiscale_entropy_auc.py +53 -41
  133. junifer/markers/complexity/perm_entropy.py +22 -36
  134. junifer/markers/complexity/range_entropy.py +22 -35
  135. junifer/markers/complexity/range_entropy_auc.py +27 -36
  136. junifer/markers/complexity/sample_entropy.py +22 -36
  137. junifer/markers/complexity/tests/test_complexity_registry.py +48 -0
  138. junifer/markers/complexity/tests/test_hurst_exponent.py +47 -36
  139. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +50 -36
  140. junifer/markers/complexity/tests/test_perm_entropy.py +48 -36
  141. junifer/markers/complexity/tests/test_range_entropy.py +47 -36
  142. junifer/markers/complexity/tests/test_range_entropy_auc.py +47 -36
  143. junifer/markers/complexity/tests/test_sample_entropy.py +48 -36
  144. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +50 -36
  145. junifer/markers/complexity/weighted_perm_entropy.py +22 -36
  146. junifer/markers/ets_rss.py +35 -31
  147. junifer/markers/falff/__init__.pyi +7 -1
  148. junifer/markers/falff/_afni_falff.py +22 -9
  149. junifer/markers/falff/_junifer_falff.py +47 -31
  150. junifer/markers/falff/falff_base.py +80 -60
  151. junifer/markers/falff/falff_maps.py +17 -41
  152. junifer/markers/falff/falff_parcels.py +26 -51
  153. junifer/markers/falff/falff_spheres.py +34 -61
  154. junifer/markers/falff/tests/test_falff_maps.py +20 -23
  155. junifer/markers/falff/tests/test_falff_mask.py +99 -0
  156. junifer/markers/falff/tests/test_falff_parcels.py +14 -10
  157. junifer/markers/falff/tests/test_falff_spheres.py +14 -10
  158. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +32 -34
  159. junifer/markers/functional_connectivity/edge_functional_connectivity_maps.py +12 -25
  160. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +19 -34
  161. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +24 -44
  162. junifer/markers/functional_connectivity/functional_connectivity_base.py +50 -40
  163. junifer/markers/functional_connectivity/functional_connectivity_maps.py +12 -25
  164. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +19 -34
  165. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +24 -45
  166. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +21 -22
  167. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_maps.py +8 -7
  168. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +8 -4
  169. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +8 -4
  170. junifer/markers/functional_connectivity/tests/test_functional_connectivity_maps.py +8 -7
  171. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +9 -5
  172. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +8 -12
  173. junifer/markers/maps_aggregation.py +77 -55
  174. junifer/markers/parcel_aggregation.py +91 -70
  175. junifer/markers/reho/__init__.pyi +7 -1
  176. junifer/markers/reho/_afni_reho.py +23 -14
  177. junifer/markers/reho/_junifer_reho.py +112 -123
  178. junifer/markers/reho/reho_base.py +115 -37
  179. junifer/markers/reho/reho_maps.py +18 -32
  180. junifer/markers/reho/reho_parcels.py +26 -41
  181. junifer/markers/reho/reho_spheres.py +33 -54
  182. junifer/markers/reho/tests/test_junifer_reho.py +178 -0
  183. junifer/markers/reho/tests/test_reho_maps.py +27 -25
  184. junifer/markers/reho/tests/test_reho_parcels.py +28 -22
  185. junifer/markers/reho/tests/test_reho_spheres.py +16 -12
  186. junifer/markers/sphere_aggregation.py +88 -72
  187. junifer/markers/temporal_snr/temporal_snr_base.py +32 -30
  188. junifer/markers/temporal_snr/temporal_snr_maps.py +9 -18
  189. junifer/markers/temporal_snr/temporal_snr_parcels.py +19 -27
  190. junifer/markers/temporal_snr/temporal_snr_spheres.py +27 -41
  191. junifer/markers/temporal_snr/tests/test_temporal_snr_base.py +2 -2
  192. junifer/markers/temporal_snr/tests/test_temporal_snr_maps.py +17 -22
  193. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +11 -3
  194. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +9 -9
  195. junifer/markers/tests/test_brainprint.py +10 -9
  196. junifer/markers/tests/test_ets_rss.py +7 -3
  197. junifer/markers/tests/test_lost_regions.py +279 -0
  198. junifer/markers/tests/test_maps_aggregation.py +67 -114
  199. junifer/markers/tests/test_marker_utils.py +40 -1
  200. junifer/markers/tests/test_markers_base.py +275 -6
  201. junifer/markers/tests/test_parcel_aggregation.py +91 -53
  202. junifer/markers/tests/test_sphere_aggregation.py +53 -47
  203. junifer/markers/utils.py +267 -5
  204. junifer/onthefly/__init__.py +2 -3
  205. junifer/onthefly/__init__.pyi +4 -0
  206. junifer/onthefly/_brainprint.py +3 -5
  207. junifer/onthefly/py.typed +0 -0
  208. junifer/onthefly/read_transform.py +11 -9
  209. junifer/onthefly/tests/test_read_transform.py +7 -7
  210. junifer/pipeline/__init__.pyi +4 -2
  211. junifer/pipeline/marker_collection.py +14 -10
  212. junifer/pipeline/pipeline_component_registry.py +25 -6
  213. junifer/pipeline/pipeline_step_mixin.py +5 -12
  214. junifer/pipeline/tests/test_data_object_dumper.py +10 -10
  215. junifer/pipeline/tests/test_marker_collection.py +145 -168
  216. junifer/pipeline/tests/test_pipeline_component_registry.py +52 -8
  217. junifer/pipeline/tests/test_pipeline_step_mixin.py +12 -26
  218. junifer/pipeline/tests/test_update_meta_mixin.py +4 -5
  219. junifer/pipeline/tests/test_workdir_manager.py +39 -0
  220. junifer/pipeline/update_meta_mixin.py +13 -10
  221. junifer/pipeline/utils.py +22 -17
  222. junifer/pipeline/workdir_manager.py +41 -32
  223. junifer/preprocess/__init__.pyi +9 -4
  224. junifer/preprocess/_temporal_filter.py +34 -33
  225. junifer/preprocess/_temporal_slicer.py +17 -30
  226. junifer/preprocess/base.py +99 -55
  227. junifer/preprocess/confounds/__init__.pyi +10 -2
  228. junifer/preprocess/confounds/fmriprep_confound_remover.py +87 -109
  229. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +64 -45
  230. junifer/preprocess/smoothing/__init__.pyi +2 -2
  231. junifer/preprocess/smoothing/_afni_smoothing.py +8 -7
  232. junifer/preprocess/smoothing/_fsl_smoothing.py +6 -4
  233. junifer/preprocess/smoothing/_nilearn_smoothing.py +7 -5
  234. junifer/preprocess/smoothing/smoothing.py +59 -40
  235. junifer/preprocess/smoothing/tests/test_smoothing.py +28 -8
  236. junifer/preprocess/tests/test_preprocess_base.py +17 -9
  237. junifer/preprocess/tests/test_temporal_filter.py +37 -16
  238. junifer/preprocess/tests/test_temporal_slicer.py +12 -12
  239. junifer/preprocess/warping/__init__.pyi +2 -2
  240. junifer/preprocess/warping/_ants_warper.py +56 -40
  241. junifer/preprocess/warping/_fsl_warper.py +49 -36
  242. junifer/preprocess/warping/_utils.py +106 -0
  243. junifer/preprocess/warping/space_warper.py +99 -98
  244. junifer/preprocess/warping/tests/test_space_warper.py +46 -41
  245. junifer/preprocess/warping/tests/test_warping_utils.py +163 -0
  246. junifer/stats.py +46 -34
  247. junifer/storage/__init__.pyi +8 -4
  248. junifer/storage/base.py +129 -64
  249. junifer/storage/hdf5.py +307 -199
  250. junifer/storage/pandas_base.py +46 -24
  251. junifer/storage/sqlite.py +99 -62
  252. junifer/storage/tests/data/collected_h5io_fork.hdf5 +0 -0
  253. junifer/storage/tests/test_hdf5.py +147 -12
  254. junifer/storage/tests/test_repeated_storage.py +293 -0
  255. junifer/storage/tests/test_sqlite.py +9 -20
  256. junifer/storage/tests/test_storage_base.py +18 -21
  257. junifer/storage/tests/test_utils.py +83 -22
  258. junifer/storage/utils.py +58 -38
  259. junifer/testing/__init__.pyi +2 -2
  260. junifer/testing/datagrabbers.py +480 -44
  261. junifer/testing/registry.py +12 -0
  262. junifer/testing/tests/test_adhdtesting_datagrabber.py +24 -0
  263. junifer/testing/tests/test_oasisvmbtesting_datagrabber.py +5 -0
  264. junifer/testing/tests/test_partlycloudytesting_datagrabber.py +52 -0
  265. junifer/testing/tests/test_spmauditory_datagrabber.py +16 -0
  266. junifer/testing/tests/test_testing_registry.py +31 -0
  267. junifer/testing/tests/test_testing_utils.py +35 -0
  268. junifer/testing/tests/test_uclacnpvbmtesting_datagrabber.py +81 -0
  269. junifer/testing/utils.py +33 -1
  270. junifer/tests/test_stats.py +1 -5
  271. junifer/typing/__init__.pyi +2 -0
  272. junifer/typing/_typing.py +34 -19
  273. junifer/typing/py.typed +0 -0
  274. junifer/utils/__init__.pyi +10 -1
  275. junifer/utils/_config.py +1 -2
  276. junifer/utils/helpers.py +68 -2
  277. junifer/utils/logging.py +31 -7
  278. junifer/utils/tests/test_helpers.py +36 -1
  279. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/METADATA +43 -35
  280. junifer-0.0.8.dist-info/RECORD +402 -0
  281. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/WHEEL +1 -1
  282. junifer/cli/tests/test_parser.py +0 -280
  283. junifer/external/h5io/h5io/__init__.py +0 -15
  284. junifer/external/h5io/h5io/_h5io.py +0 -767
  285. junifer/external/h5io/h5io/_version.py +0 -1
  286. junifer/external/h5io/h5io/chunked_array.py +0 -94
  287. junifer/external/h5io/h5io/chunked_list.py +0 -65
  288. junifer/markers/complexity/__init__.pyi +0 -17
  289. junifer-0.0.7.dev332.dist-info/RECORD +0 -353
  290. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/entry_points.txt +0 -0
  291. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/AUTHORS.rst +0 -0
  292. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/LICENSE.md +0 -0
  293. {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/top_level.txt +0 -0
@@ -9,22 +9,44 @@ import atexit
9
9
  import os
10
10
  import tempfile
11
11
  from pathlib import Path
12
- from typing import Optional, Union
12
+ from typing import Any, NoReturn
13
13
 
14
14
  import datalad
15
15
  import datalad.api as dl
16
16
  from datalad.support.exceptions import IncompleteResultsError
17
17
  from datalad.support.gitrepo import GitRepo
18
+ from pydantic import AnyUrl, Field, field_validator
18
19
 
20
+ from ..api.decorators import register_datagrabber
19
21
  from ..pipeline import WorkDirManager
20
22
  from ..typing import Element
21
- from ..utils import config, logger, raise_error, warn_with_log
22
- from .base import BaseDataGrabber
23
+ from ..utils import config, raise_error, warn_with_log
24
+ from .base import BaseDataGrabber, logger
23
25
 
24
26
 
25
27
  __all__ = ["DataladDataGrabber"]
26
28
 
27
29
 
30
+ def _create_datadir() -> Path:
31
+ """Create a temporary directory for datalad dataset."""
32
+ datadir = WorkDirManager().get_tempdir(
33
+ prefix="datalad", suffix="juniferauto"
34
+ )
35
+ logger.info(
36
+ "Created a temporary directory for datalad dataset at: "
37
+ f"{datadir.resolve()!s}"
38
+ )
39
+ return datadir
40
+
41
+
42
+ def _remove_datadir(datadir: Path) -> None:
43
+ """Remove temporary directory if it exists."""
44
+ if datadir.exists():
45
+ logger.debug(f"Removing temporary directory at: {datadir.resolve()!s}")
46
+ WorkDirManager().delete_tempdir(datadir)
47
+
48
+
49
+ @register_datagrabber
28
50
  class DataladDataGrabber(BaseDataGrabber):
29
51
  """Abstract base class for datalad-based data fetching.
30
52
 
@@ -32,17 +54,15 @@ class DataladDataGrabber(BaseDataGrabber):
32
54
 
33
55
  Parameters
34
56
  ----------
35
- rootdir : str or pathlib.Path, optional
57
+ uri : pydantic.AnyUrl
58
+ URI of the datalad sibling.
59
+ rootdir : pathlib.Path, optional
36
60
  The path within the datalad dataset to the root directory
37
- (default ".").
38
- datadir : str or pathlib.Path or None, optional
39
- That directory where the datalad dataset will be cloned. If None,
40
- the datalad dataset will be cloned into a temporary directory
41
- (default None).
42
- uri : str or None, optional
43
- URI of the datalad sibling (default None).
44
- **kwargs
45
- Keyword arguments passed to superclass.
61
+ (default Path(".")).
62
+ datadir : pathlib.Path, optional
63
+ That path where the datalad dataset will be cloned.
64
+ If not specified, the datalad dataset will be cloned into a temporary
65
+ directory.
46
66
 
47
67
  Methods
48
68
  -------
@@ -67,24 +87,62 @@ class DataladDataGrabber(BaseDataGrabber):
67
87
  This class is intended to be used as a superclass of a subclass
68
88
  with multiple inheritance.
69
89
 
90
+ If the ``datadir`` is specified and has the stem prefix as ``"datalad"``
91
+ and the stem suffix as ``"juniferauto"``, it will be automatically
92
+ deleted after use.
93
+
70
94
  """
71
95
 
72
- def __init__(
73
- self,
74
- rootdir: Union[str, Path] = ".",
75
- datadir: Union[str, Path, None] = None,
76
- uri: Optional[str] = None,
77
- **kwargs,
78
- ):
79
- if datadir is None:
80
- logger.info("`datadir` is None, creating a temporary directory")
81
- # Create temporary directory
82
- tmpdir = WorkDirManager().get_tempdir(prefix="datalad")
83
- self._tmpdir = tmpdir
84
- datadir = tmpdir / "datadir"
85
- datadir.mkdir(parents=True, exist_ok=False)
86
- logger.info(f"`datadir` set to {datadir}")
87
- cache_dir = tmpdir / ".datalad_cache"
96
+ uri: AnyUrl = Field(frozen=True)
97
+ rootdir: Path = Field(frozen=True, default=Path("."))
98
+ datadir: Path = Field(default_factory=lambda: _create_datadir())
99
+ _repodir: Path = Path(".")
100
+ # Flag to indicate if the dataset was cloned before and it might be
101
+ # dirty
102
+ datalad_dirty: bool = False
103
+ datalad_commit_id: str | None = None
104
+ datalad_id: str | None = None
105
+ _dataset: dl.Dataset | None = None
106
+ _got_files: list[str] = [] # noqa: RUF012
107
+ _was_cloned: bool = False
108
+
109
+ @field_validator("datadir", mode="after")
110
+ @classmethod
111
+ def warn_existing_datalad_autodir(cls, value: Path) -> Path:
112
+ """Warn if existing datalad autodir exists."""
113
+ if value.stem.startswith("datalad") and value.stem.endswith(
114
+ "juniferauto"
115
+ ):
116
+ warn_with_log(
117
+ f"{value.resolve()!s} already exists and will reuse assets "
118
+ "from previous run."
119
+ )
120
+ return value
121
+
122
+ @field_validator("datalad_dirty", mode="before")
123
+ @classmethod
124
+ def disable_tag(cls, value: Any) -> NoReturn:
125
+ """Disable setting datalad_dirty directly."""
126
+ raise_error(
127
+ msg="datalad_dirty cannot be set directly",
128
+ klass=ValueError,
129
+ )
130
+
131
+ def validate_datagrabber_params(self) -> None:
132
+ """Run extra logical validation for datagrabber."""
133
+ logger.debug("Initializing DataladDataGrabber")
134
+ logger.debug(f"\turi = {self.uri}")
135
+ logger.debug(f"\trootdir = {self.rootdir}")
136
+ if self.datadir.stem.startswith(
137
+ "datalad"
138
+ ) and self.datadir.stem.endswith("juniferauto"):
139
+ self._repodir = self.datadir / "dataset"
140
+ self._repodir.mkdir(parents=True, exist_ok=False)
141
+ logger.info(
142
+ "Datalad dataset installation path set to: "
143
+ f"{self._repodir.resolve()!s}"
144
+ )
145
+ cache_dir = self.datadir / ".datalad_cache"
88
146
  sockets_dir = cache_dir / "sockets"
89
147
  locks_dir = cache_dir / "locks"
90
148
  sockets_dir.mkdir(parents=True, exist_ok=False)
@@ -106,43 +164,29 @@ class DataladDataGrabber(BaseDataGrabber):
106
164
  "Datalad locks set to "
107
165
  f"{datalad.cfg.get('datalad.locations.locks')}"
108
166
  )
109
- atexit.register(self._rmtmpdir)
110
- # TODO: uri can be converted to a positional argument
111
- if uri is None:
112
- raise_error("`uri` must be provided")
113
-
114
- super().__init__(datadir=datadir, **kwargs)
115
- logger.debug("Initializing DataladDataGrabber")
116
- logger.debug(f"\turi = {uri}")
117
- logger.debug(f"\t_rootdir = {rootdir}")
118
- self.uri = uri
119
- self._rootdir = rootdir
120
- # Flag to indicate if the dataset was cloned before and it might be
121
- # dirty
122
- self.datalad_dirty = False
167
+ atexit.register(_remove_datadir, self.datadir)
168
+ else:
169
+ self._repodir = self.datadir
170
+ super().validate_datagrabber_params()
123
171
 
124
172
  def __del__(self) -> None:
125
173
  """Destructor."""
126
- if hasattr(self, "_tmpdir"):
127
- self._rmtmpdir()
128
-
129
- def _rmtmpdir(self) -> None:
130
- """Remove temporary directory if it exists."""
131
- if self._tmpdir.exists():
132
- logger.debug("Removing temporary directory")
133
- WorkDirManager().delete_tempdir(self._tmpdir)
174
+ if self.datadir.stem.startswith(
175
+ "datalad"
176
+ ) and self.datadir.stem.endswith("juniferauto"):
177
+ _remove_datadir(self.datadir)
134
178
 
135
179
  @property
136
- def datadir(self) -> Path:
137
- """Get data directory path.
180
+ def fulldir(self) -> Path:
181
+ """Get complete data directory path.
138
182
 
139
183
  Returns
140
184
  -------
141
185
  pathlib.Path
142
- Path to the data directory.
186
+ Complete path to the data directory.
143
187
 
144
188
  """
145
- return super().datadir / self._rootdir
189
+ return self._repodir / self.rootdir
146
190
 
147
191
  def _get_dataset_id_remote(self) -> tuple[str, bool]:
148
192
  """Get the dataset ID from the remote.
@@ -165,8 +209,10 @@ class DataladDataGrabber(BaseDataGrabber):
165
209
  with tempfile.TemporaryDirectory() as tmpdir:
166
210
  if not config.get("datagrabber.skipidcheck", False):
167
211
  logger.debug(f"Querying {self.uri} for dataset ID")
168
- repo = GitRepo.clone(
169
- self.uri, path=tmpdir, clone_options=["-n", "--depth=1"]
212
+ repo: GitRepo = GitRepo.clone(
213
+ str(self.uri),
214
+ path=tmpdir,
215
+ clone_options=["-n", "--depth=1"],
170
216
  )
171
217
  repo.checkout(name=".datalad/config", options=["HEAD"])
172
218
  remote_id = repo.config.get("datalad.dataset.id", None)
@@ -179,10 +225,11 @@ class DataladDataGrabber(BaseDataGrabber):
179
225
  is_dirty = False
180
226
  else:
181
227
  logger.debug("Skipping dataset ID check")
228
+ # Should be already set to the dataset
182
229
  remote_id = self._dataset.id
183
230
  is_dirty = False
184
231
  logger.debug(
185
- f"Remote dataset is {'' if is_dirty else 'not'} dirty"
232
+ f"Remote dataset is {'dirty' if is_dirty else 'not dirty'}"
186
233
  )
187
234
  if remote_id is None:
188
235
  raise_error("Could not get dataset ID from remote")
@@ -252,7 +299,7 @@ class DataladDataGrabber(BaseDataGrabber):
252
299
  return out
253
300
 
254
301
  def install(self) -> None:
255
- """Install the datalad dataset into the ``datadir``.
302
+ """Installs the datalad dataset.
256
303
 
257
304
  Raises
258
305
  ------
@@ -262,12 +309,10 @@ class DataladDataGrabber(BaseDataGrabber):
262
309
  If there is a datalad-related problem while cloning dataset.
263
310
 
264
311
  """
265
- isinstalled = dl.Dataset(self._datadir).is_installed()
266
- if isinstalled:
312
+ is_installed = dl.Dataset(self._repodir).is_installed()
313
+ if is_installed:
267
314
  logger.debug("Dataset already installed")
268
- self._got_files = []
269
- self._dataset: dl.Dataset = dl.Dataset(self._datadir)
270
-
315
+ self._dataset = dl.Dataset(self._repodir)
271
316
  # Check if dataset is already installed with a different ID
272
317
  remote_id, is_dirty = self._get_dataset_id_remote()
273
318
  if remote_id != self._dataset.id:
@@ -275,7 +320,6 @@ class DataladDataGrabber(BaseDataGrabber):
275
320
  "Dataset already installed but with a different "
276
321
  f"ID: {self._dataset.id} (local) != {remote_id} (remote)"
277
322
  )
278
-
279
323
  # Conditional reporting on dataset dirtiness
280
324
  self.datalad_dirty = is_dirty
281
325
  if self.datalad_dirty:
@@ -287,18 +331,18 @@ class DataladDataGrabber(BaseDataGrabber):
287
331
  logger.debug(f"Dataset (id: {self._dataset.id}) is clean")
288
332
 
289
333
  else:
290
- logger.debug(f"Installing dataset {self.uri} to {self._datadir}")
334
+ logger.debug(f"Installing dataset {self.uri} to {self._repodir}")
291
335
  try:
292
- self._dataset: dl.Dataset = dl.clone( # type: ignore
293
- self.uri, self._datadir, result_renderer="disabled"
336
+ self._dataset = dl.clone(
337
+ self.uri, self._repodir, result_renderer="disabled"
294
338
  )
295
339
  except IncompleteResultsError as e:
296
340
  raise_error(f"Failed to clone dataset: {e.failed}")
297
341
  logger.debug("Dataset installed")
298
- self._was_cloned = not isinstalled
299
-
300
- self.datalad_commit_id = self._dataset.repo.get_hexsha( # type: ignore
301
- self._dataset.repo.get_corresponding_branch() # type: ignore
342
+ self._was_cloned = not is_installed
343
+ # Dataset should be set already
344
+ self.datalad_commit_id = self._dataset.repo.get_hexsha(
345
+ self._dataset.repo.get_corresponding_branch()
302
346
  )
303
347
  self.datalad_id = self._dataset.id
304
348
 
@@ -307,11 +351,18 @@ class DataladDataGrabber(BaseDataGrabber):
307
351
  if self._was_cloned:
308
352
  logger.debug("Removing dataset with reckless='kill'")
309
353
  self._dataset.remove(reckless="kill", result_renderer="disabled")
310
- else:
354
+ elif len(self._got_files) > 0:
311
355
  logger.debug("Dropping files that were downloaded")
312
356
  for f in self._got_files:
313
- logger.debug(f"Dropping {f}")
314
- self._dataset.drop(f, result_renderer="disabled")
357
+ logger.debug(f"\t: {f}")
358
+ # Drop all files in a single call. The files were just obtained
359
+ # from a remote, so skip checking that they are still available
360
+ # there, which would contact the remote for each file.
361
+ self._dataset.drop(
362
+ self._got_files,
363
+ reckless="availability",
364
+ result_renderer="disabled",
365
+ )
315
366
 
316
367
  def __getitem__(self, element: Element) -> dict:
317
368
  """Implement single element indexing in the Datalad database.
@@ -321,7 +372,7 @@ class DataladDataGrabber(BaseDataGrabber):
321
372
 
322
373
  Parameters
323
374
  ----------
324
- element : str or tuple of str
375
+ element : `Element`
325
376
  The element to be indexed. If one string is provided, it is
326
377
  assumed to be a tuple with only one item. If a tuple is provided,
327
378
  each item in the tuple is the value for the replacement string