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
@@ -7,15 +7,33 @@ from __future__ import annotations
7
7
  from collections import defaultdict
8
8
  import contextlib
9
9
  from dataclasses import dataclass, field, fields
10
- from datetime import datetime
11
10
 
12
- from typing import Any, Dict, List, Optional, Tuple
11
+ from typing import Any, Generic, TYPE_CHECKING
13
12
 
14
13
  from hpcflow.sdk.log import TimeIt
15
-
16
-
17
- class PendingChanges:
18
- """Class to store pending changes and merge them into a persistent store.
14
+ from hpcflow.sdk.persistence.types import (
15
+ AnySTask,
16
+ AnySElement,
17
+ AnySElementIter,
18
+ AnySEAR,
19
+ AnySParameter,
20
+ )
21
+
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Mapping, Sequence
24
+ from datetime import datetime
25
+ from logging import Logger
26
+ from .base import PersistentStore, FileDescriptor, LoopDescriptor
27
+ from ..app import BaseApp
28
+ from ..typing import ParamSource
29
+ from ..core.json_like import JSONDocument
30
+
31
+
32
+ class PendingChanges(
33
+ Generic[AnySTask, AnySElement, AnySElementIter, AnySEAR, AnySParameter]
34
+ ):
35
+ """
36
+ Class to store pending changes and merge them into a persistent store.
19
37
 
20
38
  Parameters
21
39
  ----------
@@ -27,70 +45,91 @@ class PendingChanges:
27
45
  Map of resources, used when processing commits.
28
46
  """
29
47
 
30
- def __init__(self, app, store, resource_map):
31
- self.app = app
48
+ # These would be in the docstring except they render really wrongly!
49
+ # Type Parameters
50
+ # ---------------
51
+ # AnySTask
52
+ # The type of stored tasks.
53
+ # AnySElement
54
+ # The type of stored elements.
55
+ # AnySElementIter
56
+ # The type of stored element iterations.
57
+ # AnySEAR
58
+ # The type of stored EARs.
59
+ # AnySParameter
60
+ # The type of stored parameters.
61
+
62
+ def __init__(
63
+ self,
64
+ app: BaseApp,
65
+ store: PersistentStore[
66
+ AnySTask, AnySElement, AnySElementIter, AnySEAR, AnySParameter
67
+ ],
68
+ resource_map: CommitResourceMap,
69
+ ):
70
+ self._app = app
32
71
  self.store = store
33
72
  self.resource_map = resource_map
34
73
 
35
74
  #: Keys are new task IDs.
36
- self.add_tasks: Dict[int, AnySTask] = None
75
+ self.add_tasks: dict[int, AnySTask] = {}
37
76
  #: Keys are loop IDs, values are loop descriptors.
38
- self.add_loops: Dict[int, Dict] = None
77
+ self.add_loops: dict[int, LoopDescriptor] = {}
39
78
  #: Keys are submission IDs, values are submission descriptors.
40
- self.add_submissions: Dict[int, Dict] = None
79
+ self.add_submissions: dict[int, JSONDocument] = {}
41
80
  #: Keys are element IDs.
42
- self.add_elements: Dict[int, AnySElement] = None
81
+ self.add_elements: dict[int, AnySElement] = {}
43
82
  #: Keys are element iteration IDs.
44
- self.add_elem_iters: Dict[int, AnySElementIter] = None
83
+ self.add_elem_iters: dict[int, AnySElementIter] = {}
45
84
  #: Keys are element action run IDs.
46
- self.add_EARs: Dict[int, AnySEAR] = None
85
+ self.add_EARs: dict[int, AnySEAR] = {}
47
86
  #: Keys are parameter indices and values are tuples whose first element is data
48
87
  #: to add and whose second element is the source dict for the new data.
49
- self.add_parameters: Dict[int, AnySParameter] = None
88
+ self.add_parameters: dict[int, AnySParameter] = {}
50
89
  #: Workflow-related files (inputs, outputs) added to the persistent store.
51
- self.add_files: List[Dict] = None
90
+ self.add_files: list[FileDescriptor] = []
52
91
  #: Template components to add.
53
- self.add_template_components: Dict[str, Dict[str, Dict]] = None
92
+ self.add_template_components: dict[str, dict[str, dict]] = {}
54
93
  #: Keys are element set IDs, values are descriptors.
55
- self.add_element_sets: Dict[int, Dict] = None
94
+ self.add_element_sets: dict[int, list[Mapping]] = {}
56
95
 
57
96
  #: Keys are task IDs, and values are element IDs to add to that task.
58
- self.add_elem_IDs: Dict[int, List] = None
97
+ self.add_elem_IDs: dict[int, list[int]] = {}
59
98
  #: Keys are element IDs, and values are iteration IDs to add to that element.
60
- self.add_elem_iter_IDs: Dict[int, List] = None
99
+ self.add_elem_iter_IDs: dict[int, list[int]] = {}
61
100
  #: Keys are element iteration IDs, then EAR action index, and values are EAR IDs.
62
101
  #: This is a list of EAR IDs to add to a given element iteration action.
63
- self.add_elem_iter_EAR_IDs: Dict[int, Dict[int, List]] = None
102
+ self.add_elem_iter_EAR_IDs: dict[int, dict[int, list[int]]] = {}
64
103
  #: Submission parts to add.
65
- self.add_submission_parts: Dict[int, Dict[str, List[int]]] = None
104
+ self.add_submission_parts: dict[int, dict[str, list[int]]] = {}
66
105
 
67
106
  #: IDs of EARs to mark as initialised.
68
- self.set_EARs_initialised: List[int] = None
107
+ self.set_EARs_initialised: list[int] = []
69
108
  #: Submission IDs to attach to EARs.
70
- self.set_EAR_submission_indices: Dict[int, int] = None
109
+ self.set_EAR_submission_indices: dict[int, int] = {}
71
110
  #: IDs of EARs to mark as skipped.
72
- self.set_EAR_skips: List[int] = None
111
+ self.set_EAR_skips: list[int] = []
73
112
  #: Keys are EAR IDs and values are tuples of start time, and start dir snapshot.
74
- self.set_EAR_starts: Dict[int, Tuple[datetime, Dict], str] = None
113
+ self.set_EAR_starts: dict[int, tuple[datetime, dict[str, Any], str]] = {}
75
114
  #: Keys are EAR IDs and values are tuples of end time, end dir snapshot, exit
76
115
  #: code, and success boolean.
77
- self.set_EAR_ends: Dict[int, Tuple[datetime, Dict, int, bool]] = None
116
+ self.set_EAR_ends: dict[int, tuple[datetime, dict[str, Any], int, bool]] = {}
78
117
 
79
118
  #: Keys are IDs of jobscripts.
80
- self.set_js_metadata: Dict[int, Dict[int, Any]] = None
119
+ self.set_js_metadata: dict[int, dict[int, dict[str, Any]]] = {}
81
120
 
82
121
  #: Keys are IDs of parameters to add or modify.
83
- self.set_parameters: Dict[int, AnySParameter] = None
122
+ self.set_parameters: dict[int, tuple[Any, bool]] = {}
84
123
 
85
124
  #: Keys are parameter indices and values are dict parameter sources to merge
86
125
  #: with existing source of that parameter.
87
- self.update_param_sources: Dict[int, Dict] = None
126
+ self.update_param_sources: dict[int, ParamSource] = {}
88
127
  #: Keys are indices of loops, values are descriptions of what to update.
89
- self.update_loop_indices: Dict[int, Dict[str, int]] = None
128
+ self.update_loop_indices: dict[int, dict[str, int]] = {}
90
129
  #: Keys are indices of loops, values are number of iterations.
91
- self.update_loop_num_iters: Dict[int, int] = None
130
+ self.update_loop_num_iters: dict[int, list[list[list[int] | int]]] = {}
92
131
  #: Keys are indices of loops, values are list of parent names.
93
- self.update_loop_parents: Dict[int, List[str]] = None
132
+ self.update_loop_parents: dict[int, list[str]] = {}
94
133
 
95
134
  self.reset(is_init=True) # set up initial data structures
96
135
 
@@ -124,25 +163,22 @@ class PendingChanges:
124
163
  or bool(self.update_loop_parents)
125
164
  )
126
165
 
127
- def where_pending(self) -> List[str]:
166
+ def where_pending(self) -> list[str]:
128
167
  """
129
168
  Get the list of items for which there is some outstanding pending items.
130
169
  """
131
- return [
132
- k
133
- for k, v in self.__dict__.items()
134
- if k not in ("app", "store", "resource_map") and bool(v)
135
- ]
170
+ excluded = {"app", "store", "resource_map"}
171
+ return [k for k, v in self.__dict__.items() if k not in excluded and bool(v)]
136
172
 
137
173
  @property
138
- def logger(self):
174
+ def logger(self) -> Logger:
139
175
  """
140
176
  The logger.
141
177
  """
142
- return self.app.persistence_logger
178
+ return self._app.persistence_logger
143
179
 
144
180
  @TimeIt.decorator
145
- def commit_all(self):
181
+ def commit_all(self) -> None:
146
182
  """Commit all pending changes to disk."""
147
183
  self.logger.info(f"committing all pending changes: {self.where_pending()}")
148
184
 
@@ -155,7 +191,9 @@ class PendingChanges:
155
191
  with contextlib.ExitStack() as stack:
156
192
  for res in resources:
157
193
  # TODO: only enter required resources!
158
- stack.enter_context(self.store.using_resource(res, "update"))
194
+ stack.enter_context(
195
+ self.store.using_resource(res, "update") # type: ignore[call-overload]
196
+ )
159
197
  for meth in methods:
160
198
  getattr(self, meth)()
161
199
 
@@ -166,7 +204,7 @@ class PendingChanges:
166
204
  """Commit pending tasks to disk."""
167
205
  if self.add_tasks:
168
206
  tasks = self.store.get_tasks_by_IDs(self.add_tasks)
169
- task_ids = list(self.add_tasks.keys())
207
+ task_ids = set(self.add_tasks)
170
208
  self.logger.debug(f"commit: adding pending tasks with IDs: {task_ids!r}")
171
209
  self.store._append_tasks(tasks)
172
210
  self.store.num_tasks_cache = None # invalidate cache
@@ -182,7 +220,7 @@ class PendingChanges:
182
220
  if self.add_loops:
183
221
  # retrieve pending loops, including pending changes to num_added_iterations:
184
222
  loops = self.store.get_loops_by_IDs(self.add_loops)
185
- loop_ids = list(self.add_loops.keys())
223
+ loop_ids = set(self.add_loops)
186
224
  self.logger.debug(f"commit: adding pending loops with indices {loop_ids!r}")
187
225
  self.store._append_loops(loops)
188
226
 
@@ -203,7 +241,7 @@ class PendingChanges:
203
241
  if self.add_submissions:
204
242
  # retrieve pending submissions:
205
243
  subs = self.store.get_submissions_by_ID(self.add_submissions)
206
- sub_ids = list(self.add_submissions.keys())
244
+ sub_ids = set(self.add_submissions)
207
245
  self.logger.debug(
208
246
  f"commit: adding pending submissions with indices {sub_ids!r}"
209
247
  )
@@ -216,7 +254,7 @@ class PendingChanges:
216
254
  Commit pending submission parts to disk.
217
255
  """
218
256
  if self.add_submission_parts:
219
- self.logger.debug(f"commit: adding pending submission parts")
257
+ self.logger.debug("commit: adding pending submission parts")
220
258
  self.store._append_submission_parts(self.add_submission_parts)
221
259
  self._clear_add_submission_parts()
222
260
 
@@ -241,7 +279,7 @@ class PendingChanges:
241
279
  """
242
280
  if self.add_elements:
243
281
  elems = self.store.get_elements(self.add_elements)
244
- elem_ids = list(self.add_elements.keys())
282
+ elem_ids = set(self.add_elements)
245
283
  self.logger.debug(f"commit: adding pending elements with IDs: {elem_ids!r}")
246
284
  self.store._append_elements(elems)
247
285
  # pending iter IDs that belong to pending elements are now committed:
@@ -257,7 +295,7 @@ class PendingChanges:
257
295
  """
258
296
  # TODO: could be batched up?
259
297
  for task_id, es_js in self.add_element_sets.items():
260
- self.logger.debug(f"commit: adding pending element sets.")
298
+ self.logger.debug("commit: adding pending element sets.")
261
299
  self.store._append_element_sets(task_id, es_js)
262
300
  self._clear_add_element_sets()
263
301
 
@@ -282,8 +320,8 @@ class PendingChanges:
282
320
  Commit pending element iterations to disk.
283
321
  """
284
322
  if self.add_elem_iters:
285
- iters = self.store.get_element_iterations(self.add_elem_iters.keys())
286
- iter_ids = list(self.add_elem_iters.keys())
323
+ iters = self.store.get_element_iterations(self.add_elem_iters)
324
+ iter_ids = set(self.add_elem_iters)
287
325
  self.logger.debug(
288
326
  f"commit: adding pending element iterations with IDs: {iter_ids!r}"
289
327
  )
@@ -321,7 +359,7 @@ class PendingChanges:
321
359
  """
322
360
  if self.add_EARs:
323
361
  EARs = self.store.get_EARs(self.add_EARs)
324
- EAR_ids = list(self.add_EARs.keys())
362
+ EAR_ids = list(self.add_EARs)
325
363
  self.logger.debug(f"commit: adding pending EARs with IDs: {EAR_ids!r}")
326
364
  self.store._append_EARs(EARs)
327
365
  self.store.num_EARs_cache = None # invalidate cache
@@ -370,7 +408,7 @@ class PendingChanges:
370
408
  f"{self.set_EAR_submission_indices!r}."
371
409
  )
372
410
  self.store._update_EAR_submission_indices(self.set_EAR_submission_indices)
373
- for EAR_ID_i in self.set_EAR_submission_indices.keys():
411
+ for EAR_ID_i in self.set_EAR_submission_indices:
374
412
  self.store.EAR_cache.pop(EAR_ID_i, None) # invalidate cache
375
413
  self._clear_set_EAR_submission_indices()
376
414
 
@@ -433,25 +471,24 @@ class PendingChanges:
433
471
  """Make pending parameters persistent."""
434
472
  if self.add_parameters:
435
473
  params = self.store.get_parameters(self.add_parameters)
436
- param_ids = list(self.add_parameters.keys())
474
+ param_ids = list(self.add_parameters)
437
475
  self.logger.debug(f"commit: adding pending parameters IDs: {param_ids!r}")
438
476
  self.store._append_parameters(params)
439
477
  self._clear_add_parameters()
440
478
 
441
479
  if self.set_parameters:
442
- param_ids = list(self.set_parameters.keys())
480
+ param_ids = list(self.set_parameters)
443
481
  self.logger.debug(f"commit: setting values of parameter IDs {param_ids!r}.")
444
482
  self.store._set_parameter_values(self.set_parameters)
445
483
  for id_i in param_ids:
446
484
  self.store.parameter_cache.pop(id_i, None)
447
-
448
485
  self._clear_set_parameters()
449
486
 
450
487
  @TimeIt.decorator
451
488
  def commit_files(self) -> None:
452
489
  """Add pending files to the files directory."""
453
490
  if self.add_files:
454
- self.logger.debug(f"commit: adding pending files to the files directory.")
491
+ self.logger.debug("commit: adding pending files to the files directory.")
455
492
  self.store._append_files(self.add_files)
456
493
  self._clear_add_files()
457
494
 
@@ -461,7 +498,7 @@ class PendingChanges:
461
498
  Commit pending template components to disk.
462
499
  """
463
500
  if self.add_template_components:
464
- self.logger.debug(f"commit: adding template components.")
501
+ self.logger.debug("commit: adding template components.")
465
502
  self.store._update_template_components(self.store.get_template_components())
466
503
  self._clear_add_template_components()
467
504
 
@@ -469,7 +506,7 @@ class PendingChanges:
469
506
  def commit_param_sources(self) -> None:
470
507
  """Make pending changes to parameter sources persistent."""
471
508
  if self.update_param_sources:
472
- param_ids = list(self.update_param_sources.keys())
509
+ param_ids = list(self.update_param_sources)
473
510
  self.logger.debug(f"commit: updating sources of parameter IDs {param_ids!r}.")
474
511
  self.store._update_parameter_sources(self.update_param_sources)
475
512
  for id_i in param_ids:
@@ -507,82 +544,82 @@ class PendingChanges:
507
544
  self.store._update_loop_parents(index, parents)
508
545
  self._clear_update_loop_parents()
509
546
 
510
- def _clear_add_tasks(self):
547
+ def _clear_add_tasks(self) -> None:
511
548
  self.add_tasks = {}
512
549
 
513
- def _clear_add_loops(self):
550
+ def _clear_add_loops(self) -> None:
514
551
  self.add_loops = {}
515
552
 
516
- def _clear_add_submissions(self):
553
+ def _clear_add_submissions(self) -> None:
517
554
  self.add_submissions = {}
518
555
 
519
- def _clear_add_submission_parts(self):
556
+ def _clear_add_submission_parts(self) -> None:
520
557
  self.add_submission_parts = defaultdict(dict)
521
558
 
522
- def _clear_add_elements(self):
559
+ def _clear_add_elements(self) -> None:
523
560
  self.add_elements = {}
524
561
 
525
- def _clear_add_element_sets(self):
562
+ def _clear_add_element_sets(self) -> None:
526
563
  self.add_element_sets = defaultdict(list)
527
564
 
528
- def _clear_add_elem_iters(self):
565
+ def _clear_add_elem_iters(self) -> None:
529
566
  self.add_elem_iters = {}
530
567
 
531
- def _clear_add_EARs(self):
568
+ def _clear_add_EARs(self) -> None:
532
569
  self.add_EARs = {}
533
570
 
534
- def _clear_add_elem_IDs(self):
571
+ def _clear_add_elem_IDs(self) -> None:
535
572
  self.add_elem_IDs = defaultdict(list)
536
573
 
537
- def _clear_add_elem_iter_IDs(self):
574
+ def _clear_add_elem_iter_IDs(self) -> None:
538
575
  self.add_elem_iter_IDs = defaultdict(list)
539
576
 
540
- def _clear_add_elem_iter_EAR_IDs(self):
577
+ def _clear_add_elem_iter_EAR_IDs(self) -> None:
541
578
  self.add_elem_iter_EAR_IDs = defaultdict(lambda: defaultdict(list))
542
579
 
543
- def _clear_set_EARs_initialised(self):
580
+ def _clear_set_EARs_initialised(self) -> None:
544
581
  self.set_EARs_initialised = []
545
582
 
546
- def _clear_set_EAR_submission_indices(self):
583
+ def _clear_set_EAR_submission_indices(self) -> None:
547
584
  self.set_EAR_submission_indices = {}
548
585
 
549
- def _clear_set_EAR_starts(self):
586
+ def _clear_set_EAR_starts(self) -> None:
550
587
  self.set_EAR_starts = {}
551
588
 
552
- def _clear_set_EAR_ends(self):
589
+ def _clear_set_EAR_ends(self) -> None:
553
590
  self.set_EAR_ends = {}
554
591
 
555
- def _clear_set_EAR_skips(self):
592
+ def _clear_set_EAR_skips(self) -> None:
556
593
  self.set_EAR_skips = []
557
594
 
558
- def _clear_set_js_metadata(self):
595
+ def _clear_set_js_metadata(self) -> None:
559
596
  self.set_js_metadata = defaultdict(lambda: defaultdict(dict))
560
597
 
561
- def _clear_add_parameters(self):
598
+ def _clear_add_parameters(self) -> None:
562
599
  self.add_parameters = {}
563
600
 
564
- def _clear_add_files(self):
601
+ def _clear_add_files(self) -> None:
565
602
  self.add_files = []
566
603
 
567
- def _clear_add_template_components(self):
604
+ def _clear_add_template_components(self) -> None:
568
605
  self.add_template_components = defaultdict(dict)
569
606
 
570
- def _clear_set_parameters(self):
607
+ def _clear_set_parameters(self) -> None:
571
608
  self.set_parameters = {}
572
609
 
573
- def _clear_update_param_sources(self):
610
+ def _clear_update_param_sources(self) -> None:
574
611
  self.update_param_sources = {}
575
612
 
576
- def _clear_update_loop_indices(self):
613
+ def _clear_update_loop_indices(self) -> None:
577
614
  self.update_loop_indices = defaultdict(dict)
578
615
 
579
- def _clear_update_loop_num_iters(self):
616
+ def _clear_update_loop_num_iters(self) -> None:
580
617
  self.update_loop_num_iters = {}
581
618
 
582
- def _clear_update_loop_parents(self):
619
+ def _clear_update_loop_parents(self) -> None:
583
620
  self.update_loop_parents = {}
584
621
 
585
- def reset(self, is_init=False) -> None:
622
+ def reset(self, is_init: bool = False) -> None:
586
623
  """Clear all pending data and prepare to accept new pending data."""
587
624
 
588
625
  if not is_init and not self:
@@ -640,65 +677,67 @@ class CommitResourceMap:
640
677
  """
641
678
 
642
679
  #: Resources for :py:meth:`~.PendingChanges.commit_tasks`.
643
- commit_tasks: Optional[Tuple[str]] = tuple()
680
+ commit_tasks: tuple[str, ...] | None = tuple()
644
681
  #: Resources for :py:meth:`~.PendingChanges.commit_loops`.
645
- commit_loops: Optional[Tuple[str]] = tuple()
682
+ commit_loops: tuple[str, ...] | None = tuple()
646
683
  #: Resources for :py:meth:`~.PendingChanges.commit_submissions`.
647
- commit_submissions: Optional[Tuple[str]] = tuple()
684
+ commit_submissions: tuple[str, ...] | None = tuple()
648
685
  #: Resources for :py:meth:`~.PendingChanges.commit_submission_parts`.
649
- commit_submission_parts: Optional[Tuple[str]] = tuple()
686
+ commit_submission_parts: tuple[str, ...] | None = tuple()
650
687
  #: Resources for :py:meth:`~.PendingChanges.commit_elem_IDs`.
651
- commit_elem_IDs: Optional[Tuple[str]] = tuple()
688
+ commit_elem_IDs: tuple[str, ...] | None = tuple()
652
689
  #: Resources for :py:meth:`~.PendingChanges.commit_elements`.
653
- commit_elements: Optional[Tuple[str]] = tuple()
690
+ commit_elements: tuple[str, ...] | None = tuple()
654
691
  #: Resources for :py:meth:`~.PendingChanges.commit_element_sets`.
655
- commit_element_sets: Optional[Tuple[str]] = tuple()
692
+ commit_element_sets: tuple[str, ...] | None = tuple()
656
693
  #: Resources for :py:meth:`~.PendingChanges.commit_elem_iter_IDs`.
657
- commit_elem_iter_IDs: Optional[Tuple[str]] = tuple()
694
+ commit_elem_iter_IDs: tuple[str, ...] | None = tuple()
658
695
  #: Resources for :py:meth:`~.PendingChanges.commit_elem_iters`.
659
- commit_elem_iters: Optional[Tuple[str]] = tuple()
696
+ commit_elem_iters: tuple[str, ...] | None = tuple()
660
697
  #: Resources for :py:meth:`~.PendingChanges.commit_elem_iter_EAR_IDs`.
661
- commit_elem_iter_EAR_IDs: Optional[Tuple[str]] = tuple()
698
+ commit_elem_iter_EAR_IDs: tuple[str, ...] | None = tuple()
662
699
  #: Resources for :py:meth:`~.PendingChanges.commit_EARs_initialised`.
663
- commit_EARs_initialised: Optional[Tuple[str]] = tuple()
700
+ commit_EARs_initialised: tuple[str, ...] | None = tuple()
664
701
  #: Resources for :py:meth:`~.PendingChanges.commit_EARs`.
665
- commit_EARs: Optional[Tuple[str]] = tuple()
702
+ commit_EARs: tuple[str, ...] | None = tuple()
666
703
  #: Resources for :py:meth:`~.PendingChanges.commit_EAR_submission_indices`.
667
- commit_EAR_submission_indices: Optional[Tuple[str]] = tuple()
704
+ commit_EAR_submission_indices: tuple[str, ...] | None = tuple()
668
705
  #: Resources for :py:meth:`~.PendingChanges.commit_EAR_skips`.
669
- commit_EAR_skips: Optional[Tuple[str]] = tuple()
706
+ commit_EAR_skips: tuple[str, ...] | None = tuple()
670
707
  #: Resources for :py:meth:`~.PendingChanges.commit_EAR_starts`.
671
- commit_EAR_starts: Optional[Tuple[str]] = tuple()
708
+ commit_EAR_starts: tuple[str, ...] | None = tuple()
672
709
  #: Resources for :py:meth:`~.PendingChanges.commit_EAR_ends`.
673
- commit_EAR_ends: Optional[Tuple[str]] = tuple()
710
+ commit_EAR_ends: tuple[str, ...] | None = tuple()
674
711
  #: Resources for :py:meth:`~.PendingChanges.commit_js_metadata`.
675
- commit_js_metadata: Optional[Tuple[str]] = tuple()
712
+ commit_js_metadata: tuple[str, ...] | None = tuple()
676
713
  #: Resources for :py:meth:`~.PendingChanges.commit_parameters`.
677
- commit_parameters: Optional[Tuple[str]] = tuple()
714
+ commit_parameters: tuple[str, ...] | None = tuple()
678
715
  #: Resources for :py:meth:`~.PendingChanges.commit_files`.
679
- commit_files: Optional[Tuple[str]] = tuple()
716
+ commit_files: tuple[str, ...] | None = tuple()
680
717
  #: Resources for :py:meth:`~.PendingChanges.commit_template_components`.
681
- commit_template_components: Optional[Tuple[str]] = tuple()
718
+ commit_template_components: tuple[str, ...] | None = tuple()
682
719
  #: Resources for :py:meth:`~.PendingChanges.commit_param_sources`.
683
- commit_param_sources: Optional[Tuple[str]] = tuple()
720
+ commit_param_sources: tuple[str, ...] | None = tuple()
684
721
  #: Resources for :py:meth:`~.PendingChanges.commit_loop_indices`.
685
- commit_loop_indices: Optional[Tuple[str]] = tuple()
722
+ commit_loop_indices: tuple[str, ...] | None = tuple()
686
723
  #: Resources for :py:meth:`~.PendingChanges.commit_loop_num_iters`.
687
- commit_loop_num_iters: Optional[Tuple[str]] = tuple()
724
+ commit_loop_num_iters: tuple[str, ...] | None = tuple()
688
725
  #: Resources for :py:meth:`~.PendingChanges.commit_loop_parents`.
689
- commit_loop_parents: Optional[Tuple[str]] = tuple()
726
+ commit_loop_parents: tuple[str, ...] | None = tuple()
690
727
  #: A dict whose keys are tuples of resource labels and whose values are lists
691
728
  #: of :py:class:`PendingChanges` commit method names that require those resources.
692
729
  #:
693
730
  #: This grouping allows us to batch up commit methods by resource requirements,
694
731
  #: which in turn means we can potentially minimise, e.g., the number of network
695
732
  #: requests.
696
- groups: Dict[Tuple[str], List[str]] = field(init=False, repr=False, compare=False)
733
+ groups: Mapping[tuple[str, ...], Sequence[str]] = field(
734
+ init=False, repr=False, compare=False
735
+ )
697
736
 
698
737
  def __post_init__(self):
699
738
  self.groups = self._group_by_resource()
700
739
 
701
- def _group_by_resource(self) -> Dict[Tuple[str], List[str]]:
740
+ def _group_by_resource(self) -> Mapping[tuple[str, ...], Sequence[str]]:
702
741
  """
703
742
  Get a dict whose keys are tuples of resource labels and whose values are
704
743
  lists of :py:class:`PendingChanges` commit method names that require those
@@ -708,8 +747,9 @@ class CommitResourceMap:
708
747
  which in turn means we can potentially minimise e.g. the number of network
709
748
  requests.
710
749
  """
711
- groups = {}
712
- cur_res_group = None
750
+ groups: dict[tuple[str, ...], list[str]] = {}
751
+ # The dicts are pretending to be insertion-ordered sets
752
+ cur_res_group: tuple[dict[str, None], list[str]] | None = None
713
753
  for fld in fields(self):
714
754
  if not fld.name.startswith("commit_"):
715
755
  continue
@@ -718,28 +758,23 @@ class CommitResourceMap:
718
758
  if not cur_res_group:
719
759
  # start a new resource group: a mapping between resource labels and the
720
760
  # commit methods that require those resources:
721
- cur_res_group = [list(res_labels), [fld.name]]
761
+ cur_res_group = (dict.fromkeys(res_labels), [fld.name])
722
762
 
723
763
  elif not res_labels or set(res_labels).intersection(cur_res_group[0]):
724
764
  # there is some overlap between resource labels required in the current
725
765
  # group and this commit method, so we merge resource labels and add the
726
766
  # new commit method:
727
- cur_res_group[0] = list(set(cur_res_group[0] + list(res_labels)))
767
+ cur_res_group[0].update(dict.fromkeys(res_labels))
728
768
  cur_res_group[1].append(fld.name)
729
769
 
730
770
  else:
731
771
  # no overlap between resource labels required in the current group and
732
772
  # those required by this commit method, so append the current group, and
733
773
  # start a new group for this commit method:
734
- if tuple(cur_res_group[0]) not in groups:
735
- groups[tuple(cur_res_group[0])] = []
736
- groups[tuple(cur_res_group[0])].extend(cur_res_group[1])
737
- cur_res_group = [list(res_labels), [fld.name]]
774
+ groups.setdefault(tuple(cur_res_group[0]), []).extend(cur_res_group[1])
775
+ cur_res_group = (dict.fromkeys(res_labels), [fld.name])
738
776
 
739
777
  if cur_res_group:
740
- if tuple(cur_res_group[0]) not in groups:
741
- groups[tuple(cur_res_group[0])] = []
742
-
743
- groups[tuple(cur_res_group[0])].extend(cur_res_group[1])
778
+ groups.setdefault(tuple(cur_res_group[0]), []).extend(cur_res_group[1])
744
779
 
745
780
  return groups