hpcflow-new2 0.2.0a188__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.0a188.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.0a188.dist-info/RECORD +0 -158
  113. {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/LICENSE +0 -0
  114. {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/WHEEL +0 -0
  115. {hpcflow_new2-0.2.0a188.dist-info → hpcflow_new2-0.2.0a190.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,192 @@
1
+ """
2
+ Core enumeration types.
3
+ """
4
+ from __future__ import annotations
5
+ from collections.abc import Sequence
6
+ from dataclasses import dataclass
7
+ from enum import Enum
8
+
9
+
10
+ class ActionScopeType(Enum):
11
+ """
12
+ Types of action scope.
13
+ """
14
+
15
+ #: Scope that applies to anything.
16
+ ANY = 0
17
+ #: Scope that only applies to main scripts.
18
+ MAIN = 1
19
+ #: Scope that applies to processing steps.
20
+ PROCESSING = 2
21
+ #: Scope that applies to input file generators.
22
+ INPUT_FILE_GENERATOR = 3
23
+ #: Scope that applies to output file parsers.
24
+ OUTPUT_FILE_PARSER = 4
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class _EARStatus:
29
+ """
30
+ Model of the state of an EARStatus.
31
+ """
32
+
33
+ _value: int
34
+ #: Symbol to use when rendering a status.
35
+ symbol: str
36
+ #: Colour to use when rendering a status.
37
+ colour: str
38
+ __doc__: str = ""
39
+
40
+
41
+ class EARStatus(_EARStatus, Enum):
42
+ """Enumeration of all possible EAR statuses, and their associated status colour."""
43
+
44
+ #: Not yet associated with a submission.
45
+ pending = (
46
+ 0,
47
+ ".",
48
+ "grey46",
49
+ "Not yet associated with a submission.",
50
+ )
51
+ #: Associated with a prepared submission that is not yet submitted.
52
+ prepared = (
53
+ 1,
54
+ ".",
55
+ "grey46",
56
+ "Associated with a prepared submission that is not yet submitted.",
57
+ )
58
+ #: Submitted for execution.
59
+ submitted = (
60
+ 2,
61
+ ".",
62
+ "grey46",
63
+ "Submitted for execution.",
64
+ )
65
+ #: Executing now.
66
+ running = (
67
+ 3,
68
+ "●",
69
+ "dodger_blue1",
70
+ "Executing now.",
71
+ )
72
+ #: Not attempted due to a failure of an upstream action on which this depends,
73
+ #: or a loop termination condition being satisfied.
74
+ skipped = (
75
+ 4,
76
+ "s",
77
+ "dark_orange",
78
+ (
79
+ "Not attempted due to a failure of an upstream action on which this depends, "
80
+ "or a loop termination condition being satisfied."
81
+ ),
82
+ )
83
+ #: Aborted by the user; downstream actions will be attempted.
84
+ aborted = (
85
+ 5,
86
+ "A",
87
+ "deep_pink4",
88
+ "Aborted by the user; downstream actions will be attempted.",
89
+ )
90
+ #: Probably exited successfully.
91
+ success = (
92
+ 6,
93
+ "■",
94
+ "green3",
95
+ "Probably exited successfully.",
96
+ )
97
+ #: Probably failed.
98
+ error = (
99
+ 7,
100
+ "E",
101
+ "red3",
102
+ "Probably failed.",
103
+ )
104
+
105
+ @property
106
+ def value(self) -> int:
107
+ #: The value of the status.
108
+ return self._value
109
+
110
+ @classmethod
111
+ def get_non_running_submitted_states(cls) -> frozenset[EARStatus]:
112
+ """Return the set of all non-running states, excluding those before submission."""
113
+ return frozenset(
114
+ {
115
+ cls.skipped,
116
+ cls.aborted,
117
+ cls.success,
118
+ cls.error,
119
+ }
120
+ )
121
+
122
+ @property
123
+ def rich_repr(self) -> str:
124
+ """
125
+ The rich representation of the value.
126
+ """
127
+ return f"[{self.colour}]{self.symbol}[/{self.colour}]"
128
+
129
+
130
+ class InputSourceType(Enum):
131
+ """
132
+ The types of input sources.
133
+ """
134
+
135
+ #: Input source is an import.
136
+ IMPORT = 0
137
+ #: Input source is local.
138
+ LOCAL = 1
139
+ #: Input source is a default.
140
+ DEFAULT = 2
141
+ #: Input source is a task.
142
+ TASK = 3
143
+
144
+
145
+ class ParallelMode(Enum):
146
+ """
147
+ Potential parallel modes.
148
+ """
149
+
150
+ #: Use distributed-memory parallelism (e.g. MPI).
151
+ DISTRIBUTED = 0
152
+ #: Use shared-memory parallelism (e.g. OpenMP).
153
+ SHARED = 1
154
+ #: Use both distributed- and shared-memory parallelism.
155
+ #:
156
+ #: Note
157
+ #: ----
158
+ #: This is not yet implemented in any meaningful way!
159
+ HYBRID = 2
160
+
161
+
162
+ class ParameterPropagationMode(Enum):
163
+ """
164
+ How a parameter is propagated.
165
+ """
166
+
167
+ #: Parameter is propagated implicitly.
168
+ IMPLICIT = 0
169
+ #: Parameter is propagated explicitly.
170
+ EXPLICIT = 1
171
+ #: Parameter is never propagated.
172
+ NEVER = 2
173
+
174
+
175
+ class TaskSourceType(Enum):
176
+ """
177
+ The types of task-based input sources.
178
+ """
179
+
180
+ #: Input source is a task input.
181
+ INPUT = 0
182
+ #: Input source is a task output.
183
+ OUTPUT = 1
184
+ #: Input source is unspecified.
185
+ ANY = 2
186
+
187
+ @classmethod
188
+ def names(cls) -> Sequence[str]:
189
+ """
190
+ Get the names of the task source types.
191
+ """
192
+ return cls._member_names_
@@ -5,16 +5,18 @@ Model of an execution environment.
5
5
  from __future__ import annotations
6
6
 
7
7
  from dataclasses import dataclass
8
- from typing import List, Any
8
+ from typing import TYPE_CHECKING
9
9
 
10
- from textwrap import dedent
11
-
12
- from hpcflow.sdk import app
10
+ from hpcflow.sdk.typing import hydrate
13
11
  from hpcflow.sdk.core.errors import DuplicateExecutableError
14
12
  from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
15
13
  from hpcflow.sdk.core.object_list import ExecutablesList
16
14
  from hpcflow.sdk.core.utils import check_valid_py_identifier, get_duplicate_items
17
15
 
16
+ if TYPE_CHECKING:
17
+ from collections.abc import Mapping, Sequence
18
+ from typing import Any, ClassVar
19
+
18
20
 
19
21
  @dataclass
20
22
  class NumCores(JSONLike):
@@ -36,30 +38,14 @@ class NumCores(JSONLike):
36
38
  #: The maximum number of cores supported.
37
39
  stop: int
38
40
  #: The step in the number of cores supported. Normally 1.
39
- step: int = None
40
-
41
- def __post_init__(self):
42
- if self.step is None:
43
- self.step = 1
44
-
45
- def __contains__(self, x):
46
- if x in range(self.start, self.stop + 1, self.step):
47
- return True
48
- else:
49
- return False
41
+ step: int = 1
50
42
 
51
- def __eq__(self, other):
52
- if (
53
- type(self) == type(other)
54
- and self.start == other.start
55
- and self.stop == other.stop
56
- and self.step == other.step
57
- ):
58
- return True
59
- return False
43
+ def __contains__(self, x: int) -> bool:
44
+ return x in range(self.start, self.stop + 1, self.step)
60
45
 
61
46
 
62
47
  @dataclass
48
+ @hydrate
63
49
  class ExecutableInstance(JSONLike):
64
50
  """
65
51
  A particular instance of an executable that can support some mode of operation.
@@ -75,30 +61,26 @@ class ExecutableInstance(JSONLike):
75
61
  """
76
62
 
77
63
  #: What parallel mode is supported by this executable instance.
78
- parallel_mode: str
64
+ parallel_mode: str | None
79
65
  #: The number of cores supported by this executable instance.
80
- num_cores: Any
66
+ num_cores: NumCores
81
67
  #: The actual command to use for this executable instance.
82
68
  command: str
83
69
 
84
- def __post_init__(self):
85
- if not isinstance(self.num_cores, dict):
86
- self.num_cores = {"start": self.num_cores, "stop": self.num_cores}
87
- if not isinstance(self.num_cores, NumCores):
88
- self.num_cores = self.app.NumCores(**self.num_cores)
89
-
90
- def __eq__(self, other):
91
- if (
92
- type(self) == type(other)
93
- and self.parallel_mode == other.parallel_mode
94
- and self.num_cores == other.num_cores
95
- and self.command == other.command
96
- ):
97
- return True
98
- return False
70
+ def __init__(
71
+ self, parallel_mode: str | None, num_cores: NumCores | int | dict, command: str
72
+ ):
73
+ self.parallel_mode = parallel_mode
74
+ self.command = command
75
+ if isinstance(num_cores, NumCores):
76
+ self.num_cores = num_cores
77
+ elif isinstance(num_cores, int):
78
+ self.num_cores = NumCores(num_cores, num_cores)
79
+ else:
80
+ self.num_cores = NumCores(**num_cores)
99
81
 
100
82
  @classmethod
101
- def from_spec(cls, spec):
83
+ def from_spec(cls, spec: dict[str, Any]) -> ExecutableInstance:
102
84
  """
103
85
  Construct an instance from a specification dictionary.
104
86
  """
@@ -117,7 +99,7 @@ class Executable(JSONLike):
117
99
  The concrete instances of the application that may be present.
118
100
  """
119
101
 
120
- _child_objects = (
102
+ _child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
121
103
  ChildObjectSpec(
122
104
  name="instances",
123
105
  class_name="ExecutableInstance",
@@ -125,13 +107,13 @@ class Executable(JSONLike):
125
107
  ),
126
108
  )
127
109
 
128
- def __init__(self, label: str, instances: List[app.ExecutableInstance]):
110
+ def __init__(self, label: str, instances: list[ExecutableInstance]):
129
111
  #: The abstract name of the program.
130
112
  self.label = check_valid_py_identifier(label)
131
113
  #: The concrete instances of the application that may be present.
132
114
  self.instances = instances
133
115
 
134
- self._executables_list = None # assigned by parent
116
+ self._executables_list: ExecutablesList | None = None # assigned by parent
135
117
 
136
118
  def __repr__(self):
137
119
  return (
@@ -141,24 +123,31 @@ class Executable(JSONLike):
141
123
  f")"
142
124
  )
143
125
 
144
- def __eq__(self, other):
145
- if (
146
- type(self) == type(other)
126
+ def __eq__(self, other: Any) -> bool:
127
+ return (
128
+ isinstance(other, self.__class__)
147
129
  and self.label == other.label
148
130
  and self.instances == other.instances
149
- and self.environment.name == other.environment.name
150
- ):
151
- return True
152
- return False
131
+ and (
132
+ (
133
+ self.environment
134
+ and other.environment
135
+ and self.environment.name == other.environment.name
136
+ )
137
+ or (not self.environment and not other.environment)
138
+ )
139
+ )
153
140
 
154
141
  @property
155
- def environment(self):
142
+ def environment(self) -> Environment | None:
156
143
  """
157
144
  The environment that the executable is going to run in.
158
145
  """
159
- return self._executables_list.environment
146
+ return None if (el := self._executables_list) is None else el.environment
160
147
 
161
- def filter_instances(self, parallel_mode=None, num_cores=None):
148
+ def filter_instances(
149
+ self, parallel_mode: str | None = None, num_cores: int | None = None
150
+ ) -> list[ExecutableInstance]:
162
151
  """
163
152
  Select the instances of the executable that are compatible with the given
164
153
  requirements.
@@ -175,12 +164,12 @@ class Executable(JSONLike):
175
164
  list[ExecutableInstance]:
176
165
  The known executable instances that match the requirements.
177
166
  """
178
- out = []
179
- for i in self.instances:
180
- if parallel_mode is None or i.parallel_mode == parallel_mode:
181
- if num_cores is None or num_cores in i.num_cores:
182
- out.append(i)
183
- return out
167
+ return [
168
+ inst
169
+ for inst in self.instances
170
+ if (parallel_mode is None or inst.parallel_mode == parallel_mode)
171
+ and (num_cores is None or num_cores in inst.num_cores)
172
+ ]
184
173
 
185
174
 
186
175
  class Environment(JSONLike):
@@ -200,9 +189,8 @@ class Environment(JSONLike):
200
189
  List of abstract executables in the environment.
201
190
  """
202
191
 
203
- _hash_value = None
204
- _validation_schema = "environments_spec_schema.yaml"
205
- _child_objects = (
192
+ _validation_schema: ClassVar[str] = "environments_spec_schema.yaml"
193
+ _child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
206
194
  ChildObjectSpec(
207
195
  name="executables",
208
196
  class_name="ExecutablesList",
@@ -212,59 +200,52 @@ class Environment(JSONLike):
212
200
 
213
201
  def __init__(
214
202
  self,
215
- name,
216
- setup=None,
217
- specifiers=None,
218
- executables=None,
219
- doc="",
220
- _hash_value=None,
203
+ name: str,
204
+ setup: Sequence[str] | None = None,
205
+ specifiers: Mapping[str, str] | None = None,
206
+ executables: ExecutablesList | Sequence[Executable] | None = None,
207
+ doc: str = "",
208
+ _hash_value: str | None = None,
221
209
  ):
222
210
  #: The name of the environment.
223
211
  self.name = name
224
212
  #: Documentation for the environment.
225
213
  self.doc = doc
226
- #: Commands to run to enter the environment.
227
- self.setup = setup
228
214
  #: Dictionary of attributes that may be used to supply addional key/value pairs
229
215
  #: to look up an environment by.
230
- self.specifiers = specifiers or {}
216
+ self.specifiers: Mapping[str, str] = specifiers or {}
231
217
  #: List of abstract executables in the environment.
232
218
  self.executables = (
233
219
  executables
234
220
  if isinstance(executables, ExecutablesList)
235
- else self.app.ExecutablesList(executables or [])
221
+ else self._app.ExecutablesList(executables or ())
236
222
  )
237
223
  self._hash_value = _hash_value
238
- if self.setup:
239
- if isinstance(self.setup, str):
240
- self.setup = tuple(
241
- i.strip() for i in dedent(self.setup).strip().split("\n")
242
- )
243
- elif not isinstance(self.setup, tuple):
244
- self.setup = tuple(self.setup)
224
+ #: Commands to run to enter the environment.
225
+ self.setup: tuple[str, ...] | None
226
+ if not setup:
227
+ self.setup = None
228
+ elif isinstance(setup, str):
229
+ self.setup = tuple(cmd.strip() for cmd in setup.strip().split("\n"))
230
+ else:
231
+ self.setup = tuple(setup)
245
232
  self._set_parent_refs()
246
233
  self._validate()
247
234
 
248
- def __eq__(self, other):
249
- if (
250
- type(self) == type(other)
235
+ def __eq__(self, other: Any) -> bool:
236
+ return (
237
+ isinstance(other, self.__class__)
251
238
  and self.setup == other.setup
252
239
  and self.executables == other.executables
253
240
  and self.specifiers == other.specifiers
254
- ):
255
- return True
256
- return False
241
+ )
257
242
 
258
243
  def __repr__(self):
259
244
  return f"{self.__class__.__name__}({self.name!r})"
260
245
 
261
246
  def _validate(self):
262
- dup_labels = get_duplicate_items(i.label for i in self.executables)
263
- if dup_labels:
264
- raise DuplicateExecutableError(
265
- f"Executables must have unique `label`s within each environment, but "
266
- f"found label(s) multiple times: {dup_labels!r}"
267
- )
247
+ if dup_labels := get_duplicate_items(exe.label for exe in self.executables):
248
+ raise DuplicateExecutableError(dup_labels)
268
249
 
269
250
  @property
270
251
  def documentation(self) -> str: