hpcflow-new2 0.2.0a159__py3-none-any.whl → 0.2.0a160__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.
hpcflow/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.2.0a159"
1
+ __version__ = "0.2.0a160"
hpcflow/sdk/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Sub-package to define an extensible hpcflow application."""
2
+
2
3
  import logging
3
4
  import os
4
5
  import sys
@@ -90,6 +91,7 @@ sdk_classes = {
90
91
  "SlurmPosix": "hpcflow.sdk.submission.schedulers.slurm",
91
92
  "SGEPosix": "hpcflow.sdk.submission.schedulers.sge",
92
93
  "OutputLabel": "hpcflow.sdk.core.task",
94
+ "RunDirAppFiles": "hpcflow.sdk.core.run_dir_files",
93
95
  }
94
96
 
95
97
  # these are defined as `BaseApp` methods with an underscore prefix:
hpcflow/sdk/app.py CHANGED
@@ -20,6 +20,7 @@ import warnings
20
20
  import zipfile
21
21
  from platformdirs import user_cache_path, user_data_dir
22
22
  from reretry import retry
23
+ import rich
23
24
  from rich.console import Console, Group
24
25
  from rich.syntax import Syntax
25
26
  from rich.table import Table, box
@@ -965,6 +966,7 @@ class BaseApp(metaclass=Singleton):
965
966
  }
966
967
  return item
967
968
 
969
+ @TimeIt.decorator
968
970
  def read_known_submissions_file(self) -> List[Dict]:
969
971
  """Retrieve existing workflows that *might* be running."""
970
972
  known = []
@@ -1024,6 +1026,7 @@ class BaseApp(metaclass=Singleton):
1024
1026
 
1025
1027
  return next_id
1026
1028
 
1029
+ @TimeIt.decorator
1027
1030
  def set_inactive_in_known_subs_file(self, inactive_IDs: List[int]):
1028
1031
  """Set workflows in the known-submissions file to the non-running state.
1029
1032
 
@@ -1141,6 +1144,7 @@ class BaseApp(metaclass=Singleton):
1141
1144
  ts_name_fmt: Optional[str] = None,
1142
1145
  store_kwargs: Optional[Dict] = None,
1143
1146
  variables: Optional[Dict[str, str]] = None,
1147
+ status: Optional[bool] = True,
1144
1148
  ) -> get_app_attribute("Workflow"):
1145
1149
  """Generate a new {app_name} workflow from a file or string containing a workflow
1146
1150
  template parametrisation.
@@ -1177,10 +1181,17 @@ class BaseApp(metaclass=Singleton):
1177
1181
  Keyword arguments to pass to the store's `write_empty_workflow` method.
1178
1182
  variables
1179
1183
  String variables to substitute in `template_file_or_str`.
1184
+ status
1185
+ If True, display a live status to track workflow creation progress.
1180
1186
  """
1181
1187
 
1182
1188
  self.API_logger.info("make_workflow called")
1183
1189
 
1190
+ if status:
1191
+ console = rich.console.Console()
1192
+ status = console.status("Making persistent workflow...")
1193
+ status.start()
1194
+
1184
1195
  common = {
1185
1196
  "path": path,
1186
1197
  "name": name,
@@ -1190,6 +1201,7 @@ class BaseApp(metaclass=Singleton):
1190
1201
  "ts_name_fmt": ts_name_fmt,
1191
1202
  "store_kwargs": store_kwargs,
1192
1203
  "variables": variables,
1204
+ "status": status,
1193
1205
  }
1194
1206
 
1195
1207
  if not is_string:
@@ -1200,10 +1212,24 @@ class BaseApp(metaclass=Singleton):
1200
1212
  )
1201
1213
 
1202
1214
  elif template_format == "json":
1203
- wk = self.Workflow.from_JSON_string(JSON_str=template_file_or_str, **common)
1215
+ try:
1216
+ wk = self.Workflow.from_JSON_string(
1217
+ JSON_str=template_file_or_str, **common
1218
+ )
1219
+ except Exception:
1220
+ if status:
1221
+ status.stop()
1222
+ raise
1204
1223
 
1205
1224
  elif template_format == "yaml":
1206
- wk = self.Workflow.from_YAML_string(YAML_str=template_file_or_str, **common)
1225
+ try:
1226
+ wk = self.Workflow.from_YAML_string(
1227
+ YAML_str=template_file_or_str, **common
1228
+ )
1229
+ except Exception:
1230
+ if status:
1231
+ status.stop()
1232
+ raise
1207
1233
 
1208
1234
  elif not template_format:
1209
1235
  raise ValueError(
@@ -1216,6 +1242,10 @@ class BaseApp(metaclass=Singleton):
1216
1242
  f"Template format {template_format!r} not understood. Available template "
1217
1243
  f"formats are {ALL_TEMPLATE_FORMATS!r}."
1218
1244
  )
1245
+
1246
+ if status:
1247
+ status.stop()
1248
+
1219
1249
  return wk
1220
1250
 
1221
1251
  def _make_and_submit_workflow(
@@ -1236,6 +1266,8 @@ class BaseApp(metaclass=Singleton):
1236
1266
  add_to_known: Optional[bool] = True,
1237
1267
  return_idx: Optional[bool] = False,
1238
1268
  tasks: Optional[List[int]] = None,
1269
+ cancel: Optional[bool] = False,
1270
+ status: Optional[bool] = True,
1239
1271
  ) -> Dict[int, int]:
1240
1272
  """Generate and submit a new {app_name} workflow from a file or string containing a
1241
1273
  workflow template parametrisation.
@@ -1288,6 +1320,11 @@ class BaseApp(metaclass=Singleton):
1288
1320
  tasks
1289
1321
  List of task indices to include in this submission. By default all tasks are
1290
1322
  included.
1323
+ cancel
1324
+ Immediately cancel the submission. Useful for testing and benchmarking.
1325
+ status
1326
+ If True, display a live status to track workflow creation and submission
1327
+ progress.
1291
1328
  """
1292
1329
 
1293
1330
  self.API_logger.info("make_and_submit_workflow called")
@@ -1304,6 +1341,7 @@ class BaseApp(metaclass=Singleton):
1304
1341
  ts_name_fmt=ts_name_fmt,
1305
1342
  store_kwargs=store_kwargs,
1306
1343
  variables=variables,
1344
+ status=status,
1307
1345
  )
1308
1346
  return wk.submit(
1309
1347
  JS_parallelism=JS_parallelism,
@@ -1311,6 +1349,8 @@ class BaseApp(metaclass=Singleton):
1311
1349
  add_to_known=add_to_known,
1312
1350
  return_idx=return_idx,
1313
1351
  tasks=tasks,
1352
+ cancel=cancel,
1353
+ status=status,
1314
1354
  )
1315
1355
 
1316
1356
  def _make_demo_workflow(
@@ -1325,6 +1365,7 @@ class BaseApp(metaclass=Singleton):
1325
1365
  ts_name_fmt: Optional[str] = None,
1326
1366
  store_kwargs: Optional[Dict] = None,
1327
1367
  variables: Optional[Dict[str, str]] = None,
1368
+ status: Optional[bool] = True,
1328
1369
  ) -> get_app_attribute("Workflow"):
1329
1370
  """Generate a new {app_name} workflow from a builtin demo workflow template.
1330
1371
 
@@ -1358,10 +1399,17 @@ class BaseApp(metaclass=Singleton):
1358
1399
  Keyword arguments to pass to the store's `write_empty_workflow` method.
1359
1400
  variables
1360
1401
  String variables to substitute in the demo workflow template file.
1402
+ status
1403
+ If True, display a live status to track workflow creation progress.
1361
1404
  """
1362
1405
 
1363
1406
  self.API_logger.info("make_demo_workflow called")
1364
1407
 
1408
+ if status:
1409
+ console = rich.console.Console()
1410
+ status = console.status("Making persistent workflow...")
1411
+ status.start()
1412
+
1365
1413
  with self.get_demo_workflow_template_file(workflow_name) as template_path:
1366
1414
  wk = self.Workflow.from_file(
1367
1415
  template_path=template_path,
@@ -1374,7 +1422,10 @@ class BaseApp(metaclass=Singleton):
1374
1422
  ts_name_fmt=ts_name_fmt,
1375
1423
  store_kwargs=store_kwargs,
1376
1424
  variables=variables,
1425
+ status=status,
1377
1426
  )
1427
+ if status:
1428
+ status.stop()
1378
1429
  return wk
1379
1430
 
1380
1431
  def _make_and_submit_demo_workflow(
@@ -1394,6 +1445,8 @@ class BaseApp(metaclass=Singleton):
1394
1445
  add_to_known: Optional[bool] = True,
1395
1446
  return_idx: Optional[bool] = False,
1396
1447
  tasks: Optional[List[int]] = None,
1448
+ cancel: Optional[bool] = False,
1449
+ status: Optional[bool] = True,
1397
1450
  ) -> Dict[int, int]:
1398
1451
  """Generate and submit a new {app_name} workflow from a file or string containing a
1399
1452
  workflow template parametrisation.
@@ -1443,6 +1496,10 @@ class BaseApp(metaclass=Singleton):
1443
1496
  tasks
1444
1497
  List of task indices to include in this submission. By default all tasks are
1445
1498
  included.
1499
+ cancel
1500
+ Immediately cancel the submission. Useful for testing and benchmarking.
1501
+ status
1502
+ If True, display a live status to track submission progress.
1446
1503
  """
1447
1504
 
1448
1505
  self.API_logger.info("make_and_submit_demo_workflow called")
@@ -1465,6 +1522,8 @@ class BaseApp(metaclass=Singleton):
1465
1522
  add_to_known=add_to_known,
1466
1523
  return_idx=return_idx,
1467
1524
  tasks=tasks,
1525
+ cancel=cancel,
1526
+ status=status,
1468
1527
  )
1469
1528
 
1470
1529
  def _submit_workflow(
@@ -1556,8 +1615,13 @@ class BaseApp(metaclass=Singleton):
1556
1615
  )
1557
1616
  return shell.get_version_info(exclude_os)
1558
1617
 
1618
+ @TimeIt.decorator
1559
1619
  def _get_known_submissions(
1560
- self, max_recent: int = 3, no_update: bool = False, as_json: bool = False
1620
+ self,
1621
+ max_recent: int = 3,
1622
+ no_update: bool = False,
1623
+ as_json: bool = False,
1624
+ status: Optional[Any] = None,
1561
1625
  ):
1562
1626
  """Retrieve information about active and recently inactive finished {app_name}
1563
1627
  workflows.
@@ -1582,6 +1646,8 @@ class BaseApp(metaclass=Singleton):
1582
1646
  inactive_IDs = []
1583
1647
 
1584
1648
  try:
1649
+ if status:
1650
+ status.update("Reading known submissions file...")
1585
1651
  known_subs = self.read_known_submissions_file()
1586
1652
  except FileNotFoundError:
1587
1653
  known_subs = []
@@ -1614,6 +1680,8 @@ class BaseApp(metaclass=Singleton):
1614
1680
  out_item["deleted"] = not path_exists
1615
1681
  if path_exists:
1616
1682
  try:
1683
+ if status:
1684
+ status.update(f"Inspecting workflow {file_dat_i['path']!r}.")
1617
1685
  wk_i = self.Workflow(file_dat_i["path"])
1618
1686
  except Exception:
1619
1687
  wk_i = None
@@ -1640,15 +1708,22 @@ class BaseApp(metaclass=Singleton):
1640
1708
  out_item["deleted"] = True
1641
1709
 
1642
1710
  else:
1643
- sub = wk_i.submissions[file_dat_i["sub_idx"]]
1644
-
1645
- all_jobscripts = sub._submission_parts[submit_time_str]
1646
- out_item.update(
1647
- {
1648
- "jobscripts": all_jobscripts,
1649
- "submission": sub,
1650
- }
1651
- )
1711
+ if status:
1712
+ status.update(
1713
+ f"Reading workflow {file_dat_i['path']!r} submission info..."
1714
+ )
1715
+ with wk_i._store.cache_ctx():
1716
+ sub = wk_i.submissions[file_dat_i["sub_idx"]]
1717
+
1718
+ all_jobscripts = sub._submission_parts[submit_time_str]
1719
+ out_item.update(
1720
+ {
1721
+ "jobscripts": all_jobscripts,
1722
+ "submission": sub,
1723
+ "sub_start_time": sub.start_time,
1724
+ "sub_end_time": sub.end_time,
1725
+ }
1726
+ )
1652
1727
  if file_dat_i["is_active"]:
1653
1728
  # check it really is active:
1654
1729
  run_key = (file_dat_i["path"], file_dat_i["sub_idx"])
@@ -1694,9 +1769,7 @@ class BaseApp(metaclass=Singleton):
1694
1769
  out_access = sorted(
1695
1770
  out_access,
1696
1771
  key=lambda i: (
1697
- i["submission"].end_time
1698
- or i["submission"].start_time
1699
- or i["submit_time_obj"]
1772
+ i["sub_end_time"] or i["sub_start_time"] or i["submit_time_obj"]
1700
1773
  ),
1701
1774
  reverse=True,
1702
1775
  )
@@ -1826,6 +1899,7 @@ class BaseApp(metaclass=Singleton):
1826
1899
  run_dat = self._get_known_submissions(
1827
1900
  max_recent=max_recent,
1828
1901
  no_update=no_update,
1902
+ status=status,
1829
1903
  )
1830
1904
  except Exception:
1831
1905
  status.stop()
hpcflow/sdk/cli.py CHANGED
@@ -25,6 +25,9 @@ from hpcflow.sdk.cli_common import (
25
25
  add_to_known_opt,
26
26
  print_idx_opt,
27
27
  tasks_opt,
28
+ cancel_opt,
29
+ submit_status_opt,
30
+ make_status_opt,
28
31
  zip_path_opt,
29
32
  zip_overwrite_opt,
30
33
  zip_log_opt,
@@ -71,6 +74,7 @@ def _make_API_CLI(app):
71
74
  @ts_fmt_option
72
75
  @ts_name_fmt_option
73
76
  @variables_option
77
+ @make_status_opt
74
78
  def make_workflow(
75
79
  template_file_or_str,
76
80
  string,
@@ -82,6 +86,7 @@ def _make_API_CLI(app):
82
86
  ts_fmt=None,
83
87
  ts_name_fmt=None,
84
88
  variables=None,
89
+ status=True,
85
90
  ):
86
91
  """Generate a new {app_name} workflow.
87
92
 
@@ -100,6 +105,7 @@ def _make_API_CLI(app):
100
105
  ts_fmt=ts_fmt,
101
106
  ts_name_fmt=ts_name_fmt,
102
107
  variables=dict(variables),
108
+ status=status,
103
109
  )
104
110
  click.echo(wk.path)
105
111
 
@@ -119,6 +125,8 @@ def _make_API_CLI(app):
119
125
  @add_to_known_opt
120
126
  @print_idx_opt
121
127
  @tasks_opt
128
+ @cancel_opt
129
+ @submit_status_opt
122
130
  def make_and_submit_workflow(
123
131
  template_file_or_str,
124
132
  string,
@@ -135,6 +143,8 @@ def _make_API_CLI(app):
135
143
  add_to_known=True,
136
144
  print_idx=False,
137
145
  tasks=None,
146
+ cancel=False,
147
+ status=True,
138
148
  ):
139
149
  """Generate and submit a new {app_name} workflow.
140
150
 
@@ -159,6 +169,8 @@ def _make_API_CLI(app):
159
169
  add_to_known=add_to_known,
160
170
  return_idx=print_idx,
161
171
  tasks=tasks,
172
+ cancel=cancel,
173
+ status=status,
162
174
  )
163
175
  if print_idx:
164
176
  click.echo(out)
@@ -320,6 +332,8 @@ def _make_workflow_CLI(app):
320
332
  @add_to_known_opt
321
333
  @print_idx_opt
322
334
  @tasks_opt
335
+ @cancel_opt
336
+ @submit_status_opt
323
337
  @click.pass_context
324
338
  def submit_workflow(
325
339
  ctx,
@@ -328,6 +342,8 @@ def _make_workflow_CLI(app):
328
342
  add_to_known=True,
329
343
  print_idx=False,
330
344
  tasks=None,
345
+ cancel=False,
346
+ status=True,
331
347
  ):
332
348
  """Submit the workflow."""
333
349
  out = ctx.obj["workflow"].submit(
@@ -336,6 +352,8 @@ def _make_workflow_CLI(app):
336
352
  add_to_known=add_to_known,
337
353
  return_idx=print_idx,
338
354
  tasks=tasks,
355
+ cancel=cancel,
356
+ status=status,
339
357
  )
340
358
  if print_idx:
341
359
  click.echo(out)
hpcflow/sdk/cli_common.py CHANGED
@@ -109,6 +109,22 @@ tasks_opt = click.option(
109
109
  ),
110
110
  callback=sub_tasks_callback,
111
111
  )
112
+ cancel_opt = click.option(
113
+ "--cancel",
114
+ help="Immediately cancel the submission. Useful for testing and benchmarking.",
115
+ is_flag=True,
116
+ default=False,
117
+ )
118
+ submit_status_opt = click.option(
119
+ "--status/--no-status",
120
+ help="If True, display a live status to track submission progress.",
121
+ default=True,
122
+ )
123
+ make_status_opt = click.option(
124
+ "--status/--no-status",
125
+ help="If True, display a live status to track workflow creation progress.",
126
+ default=True,
127
+ )
112
128
  zip_path_opt = click.option(
113
129
  "--path",
114
130
  default=".",
@@ -29,6 +29,7 @@ from hpcflow.sdk.core.utils import (
29
29
  swap_nested_dict_keys,
30
30
  )
31
31
  from hpcflow.sdk.log import TimeIt
32
+ from hpcflow.sdk.core.run_dir_files import RunDirAppFiles
32
33
 
33
34
 
34
35
  ACTION_SCOPE_REGEX = r"(\w*)(?:\[(.*)\])?"
@@ -251,13 +252,16 @@ class ElementActionRun:
251
252
  @property
252
253
  def snapshot_start(self):
253
254
  if self._ss_start_obj is None and self._snapshot_start:
254
- self._ss_start_obj = JSONLikeDirSnapShot(**self._snapshot_start)
255
+ self._ss_start_obj = JSONLikeDirSnapShot(
256
+ root_path=".",
257
+ **self._snapshot_start,
258
+ )
255
259
  return self._ss_start_obj
256
260
 
257
261
  @property
258
262
  def snapshot_end(self):
259
263
  if self._ss_end_obj is None and self._snapshot_end:
260
- self._ss_end_obj = JSONLikeDirSnapShot(**self._snapshot_end)
264
+ self._ss_end_obj = JSONLikeDirSnapShot(root_path=".", **self._snapshot_end)
261
265
  return self._ss_end_obj
262
266
 
263
267
  @property
@@ -331,6 +335,7 @@ class ElementActionRun:
331
335
  run_idx=self.index,
332
336
  )
333
337
 
338
+ @TimeIt.decorator
334
339
  def get_parameter_sources(
335
340
  self,
336
341
  path: str = None,
@@ -363,6 +368,7 @@ class ElementActionRun:
363
368
  raise_on_unset=raise_on_unset,
364
369
  )
365
370
 
371
+ @TimeIt.decorator
366
372
  def get_EAR_dependencies(self, as_objects=False):
367
373
  """Get EARs that this EAR depends on."""
368
374
 
@@ -434,6 +440,7 @@ class ElementActionRun:
434
440
  return self._outputs
435
441
 
436
442
  @property
443
+ @TimeIt.decorator
437
444
  def resources(self):
438
445
  if not self._resources:
439
446
  self._resources = self.app.ElementResources(**self.get_resources())
@@ -451,6 +458,7 @@ class ElementActionRun:
451
458
  self._output_files = self.app.ElementOutputFiles(element_action_run=self)
452
459
  return self._output_files
453
460
 
461
+ @TimeIt.decorator
454
462
  def get_resources(self):
455
463
  """Resolve specific resources for this EAR, considering all applicable scopes and
456
464
  template-level resources."""
@@ -1457,11 +1465,11 @@ class Action(JSONLike):
1457
1465
 
1458
1466
  @staticmethod
1459
1467
  def get_param_dump_file_stem(js_idx: int, js_act_idx: int):
1460
- return f"js_{js_idx}_act_{js_act_idx}_inputs"
1468
+ return RunDirAppFiles.get_run_param_dump_file_prefix(js_idx, js_act_idx)
1461
1469
 
1462
1470
  @staticmethod
1463
1471
  def get_param_load_file_stem(js_idx: int, js_act_idx: int):
1464
- return f"js_{js_idx}_act_{js_act_idx}_outputs"
1472
+ return RunDirAppFiles.get_run_param_load_file_prefix(js_idx, js_act_idx)
1465
1473
 
1466
1474
  def get_param_dump_file_path_JSON(self, js_idx: int, js_act_idx: int):
1467
1475
  return Path(self.get_param_dump_file_stem(js_idx, js_act_idx) + ".json")
@@ -1945,7 +1953,7 @@ class Action(JSONLike):
1945
1953
  """\
1946
1954
  import {app_module} as app
1947
1955
  app.load_config(
1948
- log_file_path=Path("{app_package_name}.log").resolve(),
1956
+ log_file_path=Path("{run_log_file}").resolve(),
1949
1957
  config_dir=r"{cfg_dir}",
1950
1958
  config_key=r"{cfg_invoc_key}",
1951
1959
  )
@@ -1954,7 +1962,7 @@ class Action(JSONLike):
1954
1962
  EAR = wk.get_EARs_from_IDs([EAR_ID])[0]
1955
1963
  """
1956
1964
  ).format(
1957
- app_package_name=self.app.package_name,
1965
+ run_log_file=self.app.RunDirAppFiles.get_log_file_name(),
1958
1966
  app_module=self.app.module,
1959
1967
  cfg_dir=self.app.config.config_directory,
1960
1968
  cfg_invoc_key=self.app.config.config_key,
@@ -159,7 +159,7 @@ class InputFileGenerator(JSONLike):
159
159
  from pathlib import Path
160
160
  import {app_module} as app
161
161
  app.load_config(
162
- log_file_path=Path("{app_package_name}.log").resolve(),
162
+ log_file_path=Path("{run_log_file}").resolve(),
163
163
  config_dir=r"{cfg_dir}",
164
164
  config_key=r"{cfg_invoc_key}",
165
165
  )
@@ -171,7 +171,7 @@ class InputFileGenerator(JSONLike):
171
171
  """
172
172
  )
173
173
  main_block = main_block.format(
174
- app_package_name=self.app.package_name,
174
+ run_log_file=self.app.RunDirAppFiles.get_log_file_name(),
175
175
  app_module=self.app.module,
176
176
  cfg_dir=self.app.config.config_directory,
177
177
  cfg_invoc_key=self.app.config.config_key,
@@ -303,7 +303,7 @@ class OutputFileParser(JSONLike):
303
303
  from pathlib import Path
304
304
  import {app_module} as app
305
305
  app.load_config(
306
- log_file_path=Path("{app_package_name}.log").resolve(),
306
+ log_file_path=Path("{run_log_file}").resolve(),
307
307
  config_dir=r"{cfg_dir}",
308
308
  config_key=r"{cfg_invoc_key}",
309
309
  )
@@ -321,7 +321,7 @@ class OutputFileParser(JSONLike):
321
321
  """
322
322
  )
323
323
  main_block = main_block.format(
324
- app_package_name=self.app.package_name,
324
+ run_log_file=self.app.RunDirAppFiles.get_log_file_name(),
325
325
  app_module=self.app.module,
326
326
  cfg_dir=self.app.config.config_directory,
327
327
  cfg_invoc_key=self.app.config.config_key,
@@ -13,9 +13,11 @@ from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
13
13
  from hpcflow.sdk.core.parallel import ParallelMode
14
14
  from hpcflow.sdk.core.utils import (
15
15
  check_valid_py_identifier,
16
+ dict_values_process_flat,
16
17
  get_enum_by_name_or_val,
17
18
  split_param_label,
18
19
  )
20
+ from hpcflow.sdk.log import TimeIt
19
21
  from hpcflow.sdk.submission.shells import get_shell
20
22
 
21
23
 
@@ -500,6 +502,7 @@ class ElementIteration:
500
502
  if i.startswith(prefix)
501
503
  )
502
504
 
505
+ @TimeIt.decorator
503
506
  def get_data_idx(
504
507
  self,
505
508
  path: str = None,
@@ -538,6 +541,7 @@ class ElementIteration:
538
541
 
539
542
  return copy.deepcopy(data_idx)
540
543
 
544
+ @TimeIt.decorator
541
545
  def get_parameter_sources(
542
546
  self,
543
547
  path: str = None,
@@ -555,24 +559,16 @@ class ElementIteration:
555
559
  ID.
556
560
  """
557
561
  data_idx = self.get_data_idx(path, action_idx, run_idx)
558
- out = {}
559
- for k, v in data_idx.items():
560
- is_multi = False
561
- if isinstance(v, list):
562
- is_multi = True
563
- else:
564
- v = [v]
565
562
 
566
- sources_k = []
567
- for dat_idx_i in v:
568
- src = self.workflow.get_parameter_source(dat_idx_i)
569
- sources_k.append(src)
570
-
571
- if not is_multi:
572
- sources_k = src
573
-
574
- out[k] = sources_k
563
+ # the value associated with `repeats.*` is the repeats index, not a parameter ID:
564
+ for k in list(data_idx.keys()):
565
+ if k.startswith("repeats."):
566
+ data_idx.pop(k)
575
567
 
568
+ out = dict_values_process_flat(
569
+ data_idx,
570
+ callable=self.workflow.get_parameter_sources,
571
+ )
576
572
  task_key = "task_insert_ID"
577
573
 
578
574
  if use_task_index:
@@ -631,6 +627,7 @@ class ElementIteration:
631
627
 
632
628
  return out
633
629
 
630
+ @TimeIt.decorator
634
631
  def get(
635
632
  self,
636
633
  path: str = None,
@@ -856,6 +853,7 @@ class ElementIteration:
856
853
  out[res_i.scope.to_string()] = res_i._get_value()
857
854
  return out
858
855
 
856
+ @TimeIt.decorator
859
857
  def get_resources(self, action: app.Action, set_defaults: bool = False) -> Dict:
860
858
  """Resolve specific resources for the specified action of this iteration,
861
859
  considering all applicable scopes.
@@ -998,6 +996,7 @@ class Element:
998
996
  return self._iteration_IDs
999
997
 
1000
998
  @property
999
+ @TimeIt.decorator
1001
1000
  def iterations(self) -> Dict[app.ElementAction]:
1002
1001
  # TODO: fix this
1003
1002
  if self._iteration_objs is None:
@@ -0,0 +1,63 @@
1
+ import re
2
+ from hpcflow.sdk.core.utils import JSONLikeDirSnapShot
3
+
4
+
5
+ class RunDirAppFiles:
6
+ """A class to encapsulate the naming/recognition of app-created files within run
7
+ directories."""
8
+
9
+ _app_attr = "app"
10
+
11
+ CMD_FILES_RE_PATTERN = r"js_\d+_act_\d+\.?\w*"
12
+
13
+ @classmethod
14
+ def get_log_file_name(cls):
15
+ """File name for the app log file."""
16
+ return f"{cls.app.package_name}.log"
17
+
18
+ @classmethod
19
+ def get_std_file_name(cls):
20
+ """File name for stdout and stderr streams from the app."""
21
+ return f"{cls.app.package_name}_std.txt"
22
+
23
+ @staticmethod
24
+ def get_run_file_prefix(js_idx: int, js_action_idx: int):
25
+ return f"js_{js_idx}_act_{js_action_idx}"
26
+
27
+ @classmethod
28
+ def get_commands_file_name(cls, js_idx: int, js_action_idx: int, shell):
29
+ return cls.get_run_file_prefix(js_idx, js_action_idx) + shell.JS_EXT
30
+
31
+ @classmethod
32
+ def get_run_param_dump_file_prefix(cls, js_idx: int, js_action_idx: int):
33
+ """Get the prefix to a file in the run directory that the app will dump parameter
34
+ data to."""
35
+ return cls.get_run_file_prefix(js_idx, js_action_idx) + "_inputs"
36
+
37
+ @classmethod
38
+ def get_run_param_load_file_prefix(cls, js_idx: int, js_action_idx: int):
39
+ """Get the prefix to a file in the run directory that the app will load parameter
40
+ data from."""
41
+ return cls.get_run_file_prefix(js_idx, js_action_idx) + "_outputs"
42
+
43
+ @classmethod
44
+ def take_snapshot(cls):
45
+ """Take a JSONLikeDirSnapShot, and process to ignore files created by the app.
46
+
47
+ This includes command files that are invoked by jobscripts, the app log file, and
48
+ the app standard out/error file.
49
+
50
+ """
51
+ snapshot = JSONLikeDirSnapShot()
52
+ snapshot.take(".")
53
+ ss_js = snapshot.to_json_like()
54
+ ss_js.pop("root_path") # always the current working directory of the run
55
+ for k in list(ss_js["data"].keys()):
56
+ if (
57
+ k == cls.get_log_file_name()
58
+ or k == cls.get_std_file_name()
59
+ or re.match(cls.CMD_FILES_RE_PATTERN, k)
60
+ ):
61
+ ss_js["data"].pop(k)
62
+
63
+ return ss_js