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/core/rule.py CHANGED
@@ -3,16 +3,21 @@ Rules apply conditions to workflow elements or loops.
3
3
  """
4
4
 
5
5
  from __future__ import annotations
6
- from typing import Dict, Optional, Union
6
+ from typing import TYPE_CHECKING
7
7
 
8
- from valida.conditions import ConditionLike
9
- from valida.rules import Rule as ValidaRule
8
+ from valida.conditions import ConditionLike # type: ignore
9
+ from valida import Rule as ValidaRule # type: ignore
10
10
 
11
- from hpcflow.sdk import app
12
11
  from hpcflow.sdk.core.json_like import JSONLike
13
12
  from hpcflow.sdk.core.utils import get_in_container
14
13
  from hpcflow.sdk.log import TimeIt
15
14
 
15
+ if TYPE_CHECKING:
16
+ from typing import Any
17
+ from typing_extensions import TypeIs
18
+ from .actions import Action, ElementActionRun
19
+ from .element import ElementIteration
20
+
16
21
 
17
22
  class Rule(JSONLike):
18
23
  """
@@ -39,21 +44,24 @@ class Rule(JSONLike):
39
44
 
40
45
  def __init__(
41
46
  self,
42
- check_exists: Optional[str] = None,
43
- check_missing: Optional[str] = None,
44
- path: Optional[str] = None,
45
- condition: Optional[Union[Dict, ConditionLike]] = None,
46
- cast: Optional[str] = None,
47
- doc: Optional[str] = None,
47
+ check_exists: str | None = None,
48
+ check_missing: str | None = None,
49
+ path: str | None = None,
50
+ condition: dict[str, Any] | ConditionLike | None = None,
51
+ cast: str | None = None,
52
+ doc: str | None = None,
48
53
  ):
49
- if sum(i is not None for i in (check_exists, check_missing, condition)) != 1:
54
+ if sum(arg is not None for arg in (check_exists, check_missing, condition)) != 1:
50
55
  raise ValueError(
51
56
  "Specify either one of `check_exists`, `check_missing` or a `condition` "
52
57
  "(and optional `path`)"
53
58
  )
54
59
 
55
- if isinstance(condition, dict):
56
- condition = ConditionLike.from_json_like(condition)
60
+ if not isinstance(condition, dict):
61
+ #: A general condition for this rule to check.
62
+ self.condition = condition
63
+ else:
64
+ self.condition = ConditionLike.from_json_like(condition)
57
65
 
58
66
  #: If set, this rule checks this attribute exists.
59
67
  self.check_exists = check_exists
@@ -61,14 +69,12 @@ class Rule(JSONLike):
61
69
  self.check_missing = check_missing
62
70
  #: Where to look up the attribute to check (if not determined by context).
63
71
  self.path = path
64
- #: A general condition for this rule to check.
65
- self.condition = condition
66
72
  #: If set, a cast to apply prior to running the general check.
67
73
  self.cast = cast
68
74
  #: Optional descriptive text.
69
75
  self.doc = doc
70
76
 
71
- def __repr__(self):
77
+ def __repr__(self) -> str:
72
78
  out = f"{self.__class__.__name__}("
73
79
  if self.check_exists:
74
80
  out += f"check_exists={self.check_exists!r}"
@@ -84,26 +90,27 @@ class Rule(JSONLike):
84
90
  out += ")"
85
91
  return out
86
92
 
87
- def __eq__(self, other):
88
- if not isinstance(other, Rule):
93
+ def __eq__(self, other) -> bool:
94
+ if not isinstance(other, self.__class__):
89
95
  return False
90
- elif (
96
+ return (
91
97
  self.check_exists == other.check_exists
92
98
  and self.check_missing == other.check_missing
93
99
  and self.path == other.path
94
100
  and self.condition == other.condition
95
101
  and self.cast == other.cast
96
102
  and self.doc == other.doc
97
- ):
98
- return True
99
- else:
100
- return False
103
+ )
104
+
105
+ @classmethod
106
+ def __is_ElementIteration(cls, value) -> TypeIs[ElementIteration]:
107
+ return isinstance(value, cls._app.ElementIteration)
101
108
 
102
109
  @TimeIt.decorator
103
110
  def test(
104
111
  self,
105
- element_like: Union[app.ElementIteration, app.ElementActionRun],
106
- action: Optional[app.Action] = None,
112
+ element_like: ElementIteration | ElementActionRun,
113
+ action: Action | None = None,
107
114
  ) -> bool:
108
115
  """Test if the rule evaluates to true or false for a given run, or element
109
116
  iteration and action combination."""
@@ -111,33 +118,29 @@ class Rule(JSONLike):
111
118
  task = element_like.task
112
119
  schema_data_idx = element_like.data_idx
113
120
 
114
- check = self.check_exists or self.check_missing
115
- if check:
116
- param_s = check.split(".")
117
- if len(param_s) > 2:
121
+ if check := self.check_exists or self.check_missing:
122
+ if len(check.split(".")) > 2:
118
123
  # sub-parameter, so need to try to retrieve parameter data
119
124
  try:
120
125
  task._get_merged_parameter_data(
121
126
  schema_data_idx, raise_on_missing=True
122
127
  )
123
- return True if self.check_exists else False
128
+ return bool(self.check_exists)
124
129
  except ValueError:
125
- return False if self.check_exists else True
130
+ return not self.check_exists
126
131
  else:
127
132
  if self.check_exists:
128
133
  return self.check_exists in schema_data_idx
129
134
  elif self.check_missing:
130
135
  return self.check_missing not in schema_data_idx
131
-
132
136
  else:
133
- if self.path.startswith("resources."):
134
- try:
135
- # assume an `ElementIteration`
137
+ if self.path and self.path.startswith("resources."):
138
+ if self.__is_ElementIteration(element_like):
139
+ assert action is not None
136
140
  elem_res = element_like.get_resources(
137
141
  action=action, set_defaults=True
138
142
  )
139
- except TypeError:
140
- # must be an `ElementActionRun`
143
+ else:
141
144
  elem_res = element_like.get_resources()
142
145
 
143
146
  res_path = self.path.split(".")[1:]
@@ -151,7 +154,22 @@ class Rule(JSONLike):
151
154
  raise_on_unset=True,
152
155
  )
153
156
  # test the rule:
154
- # note: Valida can't `rule.test` scalars yet, so wrap it in a list and set
155
- # path to first element (see: https://github.com/hpcflow/valida/issues/9):
156
- rule = ValidaRule(path=[0], condition=self.condition, cast=self.cast)
157
- return rule.test([element_dat]).is_valid
157
+ return self._valida_check(element_dat)
158
+
159
+ # Something bizarre was specified. Don't match it!
160
+ return False
161
+
162
+ def _valida_check(self, value: Any) -> bool:
163
+ """
164
+ Check this rule against the specific object, under the assumption that we need
165
+ to use valida for the check. Does not do path tracing to select the object to
166
+ pass; that is the caller's responsibility.
167
+ """
168
+ # note: Valida can't `rule.test` scalars yet, so wrap it in a list and set
169
+ # path to first element (see: https://github.com/hpcflow/valida/issues/9):
170
+ rule = ValidaRule(
171
+ path=[0],
172
+ condition=self.condition,
173
+ cast=self.cast,
174
+ )
175
+ return rule.test([value]).is_valid
@@ -2,72 +2,83 @@
2
2
  Model of files in the run directory.
3
3
  """
4
4
 
5
+ from __future__ import annotations
5
6
  import re
7
+ from typing import Any, TYPE_CHECKING
8
+ from hpcflow.sdk.core.app_aware import AppAware
6
9
  from hpcflow.sdk.core.utils import JSONLikeDirSnapShot
7
10
 
11
+ if TYPE_CHECKING:
12
+ from re import Pattern
13
+ from typing_extensions import ClassVar
14
+ from ..submission.shells.base import Shell
8
15
 
9
- class RunDirAppFiles:
16
+
17
+ class RunDirAppFiles(AppAware):
10
18
  """A class to encapsulate the naming/recognition of app-created files within run
11
19
  directories."""
12
20
 
13
- _app_attr = "app"
14
-
15
- _CMD_FILES_RE_PATTERN = r"js_\d+_act_\d+\.?\w*"
21
+ __CMD_FILES_RE_PATTERN: ClassVar[Pattern] = re.compile(r"js_\d+_act_\d+\.?\w*")
16
22
 
17
23
  @classmethod
18
- def get_log_file_name(cls):
24
+ def get_log_file_name(cls) -> str:
19
25
  """File name for the app log file."""
20
- return f"{cls.app.package_name}.log"
26
+ return f"{cls._app.package_name}.log"
21
27
 
22
28
  @classmethod
23
- def get_std_file_name(cls):
29
+ def get_std_file_name(cls) -> str:
24
30
  """File name for stdout and stderr streams from the app."""
25
- return f"{cls.app.package_name}_std.txt"
31
+ return f"{cls._app.package_name}_std.txt"
26
32
 
27
33
  @staticmethod
28
- def get_run_file_prefix(js_idx: int, js_action_idx: int):
34
+ def get_run_file_prefix(js_idx: int | str, js_action_idx: int | str) -> str:
29
35
  """
30
36
  Get the common prefix for files associated with a run.
31
37
  """
32
38
  return f"js_{js_idx}_act_{js_action_idx}"
33
39
 
34
40
  @classmethod
35
- def get_commands_file_name(cls, js_idx: int, js_action_idx: int, shell):
41
+ def get_commands_file_name(
42
+ cls, js_idx: int | str, js_action_idx: int | str, shell: Shell
43
+ ) -> str:
36
44
  """
37
45
  Get the name of the file containing commands.
38
46
  """
39
47
  return cls.get_run_file_prefix(js_idx, js_action_idx) + shell.JS_EXT
40
48
 
41
49
  @classmethod
42
- def get_run_param_dump_file_prefix(cls, js_idx: int, js_action_idx: int):
50
+ def get_run_param_dump_file_prefix(
51
+ cls, js_idx: int | str, js_action_idx: int | str
52
+ ) -> str:
43
53
  """Get the prefix to a file in the run directory that the app will dump parameter
44
54
  data to."""
45
55
  return cls.get_run_file_prefix(js_idx, js_action_idx) + "_inputs"
46
56
 
47
57
  @classmethod
48
- def get_run_param_load_file_prefix(cls, js_idx: int, js_action_idx: int):
58
+ def get_run_param_load_file_prefix(
59
+ cls, js_idx: int | str, js_action_idx: int | str
60
+ ) -> str:
49
61
  """Get the prefix to a file in the run directory that the app will load parameter
50
62
  data from."""
51
63
  return cls.get_run_file_prefix(js_idx, js_action_idx) + "_outputs"
52
64
 
53
65
  @classmethod
54
- def take_snapshot(cls):
55
- """Take a JSONLikeDirSnapShot, and process to ignore files created by the app.
66
+ def take_snapshot(cls) -> dict[str, Any]:
67
+ """
68
+ Take a :py:class:`JSONLikeDirSnapShot`, and process to ignore files created by
69
+ the app.
56
70
 
57
71
  This includes command files that are invoked by jobscripts, the app log file, and
58
72
  the app standard out/error file.
59
-
60
73
  """
61
74
  snapshot = JSONLikeDirSnapShot()
62
75
  snapshot.take(".")
63
76
  ss_js = snapshot.to_json_like()
64
77
  ss_js.pop("root_path") # always the current working directory of the run
65
- for k in list(ss_js["data"].keys()):
66
- if (
67
- k == cls.get_log_file_name()
68
- or k == cls.get_std_file_name()
69
- or re.match(cls._CMD_FILES_RE_PATTERN, k)
70
- ):
71
- ss_js["data"].pop(k)
78
+ excluded = {cls.get_log_file_name(), cls.get_std_file_name()}
79
+ data: dict[str, Any] = ss_js["data"]
80
+ for filename in tuple(data):
81
+ if filename in excluded or cls.__CMD_FILES_RE_PATTERN.match(filename):
82
+ data.pop(filename)
72
83
 
73
84
  return ss_js