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.
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/PKG-INFO +1 -1
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/pyproject.toml +1 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/tui_subscriber.py +80 -17
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/python_script.py +25 -6
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/substitution.py +17 -0
- horus_runtime-0.2.2/src/horus_builtin/workflow/branch.py +444 -0
- horus_runtime-0.2.2/src/horus_builtin/workflow/condition.py +341 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/map.py +77 -5
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/scheduler.py +62 -6
- horus_runtime-0.2.2/src/horus_builtin/workflow/subworkflow/__init__.py +43 -0
- horus_runtime-0.2.2/src/horus_builtin/workflow/subworkflow/errors.py +24 -0
- horus_runtime-0.2.2/src/horus_builtin/workflow/subworkflow/expander.py +594 -0
- horus_runtime-0.2.2/src/horus_builtin/workflow/subworkflow/lowering.py +70 -0
- horus_runtime-0.2.2/src/horus_builtin/workflow/subworkflow/ports.py +102 -0
- horus_runtime-0.2.2/src/horus_runtime/cli.py +283 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/executor/base.py +11 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/task/base.py +10 -1
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/task/status.py +25 -1
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/workflow/base.py +154 -11
- horus_runtime-0.2.2/src/horus_runtime/core/workflow/condition.py +239 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/workflow/edge.py +37 -2
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/workflow/exceptions.py +9 -2
- horus_runtime-0.2.2/src/horus_runtime/locale/es/LC_MESSAGES/horus_runtime.mo +0 -0
- horus_runtime-0.2.2/src/horus_runtime/locale/es/LC_MESSAGES/horus_runtime.po +621 -0
- horus_runtime-0.2.2/src/horus_runtime/locale/messages.pot +528 -0
- horus_runtime-0.2.2/src/horus_runtime/packaging.py +164 -0
- horus_runtime-0.2.2/src/horus_runtime/sanitize.py +333 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/event/test_tui_subscriber.py +32 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_python_script.py +52 -0
- horus_runtime-0.2.2/tests/unit/test_packaging.py +206 -0
- horus_runtime-0.2.2/tests/unit/test_sanitize.py +456 -0
- horus_runtime-0.2.2/tests/unit/workflow/test_branch_router.py +479 -0
- horus_runtime-0.2.2/tests/unit/workflow/test_conditions.py +669 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_edges.py +29 -18
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_map.py +145 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_scheduler.py +44 -0
- horus_runtime-0.2.2/tests/unit/workflow/test_subworkflow.py +868 -0
- horus_runtime-0.2.0/src/horus_runtime/cli.py +0 -143
- horus_runtime-0.2.0/src/horus_runtime/locale/es/LC_MESSAGES/horus_runtime.mo +0 -0
- horus_runtime-0.2.0/src/horus_runtime/locale/es/LC_MESSAGES/horus_runtime.po +0 -59
- horus_runtime-0.2.0/src/horus_runtime/locale/messages.pot +0 -50
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/.gitignore +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/LICENSE +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/artifact/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/artifact/file.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/artifact/folder.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/artifact/json.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/artifact/pickle.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/artifact_event.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/log_subscriber.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/task_event.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/event/workflow_event.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/executor/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/executor/_cwd_lock.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/executor/python_exec.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/executor/python_fn.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/executor/shell.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/cli.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/common/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/common/confirm.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/common/dropdown.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/common/file.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/interaction/common/string.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/middleware/task_log_file.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/middleware/task_time.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/middleware/workflow_time.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/py.typed +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/command.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/python.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/runtime/python_string.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/target/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/target/local.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/task/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/task/function.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/task/horus_task.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/transfer/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/transfer/local_noop.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/tui.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/dag.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/horus_workflow.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_builtin/workflow/loop.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/context.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/artifact/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/artifact/base.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/artifact/exceptions.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/artifact/store.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/executor/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/executor/exceptions.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/interaction/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/interaction/base.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/interaction/exceptions.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/interaction/renderer.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/interaction/transport.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/placement.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/resources.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/runtime/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/runtime/base.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/runtime/events.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/target/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/target/base.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/target/channel.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/target/exceptions.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/task/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/task/exceptions.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/transfer/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/transfer/exceptions.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/transfer/generic.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/transfer/strategy.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/workflow/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/core/workflow/status.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/async_loop.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/base.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/bus.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/subscriber.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/event/transport.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/i18n.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/logging.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/auto_middleware.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/executor.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/interaction.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/runtime.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/target.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/target_command.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/task.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/transfer.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/middleware/workflow.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/py.typed +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/registry/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/registry/auto_registry.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/registry/auto_registry_product.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/registry/exceptions.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/settings.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/src/horus_runtime/version.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/__init__.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/conftest.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/integration/test_runtime_integration.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/artifact/test_artifact_base.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/artifact/test_builtin_artifact.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/artifact/test_store.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/boot/test_boot.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/cli/test_cli.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/cli/test_run.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/core/test_placement.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/core/test_resources.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/event/common.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/event/test_builtin_log_subscriber.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/event/test_event_system.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/executor/test_builtin_executor.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/executor/test_collect_side_artifacts.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/executor/test_executor_base.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/i18n/test_i18n.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/interaction/test_builtin_interactions.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/interaction/test_interaction.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/middleware/test_middleware.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/middleware/test_target_command.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/middleware/test_task_log_file.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/registry/test_auto_registry.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/registry/test_schema_generation.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_anchor_local_paths.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_builtin_runtime.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_python_string.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_runtime_base.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/runtime/test_substitution.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/target/test_channel.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/target/test_detach.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/target/test_local_target.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/target/test_target_base.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/task/conftest.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/task/test_builtin_function_task.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/task/test_builtin_task.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/task/test_task_base.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/test_logging.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/test_main.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/transfer/test_builtin_transfer.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/transfer/test_transfer.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_builtin_workflow.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_dynamic_mutation.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_loops.py +0 -0
- {horus_runtime-0.2.0 → horus_runtime-0.2.2}/tests/unit/workflow/test_run_layout.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
-
#
|
|
242
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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=
|
|
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.
|
|
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
|
-
"""
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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`` ``$``/``${}``
|