hpcflow-new2 0.2.0a173__tar.gz → 0.2.0a175__tar.gz

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 (155) hide show
  1. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/PKG-INFO +3 -3
  2. hpcflow_new2-0.2.0a175/hpcflow/_version.py +1 -0
  3. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/errors.py +8 -0
  4. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/loop.py +232 -46
  5. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/task.py +8 -0
  6. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/test_utils.py +14 -2
  7. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/workflow.py +99 -34
  8. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/persistence/base.py +32 -4
  9. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/persistence/json.py +6 -0
  10. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/persistence/pending.py +27 -2
  11. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/persistence/zarr.py +5 -0
  12. hpcflow_new2-0.2.0a175/hpcflow/tests/unit/test_group.py +91 -0
  13. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_loop.py +569 -1
  14. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/pyproject.toml +4 -4
  15. hpcflow_new2-0.2.0a173/hpcflow/_version.py +0 -1
  16. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/README.md +0 -0
  17. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/__init__.py +0 -0
  18. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/__pyinstaller/__init__.py +0 -0
  19. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/__pyinstaller/hook-hpcflow.py +0 -0
  20. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/app.py +0 -0
  21. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/cli.py +0 -0
  22. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/demo_data_manifest/__init__.py +0 -0
  23. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/demo_data_manifest/demo_data_manifest.json +0 -0
  24. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/__init__.py +0 -0
  25. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +0 -0
  26. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +0 -0
  27. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/demo_task_1_parse_p3.py +0 -0
  28. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/generate_t1_file_01.py +0 -0
  29. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +0 -0
  30. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +0 -0
  31. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +0 -0
  32. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +0 -0
  33. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +0 -0
  34. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +0 -0
  35. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +0 -0
  36. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +0 -0
  37. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +0 -0
  38. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_json_in_json_out.py +0 -0
  39. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +0 -0
  40. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_json_in_obj.py +0 -0
  41. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_json_out_obj.py +0 -0
  42. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +0 -0
  43. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/scripts/parse_t1_file_01.py +0 -0
  44. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/template_components/__init__.py +0 -0
  45. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/template_components/command_files.yaml +0 -0
  46. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/template_components/environments.yaml +0 -0
  47. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/template_components/parameters.yaml +0 -0
  48. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/template_components/task_schemas.yaml +0 -0
  49. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/workflows/__init__.py +0 -0
  50. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/data/workflows/workflow_1.yaml +0 -0
  51. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/examples.ipynb +0 -0
  52. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/__init__.py +0 -0
  53. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/app.py +0 -0
  54. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/cli.py +0 -0
  55. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/cli_common.py +0 -0
  56. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/config/__init__.py +0 -0
  57. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/config/callbacks.py +0 -0
  58. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/config/cli.py +0 -0
  59. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/config/config.py +0 -0
  60. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/config/config_file.py +0 -0
  61. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/config/errors.py +0 -0
  62. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/__init__.py +0 -0
  63. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/actions.py +0 -0
  64. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/command_files.py +0 -0
  65. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/commands.py +0 -0
  66. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/element.py +0 -0
  67. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/environment.py +0 -0
  68. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/json_like.py +0 -0
  69. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/object_list.py +0 -0
  70. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/parallel.py +0 -0
  71. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/parameters.py +0 -0
  72. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/rule.py +0 -0
  73. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/run_dir_files.py +0 -0
  74. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/task_schema.py +0 -0
  75. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/utils.py +0 -0
  76. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/validation.py +0 -0
  77. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/core/zarr_io.py +0 -0
  78. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/data/__init__.py +0 -0
  79. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/data/config_file_schema.yaml +0 -0
  80. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/data/config_schema.yaml +0 -0
  81. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/data/environments_spec_schema.yaml +0 -0
  82. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/data/files_spec_schema.yaml +0 -0
  83. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/data/parameters_spec_schema.yaml +0 -0
  84. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/data/task_schema_spec_schema.yaml +0 -0
  85. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/data/workflow_spec_schema.yaml +0 -0
  86. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/demo/__init__.py +0 -0
  87. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/demo/cli.py +0 -0
  88. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/helper/__init__.py +0 -0
  89. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/helper/cli.py +0 -0
  90. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/helper/helper.py +0 -0
  91. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/helper/watcher.py +0 -0
  92. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/log.py +0 -0
  93. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/persistence/__init__.py +0 -0
  94. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/persistence/store_resource.py +0 -0
  95. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/persistence/utils.py +0 -0
  96. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/runtime.py +0 -0
  97. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/__init__.py +0 -0
  98. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/jobscript.py +0 -0
  99. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/jobscript_info.py +0 -0
  100. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/schedulers/__init__.py +0 -0
  101. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/schedulers/direct.py +0 -0
  102. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/schedulers/sge.py +0 -0
  103. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/schedulers/slurm.py +0 -0
  104. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/schedulers/utils.py +0 -0
  105. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/shells/__init__.py +0 -0
  106. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/shells/base.py +0 -0
  107. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/shells/bash.py +0 -0
  108. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/shells/os_version.py +0 -0
  109. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/shells/powershell.py +0 -0
  110. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/submission/submission.py +0 -0
  111. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/sdk/typing.py +0 -0
  112. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/conftest.py +0 -0
  113. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/data/__init__.py +0 -0
  114. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/data/benchmark_N_elements.yaml +0 -0
  115. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/data/workflow_1.json +0 -0
  116. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/data/workflow_1.yaml +0 -0
  117. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/data/workflow_1_slurm.yaml +0 -0
  118. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/data/workflow_1_wsl.yaml +0 -0
  119. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/data/workflow_test_run_abort.yaml +0 -0
  120. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +0 -0
  121. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/schedulers/slurm/test_slurm_submission.py +0 -0
  122. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/scripts/test_main_scripts.py +0 -0
  123. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/shells/wsl/test_wsl_submission.py +0 -0
  124. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_action.py +0 -0
  125. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_action_rule.py +0 -0
  126. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_app.py +0 -0
  127. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_cli.py +0 -0
  128. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_command.py +0 -0
  129. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_config.py +0 -0
  130. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_config_file.py +0 -0
  131. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_element.py +0 -0
  132. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_element_iteration.py +0 -0
  133. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_element_set.py +0 -0
  134. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_input_source.py +0 -0
  135. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_input_value.py +0 -0
  136. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_json_like.py +0 -0
  137. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_object_list.py +0 -0
  138. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_parameter.py +0 -0
  139. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_persistence.py +0 -0
  140. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_resources.py +0 -0
  141. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_run.py +0 -0
  142. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_runtime.py +0 -0
  143. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_schema_input.py +0 -0
  144. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_shell.py +0 -0
  145. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_slurm.py +0 -0
  146. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_submission.py +0 -0
  147. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_task.py +0 -0
  148. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_task_schema.py +0 -0
  149. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_utils.py +0 -0
  150. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_value_sequence.py +0 -0
  151. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_workflow.py +0 -0
  152. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/unit/test_workflow_template.py +0 -0
  153. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/workflows/test_jobscript.py +0 -0
  154. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/tests/workflows/test_workflows.py +0 -0
  155. {hpcflow_new2-0.2.0a173 → hpcflow_new2-0.2.0a175}/hpcflow/viz_demo.ipynb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hpcflow-new2
3
- Version: 0.2.0a173
3
+ Version: 0.2.0a175
4
4
  Summary: Computational workflow management
5
5
  License: MIT
6
6
  Author: aplowman
@@ -22,7 +22,7 @@ Requires-Dist: msgpack (>=1.0.4,<2.0.0)
22
22
  Requires-Dist: numpy (>=1.24.4,<2.0.0) ; python_version < "3.9"
23
23
  Requires-Dist: numpy (>=1.26.4,<2.0.0) ; python_version >= "3.9"
24
24
  Requires-Dist: paramiko (>=3.2.0,<4.0.0)
25
- Requires-Dist: platformdirs (>=2.5.1,<3.0.0)
25
+ Requires-Dist: platformdirs (>=4.2.1,<5.0.0)
26
26
  Requires-Dist: psutil (>=5.9.4,<6.0.0)
27
27
  Requires-Dist: pytest (>=7.2.0,<8.0.0) ; extra == "test"
28
28
  Requires-Dist: requests (>=2.31.0,<3.0.0)
@@ -32,8 +32,8 @@ Requires-Dist: ruamel.yaml (>=0.17.20,<0.18.0)
32
32
  Requires-Dist: termcolor (>=1.1.0,<2.0.0)
33
33
  Requires-Dist: valida (>=0.7.2,<0.8.0)
34
34
  Requires-Dist: watchdog (>=2.1.9,<3.0.0)
35
+ Requires-Dist: zarr (==2.17.2) ; python_version >= "3.9"
35
36
  Requires-Dist: zarr (>=2.16.1,<3.0.0) ; python_version < "3.9"
36
- Requires-Dist: zarr (>=2.17.0,<3.0.0) ; python_version >= "3.9"
37
37
  Description-Content-Type: text/markdown
38
38
 
39
39
  <div align="center">
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0a175"
@@ -171,6 +171,10 @@ class LoopAlreadyExistsError(Exception):
171
171
  pass
172
172
 
173
173
 
174
+ class LoopTaskSubsetError(ValueError):
175
+ pass
176
+
177
+
174
178
  class SchedulerVersionsFailure(RuntimeError):
175
179
  """We couldn't get the scheduler and or shell versions."""
176
180
 
@@ -423,3 +427,7 @@ class UnknownEnvironmentPresetError(ValueError):
423
427
 
424
428
  class MultipleEnvironmentsError(ValueError):
425
429
  pass
430
+
431
+
432
+ class MissingElementGroup(ValueError):
433
+ pass
@@ -4,6 +4,7 @@ import copy
4
4
  from typing import Dict, List, Optional, Tuple, Union
5
5
 
6
6
  from hpcflow.sdk import app
7
+ from hpcflow.sdk.core.errors import LoopTaskSubsetError
7
8
  from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
8
9
  from hpcflow.sdk.core.parameters import InputSourceType
9
10
  from hpcflow.sdk.core.task import WorkflowTask
@@ -175,17 +176,25 @@ class WorkflowLoop:
175
176
  index: int,
176
177
  workflow: app.Workflow,
177
178
  template: app.Loop,
178
- num_added_iterations: int,
179
+ num_added_iterations: Dict[Tuple[int], int],
179
180
  iterable_parameters: Dict[int : List[int, List[int]]],
181
+ parents: List[str],
180
182
  ):
181
183
  self._index = index
182
184
  self._workflow = workflow
183
185
  self._template = template
184
186
  self._num_added_iterations = num_added_iterations
185
187
  self._iterable_parameters = iterable_parameters
188
+ self._parents = parents
186
189
 
187
- # incremented when a new loop iteration is added, reset on dump to disk:
188
- self._pending_num_added_iterations = 0
190
+ # appended to on adding a empty loop to the workflow that's a parent of this loop,
191
+ # reset and added to `self._parents` on dump to disk:
192
+ self._pending_parents = []
193
+
194
+ # used for `num_added_iterations` when a new loop iteration is added, or when
195
+ # parents are append to; reset to None on dump to disk. Each key is a tuple of
196
+ # parent loop indices and each value is the number of pending new iterations:
197
+ self._pending_num_added_iterations = None
189
198
 
190
199
  self._validate()
191
200
 
@@ -194,7 +203,20 @@ class WorkflowLoop:
194
203
  task_indices = self.task_indices
195
204
  task_min, task_max = task_indices[0], task_indices[-1]
196
205
  if task_indices != tuple(range(task_min, task_max + 1)):
197
- raise ValueError(f"Loop task subset must be a contiguous range")
206
+ raise LoopTaskSubsetError(
207
+ f"Loop {self.name!r}: task subset must be an ascending contiguous range, "
208
+ f"but specified task indices were: {self.task_indices!r}."
209
+ )
210
+
211
+ for task in self.downstream_tasks:
212
+ for param in self.iterable_parameters:
213
+ if param in task.template.all_schema_input_types:
214
+ raise NotImplementedError(
215
+ f"Downstream task {task.unique_name!r} of loop {self.name!r} "
216
+ f"has as one of its input parameters this loop's iterable "
217
+ f"parameter {param!r}. This parameter cannot be sourced "
218
+ f"correctly."
219
+ )
198
220
 
199
221
  def __repr__(self) -> str:
200
222
  return (
@@ -202,12 +224,57 @@ class WorkflowLoop:
202
224
  f"num_added_iterations={self.num_added_iterations!r})"
203
225
  )
204
226
 
227
+ @property
228
+ def num_added_iterations(self):
229
+
230
+ if self._pending_num_added_iterations:
231
+ return self._pending_num_added_iterations
232
+ else:
233
+ return self._num_added_iterations
234
+
235
+ def _initialise_pending_added_iters(self, added_iters_key):
236
+ if not self._pending_num_added_iterations:
237
+ self._pending_num_added_iterations = copy.deepcopy(self._num_added_iterations)
238
+
239
+ if added_iters_key not in self._pending_num_added_iterations:
240
+ self._pending_num_added_iterations[added_iters_key] = 1
241
+
242
+ def _increment_pending_added_iters(self, added_iters_key):
243
+ if not self._pending_num_added_iterations:
244
+ self._pending_num_added_iterations = copy.deepcopy(self._num_added_iterations)
245
+
246
+ self._pending_num_added_iterations[added_iters_key] += 1
247
+
248
+ def _update_parents(self, parent: app.WorkflowLoop):
249
+ self._pending_parents.append(parent.name)
250
+
251
+ if not self._pending_num_added_iterations:
252
+ self._pending_num_added_iterations = copy.deepcopy(self._num_added_iterations)
253
+
254
+ self._pending_num_added_iterations = {
255
+ tuple(list(k) + [0]): v for k, v in self._pending_num_added_iterations.items()
256
+ }
257
+
258
+ self.workflow._store.update_loop_parents(
259
+ index=self.index,
260
+ num_added_iters=self.num_added_iterations,
261
+ parents=self.parents,
262
+ )
263
+
205
264
  def _reset_pending_num_added_iters(self):
206
- self._pending_num_added_iterations = 0
265
+ self._pending_num_added_iterations = None
207
266
 
208
267
  def _accept_pending_num_added_iters(self):
209
- self._num_added_iterations = self.num_added_iterations
210
- self._reset_pending_num_added_iters()
268
+ if self._pending_num_added_iterations:
269
+ self._num_added_iterations = copy.deepcopy(self._pending_num_added_iterations)
270
+ self._reset_pending_num_added_iters()
271
+
272
+ def _reset_pending_parents(self):
273
+ self._pending_parents = []
274
+
275
+ def _accept_pending_parents(self):
276
+ self._parents += self._pending_parents
277
+ self._reset_pending_parents()
211
278
 
212
279
  @property
213
280
  def index(self):
@@ -234,6 +301,10 @@ class WorkflowLoop:
234
301
  def template(self):
235
302
  return self._template
236
303
 
304
+ @property
305
+ def parents(self) -> List[str]:
306
+ return self._parents + self._pending_parents
307
+
237
308
  @property
238
309
  def name(self):
239
310
  return self.template.name
@@ -247,8 +318,14 @@ class WorkflowLoop:
247
318
  return self.template.num_iterations
248
319
 
249
320
  @property
250
- def num_added_iterations(self):
251
- return self._num_added_iterations + self._pending_num_added_iterations
321
+ def downstream_tasks(self) -> List[app.WorkflowLoop]:
322
+ """Return tasks that are not part of the loop, and downstream from this loop."""
323
+ return self.workflow.tasks[self.task_objects[-1].index + 1 :]
324
+
325
+ @property
326
+ def upstream_tasks(self) -> List[app.WorkflowLoop]:
327
+ """Return tasks that are not part of the loop, and upstream from this loop."""
328
+ return self.workflow.tasks[: self.task_objects[0].index]
252
329
 
253
330
  @staticmethod
254
331
  def _find_iterable_parameters(loop_template: app.Loop):
@@ -263,8 +340,6 @@ class WorkflowLoop:
263
340
  all_outputs_idx[typ] = []
264
341
  all_outputs_idx[typ].append(task.insert_ID)
265
342
 
266
- all_inputs_first_idx, all_outputs_idx
267
-
268
343
  iterable_params = {}
269
344
  for typ, first_idx in all_inputs_first_idx.items():
270
345
  if typ in all_outputs_idx and first_idx <= all_outputs_idx[typ][0]:
@@ -280,38 +355,60 @@ class WorkflowLoop:
280
355
  return iterable_params
281
356
 
282
357
  @classmethod
283
- def new_empty_loop(cls, index: int, workflow: app.Workflow, template: app.Loop):
358
+ def new_empty_loop(
359
+ cls,
360
+ index: int,
361
+ workflow: app.Workflow,
362
+ template: app.Loop,
363
+ iterations: List[app.ElementIteration],
364
+ ) -> Tuple[app.WorkflowLoop, List[Dict[str, int]]]:
365
+ parent_loops = cls._get_parent_loops(index, workflow, template)
366
+ parent_names = [i.name for i in parent_loops]
367
+ num_added_iters = {}
368
+ for iter_i in iterations:
369
+ num_added_iters[tuple([iter_i.loop_idx[j] for j in parent_names])] = 1
370
+
284
371
  obj = cls(
285
372
  index=index,
286
373
  workflow=workflow,
287
374
  template=template,
288
- num_added_iterations=1,
375
+ num_added_iterations=num_added_iters,
289
376
  iterable_parameters=cls._find_iterable_parameters(template),
377
+ parents=parent_names,
290
378
  )
291
379
  return obj
292
380
 
293
- def get_parent_loops(self) -> List[app.WorkflowLoop]:
294
- """Get loops whose task subset is a superset of this loop's task subset. If two
295
- loops have identical task subsets, the first loop in the workflow loop index is
296
- considered the parent."""
381
+ @classmethod
382
+ def _get_parent_loops(
383
+ cls,
384
+ index: int,
385
+ workflow: app.Workflow,
386
+ template: app.Loop,
387
+ ) -> List[app.WorkflowLoop]:
297
388
  parents = []
298
389
  passed_self = False
299
- self_tasks = set(self.task_insert_IDs)
300
- for loop_i in self.workflow.loops:
301
- if loop_i.index == self.index:
390
+ self_tasks = set(template.task_insert_IDs)
391
+ for loop_i in workflow.loops:
392
+ if loop_i.index == index:
302
393
  passed_self = True
303
394
  continue
304
395
  other_tasks = set(loop_i.task_insert_IDs)
305
396
  if self_tasks.issubset(other_tasks):
306
- if (self_tasks == other_tasks) and passed_self:
397
+ if (self_tasks == other_tasks) and not passed_self:
307
398
  continue
308
399
  parents.append(loop_i)
309
400
  return parents
310
401
 
402
+ def get_parent_loops(self) -> List[app.WorkflowLoop]:
403
+ """Get loops whose task subset is a superset of this loop's task subset. If two
404
+ loops have identical task subsets, the first loop in the workflow loop list is
405
+ considered the child."""
406
+ return self._get_parent_loops(self.index, self.workflow, self.template)
407
+
311
408
  def get_child_loops(self) -> List[app.WorkflowLoop]:
312
409
  """Get loops whose task subset is a subset of this loop's task subset. If two
313
- loops have identical task subsets, the first loop in the workflow loop index is
314
- considered the parent."""
410
+ loops have identical task subsets, the first loop in the workflow loop list is
411
+ considered the child."""
315
412
  children = []
316
413
  passed_self = False
317
414
  self_tasks = set(self.task_insert_IDs)
@@ -321,23 +418,24 @@ class WorkflowLoop:
321
418
  continue
322
419
  other_tasks = set(loop_i.task_insert_IDs)
323
420
  if self_tasks.issuperset(other_tasks):
324
- if (self_tasks == other_tasks) and not passed_self:
421
+ if (self_tasks == other_tasks) and passed_self:
325
422
  continue
326
423
  children.append(loop_i)
424
+
425
+ # order by depth, so direct child is first:
426
+ children = sorted(children, key=lambda x: len(next(iter(x.num_added_iterations))))
327
427
  return children
328
428
 
329
429
  def add_iteration(self, parent_loop_indices=None):
330
- parent_loop_indices = parent_loop_indices or {}
331
- cur_loop_idx = self.num_added_iterations - 1
332
430
  parent_loops = self.get_parent_loops()
333
431
  child_loops = self.get_child_loops()
432
+ child_loop_names = [i.name for i in child_loops]
433
+ parent_loop_indices = parent_loop_indices or {}
434
+ if parent_loops and not parent_loop_indices:
435
+ parent_loop_indices = {i.name: 0 for i in parent_loops}
334
436
 
335
- for parent_loop in parent_loops:
336
- if parent_loop.name not in parent_loop_indices:
337
- raise ValueError(
338
- f"Parent loop {parent_loop.name!r} must be specified in "
339
- f"`parent_loop_indices`."
340
- )
437
+ iters_key = tuple([parent_loop_indices[k] for k in self.parents])
438
+ cur_loop_idx = self.num_added_iterations[iters_key] - 1
341
439
  all_new_data_idx = {} # keys are (task.insert_ID and element.index)
342
440
 
343
441
  for task in self.task_objects:
@@ -346,6 +444,16 @@ class WorkflowLoop:
346
444
  element = task.elements[elem_idx]
347
445
  inp_statuses = task.template.get_input_statuses(element.element_set)
348
446
  new_data_idx = {}
447
+ existing_inners = []
448
+ for iter_i in element.iterations:
449
+ if iter_i.loop_idx[self.name] == cur_loop_idx:
450
+ existing_inner_i = {
451
+ k: v
452
+ for k, v in iter_i.loop_idx.items()
453
+ if k in child_loop_names
454
+ }
455
+ if existing_inner_i:
456
+ existing_inners.append(existing_inner_i)
349
457
 
350
458
  # copy resources from zeroth iteration:
351
459
  for key, val in element.iterations[0].get_data_idx().items():
@@ -367,17 +475,29 @@ class WorkflowLoop:
367
475
  # parametrised:
368
476
  if task.insert_ID == iter_dat["output_tasks"][-1]:
369
477
  src_elem = element
478
+ grouped_elems = None
370
479
  else:
371
480
  src_elems = element.get_dependent_elements_recursively(
372
481
  task_insert_ID=iter_dat["output_tasks"][-1]
373
482
  )
374
- if len(src_elems) > 1:
483
+ # consider groups
484
+ inp_group_name = inp.single_labelled_data.get("group")
485
+ grouped_elems = []
486
+ for i in src_elems:
487
+ i_in_group = any(
488
+ j.name == inp_group_name for j in i.element_set.groups
489
+ )
490
+ if i_in_group:
491
+ grouped_elems.append(i)
492
+
493
+ if not grouped_elems and len(src_elems) > 1:
375
494
  raise NotImplementedError(
376
495
  f"Multiple elements found in the iterable parameter {inp!r}'s"
377
496
  f" latest output task (insert ID: "
378
497
  f"{iter_dat['output_tasks'][-1]}) that can be used to "
379
- f"parametrise the next iteration."
498
+ f"parametrise the next iteration: {src_elems!r}."
380
499
  )
500
+
381
501
  elif not src_elems:
382
502
  # TODO: maybe OK?
383
503
  raise NotImplementedError(
@@ -386,14 +506,26 @@ class WorkflowLoop:
386
506
  f"{iter_dat['output_tasks'][-1]}) that can be used to "
387
507
  f"parametrise the next iteration."
388
508
  )
389
- src_elem = src_elems[0]
390
509
 
391
- child_loop_max_iters = {
392
- i.name: i.num_added_iterations - 1 for i in child_loops
393
- }
510
+ else:
511
+ src_elem = src_elems[0]
512
+
513
+ child_loop_max_iters = {}
394
514
  parent_loop_same_iters = {
395
515
  i.name: parent_loop_indices[i.name] for i in parent_loops
396
516
  }
517
+ child_iter_parents = {
518
+ **parent_loop_same_iters,
519
+ self.name: cur_loop_idx,
520
+ }
521
+ for i in child_loops:
522
+ i_num_iters = i.num_added_iterations[
523
+ tuple(child_iter_parents[j] for j in i.parents)
524
+ ]
525
+ i_max = i_num_iters - 1
526
+ child_iter_parents[i.name] = i_max
527
+ child_loop_max_iters[i.name] = i_max
528
+
397
529
  source_iter_loop_idx = {
398
530
  **child_loop_max_iters,
399
531
  **parent_loop_same_iters,
@@ -403,12 +535,32 @@ class WorkflowLoop:
403
535
  # identify the ElementIteration from which this input should be
404
536
  # parametrised:
405
537
  source_iter = None
406
- for iter_i in src_elem.iterations:
407
- if iter_i.loop_idx == source_iter_loop_idx:
408
- source_iter = iter_i
409
- break
538
+ if grouped_elems:
539
+ source_iter = []
540
+ for src_elem in grouped_elems:
541
+ for iter_i in src_elem.iterations:
542
+ if iter_i.loop_idx == source_iter_loop_idx:
543
+ source_iter.append(iter_i)
544
+ break
545
+ else:
546
+ for iter_i in src_elem.iterations:
547
+ if iter_i.loop_idx == source_iter_loop_idx:
548
+ source_iter = iter_i
549
+ break
550
+
551
+ if not source_iter:
552
+ raise RuntimeError(
553
+ f"Could not find a source iteration with loop_idx: "
554
+ f"{source_iter_loop_idx!r}."
555
+ )
410
556
 
411
- inp_dat_idx = source_iter.get_data_idx()[f"outputs.{inp.typ}"]
557
+ if grouped_elems:
558
+ inp_dat_idx = [
559
+ i.get_data_idx()[f"outputs.{inp.typ}"]
560
+ for i in source_iter
561
+ ]
562
+ else:
563
+ inp_dat_idx = source_iter.get_data_idx()[f"outputs.{inp.typ}"]
412
564
  new_data_idx[f"inputs.{inp.typ}"] = inp_dat_idx
413
565
 
414
566
  else:
@@ -467,11 +619,16 @@ class WorkflowLoop:
467
619
  task_insert_ID=task.insert_ID
468
620
  )
469
621
  )
622
+ # filter src_elems_i for matching element IDs:
623
+ src_elems_i = [
624
+ i for i in src_elems_i if i.id_ == element.id_
625
+ ]
470
626
  if (
471
627
  len(src_elems_i) == 1
472
628
  and src_elems_i[0].id_ == element.id_
473
629
  ):
474
630
  new_sources.append((tiID, e_idx))
631
+
475
632
  if is_group:
476
633
  inp_dat_idx = [
477
634
  all_new_data_idx[i][prev_dat_idx_key]
@@ -515,21 +672,50 @@ class WorkflowLoop:
515
672
  i for i in new_data_idx.keys() if len(i.split(".")) == 2
516
673
  )
517
674
  all_new_data_idx[(task.insert_ID, element.index)] = new_data_idx
675
+
676
+ new_loop_idx = {
677
+ **parent_loop_indices,
678
+ self.name: cur_loop_idx + 1,
679
+ **{
680
+ child.name: 0
681
+ for child in child_loops
682
+ if task.insert_ID in child.task_insert_IDs
683
+ },
684
+ }
685
+ # increment num_added_iterations on child loop for this parent loop index:
686
+ for i in child_loops:
687
+ added_iters_key_chd = tuple([new_loop_idx[j] for j in i.parents])
688
+ i._initialise_pending_added_iters(added_iters_key_chd)
689
+
518
690
  iter_ID_i = self.workflow._store.add_element_iteration(
519
691
  element_ID=element.id_,
520
692
  data_idx=new_data_idx,
521
693
  schema_parameters=list(schema_params),
522
- loop_idx={**parent_loop_indices, self.name: cur_loop_idx + 1},
694
+ loop_idx=new_loop_idx,
523
695
  )
524
696
 
525
697
  task.initialise_EARs()
526
698
 
527
- self._pending_num_added_iterations += 1
699
+ added_iters_key = tuple(parent_loop_indices[k] for k in self.parents)
700
+ self._increment_pending_added_iters(added_iters_key)
528
701
  self.workflow._store.update_loop_num_iters(
529
702
  index=self.index,
530
- num_iters=self.num_added_iterations,
703
+ num_added_iters=self.num_added_iterations,
531
704
  )
532
705
 
706
+ # add iterations to fixed-number-iteration children only:
707
+ for child in child_loops[::-1]:
708
+ if child.num_iterations is not None:
709
+ for _ in range(child.num_iterations - 1):
710
+ par_idx = {k: 0 for k in child.parents}
711
+ child.add_iteration(
712
+ parent_loop_indices={
713
+ **par_idx,
714
+ **parent_loop_indices,
715
+ self.name: cur_loop_idx + 1,
716
+ }
717
+ )
718
+
533
719
  def test_termination(self, element_iter):
534
720
  """Check if a loop should terminate, given the specified completed element
535
721
  iteration."""
@@ -19,6 +19,7 @@ from .errors import (
19
19
  InapplicableInputSourceElementIters,
20
20
  MalformedNestingOrderPath,
21
21
  MayNeedObjectError,
22
+ MissingElementGroup,
22
23
  MissingInputs,
23
24
  NoAvailableElementSetsError,
24
25
  NoCoincidentInputSources,
@@ -1612,6 +1613,13 @@ class WorkflowTask:
1612
1613
 
1613
1614
  # TODO: this only goes to one level of dependency
1614
1615
 
1616
+ if not group_dat_idx:
1617
+ raise MissingElementGroup(
1618
+ f"Adding elements to task {self.unique_name!r}: no "
1619
+ f"element group named {inp_group_name!r} found for input "
1620
+ f"{labelled_path_i!r}."
1621
+ )
1622
+
1615
1623
  grp_idx = [group_dat_idx] # TODO: generalise to multiple groups
1616
1624
 
1617
1625
  if self.app.InputSource.local() in sources_i:
@@ -43,7 +43,6 @@ def make_schemas(ins_outs, ret_list=False):
43
43
  output_file_parsers=out_file_parsers,
44
44
  environments=[hf.ActionEnvironment("env_1")],
45
45
  )
46
- print(f"{ins_i=}")
47
46
  out.append(
48
47
  hf.TaskSchema(
49
48
  objective=obj,
@@ -97,12 +96,14 @@ def make_tasks(
97
96
  local_resources=None,
98
97
  nesting_orders=None,
99
98
  input_sources=None,
99
+ groups=None,
100
100
  ):
101
101
  local_inputs = local_inputs or {}
102
102
  local_sequences = local_sequences or {}
103
103
  local_resources = local_resources or {}
104
104
  nesting_orders = nesting_orders or {}
105
105
  input_sources = input_sources or {}
106
+ groups = groups or {}
106
107
  schemas = make_schemas(schemas_spec, ret_list=True)
107
108
  tasks = []
108
109
  for s_idx, s in enumerate(schemas):
@@ -126,6 +127,7 @@ def make_tasks(
126
127
  resources=res,
127
128
  nesting_order=nesting_orders.get(s_idx, {}),
128
129
  input_sources=input_sources.get(s_idx, None),
130
+ groups=groups.get(s_idx),
129
131
  )
130
132
  tasks.append(task)
131
133
  return tasks
@@ -140,6 +142,8 @@ def make_workflow(
140
142
  nesting_orders=None,
141
143
  input_sources=None,
142
144
  resources=None,
145
+ loops=None,
146
+ groups=None,
143
147
  name="w1",
144
148
  overwrite=False,
145
149
  store="zarr",
@@ -151,9 +155,17 @@ def make_workflow(
151
155
  local_resources=local_resources,
152
156
  nesting_orders=nesting_orders,
153
157
  input_sources=input_sources,
158
+ groups=groups,
154
159
  )
160
+ template = {
161
+ "name": name,
162
+ "tasks": tasks,
163
+ "resources": resources,
164
+ }
165
+ if loops:
166
+ template["loops"] = loops
155
167
  wk = hf.Workflow.from_template(
156
- hf.WorkflowTemplate(name=name, tasks=tasks, resources=resources),
168
+ hf.WorkflowTemplate(**template),
157
169
  path=path,
158
170
  name=name,
159
171
  overwrite=overwrite,