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
@@ -5,15 +5,38 @@
5
5
 
6
6
  import shutil
7
7
  import textwrap
8
+ from enum import StrEnum
8
9
  from pathlib import Path
9
- from typing import Optional
10
+ from typing import Any
10
11
 
11
12
  from ...typing import Elements
12
- from ...utils import logger, make_executable, raise_error, run_ext_cmd
13
- from .queue_context_adapter import QueueContextAdapter
13
+ from ...utils import make_executable, raise_error, run_ext_cmd
14
+ from .queue_context_adapter import (
15
+ EnvKind,
16
+ EnvShell,
17
+ QueueContextAdapter,
18
+ QueueContextEnv,
19
+ logger,
20
+ )
14
21
 
15
22
 
16
- __all__ = ["HTCondorAdapter"]
23
+ __all__ = ["HTCondorAdapter", "HTCondorCollect"]
24
+
25
+
26
+ class HTCondorCollect(StrEnum):
27
+ """Accepted HTCondor collect commands.
28
+
29
+ * ``"yes"``: Submit "collect" task and run even if some of the jobs
30
+ fail.
31
+ * ``"on_success_only"``: Submit "collect" task and run only if all jobs
32
+ succeed.
33
+ * ``"no"``: Do not submit "collect" task.
34
+
35
+ """
36
+
37
+ Yes = "yes"
38
+ No = "no"
39
+ OnSuccessOnly = "on_success_only"
17
40
 
18
41
 
19
42
  class HTCondorAdapter(QueueContextAdapter):
@@ -27,14 +50,14 @@ class HTCondorAdapter(QueueContextAdapter):
27
50
  The path to the job directory.
28
51
  yaml_config_path : pathlib.Path
29
52
  The path to the YAML config file.
30
- elements : list of str or tuple
53
+ elements : ``Elements``
31
54
  Element(s) to process. Will be used to index the DataGrabber.
32
55
  pre_run : str or None, optional
33
- Extra bash commands to source before the run (default None).
56
+ Extra shell commands to source before the run (default None).
34
57
  pre_collect : str or None, optional
35
- Extra bash commands to source before the collect (default None).
36
- env : dict, optional
37
- The Python environment configuration. If None, will run without a
58
+ Extra shell commands to source before the collect (default None).
59
+ env : :class:`.QueueContextEnv` or None, optional
60
+ The environment configuration. If None, will run without a
38
61
  virtual environment of any kind (default None).
39
62
  verbose : str, optional
40
63
  The level of verbosity (default "info").
@@ -49,25 +72,12 @@ class HTCondorAdapter(QueueContextAdapter):
49
72
  The size of disk (HDD or SSD) to use (default "1G").
50
73
  extra_preamble : str or None, optional
51
74
  Extra commands to pass to HTCondor (default None).
52
- collect : {"yes", "on_success_only", "no"}, optional
75
+ collect_task : :class:`.HTCondorCollect`, optional
53
76
  Whether to submit "collect" task for junifer (default "yes").
54
- Valid options are:
55
-
56
- * "yes": Submit "collect" task and run even if some of the jobs
57
- fail.
58
- * "on_success_only": Submit "collect" task and run only if all jobs
59
- succeed.
60
- * "no": Do not submit "collect" task.
61
-
62
77
  submit : bool, optional
63
78
  Whether to submit the jobs. In any case, .dag files will be created
64
79
  for submission (default False).
65
80
 
66
- Raises
67
- ------
68
- ValueError
69
- If ``collect`` is invalid or if ``env`` is invalid.
70
-
71
81
  See Also
72
82
  --------
73
83
  QueueContextAdapter :
@@ -77,144 +87,67 @@ class HTCondorAdapter(QueueContextAdapter):
77
87
 
78
88
  """
79
89
 
80
- def __init__(
81
- self,
82
- job_name: str,
83
- job_dir: Path,
84
- yaml_config_path: Path,
85
- elements: Elements,
86
- pre_run: Optional[str] = None,
87
- pre_collect: Optional[str] = None,
88
- env: Optional[dict[str, str]] = None,
89
- verbose: str = "info",
90
- verbose_datalad: Optional[str] = None,
91
- cpus: int = 1,
92
- mem: str = "8G",
93
- disk: str = "1G",
94
- extra_preamble: Optional[str] = None,
95
- collect: str = "yes",
96
- submit: bool = False,
97
- ) -> None:
98
- """Initialize the class."""
99
- self._job_name = job_name
100
- self._job_dir = job_dir
101
- self._yaml_config_path = yaml_config_path
102
- self._elements = elements
103
- self._pre_run = pre_run
104
- self._pre_collect = pre_collect
105
- self._check_env(env)
106
- self._verbose = verbose
107
- self._verbose_datalad = verbose_datalad
108
- self._cpus = cpus
109
- self._mem = mem
110
- self._disk = disk
111
- self._extra_preamble = extra_preamble
112
- self._collect = self._check_collect(collect)
113
- self._submit = submit
114
-
115
- self._log_dir = self._job_dir / "logs"
116
- self._pre_run_path = self._job_dir / "pre_run.sh"
117
- self._pre_collect_path = self._job_dir / "pre_collect.sh"
118
- self._submit_run_path = self._job_dir / f"run_{self._job_name}.submit"
119
- self._submit_collect_path = (
120
- self._job_dir / f"collect_{self._job_name}.submit"
121
- )
122
- self._dag_path = self._job_dir / f"{self._job_name}.dag"
123
-
124
- def _check_env(self, env: Optional[dict[str, str]]) -> None:
125
- """Check value of env parameter on init.
126
-
127
- Parameters
128
- ----------
129
- env : dict or None
130
- The value of env parameter.
131
-
132
- Raises
133
- ------
134
- ValueError
135
- If ``env.kind`` is invalid or
136
- if ``env.shell`` is invalid.
137
-
138
- """
139
- # Set env related variables
140
- if env is None:
141
- env = {"kind": "local"}
142
- # Check env kind
143
- valid_env_kinds = ["conda", "venv", "local"]
144
- if env["kind"] not in valid_env_kinds:
145
- raise_error(
146
- f"Invalid value for `env.kind`: {env['kind']}, "
147
- f"must be one of {valid_env_kinds}"
90
+ job_name: str
91
+ job_dir: Path
92
+ yaml_config_path: Path
93
+ elements: Elements
94
+ pre_run: str | None = None
95
+ pre_collect: str | None = None
96
+ env: QueueContextEnv | None = None
97
+ verbose: str = "info"
98
+ verbose_datalad: str | None = None
99
+ cpus: int = 1
100
+ mem: str = "8G"
101
+ disk: str = "1G"
102
+ extra_preamble: str | None = None
103
+ collect_task: HTCondorCollect = HTCondorCollect.Yes
104
+ submit: bool = False
105
+
106
+ def model_post_init(self, context: Any): # noqa: D102
107
+ if self.env is None:
108
+ self.env = QueueContextEnv(
109
+ kind=EnvKind.Local, shell=EnvShell.Bash, name=""
148
110
  )
111
+ if self.env["kind"] == EnvKind.Local:
112
+ # No virtual environment
113
+ self._executable = "junifer"
114
+ self._arguments = ""
149
115
  else:
150
- # Check shell
151
- shell = env.get("shell", "bash")
152
- valid_shells = ["bash", "zsh"]
153
- if shell not in valid_shells:
154
- raise_error(
155
- f"Invalid value for `env.shell`: {shell}, "
156
- f"must be one of {valid_shells}"
157
- )
158
- self._shell = shell
159
- # Set variables
160
- if env["kind"] == "local":
161
- # No virtual environment
162
- self._executable = "junifer"
163
- self._arguments = ""
164
- else:
165
- self._executable = f"run_{env['kind']}.{self._shell}"
166
- self._arguments = f"{env['name']} junifer"
167
- self._exec_path = self._job_dir / self._executable
168
-
169
- def _check_collect(self, collect: str) -> str:
170
- """Check value of collect parameter on init.
171
-
172
- Parameters
173
- ----------
174
- collect : str
175
- The value of collect parameter.
176
-
177
- Returns
178
- -------
179
- str
180
- The checked value of collect parameter.
181
-
182
- Raises
183
- ------
184
- ValueError
185
- If ``collect`` is invalid.
186
-
187
- """
188
- valid_options = ["yes", "no", "on_success_only"]
189
- if collect not in valid_options:
190
- raise_error(
191
- f"Invalid value for `collect`: {collect}, "
192
- f"must be one of {valid_options}"
193
- )
194
- else:
195
- return collect
116
+ if self.env["name"] is None:
117
+ raise_error("`env.name` is required")
118
+ self._executable = f"run_{self.env['kind']}.{self.env['shell']}"
119
+ self._arguments = f"{self.env['name']} junifer"
120
+ self._exec_path = self.job_dir / self._executable
121
+ self._log_dir = self.job_dir / "logs"
122
+ self._pre_run_path = self.job_dir / "pre_run.sh"
123
+ self._pre_collect_path = self.job_dir / "pre_collect.sh"
124
+ self._submit_run_path = self.job_dir / f"run_{self.job_name}.submit"
125
+ self._submit_collect_path = (
126
+ self.job_dir / f"collect_{self.job_name}.submit"
127
+ )
128
+ self._dag_path = self.job_dir / f"{self.job_name}.dag"
196
129
 
197
- def pre_run(self) -> str:
130
+ def pre_run_cmds(self) -> str:
198
131
  """Return pre-run commands."""
199
132
  fixed = (
200
- f"#!/usr/bin/env {self._shell}\n\n"
133
+ f"#!/usr/bin/env {self.env['shell']}\n\n"
201
134
  "# This script is auto-generated by junifer.\n\n"
202
135
  "# Force datalad to run in non-interactive mode\n"
203
136
  "DATALAD_UI_INTERACTIVE=false\n"
204
137
  )
205
- var = self._pre_run or ""
138
+ var = self.pre_run or ""
206
139
  return fixed + "\n" + var
207
140
 
208
- def run(self) -> str:
141
+ def run_cmds(self) -> str:
209
142
  """Return run commands."""
210
- verbose_args = f"--verbose {self._verbose} "
211
- if self._verbose_datalad is not None:
143
+ verbose_args = f"--verbose {self.verbose} "
144
+ if self.verbose_datalad is not None:
212
145
  verbose_args = (
213
- f"{verbose_args} --verbose-datalad {self._verbose_datalad} "
146
+ f"{verbose_args} --verbose-datalad {self.verbose_datalad} "
214
147
  )
215
148
  junifer_run_args = (
216
149
  "run "
217
- f"{self._yaml_config_path.resolve()!s} "
150
+ f"{self.yaml_config_path.resolve()!s} "
218
151
  f"{verbose_args}"
219
152
  "--element $(element)"
220
153
  )
@@ -227,11 +160,11 @@ class HTCondorAdapter(QueueContextAdapter):
227
160
  "universe = vanilla\n"
228
161
  "getenv = True\n\n"
229
162
  "# Resources\n"
230
- f"request_cpus = {self._cpus}\n"
231
- f"request_memory = {self._mem}\n"
232
- f"request_disk = {self._disk}\n\n"
163
+ f"request_cpus = {self.cpus}\n"
164
+ f"request_memory = {self.mem}\n"
165
+ f"request_disk = {self.disk}\n\n"
233
166
  "# Executable\n"
234
- f"initial_dir = {self._job_dir.resolve()!s}\n"
167
+ f"initial_dir = {self.job_dir.resolve()!s}\n"
235
168
  f"executable = $(initial_dir)/{self._executable}\n"
236
169
  f"transfer_executable = False\n\n"
237
170
  f"arguments = {self._arguments} {junifer_run_args}\n\n"
@@ -240,31 +173,31 @@ class HTCondorAdapter(QueueContextAdapter):
240
173
  f"output = {log_dir_prefix}.out\n"
241
174
  f"error = {log_dir_prefix}.err\n"
242
175
  )
243
- var = self._extra_preamble or ""
176
+ var = self.extra_preamble or ""
244
177
  return fixed + "\n" + var + "\n" + "queue"
245
178
 
246
- def pre_collect(self) -> str:
179
+ def pre_collect_cmds(self) -> str:
247
180
  """Return pre-collect commands."""
248
181
  fixed = (
249
- f"#!/usr/bin/env {self._shell}\n\n"
182
+ f"#!/usr/bin/env {self.env['shell']}\n\n"
250
183
  "# This script is auto-generated by junifer.\n"
251
184
  )
252
- var = self._pre_collect or ""
185
+ var = self.pre_collect or ""
253
186
  # Add commands if collect="yes"
254
- if self._collect == "yes":
187
+ if self.collect_task == "yes":
255
188
  var += 'if [ "${1}" == "4" ]; then\n exit 1\nfi\n'
256
189
  return fixed + "\n" + var
257
190
 
258
- def collect(self) -> str:
191
+ def collect_cmds(self) -> str:
259
192
  """Return collect commands."""
260
- verbose_args = f"--verbose {self._verbose} "
261
- if self._verbose_datalad is not None:
193
+ verbose_args = f"--verbose {self.verbose} "
194
+ if self.verbose_datalad is not None:
262
195
  verbose_args = (
263
- f"{verbose_args} --verbose-datalad {self._verbose_datalad} "
196
+ f"{verbose_args} --verbose-datalad {self.verbose_datalad} "
264
197
  )
265
198
 
266
199
  junifer_collect_args = (
267
- f"collect {self._yaml_config_path.resolve()!s} {verbose_args}"
200
+ f"collect {self.yaml_config_path.resolve()!s} {verbose_args}"
268
201
  )
269
202
  log_dir_prefix = f"{self._log_dir.resolve()!s}/junifer_collect"
270
203
  fixed = (
@@ -273,11 +206,11 @@ class HTCondorAdapter(QueueContextAdapter):
273
206
  "universe = vanilla\n"
274
207
  "getenv = True\n\n"
275
208
  "# Resources\n"
276
- f"request_cpus = {self._cpus}\n"
277
- f"request_memory = {self._mem}\n"
278
- f"request_disk = {self._disk}\n\n"
209
+ f"request_cpus = {self.cpus}\n"
210
+ f"request_memory = {self.mem}\n"
211
+ f"request_disk = {self.disk}\n\n"
279
212
  "# Executable\n"
280
- f"initial_dir = {self._job_dir.resolve()!s}\n"
213
+ f"initial_dir = {self.job_dir.resolve()!s}\n"
281
214
  f"executable = $(initial_dir)/{self._executable}\n"
282
215
  "transfer_executable = False\n\n"
283
216
  f"arguments = {self._arguments} {junifer_collect_args}\n\n"
@@ -286,13 +219,13 @@ class HTCondorAdapter(QueueContextAdapter):
286
219
  f"output = {log_dir_prefix}.out\n"
287
220
  f"error = {log_dir_prefix}.err\n"
288
221
  )
289
- var = self._extra_preamble or ""
222
+ var = self.extra_preamble or ""
290
223
  return fixed + "\n" + var + "\n" + "queue"
291
224
 
292
- def dag(self) -> str:
225
+ def dag_cmds(self) -> str:
293
226
  """Return HTCondor DAG commands."""
294
227
  fixed = ""
295
- for idx, element in enumerate(self._elements):
228
+ for idx, element in enumerate(self.elements):
296
229
  # Stringify elements if tuple for operation
297
230
  str_element = (
298
231
  ",".join(element) if isinstance(element, tuple) else element
@@ -307,15 +240,15 @@ class HTCondorAdapter(QueueContextAdapter):
307
240
  f'log_element="{log_element}"\n\n' # double quoted
308
241
  )
309
242
  var = ""
310
- if self._collect == "yes":
243
+ if self.collect_task == "yes":
311
244
  var += (
312
245
  f"FINAL collect {self._submit_collect_path}\n"
313
246
  f"SCRIPT PRE collect {self._pre_collect_path.as_posix()} "
314
247
  "$DAG_STATUS\n"
315
248
  )
316
- elif self._collect == "on_success_only":
249
+ elif self.collect_task == "on_success_only":
317
250
  var += f"JOB collect {self._submit_collect_path}\nPARENT "
318
- for idx, _ in enumerate(self._elements):
251
+ for idx, _ in enumerate(self.elements):
319
252
  var += f"run{idx} "
320
253
  var += "CHILD collect\n"
321
254
 
@@ -326,7 +259,7 @@ class HTCondorAdapter(QueueContextAdapter):
326
259
  logger.info("Creating HTCondor job")
327
260
  # Create logs
328
261
  logger.info(
329
- f"Creating logs directory under {self._job_dir.resolve()!s}"
262
+ f"Creating logs directory under {self.job_dir.resolve()!s}"
330
263
  )
331
264
  self._log_dir.mkdir(exist_ok=True, parents=True)
332
265
  # Copy executable if not local
@@ -341,46 +274,50 @@ class HTCondorAdapter(QueueContextAdapter):
341
274
  make_executable(self._exec_path)
342
275
  # Create pre run
343
276
  logger.info(
344
- f"Writing {self._pre_run_path.name} to {self._job_dir.resolve()!s}"
277
+ f"Writing {self._pre_run_path.name} to {self.job_dir.resolve()!s}"
345
278
  )
346
279
  self._pre_run_path.touch()
347
- self._pre_run_path.write_text(textwrap.dedent(self.pre_run()))
280
+ self._pre_run_path.write_text(textwrap.dedent(self.pre_run_cmds()))
348
281
  make_executable(self._pre_run_path)
349
282
  # Create run
350
283
  logger.debug(
351
284
  f"Writing {self._submit_run_path.name} to "
352
- f"{self._job_dir.resolve()!s}"
285
+ f"{self.job_dir.resolve()!s}"
353
286
  )
354
287
  self._submit_run_path.touch()
355
- self._submit_run_path.write_text(textwrap.dedent(self.run()))
288
+ self._submit_run_path.write_text(textwrap.dedent(self.run_cmds()))
356
289
  # Create pre collect
357
290
  logger.info(
358
291
  f"Writing {self._pre_collect_path.name} to "
359
- f"{self._job_dir.resolve()!s}"
292
+ f"{self.job_dir.resolve()!s}"
360
293
  )
361
294
  self._pre_collect_path.touch()
362
- self._pre_collect_path.write_text(textwrap.dedent(self.pre_collect()))
295
+ self._pre_collect_path.write_text(
296
+ textwrap.dedent(self.pre_collect_cmds())
297
+ )
363
298
  make_executable(self._pre_collect_path)
364
299
  # Create collect
365
300
  logger.debug(
366
301
  f"Writing {self._submit_collect_path.name} to "
367
- f"{self._job_dir.resolve()!s}"
302
+ f"{self.job_dir.resolve()!s}"
368
303
  )
369
304
  self._submit_collect_path.touch()
370
- self._submit_collect_path.write_text(textwrap.dedent(self.collect()))
305
+ self._submit_collect_path.write_text(
306
+ textwrap.dedent(self.collect_cmds())
307
+ )
371
308
  # Create DAG
372
309
  logger.debug(
373
- f"Writing {self._dag_path.name} to {self._job_dir.resolve()!s}"
310
+ f"Writing {self._dag_path.name} to {self.job_dir.resolve()!s}"
374
311
  )
375
312
  self._dag_path.touch()
376
- self._dag_path.write_text(textwrap.dedent(self.dag()))
313
+ self._dag_path.write_text(textwrap.dedent(self.dag_cmds()))
377
314
  # Submit if required
378
315
  condor_submit_dag_cmd = [
379
316
  "condor_submit_dag",
380
317
  "-include_env HOME",
381
318
  f"{self._dag_path.resolve()!s}",
382
319
  ]
383
- if self._submit:
320
+ if self.submit:
384
321
  run_ext_cmd(name="condor_submit_dag", cmd=condor_submit_dag_cmd)
385
322
  else:
386
323
  logger.info(
@@ -3,24 +3,68 @@
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
4
  # License: AGPL
5
5
 
6
+ import sys
7
+
8
+
9
+ if sys.version_info < (3, 12): # pragma: no cover
10
+ from typing_extensions import TypedDict
11
+ else:
12
+ from typing import TypedDict
13
+
6
14
  from abc import ABC, abstractmethod
15
+ from enum import StrEnum
16
+ from typing import Required
17
+
18
+ import structlog
19
+ from pydantic import BaseModel, ConfigDict
7
20
 
8
21
  from ...utils import raise_error
9
22
 
10
23
 
11
- __all__ = ["QueueContextAdapter"]
24
+ __all__ = ["EnvKind", "EnvShell", "QueueContextAdapter", "QueueContextEnv"]
25
+
26
+ _log = structlog.get_logger("junifer")
27
+ logger = _log.bind(pkg="api")
28
+
29
+
30
+ class EnvKind(StrEnum):
31
+ """Accepted Python environment kind."""
32
+
33
+ Venv = "venv"
34
+ Conda = "conda"
35
+ Local = "local"
12
36
 
13
37
 
14
- class QueueContextAdapter(ABC):
38
+ class EnvShell(StrEnum):
39
+ """Accepted environment shell."""
40
+
41
+ Bash = "bash"
42
+ Zsh = "zsh"
43
+
44
+
45
+ class QueueContextEnv(TypedDict, total=False):
46
+ """Accepted environment configuration for queue context."""
47
+
48
+ kind: Required[EnvKind]
49
+ name: str
50
+ shell: Required[EnvShell]
51
+
52
+
53
+ class QueueContextAdapter(BaseModel, ABC):
15
54
  """Abstract base class for queue context adapter.
16
55
 
17
- For every interface that is required, one needs to provide a concrete
56
+ For every queue context, one needs to provide a concrete
18
57
  implementation of this abstract class.
19
58
 
20
59
  """
21
60
 
61
+ model_config = ConfigDict(
62
+ use_enum_values=True,
63
+ extra="forbid",
64
+ )
65
+
22
66
  @abstractmethod
23
- def pre_run(self) -> str:
67
+ def pre_run_cmds(self) -> str:
24
68
  """Return pre-run commands."""
25
69
  raise_error(
26
70
  msg="Concrete classes need to implement pre_run()",
@@ -28,7 +72,7 @@ class QueueContextAdapter(ABC):
28
72
  )
29
73
 
30
74
  @abstractmethod
31
- def run(self) -> str:
75
+ def run_cmds(self) -> str:
32
76
  """Return run commands."""
33
77
  raise_error(
34
78
  msg="Concrete classes need to implement run()",
@@ -36,7 +80,7 @@ class QueueContextAdapter(ABC):
36
80
  )
37
81
 
38
82
  @abstractmethod
39
- def pre_collect(self) -> str:
83
+ def pre_collect_cmds(self) -> str:
40
84
  """Return pre-collect commands."""
41
85
  raise_error(
42
86
  msg="Concrete classes need to implement pre_collect()",
@@ -44,7 +88,7 @@ class QueueContextAdapter(ABC):
44
88
  )
45
89
 
46
90
  @abstractmethod
47
- def collect(self) -> str:
91
+ def collect_cmds(self) -> str:
48
92
  """Return collect commands."""
49
93
  raise_error(
50
94
  msg="Concrete classes need to implement collect()",
@@ -5,37 +5,12 @@
5
5
 
6
6
  import logging
7
7
  from pathlib import Path
8
- from typing import Optional, Union
9
8
 
10
9
  import pytest
11
10
 
12
11
  from junifer.api.queue_context import GnuParallelLocalAdapter
13
12
 
14
13
 
15
- def test_GnuParallelLocalAdapter_env_kind_error() -> None:
16
- """Test error for invalid env kind."""
17
- with pytest.raises(ValueError, match=r"Invalid value for `env.kind`"):
18
- GnuParallelLocalAdapter(
19
- job_name="check_env_kind",
20
- job_dir=Path("."),
21
- yaml_config_path=Path("."),
22
- elements=["sub01"],
23
- env={"kind": "jambalaya"},
24
- )
25
-
26
-
27
- def test_GnuParallelLocalAdapter_env_shell_error() -> None:
28
- """Test error for invalid env shell."""
29
- with pytest.raises(ValueError, match=r"Invalid value for `env.shell`"):
30
- GnuParallelLocalAdapter(
31
- job_name="check_env_shell",
32
- job_dir=Path("."),
33
- yaml_config_path=Path("."),
34
- elements=["sub01"],
35
- env={"kind": "conda", "shell": "fish"},
36
- )
37
-
38
-
39
14
  @pytest.mark.parametrize(
40
15
  "elements, expected_text",
41
16
  [
@@ -44,7 +19,7 @@ def test_GnuParallelLocalAdapter_env_shell_error() -> None:
44
19
  ],
45
20
  )
46
21
  def test_GnuParallelLocalAdapter_elements(
47
- elements: list[Union[str, tuple]],
22
+ elements: list[str | tuple],
48
23
  expected_text: str,
49
24
  ) -> None:
50
25
  """Test GnuParallelLocalAdapter elements().
@@ -63,7 +38,7 @@ def test_GnuParallelLocalAdapter_elements(
63
38
  yaml_config_path=Path("."),
64
39
  elements=elements,
65
40
  )
66
- assert expected_text in adapter.elements()
41
+ assert expected_text in adapter.elements_to_run()
67
42
 
68
43
 
69
44
  @pytest.mark.parametrize(
@@ -76,7 +51,7 @@ def test_GnuParallelLocalAdapter_elements(
76
51
  ],
77
52
  )
78
53
  def test_GnuParallelLocalAdapter_pre_run(
79
- pre_run: Optional[str],
54
+ pre_run: str | None,
80
55
  expected_text: str,
81
56
  shell: str,
82
57
  ) -> None:
@@ -100,8 +75,8 @@ def test_GnuParallelLocalAdapter_pre_run(
100
75
  env={"kind": "conda", "name": "junifer", "shell": shell},
101
76
  pre_run=pre_run,
102
77
  )
103
- assert shell in adapter.pre_run()
104
- assert expected_text in adapter.pre_run()
78
+ assert shell in adapter.pre_run_cmds()
79
+ assert expected_text in adapter.pre_run_cmds()
105
80
 
106
81
 
107
82
  @pytest.mark.parametrize(
@@ -114,7 +89,7 @@ def test_GnuParallelLocalAdapter_pre_run(
114
89
  ],
115
90
  )
116
91
  def test_GnuParallelLocalAdapter_pre_collect(
117
- pre_collect: Optional[str],
92
+ pre_collect: str | None,
118
93
  expected_text: str,
119
94
  shell: str,
120
95
  ) -> None:
@@ -138,8 +113,8 @@ def test_GnuParallelLocalAdapter_pre_collect(
138
113
  env={"kind": "venv", "name": "junifer", "shell": shell},
139
114
  pre_collect=pre_collect,
140
115
  )
141
- assert shell in adapter.pre_collect()
142
- assert expected_text in adapter.pre_collect()
116
+ assert shell in adapter.pre_collect_cmds()
117
+ assert expected_text in adapter.pre_collect_cmds()
143
118
 
144
119
 
145
120
  def test_GnuParallelLocalAdapter_run() -> None:
@@ -150,7 +125,7 @@ def test_GnuParallelLocalAdapter_run() -> None:
150
125
  yaml_config_path=Path("."),
151
126
  elements=["sub01"],
152
127
  )
153
- assert "run" in adapter.run()
128
+ assert "run" in adapter.run_cmds()
154
129
 
155
130
 
156
131
  def test_GnuParallelLocalAdapter_collect() -> None:
@@ -161,7 +136,7 @@ def test_GnuParallelLocalAdapter_collect() -> None:
161
136
  yaml_config_path=Path("."),
162
137
  elements=["sub01"],
163
138
  )
164
- assert "collect" in adapter.collect()
139
+ assert "collect" in adapter.collect_cmds()
165
140
 
166
141
 
167
142
  @pytest.mark.parametrize(