hpcflow-new2 0.2.0a189__py3-none-any.whl → 0.2.0a190__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 (115) hide show
  1. hpcflow/__pyinstaller/hook-hpcflow.py +8 -6
  2. hpcflow/_version.py +1 -1
  3. hpcflow/app.py +1 -0
  4. hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +1 -1
  5. hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +1 -1
  6. hpcflow/sdk/__init__.py +21 -15
  7. hpcflow/sdk/app.py +2133 -770
  8. hpcflow/sdk/cli.py +281 -250
  9. hpcflow/sdk/cli_common.py +6 -2
  10. hpcflow/sdk/config/__init__.py +1 -1
  11. hpcflow/sdk/config/callbacks.py +77 -42
  12. hpcflow/sdk/config/cli.py +126 -103
  13. hpcflow/sdk/config/config.py +578 -311
  14. hpcflow/sdk/config/config_file.py +131 -95
  15. hpcflow/sdk/config/errors.py +112 -85
  16. hpcflow/sdk/config/types.py +145 -0
  17. hpcflow/sdk/core/actions.py +1054 -994
  18. hpcflow/sdk/core/app_aware.py +24 -0
  19. hpcflow/sdk/core/cache.py +81 -63
  20. hpcflow/sdk/core/command_files.py +275 -185
  21. hpcflow/sdk/core/commands.py +111 -107
  22. hpcflow/sdk/core/element.py +724 -503
  23. hpcflow/sdk/core/enums.py +192 -0
  24. hpcflow/sdk/core/environment.py +74 -93
  25. hpcflow/sdk/core/errors.py +398 -51
  26. hpcflow/sdk/core/json_like.py +540 -272
  27. hpcflow/sdk/core/loop.py +380 -334
  28. hpcflow/sdk/core/loop_cache.py +160 -43
  29. hpcflow/sdk/core/object_list.py +370 -207
  30. hpcflow/sdk/core/parameters.py +728 -600
  31. hpcflow/sdk/core/rule.py +59 -41
  32. hpcflow/sdk/core/run_dir_files.py +33 -22
  33. hpcflow/sdk/core/task.py +1546 -1325
  34. hpcflow/sdk/core/task_schema.py +240 -196
  35. hpcflow/sdk/core/test_utils.py +126 -88
  36. hpcflow/sdk/core/types.py +387 -0
  37. hpcflow/sdk/core/utils.py +410 -305
  38. hpcflow/sdk/core/validation.py +82 -9
  39. hpcflow/sdk/core/workflow.py +1192 -1028
  40. hpcflow/sdk/core/zarr_io.py +98 -137
  41. hpcflow/sdk/demo/cli.py +46 -33
  42. hpcflow/sdk/helper/cli.py +18 -16
  43. hpcflow/sdk/helper/helper.py +75 -63
  44. hpcflow/sdk/helper/watcher.py +61 -28
  45. hpcflow/sdk/log.py +83 -59
  46. hpcflow/sdk/persistence/__init__.py +8 -31
  47. hpcflow/sdk/persistence/base.py +988 -586
  48. hpcflow/sdk/persistence/defaults.py +6 -0
  49. hpcflow/sdk/persistence/discovery.py +38 -0
  50. hpcflow/sdk/persistence/json.py +408 -153
  51. hpcflow/sdk/persistence/pending.py +158 -123
  52. hpcflow/sdk/persistence/store_resource.py +37 -22
  53. hpcflow/sdk/persistence/types.py +307 -0
  54. hpcflow/sdk/persistence/utils.py +14 -11
  55. hpcflow/sdk/persistence/zarr.py +477 -420
  56. hpcflow/sdk/runtime.py +44 -41
  57. hpcflow/sdk/submission/{jobscript_info.py → enums.py} +39 -12
  58. hpcflow/sdk/submission/jobscript.py +444 -404
  59. hpcflow/sdk/submission/schedulers/__init__.py +133 -40
  60. hpcflow/sdk/submission/schedulers/direct.py +97 -71
  61. hpcflow/sdk/submission/schedulers/sge.py +132 -126
  62. hpcflow/sdk/submission/schedulers/slurm.py +263 -268
  63. hpcflow/sdk/submission/schedulers/utils.py +7 -2
  64. hpcflow/sdk/submission/shells/__init__.py +14 -15
  65. hpcflow/sdk/submission/shells/base.py +102 -29
  66. hpcflow/sdk/submission/shells/bash.py +72 -55
  67. hpcflow/sdk/submission/shells/os_version.py +31 -30
  68. hpcflow/sdk/submission/shells/powershell.py +37 -29
  69. hpcflow/sdk/submission/submission.py +203 -257
  70. hpcflow/sdk/submission/types.py +143 -0
  71. hpcflow/sdk/typing.py +163 -12
  72. hpcflow/tests/conftest.py +8 -6
  73. hpcflow/tests/schedulers/slurm/test_slurm_submission.py +5 -2
  74. hpcflow/tests/scripts/test_main_scripts.py +60 -30
  75. hpcflow/tests/shells/wsl/test_wsl_submission.py +6 -4
  76. hpcflow/tests/unit/test_action.py +86 -75
  77. hpcflow/tests/unit/test_action_rule.py +9 -4
  78. hpcflow/tests/unit/test_app.py +13 -6
  79. hpcflow/tests/unit/test_cli.py +1 -1
  80. hpcflow/tests/unit/test_command.py +71 -54
  81. hpcflow/tests/unit/test_config.py +20 -15
  82. hpcflow/tests/unit/test_config_file.py +21 -18
  83. hpcflow/tests/unit/test_element.py +58 -62
  84. hpcflow/tests/unit/test_element_iteration.py +3 -1
  85. hpcflow/tests/unit/test_element_set.py +29 -19
  86. hpcflow/tests/unit/test_group.py +4 -2
  87. hpcflow/tests/unit/test_input_source.py +116 -93
  88. hpcflow/tests/unit/test_input_value.py +29 -24
  89. hpcflow/tests/unit/test_json_like.py +44 -35
  90. hpcflow/tests/unit/test_loop.py +65 -58
  91. hpcflow/tests/unit/test_object_list.py +17 -12
  92. hpcflow/tests/unit/test_parameter.py +16 -7
  93. hpcflow/tests/unit/test_persistence.py +48 -35
  94. hpcflow/tests/unit/test_resources.py +20 -18
  95. hpcflow/tests/unit/test_run.py +8 -3
  96. hpcflow/tests/unit/test_runtime.py +2 -1
  97. hpcflow/tests/unit/test_schema_input.py +23 -15
  98. hpcflow/tests/unit/test_shell.py +3 -2
  99. hpcflow/tests/unit/test_slurm.py +8 -7
  100. hpcflow/tests/unit/test_submission.py +39 -19
  101. hpcflow/tests/unit/test_task.py +352 -247
  102. hpcflow/tests/unit/test_task_schema.py +33 -20
  103. hpcflow/tests/unit/test_utils.py +9 -11
  104. hpcflow/tests/unit/test_value_sequence.py +15 -12
  105. hpcflow/tests/unit/test_workflow.py +114 -83
  106. hpcflow/tests/unit/test_workflow_template.py +0 -1
  107. hpcflow/tests/workflows/test_jobscript.py +2 -1
  108. hpcflow/tests/workflows/test_workflows.py +18 -13
  109. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/METADATA +2 -1
  110. hpcflow_new2-0.2.0a190.dist-info/RECORD +165 -0
  111. hpcflow/sdk/core/parallel.py +0 -21
  112. hpcflow_new2-0.2.0a189.dist-info/RECORD +0 -158
  113. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/LICENSE +0 -0
  114. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/WHEEL +0 -0
  115. {hpcflow_new2-0.2.0a189.dist-info → hpcflow_new2-0.2.0a190.dist-info}/entry_points.txt +0 -0
hpcflow/sdk/cli_common.py CHANGED
@@ -1,17 +1,21 @@
1
1
  """Click CLI options that are used as decorators in multiple modules."""
2
2
 
3
+ from __future__ import annotations
3
4
  import click
4
5
 
5
6
  from hpcflow.sdk.core import ALL_TEMPLATE_FORMATS
6
- from hpcflow.sdk.persistence import ALL_STORE_FORMATS, DEFAULT_STORE_FORMAT
7
+ from hpcflow.sdk.persistence.defaults import DEFAULT_STORE_FORMAT
8
+ from hpcflow.sdk.persistence.discovery import ALL_STORE_FORMATS
7
9
 
8
10
 
9
- def sub_tasks_callback(ctx, param, value):
11
+ def sub_tasks_callback(ctx, param, value: str | None) -> list[int] | None:
10
12
  """
11
13
  Parse subtasks.
12
14
  """
13
15
  if value:
14
16
  return [int(i) for i in value.split(",")]
17
+ else:
18
+ return None
15
19
 
16
20
 
17
21
  #: Standard option
@@ -2,4 +2,4 @@
2
2
  Configuration loading and manipulation.
3
3
  """
4
4
 
5
- from .config import Config, ConfigFile, ConfigOptions, DEFAULT_CONFIG
5
+ from hpcflow.sdk.config.config import Config, ConfigFile, ConfigOptions, DEFAULT_CONFIG
@@ -1,44 +1,58 @@
1
1
  """Module that defines built-in callback functions for configuration item values."""
2
2
 
3
-
3
+ from __future__ import annotations
4
4
  import os
5
5
  import re
6
- import fsspec
6
+ import fsspec # type: ignore
7
+ from typing import overload, TYPE_CHECKING
7
8
  from hpcflow.sdk.core.errors import UnsupportedSchedulerError, UnsupportedShellError
8
-
9
9
  from hpcflow.sdk.submission.shells import get_supported_shells
10
10
 
11
+ if TYPE_CHECKING:
12
+ from typing import Any, TypeVar
13
+ from .config import Config
14
+ from ..typing import PathLike
15
+
16
+ T = TypeVar("T")
11
17
 
12
- def callback_vars(config, value):
18
+
19
+ def callback_vars(config: Config, value) -> str:
13
20
  """
14
21
  Callback that substitutes configuration variables.
15
22
  """
16
23
 
17
- def vars_repl(match_obj):
18
- var_name = match_obj.groups()[0]
19
- return config._variables[var_name]
24
+ def vars_repl(match_obj: re.Match[str]) -> str:
25
+ return config._variables[match_obj[1]]
20
26
 
21
- vars_join = "|".join(list(config._variables.keys()))
22
- vars_regex = r"\<\<(" + vars_join + r")\>\>"
23
- value = re.sub(
27
+ vars_regex = rf"\<\<({ '|'.join(config._variables) })\>\>"
28
+ return re.sub(
24
29
  pattern=vars_regex,
25
30
  repl=vars_repl,
26
31
  string=str(value),
27
32
  )
28
- return value
29
33
 
30
34
 
31
- def callback_file_paths(config, file_path):
35
+ @overload
36
+ def callback_file_paths(config: Config, file_path: PathLike) -> PathLike:
37
+ ...
38
+
39
+
40
+ @overload
41
+ def callback_file_paths(config: Config, file_path: list[PathLike]) -> list[PathLike]:
42
+ ...
43
+
44
+
45
+ def callback_file_paths(config: Config, file_path: PathLike | list[PathLike]):
32
46
  """
33
47
  Callback that resolves file paths.
34
48
  """
35
49
  if isinstance(file_path, list):
36
- return [config._resolve_path(i) for i in file_path]
50
+ return [config._resolve_path(path) for path in file_path]
37
51
  else:
38
52
  return config._resolve_path(file_path)
39
53
 
40
54
 
41
- def callback_bool(config, value):
55
+ def callback_bool(config: Config, value: str | bool) -> bool:
42
56
  """
43
57
  Callback that coerces values to boolean.
44
58
  """
@@ -52,19 +66,36 @@ def callback_bool(config, value):
52
66
  return value
53
67
 
54
68
 
55
- def callback_lowercase(config, value):
69
+ @overload
70
+ def callback_lowercase(config: Config, value: list[str]) -> list[str]:
71
+ ...
72
+
73
+
74
+ @overload
75
+ def callback_lowercase(config: Config, value: dict[str, T]) -> dict[str, T]:
76
+ ...
77
+
78
+
79
+ @overload
80
+ def callback_lowercase(config: Config, value: str) -> str:
81
+ ...
82
+
83
+
84
+ def callback_lowercase(
85
+ config: Config, value: list[str] | dict[str, T] | str
86
+ ) -> list[str] | dict[str, T] | str:
56
87
  """
57
88
  Callback that forces a string to lower case.
58
89
  """
59
90
  if isinstance(value, list):
60
- return [i.lower() for i in value]
91
+ return [item.lower() for item in value]
61
92
  elif isinstance(value, dict):
62
93
  return {k.lower(): v for k, v in value.items()}
63
94
  else:
64
95
  return value.lower()
65
96
 
66
97
 
67
- def exists_in_schedulers(config, value):
98
+ def exists_in_schedulers(config: Config, value: T) -> T:
68
99
  """
69
100
  Callback that tests that a value is a supported scheduler name.
70
101
  """
@@ -77,21 +108,25 @@ def exists_in_schedulers(config, value):
77
108
  return value
78
109
 
79
110
 
80
- def callback_supported_schedulers(config, schedulers):
111
+ def callback_supported_schedulers(
112
+ config: Config, schedulers: dict[str, Any]
113
+ ) -> dict[str, Any]:
81
114
  """
82
115
  Callback that tests that all values are names of supported schedulers.
83
116
  """
84
117
  # validate against supported schedulers according to the OS - this won't validate that
85
118
  # a particular scheduler actually exists on this system:
86
- available = config._app.get_OS_supported_schedulers()
87
- for k in schedulers:
88
- if k not in available:
89
- raise UnsupportedSchedulerError(scheduler=k, available=available)
90
-
119
+ available = set(config._app.get_OS_supported_schedulers())
120
+ if any((witness := k) not in available for k in schedulers):
121
+ raise UnsupportedSchedulerError(scheduler=witness, available=available)
91
122
  return schedulers
92
123
 
93
124
 
94
- def set_scheduler_invocation_match(config, scheduler: str):
125
+ def _hostname_in_invocation(config: Config) -> bool:
126
+ return "hostname" in config._file.get_invocation(config._config_key)["match"]
127
+
128
+
129
+ def set_scheduler_invocation_match(config: Config, scheduler: str) -> None:
95
130
  """Invoked on set of `default_scheduler`.
96
131
 
97
132
  For clusters with "proper" schedulers (SGE, SLURM, etc.), login nodes are typically
@@ -100,25 +135,25 @@ def set_scheduler_invocation_match(config, scheduler: str):
100
135
  that on clusters the hostname match is explicitly set.
101
136
 
102
137
  """
103
- default_args = config.get(f"schedulers.{scheduler}").get("defaults", {})
104
138
  sched = config._app.get_scheduler(
105
139
  scheduler_name=scheduler,
106
140
  os_name=os.name,
107
- scheduler_args=default_args,
141
+ scheduler_args=config.get(f"schedulers.{scheduler}").get("defaults", {}),
108
142
  )
109
- if hasattr(sched, "DEFAULT_LOGIN_NODE_MATCH"):
110
- if "hostname" not in config._file.get_invocation(config._config_key)["match"]:
143
+ if isinstance(sched, config._app.QueuedScheduler):
144
+ if not _hostname_in_invocation(config):
111
145
  config._file.update_invocation(
112
146
  config_key=config._config_key,
113
147
  match={"hostname": sched.DEFAULT_LOGIN_NODE_MATCH},
114
148
  )
115
149
 
116
150
 
117
- def callback_scheduler_set_up(config, schedulers):
151
+ def callback_scheduler_set_up(
152
+ config: Config, schedulers: dict[str, Any]
153
+ ) -> dict[str, Any]:
118
154
  """Invoked on set of `schedulers`.
119
155
 
120
156
  Runs scheduler-specific config initialisation.
121
-
122
157
  """
123
158
  for k, v in schedulers.items():
124
159
  sched = config._app.get_scheduler(
@@ -126,19 +161,19 @@ def callback_scheduler_set_up(config, schedulers):
126
161
  os_name=os.name,
127
162
  scheduler_args=v.get("defaults", {}),
128
163
  )
129
- if hasattr(sched, "get_login_nodes"):
130
- # some `Scheduler` classes have a `get_login_nodes` method which can be used
164
+
165
+ if isinstance(sched, config._app.SGEPosix):
166
+ # some `QueuedScheduler` classes have a `get_login_nodes` method which can be used
131
167
  # to populate the names of login nodes explicitly, if not already set:
132
- if "hostname" not in config._file.get_invocation(config._config_key)["match"]:
133
- login_nodes = sched.get_login_nodes()
168
+ if not _hostname_in_invocation(config):
134
169
  config._file.update_invocation(
135
170
  config_key=config._config_key,
136
- match={"hostname": login_nodes},
171
+ match={"hostname": sched.get_login_nodes()},
137
172
  )
138
173
  return schedulers
139
174
 
140
175
 
141
- def callback_supported_shells(config, shell_name):
176
+ def callback_supported_shells(config: Config, shell_name: str) -> str:
142
177
  """
143
178
  Callback that tests if a shell names is supported on this OS.
144
179
  """
@@ -148,31 +183,31 @@ def callback_supported_shells(config, shell_name):
148
183
  return shell_name
149
184
 
150
185
 
151
- def set_callback_file_paths(config, value):
186
+ def set_callback_file_paths(config: Config, value: PathLike | list[PathLike]) -> None:
152
187
  """Check the file(s) is/are accessible. This is only done on `config.set` (and not on
153
188
  `config.get` or `config._validate`) because it could be expensive in the case of remote
154
189
  files."""
155
190
  value = callback_file_paths(config, value)
156
191
 
157
- to_check = value
158
- if not isinstance(value, list):
159
- to_check = [value]
192
+ to_check = value if isinstance(value, list) else [value]
160
193
 
161
194
  for file_path in to_check:
195
+ if file_path is None:
196
+ continue
162
197
  with fsspec.open(file_path, mode="rt") as fh:
163
198
  pass
164
199
  # TODO: also check something in it?
165
200
  print(f"Checked access to: {file_path}")
166
201
 
167
202
 
168
- def check_load_data_files(config, value):
203
+ def check_load_data_files(config: Config, value: Any) -> None:
169
204
  """Check data files (e.g., task schema files) can be loaded successfully. This is only
170
205
  done on `config.set` (and not on `config.get` or `config._validate`) because it could
171
206
  be expensive in the case of remote files."""
172
207
  config._app.reload_template_components(warn=False)
173
208
 
174
209
 
175
- def callback_update_log_console_level(config, value):
210
+ def callback_update_log_console_level(config: Config, value: str) -> None:
176
211
  """
177
212
  Callback to set the logging level.
178
213
  """