junifer 0.0.5.dev240__py3-none-any.whl → 0.0.6__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 (279) hide show
  1. junifer/__init__.py +2 -31
  2. junifer/__init__.pyi +37 -0
  3. junifer/_version.py +9 -4
  4. junifer/api/__init__.py +3 -5
  5. junifer/api/__init__.pyi +4 -0
  6. junifer/api/decorators.py +14 -19
  7. junifer/api/functions.py +165 -109
  8. junifer/api/py.typed +0 -0
  9. junifer/api/queue_context/__init__.py +2 -4
  10. junifer/api/queue_context/__init__.pyi +5 -0
  11. junifer/api/queue_context/gnu_parallel_local_adapter.py +22 -6
  12. junifer/api/queue_context/htcondor_adapter.py +23 -6
  13. junifer/api/queue_context/py.typed +0 -0
  14. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +3 -3
  15. junifer/api/queue_context/tests/test_htcondor_adapter.py +3 -3
  16. junifer/api/tests/test_functions.py +168 -74
  17. junifer/cli/__init__.py +24 -0
  18. junifer/cli/__init__.pyi +3 -0
  19. junifer/{api → cli}/cli.py +141 -125
  20. junifer/cli/parser.py +235 -0
  21. junifer/cli/py.typed +0 -0
  22. junifer/{api → cli}/tests/test_cli.py +8 -8
  23. junifer/{api/tests/test_api_utils.py → cli/tests/test_cli_utils.py} +5 -4
  24. junifer/{api → cli}/tests/test_parser.py +2 -2
  25. junifer/{api → cli}/utils.py +6 -16
  26. junifer/configs/juseless/__init__.py +2 -2
  27. junifer/configs/juseless/__init__.pyi +3 -0
  28. junifer/configs/juseless/datagrabbers/__init__.py +2 -12
  29. junifer/configs/juseless/datagrabbers/__init__.pyi +13 -0
  30. junifer/configs/juseless/datagrabbers/ixi_vbm.py +2 -2
  31. junifer/configs/juseless/datagrabbers/py.typed +0 -0
  32. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -2
  33. junifer/configs/juseless/datagrabbers/ucla.py +4 -4
  34. junifer/configs/juseless/py.typed +0 -0
  35. junifer/conftest.py +25 -0
  36. junifer/data/__init__.py +2 -42
  37. junifer/data/__init__.pyi +29 -0
  38. junifer/data/_dispatch.py +248 -0
  39. junifer/data/coordinates/__init__.py +9 -0
  40. junifer/data/coordinates/__init__.pyi +5 -0
  41. junifer/data/coordinates/_ants_coordinates_warper.py +104 -0
  42. junifer/data/coordinates/_coordinates.py +385 -0
  43. junifer/data/coordinates/_fsl_coordinates_warper.py +81 -0
  44. junifer/data/{tests → coordinates/tests}/test_coordinates.py +26 -33
  45. junifer/data/masks/__init__.py +9 -0
  46. junifer/data/masks/__init__.pyi +6 -0
  47. junifer/data/masks/_ants_mask_warper.py +177 -0
  48. junifer/data/masks/_fsl_mask_warper.py +106 -0
  49. junifer/data/masks/_masks.py +802 -0
  50. junifer/data/{tests → masks/tests}/test_masks.py +67 -63
  51. junifer/data/parcellations/__init__.py +9 -0
  52. junifer/data/parcellations/__init__.pyi +6 -0
  53. junifer/data/parcellations/_ants_parcellation_warper.py +166 -0
  54. junifer/data/parcellations/_fsl_parcellation_warper.py +89 -0
  55. junifer/data/parcellations/_parcellations.py +1388 -0
  56. junifer/data/{tests → parcellations/tests}/test_parcellations.py +165 -295
  57. junifer/data/pipeline_data_registry_base.py +76 -0
  58. junifer/data/py.typed +0 -0
  59. junifer/data/template_spaces.py +44 -79
  60. junifer/data/tests/test_data_utils.py +1 -2
  61. junifer/data/tests/test_template_spaces.py +8 -4
  62. junifer/data/utils.py +109 -4
  63. junifer/datagrabber/__init__.py +2 -26
  64. junifer/datagrabber/__init__.pyi +27 -0
  65. junifer/datagrabber/aomic/__init__.py +2 -4
  66. junifer/datagrabber/aomic/__init__.pyi +5 -0
  67. junifer/datagrabber/aomic/id1000.py +81 -52
  68. junifer/datagrabber/aomic/piop1.py +83 -55
  69. junifer/datagrabber/aomic/piop2.py +85 -56
  70. junifer/datagrabber/aomic/py.typed +0 -0
  71. junifer/datagrabber/aomic/tests/test_id1000.py +19 -12
  72. junifer/datagrabber/aomic/tests/test_piop1.py +52 -18
  73. junifer/datagrabber/aomic/tests/test_piop2.py +50 -17
  74. junifer/datagrabber/base.py +22 -18
  75. junifer/datagrabber/datalad_base.py +71 -34
  76. junifer/datagrabber/dmcc13_benchmark.py +31 -18
  77. junifer/datagrabber/hcp1200/__init__.py +2 -3
  78. junifer/datagrabber/hcp1200/__init__.pyi +4 -0
  79. junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -3
  80. junifer/datagrabber/hcp1200/hcp1200.py +26 -15
  81. junifer/datagrabber/hcp1200/py.typed +0 -0
  82. junifer/datagrabber/hcp1200/tests/test_hcp1200.py +8 -2
  83. junifer/datagrabber/multiple.py +14 -9
  84. junifer/datagrabber/pattern.py +132 -96
  85. junifer/datagrabber/pattern_validation_mixin.py +206 -94
  86. junifer/datagrabber/py.typed +0 -0
  87. junifer/datagrabber/tests/test_datalad_base.py +27 -12
  88. junifer/datagrabber/tests/test_dmcc13_benchmark.py +28 -11
  89. junifer/datagrabber/tests/test_multiple.py +48 -2
  90. junifer/datagrabber/tests/test_pattern_datalad.py +1 -1
  91. junifer/datagrabber/tests/test_pattern_validation_mixin.py +6 -6
  92. junifer/datareader/__init__.py +2 -2
  93. junifer/datareader/__init__.pyi +3 -0
  94. junifer/datareader/default.py +6 -6
  95. junifer/datareader/py.typed +0 -0
  96. junifer/external/nilearn/__init__.py +2 -3
  97. junifer/external/nilearn/__init__.pyi +4 -0
  98. junifer/external/nilearn/junifer_connectivity_measure.py +25 -17
  99. junifer/external/nilearn/junifer_nifti_spheres_masker.py +4 -4
  100. junifer/external/nilearn/py.typed +0 -0
  101. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +17 -16
  102. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +2 -3
  103. junifer/markers/__init__.py +2 -38
  104. junifer/markers/__init__.pyi +37 -0
  105. junifer/markers/base.py +11 -14
  106. junifer/markers/brainprint.py +12 -14
  107. junifer/markers/complexity/__init__.py +2 -18
  108. junifer/markers/complexity/__init__.pyi +17 -0
  109. junifer/markers/complexity/complexity_base.py +9 -11
  110. junifer/markers/complexity/hurst_exponent.py +7 -7
  111. junifer/markers/complexity/multiscale_entropy_auc.py +7 -7
  112. junifer/markers/complexity/perm_entropy.py +7 -7
  113. junifer/markers/complexity/py.typed +0 -0
  114. junifer/markers/complexity/range_entropy.py +7 -7
  115. junifer/markers/complexity/range_entropy_auc.py +7 -7
  116. junifer/markers/complexity/sample_entropy.py +7 -7
  117. junifer/markers/complexity/tests/test_complexity_base.py +1 -1
  118. junifer/markers/complexity/tests/test_hurst_exponent.py +5 -5
  119. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +5 -5
  120. junifer/markers/complexity/tests/test_perm_entropy.py +5 -5
  121. junifer/markers/complexity/tests/test_range_entropy.py +5 -5
  122. junifer/markers/complexity/tests/test_range_entropy_auc.py +5 -5
  123. junifer/markers/complexity/tests/test_sample_entropy.py +5 -5
  124. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +5 -5
  125. junifer/markers/complexity/weighted_perm_entropy.py +7 -7
  126. junifer/markers/ets_rss.py +12 -11
  127. junifer/markers/falff/__init__.py +2 -3
  128. junifer/markers/falff/__init__.pyi +4 -0
  129. junifer/markers/falff/_afni_falff.py +38 -45
  130. junifer/markers/falff/_junifer_falff.py +16 -19
  131. junifer/markers/falff/falff_base.py +7 -11
  132. junifer/markers/falff/falff_parcels.py +9 -9
  133. junifer/markers/falff/falff_spheres.py +8 -8
  134. junifer/markers/falff/py.typed +0 -0
  135. junifer/markers/falff/tests/test_falff_spheres.py +3 -1
  136. junifer/markers/functional_connectivity/__init__.py +2 -12
  137. junifer/markers/functional_connectivity/__init__.pyi +13 -0
  138. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +9 -8
  139. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +8 -8
  140. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +7 -7
  141. junifer/markers/functional_connectivity/functional_connectivity_base.py +13 -12
  142. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +8 -8
  143. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +7 -7
  144. junifer/markers/functional_connectivity/py.typed +0 -0
  145. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +1 -2
  146. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +1 -2
  147. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +6 -6
  148. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +5 -5
  149. junifer/markers/parcel_aggregation.py +22 -17
  150. junifer/markers/py.typed +0 -0
  151. junifer/markers/reho/__init__.py +2 -3
  152. junifer/markers/reho/__init__.pyi +4 -0
  153. junifer/markers/reho/_afni_reho.py +29 -35
  154. junifer/markers/reho/_junifer_reho.py +13 -14
  155. junifer/markers/reho/py.typed +0 -0
  156. junifer/markers/reho/reho_base.py +7 -11
  157. junifer/markers/reho/reho_parcels.py +10 -10
  158. junifer/markers/reho/reho_spheres.py +9 -9
  159. junifer/markers/sphere_aggregation.py +22 -17
  160. junifer/markers/temporal_snr/__init__.py +2 -3
  161. junifer/markers/temporal_snr/__init__.pyi +4 -0
  162. junifer/markers/temporal_snr/py.typed +0 -0
  163. junifer/markers/temporal_snr/temporal_snr_base.py +11 -10
  164. junifer/markers/temporal_snr/temporal_snr_parcels.py +8 -8
  165. junifer/markers/temporal_snr/temporal_snr_spheres.py +7 -7
  166. junifer/markers/tests/test_ets_rss.py +3 -3
  167. junifer/markers/tests/test_parcel_aggregation.py +24 -24
  168. junifer/markers/tests/test_sphere_aggregation.py +6 -6
  169. junifer/markers/utils.py +3 -3
  170. junifer/onthefly/__init__.py +2 -1
  171. junifer/onthefly/_brainprint.py +138 -0
  172. junifer/onthefly/read_transform.py +5 -8
  173. junifer/pipeline/__init__.py +2 -10
  174. junifer/pipeline/__init__.pyi +13 -0
  175. junifer/{markers/collection.py → pipeline/marker_collection.py} +8 -14
  176. junifer/pipeline/pipeline_component_registry.py +294 -0
  177. junifer/pipeline/pipeline_step_mixin.py +15 -11
  178. junifer/pipeline/py.typed +0 -0
  179. junifer/{markers/tests/test_collection.py → pipeline/tests/test_marker_collection.py} +2 -3
  180. junifer/pipeline/tests/test_pipeline_component_registry.py +200 -0
  181. junifer/pipeline/tests/test_pipeline_step_mixin.py +36 -37
  182. junifer/pipeline/tests/test_update_meta_mixin.py +4 -4
  183. junifer/pipeline/tests/test_workdir_manager.py +43 -0
  184. junifer/pipeline/update_meta_mixin.py +21 -17
  185. junifer/pipeline/utils.py +6 -6
  186. junifer/pipeline/workdir_manager.py +19 -5
  187. junifer/preprocess/__init__.py +2 -10
  188. junifer/preprocess/__init__.pyi +11 -0
  189. junifer/preprocess/base.py +10 -10
  190. junifer/preprocess/confounds/__init__.py +2 -2
  191. junifer/preprocess/confounds/__init__.pyi +3 -0
  192. junifer/preprocess/confounds/fmriprep_confound_remover.py +243 -64
  193. junifer/preprocess/confounds/py.typed +0 -0
  194. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +121 -14
  195. junifer/preprocess/py.typed +0 -0
  196. junifer/preprocess/smoothing/__init__.py +2 -2
  197. junifer/preprocess/smoothing/__init__.pyi +3 -0
  198. junifer/preprocess/smoothing/_afni_smoothing.py +40 -40
  199. junifer/preprocess/smoothing/_fsl_smoothing.py +22 -32
  200. junifer/preprocess/smoothing/_nilearn_smoothing.py +35 -14
  201. junifer/preprocess/smoothing/py.typed +0 -0
  202. junifer/preprocess/smoothing/smoothing.py +11 -13
  203. junifer/preprocess/warping/__init__.py +2 -2
  204. junifer/preprocess/warping/__init__.pyi +3 -0
  205. junifer/preprocess/warping/_ants_warper.py +136 -32
  206. junifer/preprocess/warping/_fsl_warper.py +73 -22
  207. junifer/preprocess/warping/py.typed +0 -0
  208. junifer/preprocess/warping/space_warper.py +39 -11
  209. junifer/preprocess/warping/tests/test_space_warper.py +5 -9
  210. junifer/py.typed +0 -0
  211. junifer/stats.py +5 -5
  212. junifer/storage/__init__.py +2 -10
  213. junifer/storage/__init__.pyi +11 -0
  214. junifer/storage/base.py +47 -13
  215. junifer/storage/hdf5.py +95 -33
  216. junifer/storage/pandas_base.py +12 -11
  217. junifer/storage/py.typed +0 -0
  218. junifer/storage/sqlite.py +11 -11
  219. junifer/storage/tests/test_hdf5.py +86 -4
  220. junifer/storage/tests/test_sqlite.py +2 -2
  221. junifer/storage/tests/test_storage_base.py +5 -2
  222. junifer/storage/tests/test_utils.py +33 -7
  223. junifer/storage/utils.py +95 -9
  224. junifer/testing/__init__.py +2 -3
  225. junifer/testing/__init__.pyi +4 -0
  226. junifer/testing/datagrabbers.py +10 -11
  227. junifer/testing/py.typed +0 -0
  228. junifer/testing/registry.py +4 -7
  229. junifer/testing/tests/test_testing_registry.py +9 -17
  230. junifer/tests/test_stats.py +2 -2
  231. junifer/typing/__init__.py +9 -0
  232. junifer/typing/__init__.pyi +31 -0
  233. junifer/typing/_typing.py +68 -0
  234. junifer/utils/__init__.py +2 -12
  235. junifer/utils/__init__.pyi +18 -0
  236. junifer/utils/_config.py +110 -0
  237. junifer/utils/_yaml.py +16 -0
  238. junifer/utils/helpers.py +6 -6
  239. junifer/utils/logging.py +117 -8
  240. junifer/utils/py.typed +0 -0
  241. junifer/{pipeline → utils}/singleton.py +19 -14
  242. junifer/utils/tests/test_config.py +59 -0
  243. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/METADATA +43 -38
  244. junifer-0.0.6.dist-info/RECORD +350 -0
  245. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/WHEEL +1 -1
  246. junifer-0.0.6.dist-info/entry_points.txt +2 -0
  247. junifer/api/parser.py +0 -118
  248. junifer/data/coordinates.py +0 -408
  249. junifer/data/masks.py +0 -670
  250. junifer/data/parcellations.py +0 -1828
  251. junifer/pipeline/registry.py +0 -177
  252. junifer/pipeline/tests/test_registry.py +0 -150
  253. junifer-0.0.5.dev240.dist-info/RECORD +0 -275
  254. junifer-0.0.5.dev240.dist-info/entry_points.txt +0 -2
  255. /junifer/{api → cli}/tests/data/gmd_mean.yaml +0 -0
  256. /junifer/{api → cli}/tests/data/gmd_mean_htcondor.yaml +0 -0
  257. /junifer/{api → cli}/tests/data/partly_cloudy_agg_mean_tian.yml +0 -0
  258. /junifer/data/{VOIs → coordinates/VOIs}/meta/AutobiographicalMemory_VOIs.txt +0 -0
  259. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAC_VOIs.txt +0 -0
  260. /junifer/data/{VOIs → coordinates/VOIs}/meta/CogAR_VOIs.txt +0 -0
  261. /junifer/data/{VOIs → coordinates/VOIs}/meta/DMNBuckner_VOIs.txt +0 -0
  262. /junifer/data/{VOIs → coordinates/VOIs}/meta/Dosenbach2010_MNI_VOIs.txt +0 -0
  263. /junifer/data/{VOIs → coordinates/VOIs}/meta/Empathy_VOIs.txt +0 -0
  264. /junifer/data/{VOIs → coordinates/VOIs}/meta/Motor_VOIs.txt +0 -0
  265. /junifer/data/{VOIs → coordinates/VOIs}/meta/MultiTask_VOIs.txt +0 -0
  266. /junifer/data/{VOIs → coordinates/VOIs}/meta/PhysioStress_VOIs.txt +0 -0
  267. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2011_MNI_VOIs.txt +0 -0
  268. /junifer/data/{VOIs → coordinates/VOIs}/meta/Power2013_MNI_VOIs.tsv +0 -0
  269. /junifer/data/{VOIs → coordinates/VOIs}/meta/Rew_VOIs.txt +0 -0
  270. /junifer/data/{VOIs → coordinates/VOIs}/meta/Somatosensory_VOIs.txt +0 -0
  271. /junifer/data/{VOIs → coordinates/VOIs}/meta/ToM_VOIs.txt +0 -0
  272. /junifer/data/{VOIs → coordinates/VOIs}/meta/VigAtt_VOIs.txt +0 -0
  273. /junifer/data/{VOIs → coordinates/VOIs}/meta/WM_VOIs.txt +0 -0
  274. /junifer/data/{VOIs → coordinates/VOIs}/meta/eMDN_VOIs.txt +0 -0
  275. /junifer/data/{VOIs → coordinates/VOIs}/meta/eSAD_VOIs.txt +0 -0
  276. /junifer/data/{VOIs → coordinates/VOIs}/meta/extDMN_VOIs.txt +0 -0
  277. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/AUTHORS.rst +0 -0
  278. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info/licenses}/LICENSE.md +0 -0
  279. {junifer-0.0.5.dev240.dist-info → junifer-0.0.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,31 @@
1
+ __all__ = [
2
+ "DataGrabberLike",
3
+ "PreprocessorLike",
4
+ "MarkerLike",
5
+ "StorageLike",
6
+ "PipelineComponent",
7
+ "Dependencies",
8
+ "ConditionalDependencies",
9
+ "ExternalDependencies",
10
+ "MarkerInOutMappings",
11
+ "DataGrabberPatterns",
12
+ "ConfigVal",
13
+ "Element",
14
+ "Elements",
15
+ ]
16
+
17
+ from ._typing import (
18
+ DataGrabberLike,
19
+ PreprocessorLike,
20
+ MarkerLike,
21
+ StorageLike,
22
+ PipelineComponent,
23
+ Dependencies,
24
+ ConditionalDependencies,
25
+ ExternalDependencies,
26
+ MarkerInOutMappings,
27
+ DataGrabberPatterns,
28
+ ConfigVal,
29
+ Element,
30
+ Elements,
31
+ )
@@ -0,0 +1,68 @@
1
+ """Provide type hints for internal and external use."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from collections.abc import MutableMapping, Sequence
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Union,
10
+ )
11
+
12
+
13
+ if TYPE_CHECKING:
14
+ from ..datagrabber import BaseDataGrabber
15
+ from ..datareader import DefaultDataReader
16
+ from ..markers import BaseMarker
17
+ from ..preprocess import BasePreprocessor
18
+ from ..storage import BaseFeatureStorage
19
+
20
+
21
+ __all__ = [
22
+ "ConditionalDependencies",
23
+ "ConfigVal",
24
+ "DataGrabberLike",
25
+ "DataGrabberPatterns",
26
+ "Dependencies",
27
+ "Element",
28
+ "Elements",
29
+ "ExternalDependencies",
30
+ "MarkerInOutMappings",
31
+ "MarkerLike",
32
+ "PipelineComponent",
33
+ "PreprocessorLike",
34
+ "StorageLike",
35
+ ]
36
+
37
+
38
+ DataGrabberLike = type["BaseDataGrabber"]
39
+ PreprocessorLike = type["BasePreprocessor"]
40
+ MarkerLike = type["BaseMarker"]
41
+ StorageLike = type["BaseFeatureStorage"]
42
+ PipelineComponent = Union[
43
+ "DataGrabberLike",
44
+ "DefaultDataReader",
45
+ "PreprocessorLike",
46
+ "MarkerLike",
47
+ "StorageLike",
48
+ ]
49
+ Dependencies = set[str]
50
+ ConditionalDependencies = Sequence[
51
+ MutableMapping[
52
+ str,
53
+ Union[
54
+ str,
55
+ PipelineComponent,
56
+ Sequence[str],
57
+ Sequence[PipelineComponent],
58
+ ],
59
+ ]
60
+ ]
61
+ ExternalDependencies = Sequence[MutableMapping[str, Union[str, Sequence[str]]]]
62
+ MarkerInOutMappings = MutableMapping[str, MutableMapping[str, str]]
63
+ DataGrabberPatterns = dict[
64
+ str, Union[dict[str, str], Sequence[dict[str, str]]]
65
+ ]
66
+ ConfigVal = Union[bool, int, float, str]
67
+ Element = Union[str, tuple[str, ...]]
68
+ Elements = Sequence[Element]
junifer/utils/__init__.py CHANGED
@@ -4,17 +4,7 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
- from .fs import make_executable
8
- from .logging import configure_logging, logger, raise_error, warn_with_log
9
- from .helpers import run_ext_cmd, deep_update
7
+ import lazy_loader as lazy
10
8
 
11
9
 
12
- __all__ = [
13
- "make_executable",
14
- "configure_logging",
15
- "logger",
16
- "raise_error",
17
- "warn_with_log",
18
- "run_ext_cmd",
19
- "deep_update",
20
- ]
10
+ __getattr__, __dir__, __all__ = lazy.attach_stub(__name__, __file__)
@@ -0,0 +1,18 @@
1
+ __all__ = [
2
+ "make_executable",
3
+ "configure_logging",
4
+ "config",
5
+ "logger",
6
+ "raise_error",
7
+ "warn_with_log",
8
+ "run_ext_cmd",
9
+ "deep_update",
10
+ "yaml",
11
+ "ConfigManager",
12
+ ]
13
+
14
+ from .fs import make_executable
15
+ from .logging import configure_logging, logger, raise_error, warn_with_log
16
+ from ._config import config, ConfigManager
17
+ from .helpers import run_ext_cmd, deep_update
18
+ from ._yaml import yaml
@@ -0,0 +1,110 @@
1
+ """Provide junifer global configuration."""
2
+
3
+ # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # Synchon Mandal <s.mandal@fz-juelich.de>
5
+ # License: AGPL
6
+
7
+ import os
8
+ from typing import Optional
9
+
10
+ from ..typing import ConfigVal
11
+ from .logging import logger
12
+ from .singleton import Singleton
13
+
14
+
15
+ __all__ = ["ConfigManager", "config"]
16
+
17
+
18
+ class ConfigManager(metaclass=Singleton):
19
+ """Manage configuration parameters.
20
+
21
+ Attributes
22
+ ----------
23
+ _config : dict
24
+ Configuration parameters.
25
+
26
+ """
27
+
28
+ def __init__(self) -> None:
29
+ """Initialize the class."""
30
+ self._config = {}
31
+ # Initial setup from process env
32
+ self._reload()
33
+
34
+ def _reload(self) -> None:
35
+ """Reload env vars."""
36
+ for t_var in os.environ:
37
+ if t_var.startswith("JUNIFER_"):
38
+ # Set correct type
39
+ var_value = os.environ[t_var]
40
+ # bool
41
+ if var_value.lower() == "true":
42
+ var_value = True
43
+ elif var_value.lower() == "false":
44
+ var_value = False
45
+ # numeric
46
+ else:
47
+ try:
48
+ var_value = int(var_value)
49
+ except ValueError:
50
+ try:
51
+ var_value = float(var_value)
52
+ except ValueError:
53
+ pass
54
+ # Set value
55
+ var_name = (
56
+ t_var.replace("JUNIFER_", "").lower().replace("_", ".")
57
+ )
58
+ logger.debug(
59
+ f"Setting `{var_name}` from environment to "
60
+ f"`{var_value}` (type: {type(var_value)})"
61
+ )
62
+ self._config[var_name] = var_value
63
+
64
+ def get(self, key: str, default: Optional[ConfigVal] = None) -> ConfigVal:
65
+ """Get configuration parameter.
66
+
67
+ Parameters
68
+ ----------
69
+ key : str
70
+ The configuration key to get.
71
+ default : bool or int or float or None, optional
72
+ The default value to return if the key is not found (default None).
73
+
74
+ Returns
75
+ -------
76
+ bool or int or float
77
+ The configuration value.
78
+
79
+ """
80
+ return self._config.get(key, default)
81
+
82
+ def set(self, key: str, val: ConfigVal) -> None:
83
+ """Set configuration parameter.
84
+
85
+ Parameters
86
+ ----------
87
+ key : str
88
+ The configuration key to set.
89
+ val : bool or int or float
90
+ The value to set ``key`` to.
91
+
92
+ """
93
+ logger.debug(f"Setting `{key}` to `{val}` (type: {type(val)})")
94
+ self._config[key] = val
95
+
96
+ def delete(self, key: str) -> None:
97
+ """Delete configuration parameter.
98
+
99
+ Parameters
100
+ ----------
101
+ key : str
102
+ The configuration key to delete.
103
+
104
+ """
105
+ logger.debug(f"Deleting `{key}` from config")
106
+ _ = self._config.pop(key)
107
+
108
+
109
+ # Initialize here to access from anywhere
110
+ config = ConfigManager()
junifer/utils/_yaml.py ADDED
@@ -0,0 +1,16 @@
1
+ """Provide YAML config definition for junifer use."""
2
+
3
+ # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # License: AGPL
5
+
6
+ from ruamel.yaml import YAML
7
+
8
+
9
+ __all__ = ["yaml"]
10
+
11
+
12
+ # Configure YAML class once for further use
13
+ yaml = YAML()
14
+ yaml.default_flow_style = False
15
+ yaml.allow_unicode = True
16
+ yaml.indent(mapping=2, sequence=4, offset=2)
junifer/utils/helpers.py CHANGED
@@ -5,15 +5,15 @@
5
5
 
6
6
  import collections.abc
7
7
  import subprocess
8
- from typing import Dict, List
8
+ import sys
9
9
 
10
10
  from .logging import logger, raise_error
11
11
 
12
12
 
13
- __all__ = ["run_ext_cmd", "deep_update"]
13
+ __all__ = ["deep_update", "run_ext_cmd"]
14
14
 
15
15
 
16
- def run_ext_cmd(name: str, cmd: List[str]) -> None:
16
+ def run_ext_cmd(name: str, cmd: list[str]) -> None:
17
17
  """Run external command via subprocess.
18
18
 
19
19
  Parameters
@@ -45,19 +45,19 @@ def run_ext_cmd(name: str, cmd: List[str]) -> None:
45
45
  if process.returncode == 0:
46
46
  logger.info(
47
47
  f"{name} command succeeded with the following output:\n"
48
- f"{process.stdout}"
48
+ f"{process.stdout.decode(sys.stdout.encoding)}"
49
49
  )
50
50
  else:
51
51
  raise_error(
52
52
  msg=(
53
53
  f"{name} command failed with the following error:\n"
54
- f"{process.stdout}"
54
+ f"{process.stdout.decode(sys.stdout.encoding)}"
55
55
  ),
56
56
  klass=RuntimeError,
57
57
  )
58
58
 
59
59
 
60
- def deep_update(d: Dict, u: Dict) -> Dict:
60
+ def deep_update(d: dict, u: dict) -> dict:
61
61
  """Deep update `d` with `u`.
62
62
 
63
63
  From: "https://stackoverflow.com/questions/3232943/update-value-of-a-nested
junifer/utils/logging.py CHANGED
@@ -4,6 +4,7 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
+ import os
7
8
  import sys
8
9
 
9
10
 
@@ -16,7 +17,7 @@ import logging
16
17
  import warnings
17
18
  from pathlib import Path
18
19
  from subprocess import PIPE, Popen, TimeoutExpired
19
- from typing import Dict, NoReturn, Optional, Type, Union
20
+ from typing import ClassVar, NoReturn, Optional, Union
20
21
  from warnings import warn
21
22
 
22
23
  import datalad
@@ -24,9 +25,9 @@ import datalad
24
25
 
25
26
  __all__ = [
26
27
  "WrapStdOut",
28
+ "configure_logging",
27
29
  "get_versions",
28
30
  "log_versions",
29
- "configure_logging",
30
31
  "raise_error",
31
32
  "warn_with_log",
32
33
  ]
@@ -80,6 +81,59 @@ class WrapStdOut(logging.StreamHandler):
80
81
  raise AttributeError(f"'file' object has not attribute '{name}'")
81
82
 
82
83
 
84
+ class ColorFormatter(logging.Formatter):
85
+ """Color formatter for logging messages.
86
+
87
+ Parameters
88
+ ----------
89
+ fmt : str
90
+ The format string for the logging message.
91
+ datefmt : str, optional
92
+ The format string for the date.
93
+
94
+ """
95
+
96
+ BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
97
+
98
+ COLORS: ClassVar[dict[str, int]] = {
99
+ "WARNING": YELLOW,
100
+ "INFO": GREEN,
101
+ "DEBUG": BLUE,
102
+ "CRITICAL": MAGENTA,
103
+ "ERROR": RED,
104
+ }
105
+
106
+ RESET_SEQ: str = "\033[0m"
107
+ COLOR_SEQ: str = "\033[1;%dm"
108
+ BOLD_SEQ: str = "\033[1m"
109
+
110
+ def __init__(self, fmt: str, datefmt: Optional[str] = None) -> None:
111
+ """Initialize the ColorFormatter."""
112
+ logging.Formatter.__init__(self, fmt, datefmt)
113
+
114
+ def format(self, record: logging.LogRecord) -> str:
115
+ """Format the log record.
116
+
117
+ Parameters
118
+ ----------
119
+ record : logging.LogRecord
120
+ The log record to format.
121
+
122
+ Returns
123
+ -------
124
+ str
125
+ The formatted log record.
126
+
127
+ """
128
+ levelname = record.levelname
129
+ if levelname in self.COLORS:
130
+ levelname_color = (
131
+ self.COLOR_SEQ % (30 + self.COLORS[levelname]) + levelname
132
+ )
133
+ record.levelname = levelname_color + self.RESET_SEQ
134
+ return logging.Formatter.format(self, record)
135
+
136
+
83
137
  def _get_git_head(path: Path) -> str:
84
138
  """Aux function to read HEAD from git.
85
139
 
@@ -119,7 +173,7 @@ def _get_git_head(path: Path) -> str:
119
173
  return proc_stdout
120
174
 
121
175
 
122
- def get_versions() -> Dict:
176
+ def get_versions() -> dict:
123
177
  """Import stuff and get versions if module.
124
178
 
125
179
  Returns
@@ -189,7 +243,7 @@ def _close_handlers(logger: logging.Logger) -> None:
189
243
  logger.removeHandler(handler)
190
244
 
191
245
 
192
- def _safe_log(versions: Dict, name: str) -> None:
246
+ def _safe_log(versions: dict, name: str) -> None:
193
247
  """Log with safety.
194
248
 
195
249
  Parameters
@@ -237,11 +291,51 @@ def log_versions(tbox_path: Optional[Path] = None) -> None:
237
291
  pass
238
292
 
239
293
 
294
+ def _can_use_color(handler: logging.Handler) -> bool:
295
+ """Check if color can be used in the logging output.
296
+
297
+ Parameters
298
+ ----------
299
+ handler : logging.Handler
300
+ The logging handler to check for color support.
301
+
302
+ Returns
303
+ -------
304
+ bool
305
+ Whether color can be used in the logging output.
306
+
307
+ """
308
+ if isinstance(handler, logging.FileHandler):
309
+ # Do not use colors in file handlers
310
+ return False
311
+ else:
312
+ stream = handler.stream
313
+ if hasattr(stream, "isatty") and stream.isatty():
314
+ valid_terms = [
315
+ "xterm-256color",
316
+ "xterm-kitty",
317
+ "xterm-color",
318
+ ]
319
+ this_term = os.getenv("TERM", None)
320
+ if this_term is not None:
321
+ if this_term in valid_terms:
322
+ return True
323
+ if this_term.endswith("256color") or this_term.endswith("256"):
324
+ return True
325
+ if this_term == "dumb" and os.getenv("CI", False):
326
+ return True
327
+ if os.getenv("COLORTERM", False):
328
+ return True
329
+ # No TTY, no color
330
+ return False
331
+
332
+
240
333
  def configure_logging(
241
334
  level: Union[int, str] = "WARNING",
242
335
  fname: Optional[Union[str, Path]] = None,
243
336
  overwrite: Optional[bool] = None,
244
337
  output_format=None,
338
+ level_datalad: Union[int, str, None] = None,
245
339
  ) -> None:
246
340
  """Configure the logging functionality.
247
341
 
@@ -264,6 +358,10 @@ def configure_logging(
264
358
  e.g., ``"%(asctime)s - %(levelname)s - %(message)s"``.
265
359
  If None, default string format is used
266
360
  (default ``"%(asctime)s - %(name)s - %(levelname)s - %(message)s"``).
361
+ level_datalad : int or {"DEBUG", "INFO", "WARNING", "ERROR"}, optional
362
+ The level of the messages to print for datalad. If string, it will be
363
+ interpreted as elements of logging. If None, it will be set as the
364
+ ``level`` parameter (default None).
267
365
 
268
366
  """
269
367
  _close_handlers(logger) # close relevant logger handlers
@@ -297,18 +395,29 @@ def configure_logging(
297
395
  # "%(asctime)s [%(levelname)s] %(message)s "
298
396
  # "(%(filename)s:%(lineno)s)"
299
397
  # )
300
- formatter = logging.Formatter(fmt=output_format)
398
+ if _can_use_color(lh):
399
+ formatter = ColorFormatter(fmt=output_format)
400
+ else:
401
+ formatter = logging.Formatter(fmt=output_format)
301
402
 
302
403
  lh.setFormatter(formatter) # set formatter
303
404
  logger.setLevel(level) # set level
304
- datalad.log.lgr.setLevel(level) # set level for datalad
405
+
406
+ # Set datalad logging level accordingly
407
+ if level_datalad is not None:
408
+ if isinstance(level_datalad, str):
409
+ level_datalad = _logging_types[level_datalad]
410
+ else:
411
+ level_datalad = level
412
+ datalad.log.lgr.setLevel(level_datalad) # set level for datalad
413
+
305
414
  logger.addHandler(lh) # set handler
306
415
  log_versions() # log versions of installed packages
307
416
 
308
417
 
309
418
  def raise_error(
310
419
  msg: str,
311
- klass: Type[Exception] = ValueError,
420
+ klass: type[Exception] = ValueError,
312
421
  exception: Optional[Exception] = None,
313
422
  ) -> NoReturn:
314
423
  """Raise error, but first log it.
@@ -331,7 +440,7 @@ def raise_error(
331
440
 
332
441
 
333
442
  def warn_with_log(
334
- msg: str, category: Optional[Type[Warning]] = RuntimeWarning
443
+ msg: str, category: Optional[type[Warning]] = RuntimeWarning
335
444
  ) -> None:
336
445
  """Warn, but first log it.
337
446
 
junifer/utils/py.typed ADDED
File without changes
@@ -1,15 +1,17 @@
1
1
  """Provide a singleton class to be used by pipeline components."""
2
2
 
3
3
  # Authors: Synchon Mandal <s.mandal@fz-juelich.de>
4
+ # Federico Raimondo <f.raimondo@fz-juelich.de>
4
5
  # License: AGPL
5
6
 
6
- from typing import Any, Dict, Type
7
+ from abc import ABCMeta
8
+ from typing import Any, ClassVar
7
9
 
8
10
 
9
- __all__ = ["singleton"]
11
+ __all__ = ["ABCSingleton", "Singleton"]
10
12
 
11
13
 
12
- def singleton(cls: Type) -> Type:
14
+ class Singleton(type):
13
15
  """Make a class singleton.
14
16
 
15
17
  Parameters
@@ -17,15 +19,11 @@ def singleton(cls: Type) -> Type:
17
19
  cls : class
18
20
  The class to designate as singleton.
19
21
 
20
- Returns
21
- -------
22
- class
23
- The only instance of the class.
24
-
25
22
  """
26
- instances: Dict = {}
27
23
 
28
- def get_instance(*args: Any, **kwargs: Any) -> Type:
24
+ instances: ClassVar[dict] = {}
25
+
26
+ def __call__(cls, *args: Any, **kwargs: Any) -> type:
29
27
  """Get the only instance for a class.
30
28
 
31
29
  Parameters
@@ -41,8 +39,15 @@ def singleton(cls: Type) -> Type:
41
39
  The only instance of the class.
42
40
 
43
41
  """
44
- if cls not in instances:
45
- instances[cls] = cls(*args, **kwargs)
46
- return instances[cls]
42
+ if cls not in cls.instances:
43
+ cls.instances[cls] = super(Singleton, cls).__call__( # noqa: UP008
44
+ *args, **kwargs
45
+ )
46
+
47
+ return cls.instances[cls]
48
+
49
+
50
+ class ABCSingleton(ABCMeta, Singleton):
51
+ """Make an abstract class a singleton."""
47
52
 
48
- return get_instance
53
+ pass
@@ -0,0 +1,59 @@
1
+ """Provide tests for ConfigManager."""
2
+
3
+ # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # Synchon Mandal <s.mandal@fz-juelich.de>
5
+ # License: AGPL
6
+
7
+ import os
8
+
9
+ import pytest
10
+
11
+ from junifer.typing import ConfigVal
12
+ from junifer.utils import config
13
+ from junifer.utils._config import ConfigManager
14
+
15
+
16
+ def test_config_manager_singleton() -> None:
17
+ """Test that ConfigManager is a singleton."""
18
+ config_mgr_1 = ConfigManager()
19
+ config_mgr_2 = ConfigManager()
20
+ assert id(config_mgr_1) == id(config_mgr_2)
21
+
22
+
23
+ def test_config_manager() -> None:
24
+ """Test config operations for ConfigManager."""
25
+ # Get non-existing with default
26
+ assert config.get(key="scooby") is None
27
+ # Set
28
+ config.set(key="scooby", val=True)
29
+ # Get existing
30
+ assert config.get("scooby")
31
+ # Delete
32
+ config.delete("scooby")
33
+ # Get non-existing with default
34
+ assert config.get(key="scooby") is None
35
+
36
+
37
+ @pytest.mark.parametrize(
38
+ "val, expected_val",
39
+ [("TRUE", True), ("FALSE", False), ("1", 1), ("0.0", 0.0)],
40
+ )
41
+ def test_config_manager_env_reload(val: str, expected_val: ConfigVal) -> None:
42
+ """Test config parsing from env reload.
43
+
44
+ Parameters
45
+ ----------
46
+ val : str
47
+ The parametrized values.
48
+ expected_val : bool or int or float
49
+ The parametrized expected value.
50
+
51
+ """
52
+ # Set env var
53
+ os.environ["JUNIFER_TESTME"] = val
54
+ # Check
55
+ config._reload()
56
+ assert config.get("testme") == expected_val
57
+ # Cleanup
58
+ del os.environ["JUNIFER_TESTME"]
59
+ config.delete("testme")