horus-runtime 0.2.0__tar.gz → 0.2.2__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 (189) hide show
  1. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/PKG-INFO +1 -1
  2. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/pyproject.toml +1 -0
  3. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/tui_subscriber.py +80 -17
  4. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/python_script.py +25 -6
  5. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/substitution.py +17 -0
  6. horus_runtime-0.2.2/src/horus_builtin/workflow/branch.py +444 -0
  7. horus_runtime-0.2.2/src/horus_builtin/workflow/condition.py +341 -0
  8. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/map.py +77 -5
  9. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/scheduler.py +62 -6
  10. horus_runtime-0.2.2/src/horus_builtin/workflow/subworkflow/__init__.py +43 -0
  11. horus_runtime-0.2.2/src/horus_builtin/workflow/subworkflow/errors.py +24 -0
  12. horus_runtime-0.2.2/src/horus_builtin/workflow/subworkflow/expander.py +594 -0
  13. horus_runtime-0.2.2/src/horus_builtin/workflow/subworkflow/lowering.py +70 -0
  14. horus_runtime-0.2.2/src/horus_builtin/workflow/subworkflow/ports.py +102 -0
  15. horus_runtime-0.2.2/src/horus_runtime/cli.py +283 -0
  16. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/executor/base.py +11 -0
  17. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/task/base.py +10 -1
  18. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/task/status.py +25 -1
  19. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/workflow/base.py +154 -11
  20. horus_runtime-0.2.2/src/horus_runtime/core/workflow/condition.py +239 -0
  21. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/workflow/edge.py +37 -2
  22. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/workflow/exceptions.py +9 -2
  23. horus_runtime-0.2.2/src/horus_runtime/locale/es/LC_MESSAGES/horus_runtime.mo +0 -0
  24. horus_runtime-0.2.2/src/horus_runtime/locale/es/LC_MESSAGES/horus_runtime.po +621 -0
  25. horus_runtime-0.2.2/src/horus_runtime/locale/messages.pot +528 -0
  26. horus_runtime-0.2.2/src/horus_runtime/packaging.py +164 -0
  27. horus_runtime-0.2.2/src/horus_runtime/sanitize.py +333 -0
  28. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/event/test_tui_subscriber.py +32 -0
  29. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_python_script.py +52 -0
  30. horus_runtime-0.2.2/tests/unit/test_packaging.py +206 -0
  31. horus_runtime-0.2.2/tests/unit/test_sanitize.py +456 -0
  32. horus_runtime-0.2.2/tests/unit/workflow/test_branch_router.py +479 -0
  33. horus_runtime-0.2.2/tests/unit/workflow/test_conditions.py +669 -0
  34. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_edges.py +29 -18
  35. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_map.py +145 -0
  36. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_scheduler.py +44 -0
  37. horus_runtime-0.2.2/tests/unit/workflow/test_subworkflow.py +868 -0
  38. horus_runtime-0.2.0/src/horus_runtime/cli.py +0 -143
  39. horus_runtime-0.2.0/src/horus_runtime/locale/es/LC_MESSAGES/horus_runtime.mo +0 -0
  40. horus_runtime-0.2.0/src/horus_runtime/locale/es/LC_MESSAGES/horus_runtime.po +0 -59
  41. horus_runtime-0.2.0/src/horus_runtime/locale/messages.pot +0 -50
  42. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/.gitignore +0 -0
  43. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/LICENSE +0 -0
  44. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/__init__.py +0 -0
  45. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/artifact/__init__.py +0 -0
  46. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/artifact/file.py +0 -0
  47. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/artifact/folder.py +0 -0
  48. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/artifact/json.py +0 -0
  49. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/artifact/pickle.py +0 -0
  50. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/__init__.py +0 -0
  51. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/artifact_event.py +0 -0
  52. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/log_subscriber.py +0 -0
  53. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/task_event.py +0 -0
  54. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/workflow_event.py +0 -0
  55. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/executor/__init__.py +0 -0
  56. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/executor/_cwd_lock.py +0 -0
  57. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/executor/python_exec.py +0 -0
  58. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/executor/python_fn.py +0 -0
  59. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/executor/shell.py +0 -0
  60. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/__init__.py +0 -0
  61. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/cli.py +0 -0
  62. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/common/__init__.py +0 -0
  63. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/common/confirm.py +0 -0
  64. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/common/dropdown.py +0 -0
  65. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/common/file.py +0 -0
  66. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/common/string.py +0 -0
  67. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/middleware/task_log_file.py +0 -0
  68. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/middleware/task_time.py +0 -0
  69. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/middleware/workflow_time.py +0 -0
  70. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/py.typed +0 -0
  71. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/__init__.py +0 -0
  72. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/command.py +0 -0
  73. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/python.py +0 -0
  74. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/python_string.py +0 -0
  75. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/target/__init__.py +0 -0
  76. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/target/local.py +0 -0
  77. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/task/__init__.py +0 -0
  78. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/task/function.py +0 -0
  79. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/task/horus_task.py +0 -0
  80. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/transfer/__init__.py +0 -0
  81. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/transfer/local_noop.py +0 -0
  82. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/tui.py +0 -0
  83. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/__init__.py +0 -0
  84. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/dag.py +0 -0
  85. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/horus_workflow.py +0 -0
  86. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/loop.py +0 -0
  87. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/__init__.py +0 -0
  88. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/context.py +0 -0
  89. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/__init__.py +0 -0
  90. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/artifact/__init__.py +0 -0
  91. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/artifact/base.py +0 -0
  92. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/artifact/exceptions.py +0 -0
  93. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/artifact/store.py +0 -0
  94. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/executor/__init__.py +0 -0
  95. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/executor/exceptions.py +0 -0
  96. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/interaction/__init__.py +0 -0
  97. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/interaction/base.py +0 -0
  98. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/interaction/exceptions.py +0 -0
  99. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/interaction/renderer.py +0 -0
  100. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/interaction/transport.py +0 -0
  101. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/placement.py +0 -0
  102. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/resources.py +0 -0
  103. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/runtime/__init__.py +0 -0
  104. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/runtime/base.py +0 -0
  105. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/runtime/events.py +0 -0
  106. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/target/__init__.py +0 -0
  107. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/target/base.py +0 -0
  108. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/target/channel.py +0 -0
  109. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/target/exceptions.py +0 -0
  110. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/task/__init__.py +0 -0
  111. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/task/exceptions.py +0 -0
  112. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/transfer/__init__.py +0 -0
  113. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/transfer/exceptions.py +0 -0
  114. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/transfer/generic.py +0 -0
  115. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/transfer/strategy.py +0 -0
  116. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/workflow/__init__.py +0 -0
  117. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/workflow/status.py +0 -0
  118. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/__init__.py +0 -0
  119. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/async_loop.py +0 -0
  120. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/base.py +0 -0
  121. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/bus.py +0 -0
  122. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/subscriber.py +0 -0
  123. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/transport.py +0 -0
  124. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/i18n.py +0 -0
  125. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/logging.py +0 -0
  126. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/__init__.py +0 -0
  127. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/auto_middleware.py +0 -0
  128. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/executor.py +0 -0
  129. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/interaction.py +0 -0
  130. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/runtime.py +0 -0
  131. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/target.py +0 -0
  132. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/target_command.py +0 -0
  133. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/task.py +0 -0
  134. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/transfer.py +0 -0
  135. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/workflow.py +0 -0
  136. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/py.typed +0 -0
  137. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/registry/__init__.py +0 -0
  138. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/registry/auto_registry.py +0 -0
  139. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/registry/auto_registry_product.py +0 -0
  140. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/registry/exceptions.py +0 -0
  141. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/settings.py +0 -0
  142. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/version.py +0 -0
  143. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/__init__.py +0 -0
  144. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/conftest.py +0 -0
  145. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/integration/test_runtime_integration.py +0 -0
  146. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/artifact/test_artifact_base.py +0 -0
  147. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/artifact/test_builtin_artifact.py +0 -0
  148. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/artifact/test_store.py +0 -0
  149. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/boot/test_boot.py +0 -0
  150. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/cli/test_cli.py +0 -0
  151. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/cli/test_run.py +0 -0
  152. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/core/test_placement.py +0 -0
  153. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/core/test_resources.py +0 -0
  154. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/event/common.py +0 -0
  155. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/event/test_builtin_log_subscriber.py +0 -0
  156. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/event/test_event_system.py +0 -0
  157. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/executor/test_builtin_executor.py +0 -0
  158. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/executor/test_collect_side_artifacts.py +0 -0
  159. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/executor/test_executor_base.py +0 -0
  160. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/i18n/test_i18n.py +0 -0
  161. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/interaction/test_builtin_interactions.py +0 -0
  162. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/interaction/test_interaction.py +0 -0
  163. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/middleware/test_middleware.py +0 -0
  164. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/middleware/test_target_command.py +0 -0
  165. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/middleware/test_task_log_file.py +0 -0
  166. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/registry/test_auto_registry.py +0 -0
  167. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/registry/test_schema_generation.py +0 -0
  168. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_anchor_local_paths.py +0 -0
  169. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_builtin_runtime.py +0 -0
  170. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_python_string.py +0 -0
  171. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_runtime_base.py +0 -0
  172. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_substitution.py +0 -0
  173. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/target/test_channel.py +0 -0
  174. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/target/test_detach.py +0 -0
  175. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/target/test_local_target.py +0 -0
  176. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/target/test_target_base.py +0 -0
  177. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/task/conftest.py +0 -0
  178. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/task/test_builtin_function_task.py +0 -0
  179. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/task/test_builtin_task.py +0 -0
  180. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/task/test_task_base.py +0 -0
  181. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/test_logging.py +0 -0
  182. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/test_main.py +0 -0
  183. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/transfer/test_builtin_transfer.py +0 -0
  184. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/transfer/test_transfer.py +0 -0
  185. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_builtin_workflow.py +0 -0
  186. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_dynamic_mutation.py +0 -0
  187. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_loops.py +0 -0
  188. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_run_layout.py +0 -0
  189. {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: horus-runtime
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: The Horus workflow manager runtime. Execute advanced workflows with multi-environment support, HPC integration, automatic artifact management, and more.
5
5
  License-File: LICENSE
6
6
  Classifier: License :: OSI Approved :: GNU Affero General Public License v3
@@ -65,6 +65,7 @@ local = "horus_builtin.target.local"
65
65
 
66
66
  [project.entry-points."horus.task"]
67
67
  horus_task = "horus_builtin.task.horus_task"
68
+ subworkflow = "horus_builtin.workflow.subworkflow.expander"
68
69
 
69
70
  [project.entry-points."horus.transfer"]
70
71
  local_to_local = "horus_builtin.transfer.local_noop"
@@ -139,6 +139,10 @@ _SECONDS_PER_MINUTE = 60
139
139
  _LOG_LINES = 8
140
140
  _TRANSFER_LINGER_S = 2.0
141
141
 
142
+ # Live refresh rate. The spinner advances at this same rate so its animation
143
+ # can't beat against the frame rate.
144
+ _REFRESH_HZ = 8
145
+
142
146
 
143
147
  class _LogLine(NamedTuple):
144
148
  """One rendered entry in the log/event pane."""
@@ -161,7 +165,8 @@ def _make_console() -> Console:
161
165
 
162
166
  def _spinner_frame() -> str:
163
167
  """Pick a spinner glyph from the wall clock (animates via Live refresh)."""
164
- return _SPINNER_FRAMES[int(time.monotonic() * 10) % len(_SPINNER_FRAMES)]
168
+ tick = int(time.monotonic() * _REFRESH_HZ)
169
+ return _SPINNER_FRAMES[tick % len(_SPINNER_FRAMES)]
165
170
 
166
171
 
167
172
  def _fmt_duration(seconds: float | None) -> str:
@@ -238,8 +243,9 @@ class WorkflowTUISubscriber(BaseEventSubscriber):
238
243
  _live: Live | None = PrivateAttr(default=None)
239
244
  _workflow: BaseWorkflow | None = PrivateAttr(default=None)
240
245
 
241
- # Execution scope (planned task ids) for an honest progress total.
242
- _scope: set[str] = PrivateAttr(default_factory=set)
246
+ # Task the run was triggered from; the execution scope behind the
247
+ # progress total is re-planned from it on every frame (see _scope_ids).
248
+ _trigger: str | None = PrivateAttr(default=None)
243
249
  _started_at: float | None = PrivateAttr(default=None)
244
250
 
245
251
  # Per-task timing, derived from status transitions / completion events.
@@ -263,23 +269,44 @@ class WorkflowTUISubscriber(BaseEventSubscriber):
263
269
  Render *workflow*'s tasks before the run starts.
264
270
  """
265
271
  self._workflow = workflow
272
+ self._trigger = trigger_id or (
273
+ workflow.tasks[0].id if workflow.tasks else None
274
+ )
275
+
276
+ def _scope_ids(self, workflow: BaseWorkflow) -> set[str]:
277
+ """
278
+ Ids in the run's execution scope, re-planned from *workflow*'s
279
+ current tasks and edges.
280
+
281
+ A ``map:`` or ``sub:`` expander adds its clones, and the edges that
282
+ reach the gather task, only when it runs (see
283
+ :meth:`~horus_runtime.core.workflow.base.BaseWorkflow.expand`), so a
284
+ plan frozen before the run undercounts every fan-out: a five-clone
285
+ loop map reported ``1/1 tasks``. Re-planning per frame counts the
286
+ DAG as it actually is.
287
+ """
288
+ if self._trigger is None:
289
+ return {t.id for t in workflow.tasks}
266
290
  try:
267
- target = trigger_id or (
268
- workflow.tasks[0].id if workflow.tasks else None
269
- )
270
- if target is not None:
271
- self._scope = set(
272
- execution_plan(
273
- workflow.tasks, trigger_id=target, edges=workflow.edges
274
- )
291
+ return set(
292
+ execution_plan(
293
+ workflow.tasks,
294
+ trigger_id=self._trigger,
295
+ edges=workflow.edges,
275
296
  )
297
+ )
276
298
  except Exception:
277
- self._scope = {t.id for t in workflow.tasks}
299
+ return {t.id for t in workflow.tasks}
278
300
 
279
301
  @contextmanager
280
302
  def live(self) -> Iterator[None]:
281
303
  """Drive the Rich ``Live`` dashboard for the ``with`` body.
282
304
 
305
+ Runs on the terminal's alternate screen (``screen=True``) so Rich
306
+ diffs frames into a dedicated buffer instead of erasing and repainting
307
+ scrollback lines, which is what made the display flicker as the
308
+ dashboard's height changed between frames.
309
+
283
310
  Redirects loguru's terminal sink into the log pane for the duration so
284
311
  log lines never corrupt the display, restoring it on exit.
285
312
  """
@@ -290,8 +317,9 @@ class WorkflowTUISubscriber(BaseEventSubscriber):
290
317
  with Live(
291
318
  view,
292
319
  console=self._console,
293
- refresh_per_second=8,
320
+ refresh_per_second=_REFRESH_HZ,
294
321
  transient=False,
322
+ screen=True,
295
323
  ) as live:
296
324
  self._live = live
297
325
  try:
@@ -301,11 +329,12 @@ class WorkflowTUISubscriber(BaseEventSubscriber):
301
329
  raise
302
330
  finally:
303
331
  self._live = None
304
- # Final repaint so terminating statuses (and any error
305
- # panel) are shown before Live tears down.
306
- live.update(view)
307
332
  finally:
308
333
  horus_logger.restore_terminal()
334
+ # The alternate screen is gone by now, so leave a receipt of the
335
+ # final state behind in normal scrollback. Runs on the failure
336
+ # path too, so a crash still reports what happened.
337
+ self._console.print(self._render_summary())
309
338
 
310
339
  def handle(self, event: BaseEvent) -> None:
311
340
  """React to bus events: pause for interactions, feed the log pane."""
@@ -458,7 +487,7 @@ class WorkflowTUISubscriber(BaseEventSubscriber):
458
487
  return Panel(line, border_style=style, padding=(0, 1))
459
488
 
460
489
  def _render_progress(self, workflow: BaseWorkflow) -> RenderableType:
461
- scope = self._scope or {t.id for t in workflow.tasks}
490
+ scope = self._scope_ids(workflow)
462
491
  total = len(scope)
463
492
  tasks = [t for t in workflow.tasks if t.id in scope]
464
493
  done = sum(1 for t in tasks if t.status in _TERMINAL)
@@ -564,6 +593,40 @@ class WorkflowTUISubscriber(BaseEventSubscriber):
564
593
  body, title=_("Failed"), border_style="red", padding=(0, 1)
565
594
  )
566
595
 
596
+ def _render_summary(self) -> RenderableType:
597
+ """One-line receipt of the final state, for normal scrollback.
598
+
599
+ The dashboard lives on the alternate screen and disappears when Live
600
+ tears down, so this is all the user is left with after a run.
601
+ """
602
+ workflow = self._workflow
603
+ if workflow is None:
604
+ return Text(_("No workflow ran."), style="dim")
605
+
606
+ scope = self._scope_ids(workflow)
607
+ tasks = [t for t in workflow.tasks if t.id in scope]
608
+ done = sum(1 for t in tasks if t.status in _TERMINAL)
609
+ elapsed = (
610
+ None
611
+ if self._started_at is None
612
+ else time.monotonic() - self._started_at
613
+ )
614
+ line = Text.assemble(
615
+ (workflow.name, "bold"),
616
+ (" · ", "dim"),
617
+ (
618
+ workflow.status.value.upper(),
619
+ _WF_STATUS_STYLE.get(workflow.status, "white"),
620
+ ),
621
+ (" · ", "dim"),
622
+ (f"{done}/{len(tasks)} " + _("tasks"), "dim"),
623
+ (" · ", "dim"),
624
+ (_fmt_duration(elapsed), "dim"),
625
+ )
626
+ if self._error is None:
627
+ return line
628
+ return Group(line, self._render_error())
629
+
567
630
 
568
631
  def render_workflow(workflow: BaseWorkflow, trigger_id: str) -> None:
569
632
  """
@@ -25,6 +25,9 @@ from pathlib import Path
25
25
  from typing import TYPE_CHECKING, ClassVar
26
26
 
27
27
  from horus_builtin.runtime.command import CommandRuntime
28
+ from horus_builtin.runtime.substitution import (
29
+ is_template as _is_template,
30
+ )
28
31
  from horus_builtin.runtime.substitution import substitute
29
32
  from horus_runtime.context import HorusContext
30
33
  from horus_runtime.core.runtime.events import RuntimeEvent
@@ -67,16 +70,32 @@ class PythonScriptRuntime(CommandRuntime):
67
70
  command: str = ""
68
71
 
69
72
  def anchor_local_paths(self, base: Path) -> None:
70
- """Resolve ``script`` against ``base`` if it is a relative path."""
73
+ """
74
+ Resolve ``script`` against ``base`` if it is a relative path.
75
+
76
+ A templated script (see :meth:`_setup_runtime`) names an artifact
77
+ rather than a file on this machine, so it is left untouched.
78
+ """
79
+ if _is_template(self.script):
80
+ return
71
81
  if not self.script.is_absolute():
72
82
  self.script = (base / self.script).resolve()
73
83
 
74
84
  async def _setup_runtime(self, task: "BaseTask") -> str:
75
- remote_path = f"{task.working_dir}/{self.script.name}"
76
-
77
- # Placing the file also creates task.working_dir on the target, which
78
- # the executor uses as cwd and where the script's outputs land.
79
- await task.target.put_file(self.script, remote_path)
85
+ if _is_template(self.script):
86
+ # ``script: ${my_script}`` names an input artifact instead of a
87
+ # file on the orchestrator. The transfer layer has already placed
88
+ # it on the target, so resolve its on-target path and skip the
89
+ # upload -- the orchestrator may not have the file at all (this is
90
+ # how tc-os runs an imported workflow).
91
+ remote_path = substitute(str(self.script), task)
92
+ await task.target.mkdir(task.working_dir)
93
+ else:
94
+ remote_path = f"{task.working_dir}/{self.script.name}"
95
+
96
+ # Placing the file also creates task.working_dir on the target,
97
+ # which the executor uses as cwd and where the outputs land.
98
+ await task.target.put_file(self.script, remote_path)
80
99
 
81
100
  args = substitute(self.args, task) if self.args else ""
82
101
  cmd = f"{self.python} {shlex.quote(remote_path)} {args}".rstrip()
@@ -42,6 +42,8 @@ from typing import TYPE_CHECKING
42
42
  from horus_runtime.i18n import tr as _
43
43
 
44
44
  if TYPE_CHECKING:
45
+ from pathlib import PurePath
46
+
45
47
  from horus_runtime.core.artifact.base import BaseArtifact
46
48
  from horus_runtime.core.target.base import BaseTarget
47
49
  from horus_runtime.core.task.base import BaseTask
@@ -122,6 +124,21 @@ class _Resolver(Mapping[str, str]):
122
124
  return len(self._roots)
123
125
 
124
126
 
127
+ def is_template(value: "str | PurePath") -> bool:
128
+ """
129
+ True if *value* carries a ``$`` placeholder and so must be rendered by
130
+ :func:`substitute` against a task rather than used verbatim.
131
+
132
+ Fields that normally hold a path on the orchestrator (a script, a conda
133
+ environment file) accept ``${artifact_id}`` instead, naming an input
134
+ artifact that the transfer layer has already placed on the target. That
135
+ lets a workflow run on a machine that never had the original file.
136
+
137
+ ``$$`` is the escape for a literal ``$`` and does not make a template.
138
+ """
139
+ return "$" in str(value).replace("$$", "")
140
+
141
+
125
142
  def substitute(template: str, task: "BaseTask") -> str:
126
143
  """
127
144
  Render *template* against *task* using ``string.Template`` ``$``/``${}``