hpcflow-new2 0.2.0a162__tar.gz → 0.2.0a164__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.
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/PKG-INFO +1 -1
- hpcflow_new2-0.2.0a164/hpcflow/_version.py +1 -0
- hpcflow_new2-0.2.0a164/hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/app.py +29 -42
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/cli.py +1 -1
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/actions.py +87 -21
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/command_files.py +6 -4
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/commands.py +21 -2
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/element.py +39 -8
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/errors.py +16 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/object_list.py +26 -14
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/parameters.py +21 -3
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/task.py +111 -4
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/task_schema.py +17 -2
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/test_utils.py +5 -2
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/workflow.py +93 -5
- hpcflow_new2-0.2.0a164/hpcflow/sdk/data/workflow_spec_schema.yaml +20 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/demo/cli.py +1 -1
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/persistence/base.py +6 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/persistence/zarr.py +2 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/submission.py +21 -10
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/scripts/test_main_scripts.py +60 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_action.py +186 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_element.py +27 -25
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_element_set.py +32 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_parameter.py +11 -9
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_persistence.py +4 -1
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_resources.py +7 -9
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_schema_input.py +8 -8
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_task.py +26 -27
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_task_schema.py +39 -8
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_value_sequence.py +5 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_workflow.py +4 -9
- hpcflow_new2-0.2.0a164/hpcflow/tests/unit/test_workflow_template.py +156 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/pyproject.toml +2 -2
- hpcflow_new2-0.2.0a162/hpcflow/_version.py +0 -1
- hpcflow_new2-0.2.0a162/hpcflow/sdk/data/workflow_spec_schema.yaml +0 -64
- hpcflow_new2-0.2.0a162/hpcflow/tests/unit/test_workflow_template.py +0 -35
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/README.md +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/__pyinstaller/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/__pyinstaller/hook-hpcflow.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/app.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/cli.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/demo_data_manifest/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/demo_data_manifest/demo_data_manifest.json +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/demo_task_1_parse_p3.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/generate_t1_file_01.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_json_in_json_out.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_json_in_obj.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_json_out_obj.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/scripts/parse_t1_file_01.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/template_components/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/template_components/command_files.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/template_components/environments.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/template_components/parameters.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/template_components/task_schemas.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/workflows/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/data/workflows/workflow_1.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/examples.ipynb +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/cli_common.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/config/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/config/callbacks.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/config/cli.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/config/config.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/config/config_file.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/config/errors.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/environment.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/json_like.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/loop.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/parallel.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/rule.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/run_dir_files.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/utils.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/validation.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/core/zarr_io.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/data/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/data/config_file_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/data/config_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/data/environments_spec_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/data/files_spec_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/data/parameters_spec_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/data/task_schema_spec_schema.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/demo/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/helper/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/helper/cli.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/helper/helper.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/helper/watcher.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/log.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/persistence/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/persistence/json.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/persistence/pending.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/persistence/store_resource.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/persistence/utils.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/runtime.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/jobscript.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/jobscript_info.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/schedulers/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/schedulers/direct.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/schedulers/sge.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/schedulers/slurm.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/schedulers/utils.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/shells/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/shells/base.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/shells/bash.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/shells/os_version.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/submission/shells/powershell.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/sdk/typing.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/conftest.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/data/__init__.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/data/benchmark_N_elements.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/data/workflow_1.json +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/data/workflow_1.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/data/workflow_1_slurm.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/data/workflow_1_wsl.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/data/workflow_test_run_abort.yaml +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/schedulers/slurm/test_slurm_submission.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/shells/wsl/test_wsl_submission.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_action_rule.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_app.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_cli.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_command.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_config.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_config_file.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_element_iteration.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_input_source.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_input_value.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_json_like.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_loop.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_object_list.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_run.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_runtime.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_shell.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_slurm.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_submission.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/unit/test_utils.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/workflows/test_jobscript.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/tests/workflows/test_workflows.py +0 -0
- {hpcflow_new2-0.2.0a162 → hpcflow_new2-0.2.0a164}/hpcflow/viz_demo.ipynb +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "0.2.0a164"
|
@@ -729,49 +729,28 @@ class BaseApp(metaclass=Singleton):
|
|
729
729
|
|
730
730
|
@TimeIt.decorator
|
731
731
|
def _load_scripts(self):
|
732
|
-
from setuptools import find_packages
|
733
732
|
|
734
733
|
# TODO: load custom directories / custom functions (via decorator)
|
734
|
+
scripts_package = f"{self.package_name}.{self.scripts_dir}"
|
735
735
|
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
)
|
742
|
-
packages = [root_scripts_dir] + [root_scripts_dir + "." + i for i in packages]
|
743
|
-
packages = [self.package_name + "." + i for i in packages]
|
744
|
-
num_root_dirs = len(root_scripts_dir.split(".")) + 1
|
736
|
+
try:
|
737
|
+
ctx = resources.as_file(resources.files(scripts_package))
|
738
|
+
except AttributeError:
|
739
|
+
# < python 3.9; `resource.path` deprecated since 3.11
|
740
|
+
ctx = resources.path(scripts_package, "")
|
745
741
|
|
746
742
|
scripts = {}
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
contents = resources.contents(pkg)
|
759
|
-
_is_rsrc = lambda pkg, name: resources.is_resource(pkg, name)
|
760
|
-
|
761
|
-
script_names = (
|
762
|
-
name for name in contents if name != "__init__.py" and _is_rsrc(pkg, name)
|
763
|
-
)
|
764
|
-
|
765
|
-
for i in script_names:
|
766
|
-
script_key = "/".join(pkg.split(".")[num_root_dirs:] + [i])
|
767
|
-
try:
|
768
|
-
script_ctx = resources.as_file(resources.files(pkg).joinpath(i))
|
769
|
-
except AttributeError:
|
770
|
-
# < python 3.9; `resource.path` deprecated since 3.11
|
771
|
-
script_ctx = resources.path(pkg, i)
|
772
|
-
|
773
|
-
with script_ctx as script:
|
774
|
-
scripts[script_key] = script
|
743
|
+
with ctx as path:
|
744
|
+
for dirpath, _, filenames in os.walk(path):
|
745
|
+
dirpath = Path(dirpath)
|
746
|
+
if dirpath.name == "__pycache__":
|
747
|
+
continue
|
748
|
+
for filename in filenames:
|
749
|
+
if filename == "__init__.py":
|
750
|
+
continue
|
751
|
+
val = dirpath.joinpath(filename)
|
752
|
+
key = str(val.relative_to(path).as_posix())
|
753
|
+
scripts[key] = Path(val)
|
775
754
|
|
776
755
|
return scripts
|
777
756
|
|
@@ -1380,15 +1359,19 @@ class BaseApp(metaclass=Singleton):
|
|
1380
1359
|
variables=variables,
|
1381
1360
|
status=status,
|
1382
1361
|
)
|
1383
|
-
|
1362
|
+
submitted_js = wk.submit(
|
1384
1363
|
JS_parallelism=JS_parallelism,
|
1385
1364
|
wait=wait,
|
1386
1365
|
add_to_known=add_to_known,
|
1387
|
-
return_idx=
|
1366
|
+
return_idx=True,
|
1388
1367
|
tasks=tasks,
|
1389
1368
|
cancel=cancel,
|
1390
1369
|
status=status,
|
1391
1370
|
)
|
1371
|
+
if return_idx:
|
1372
|
+
return (wk, submitted_js)
|
1373
|
+
else:
|
1374
|
+
return wk
|
1392
1375
|
|
1393
1376
|
def _make_demo_workflow(
|
1394
1377
|
self,
|
@@ -1553,15 +1536,19 @@ class BaseApp(metaclass=Singleton):
|
|
1553
1536
|
store_kwargs=store_kwargs,
|
1554
1537
|
variables=variables,
|
1555
1538
|
)
|
1556
|
-
|
1539
|
+
submitted_js = wk.submit(
|
1557
1540
|
JS_parallelism=JS_parallelism,
|
1558
1541
|
wait=wait,
|
1559
1542
|
add_to_known=add_to_known,
|
1560
|
-
return_idx=
|
1543
|
+
return_idx=True,
|
1561
1544
|
tasks=tasks,
|
1562
1545
|
cancel=cancel,
|
1563
1546
|
status=status,
|
1564
1547
|
)
|
1548
|
+
if return_idx:
|
1549
|
+
return (wk, submitted_js)
|
1550
|
+
else:
|
1551
|
+
return wk
|
1565
1552
|
|
1566
1553
|
def _submit_workflow(
|
1567
1554
|
self,
|
@@ -173,7 +173,7 @@ def _make_API_CLI(app):
|
|
173
173
|
status=status,
|
174
174
|
)
|
175
175
|
if print_idx:
|
176
|
-
click.echo(out)
|
176
|
+
click.echo(out[1])
|
177
177
|
|
178
178
|
@click.command(context_settings={"ignore_unknown_options": True})
|
179
179
|
@click.argument("py_test_args", nargs=-1, type=click.UNPROCESSED)
|
@@ -16,6 +16,7 @@ from watchdog.utils.dirsnapshot import DirectorySnapshotDiff
|
|
16
16
|
from hpcflow.sdk import app
|
17
17
|
from hpcflow.sdk.core import ABORT_EXIT_CODE
|
18
18
|
from hpcflow.sdk.core.errors import (
|
19
|
+
ActionEnvironmentMissingNameError,
|
19
20
|
MissingCompatibleActionEnvironment,
|
20
21
|
OutputFileParserNoOutputError,
|
21
22
|
UnknownScriptDataKey,
|
@@ -140,6 +141,7 @@ class ElementActionRun:
|
|
140
141
|
element_action,
|
141
142
|
index: int,
|
142
143
|
data_idx: Dict,
|
144
|
+
commands_idx: List[int],
|
143
145
|
start_time: Union[datetime, None],
|
144
146
|
end_time: Union[datetime, None],
|
145
147
|
snapshot_start: Union[Dict, None],
|
@@ -156,6 +158,7 @@ class ElementActionRun:
|
|
156
158
|
self._element_action = element_action
|
157
159
|
self._index = index # local index of this run with the action
|
158
160
|
self._data_idx = data_idx
|
161
|
+
self._commands_idx = commands_idx
|
159
162
|
self._start_time = start_time
|
160
163
|
self._end_time = end_time
|
161
164
|
self._submission_idx = submission_idx
|
@@ -221,6 +224,10 @@ class ElementActionRun:
|
|
221
224
|
def data_idx(self):
|
222
225
|
return self._data_idx
|
223
226
|
|
227
|
+
@property
|
228
|
+
def commands_idx(self):
|
229
|
+
return self._commands_idx
|
230
|
+
|
224
231
|
@property
|
225
232
|
def metadata(self):
|
226
233
|
return self._metadata
|
@@ -458,14 +465,18 @@ class ElementActionRun:
|
|
458
465
|
self._output_files = self.app.ElementOutputFiles(element_action_run=self)
|
459
466
|
return self._output_files
|
460
467
|
|
468
|
+
@property
|
469
|
+
def env_spec(self) -> Dict[str, Any]:
|
470
|
+
return self.resources.environments[self.action.get_environment_name()]
|
471
|
+
|
461
472
|
@TimeIt.decorator
|
462
473
|
def get_resources(self):
|
463
474
|
"""Resolve specific resources for this EAR, considering all applicable scopes and
|
464
475
|
template-level resources."""
|
465
476
|
return self.element_iteration.get_resources(self.action)
|
466
477
|
|
467
|
-
def
|
468
|
-
return self.action.
|
478
|
+
def get_environment_spec(self) -> str:
|
479
|
+
return self.action.get_environment_spec()
|
469
480
|
|
470
481
|
def get_environment(self) -> app.Environment:
|
471
482
|
return self.action.get_environment()
|
@@ -542,6 +553,9 @@ class ElementActionRun:
|
|
542
553
|
else:
|
543
554
|
out[key] = val_i
|
544
555
|
|
556
|
+
if self.action.script_pass_env_spec:
|
557
|
+
out["env_spec"] = self.env_spec
|
558
|
+
|
545
559
|
return out
|
546
560
|
|
547
561
|
def get_input_values_direct(self, label_dict: bool = True):
|
@@ -562,6 +576,10 @@ class ElementActionRun:
|
|
562
576
|
typ = i.path[len("inputs.") :]
|
563
577
|
if typ in input_types:
|
564
578
|
inputs[typ] = i.value
|
579
|
+
|
580
|
+
if self.action.script_pass_env_spec:
|
581
|
+
inputs["env_spec"] = self.env_spec
|
582
|
+
|
565
583
|
return inputs
|
566
584
|
|
567
585
|
def get_OFP_output_files(self) -> Dict[str, Union[str, List[str]]]:
|
@@ -585,6 +603,10 @@ class ElementActionRun:
|
|
585
603
|
inputs = {}
|
586
604
|
for inp_typ in self.action.output_file_parsers[0].inputs or []:
|
587
605
|
inputs[inp_typ] = self.get(f"inputs.{inp_typ}")
|
606
|
+
|
607
|
+
if self.action.script_pass_env_spec:
|
608
|
+
inputs["env_spec"] = self.env_spec
|
609
|
+
|
588
610
|
return inputs
|
589
611
|
|
590
612
|
def get_OFP_outputs(self) -> Dict[str, Union[str, List[str]]]:
|
@@ -626,7 +648,7 @@ class ElementActionRun:
|
|
626
648
|
|
627
649
|
# write the script if it is specified as a app data script, otherwise we assume
|
628
650
|
# the script already exists in the working directory:
|
629
|
-
snip_path = self.action.get_snippet_script_path(self.action.script)
|
651
|
+
snip_path = self.action.get_snippet_script_path(self.action.script, self.env_spec)
|
630
652
|
if snip_path:
|
631
653
|
script_name = snip_path.name
|
632
654
|
source_str = self.action.compose_source(snip_path)
|
@@ -677,32 +699,35 @@ class ElementActionRun:
|
|
677
699
|
"stdout"/"stderr").
|
678
700
|
"""
|
679
701
|
self.app.persistence_logger.debug("EAR.compose_commands")
|
702
|
+
env_spec = self.env_spec
|
703
|
+
|
680
704
|
for ifg in self.action.input_file_generators:
|
681
705
|
# TODO: there should only be one at this stage if expanded?
|
682
|
-
ifg.write_source(self.action)
|
706
|
+
ifg.write_source(self.action, env_spec)
|
683
707
|
|
684
708
|
for ofp in self.action.output_file_parsers:
|
685
709
|
# TODO: there should only be one at this stage if expanded?
|
686
710
|
if ofp.output is None:
|
687
711
|
raise OutputFileParserNoOutputError()
|
688
|
-
ofp.write_source(self.action)
|
712
|
+
ofp.write_source(self.action, env_spec)
|
689
713
|
|
690
714
|
if self.action.script:
|
691
715
|
self.write_source(js_idx=jobscript.index, js_act_idx=JS_action_idx)
|
692
716
|
|
693
717
|
command_lns = []
|
694
|
-
|
695
|
-
env = jobscript.submission.environments.get(env_label)
|
718
|
+
env = jobscript.submission.environments.get(**env_spec)
|
696
719
|
if env.setup:
|
697
720
|
command_lns += list(env.setup)
|
698
721
|
|
699
722
|
shell_vars = {} # keys are cmd_idx, each value is a list of tuples
|
700
723
|
for cmd_idx, command in enumerate(self.action.commands):
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
724
|
+
if cmd_idx in self.commands_idx:
|
725
|
+
# only execute commands that have no rules, or all valid rules:
|
726
|
+
cmd_str, shell_vars_i = command.get_command_line(
|
727
|
+
EAR=self, shell=jobscript.shell, env=env
|
728
|
+
)
|
729
|
+
shell_vars[cmd_idx] = shell_vars_i
|
730
|
+
command_lns.append(cmd_str)
|
706
731
|
|
707
732
|
commands = "\n".join(command_lns) + "\n"
|
708
733
|
|
@@ -953,13 +978,24 @@ class ActionEnvironment(JSONLike):
|
|
953
978
|
),
|
954
979
|
)
|
955
980
|
|
956
|
-
environment: str
|
981
|
+
environment: Union[str, Dict[str, Any]]
|
957
982
|
scope: Optional[app.ActionScope] = None
|
958
983
|
|
959
984
|
def __post_init__(self):
|
960
985
|
if self.scope is None:
|
961
986
|
self.scope = self.app.ActionScope.any()
|
962
987
|
|
988
|
+
orig_env = copy.deepcopy(self.environment)
|
989
|
+
if isinstance(self.environment, str):
|
990
|
+
self.environment = {"name": self.environment}
|
991
|
+
|
992
|
+
if "name" not in self.environment:
|
993
|
+
raise ActionEnvironmentMissingNameError(
|
994
|
+
f"The action-environment environment specification must include a string "
|
995
|
+
f"`name` key, or be specified as string that is that name. Provided "
|
996
|
+
f"environment key was {orig_env!r}."
|
997
|
+
)
|
998
|
+
|
963
999
|
|
964
1000
|
class ActionRule(JSONLike):
|
965
1001
|
"""Class to represent a rule/condition that must be True if an action is to be
|
@@ -997,6 +1033,7 @@ class ActionRule(JSONLike):
|
|
997
1033
|
|
998
1034
|
self.rule = rule
|
999
1035
|
self.action = None # assigned by parent action
|
1036
|
+
self.command = None # assigned by parent command
|
1000
1037
|
|
1001
1038
|
def __eq__(self, other):
|
1002
1039
|
if type(other) is not self.__class__:
|
@@ -1089,6 +1126,7 @@ class Action(JSONLike):
|
|
1089
1126
|
script_data_out: Optional[str] = None,
|
1090
1127
|
script_data_files_use_opt: Optional[bool] = False,
|
1091
1128
|
script_exe: Optional[str] = None,
|
1129
|
+
script_pass_env_spec: Optional[bool] = False,
|
1092
1130
|
abortable: Optional[bool] = False,
|
1093
1131
|
input_file_generators: Optional[List[app.InputFileGenerator]] = None,
|
1094
1132
|
output_file_parsers: Optional[List[app.OutputFileParser]] = None,
|
@@ -1117,6 +1155,7 @@ class Action(JSONLike):
|
|
1117
1155
|
script_data_files_use_opt if not self.script_is_python else True
|
1118
1156
|
)
|
1119
1157
|
self.script_exe = script_exe.lower() if script_exe else None
|
1158
|
+
self.script_pass_env_spec = script_pass_env_spec
|
1120
1159
|
self.environments = environments or [
|
1121
1160
|
self.app.ActionEnvironment(environment="null_env")
|
1122
1161
|
]
|
@@ -1413,7 +1452,10 @@ class Action(JSONLike):
|
|
1413
1452
|
commands=self.commands,
|
1414
1453
|
)
|
1415
1454
|
|
1416
|
-
def
|
1455
|
+
def get_environment_name(self) -> str:
|
1456
|
+
return self.get_environment_spec()["name"]
|
1457
|
+
|
1458
|
+
def get_environment_spec(self) -> Dict[str, Any]:
|
1417
1459
|
if not self._from_expand:
|
1418
1460
|
raise RuntimeError(
|
1419
1461
|
f"Cannot choose a single environment from this action because it is not "
|
@@ -1422,7 +1464,7 @@ class Action(JSONLike):
|
|
1422
1464
|
return self.environments[0].environment
|
1423
1465
|
|
1424
1466
|
def get_environment(self) -> app.Environment:
|
1425
|
-
return self.app.envs.get(self.
|
1467
|
+
return self.app.envs.get(**self.get_environment_spec())
|
1426
1468
|
|
1427
1469
|
@staticmethod
|
1428
1470
|
def is_snippet_script(script: str) -> bool:
|
@@ -1442,7 +1484,9 @@ class Action(JSONLike):
|
|
1442
1484
|
return script
|
1443
1485
|
|
1444
1486
|
@classmethod
|
1445
|
-
def get_snippet_script_str(
|
1487
|
+
def get_snippet_script_str(
|
1488
|
+
cls, script, env_spec: Optional[Dict[str, Any]] = None
|
1489
|
+
) -> str:
|
1446
1490
|
if not cls.is_snippet_script(script):
|
1447
1491
|
raise ValueError(
|
1448
1492
|
f"Must be an app-data script name (e.g. "
|
@@ -1450,14 +1494,24 @@ class Action(JSONLike):
|
|
1450
1494
|
)
|
1451
1495
|
pattern = r"\<\<script:(.*:?)\>\>"
|
1452
1496
|
match_obj = re.match(pattern, script)
|
1453
|
-
|
1497
|
+
out = match_obj.group(1)
|
1498
|
+
|
1499
|
+
if env_spec:
|
1500
|
+
out = re.sub(
|
1501
|
+
pattern=r"\<\<env:(.*?)\>\>",
|
1502
|
+
repl=lambda match_obj: env_spec[match_obj.group(1)],
|
1503
|
+
string=out,
|
1504
|
+
)
|
1505
|
+
return out
|
1454
1506
|
|
1455
1507
|
@classmethod
|
1456
|
-
def get_snippet_script_path(
|
1508
|
+
def get_snippet_script_path(
|
1509
|
+
cls, script_path, env_spec: Optional[Dict[str, Any]] = None
|
1510
|
+
) -> Path:
|
1457
1511
|
if not cls.is_snippet_script(script_path):
|
1458
1512
|
return False
|
1459
1513
|
|
1460
|
-
path = cls.get_snippet_script_str(script_path)
|
1514
|
+
path = cls.get_snippet_script_str(script_path, env_spec)
|
1461
1515
|
if path in cls.app.scripts:
|
1462
1516
|
path = cls.app.scripts.get(path)
|
1463
1517
|
|
@@ -1527,7 +1581,9 @@ class Action(JSONLike):
|
|
1527
1581
|
input_file_generators=[ifg],
|
1528
1582
|
environments=[self.get_input_file_generator_action_env(ifg)],
|
1529
1583
|
rules=main_rules + ifg.get_action_rules(),
|
1584
|
+
script_pass_env_spec=ifg.script_pass_env_spec,
|
1530
1585
|
abortable=ifg.abortable,
|
1586
|
+
# TODO: add script_data_in etc? and to OFP?
|
1531
1587
|
)
|
1532
1588
|
act_i._task_schema = self.task_schema
|
1533
1589
|
if ifg.input_file not in inp_files:
|
@@ -1558,6 +1614,7 @@ class Action(JSONLike):
|
|
1558
1614
|
output_file_parsers=[ofp],
|
1559
1615
|
environments=[self.get_output_file_parser_action_env(ofp)],
|
1560
1616
|
rules=list(self.rules) + ofp.get_action_rules(),
|
1617
|
+
script_pass_env_spec=ofp.script_pass_env_spec,
|
1561
1618
|
abortable=ofp.abortable,
|
1562
1619
|
)
|
1563
1620
|
act_i._task_schema = self.task_schema
|
@@ -1616,6 +1673,7 @@ class Action(JSONLike):
|
|
1616
1673
|
script_data_in=self.script_data_in,
|
1617
1674
|
script_data_out=self.script_data_out,
|
1618
1675
|
script_exe=self.script_exe,
|
1676
|
+
script_pass_env_spec=self.script_pass_env_spec,
|
1619
1677
|
environments=[self.get_commands_action_env()],
|
1620
1678
|
abortable=self.abortable,
|
1621
1679
|
rules=main_rules,
|
@@ -1909,9 +1967,17 @@ class Action(JSONLike):
|
|
1909
1967
|
return True
|
1910
1968
|
|
1911
1969
|
@TimeIt.decorator
|
1912
|
-
def test_rules(self, element_iter) -> List[
|
1970
|
+
def test_rules(self, element_iter) -> Tuple[bool, List[int]]:
|
1913
1971
|
"""Test all rules against the specified element iteration."""
|
1914
|
-
|
1972
|
+
rules_valid = [rule.test(element_iteration=element_iter) for rule in self.rules]
|
1973
|
+
action_valid = all(rules_valid)
|
1974
|
+
commands_idx = []
|
1975
|
+
if action_valid:
|
1976
|
+
for cmd_idx, cmd in enumerate(self.commands):
|
1977
|
+
if any(not i.test(element_iteration=element_iter) for i in cmd.rules):
|
1978
|
+
continue
|
1979
|
+
commands_idx.append(cmd_idx)
|
1980
|
+
return action_valid, commands_idx
|
1915
1981
|
|
1916
1982
|
def get_required_executables(self) -> Tuple[str]:
|
1917
1983
|
"""Return executable labels required by this action."""
|
@@ -131,6 +131,7 @@ class InputFileGenerator(JSONLike):
|
|
131
131
|
inputs: List[app.Parameter]
|
132
132
|
script: str = None
|
133
133
|
environment: app.Environment = None
|
134
|
+
script_pass_env_spec: Optional[bool] = False
|
134
135
|
abortable: Optional[bool] = False
|
135
136
|
rules: Optional[List[app.ActionRule]] = None
|
136
137
|
|
@@ -188,11 +189,11 @@ class InputFileGenerator(JSONLike):
|
|
188
189
|
out = out.format(script_str=script_str, main_block=main_block)
|
189
190
|
return out
|
190
191
|
|
191
|
-
def write_source(self, action):
|
192
|
+
def write_source(self, action, env_spec: Dict[str, Any]):
|
192
193
|
|
193
194
|
# write the script if it is specified as a snippet script, otherwise we assume
|
194
195
|
# the script already exists in the working directory:
|
195
|
-
snip_path = action.get_snippet_script_path(self.script)
|
196
|
+
snip_path = action.get_snippet_script_path(self.script, env_spec)
|
196
197
|
if snip_path:
|
197
198
|
source_str = self.compose_source(snip_path)
|
198
199
|
with Path(snip_path.name).open("wt", newline="\n") as fp:
|
@@ -255,6 +256,7 @@ class OutputFileParser(JSONLike):
|
|
255
256
|
inputs: List[str] = None
|
256
257
|
outputs: List[str] = None
|
257
258
|
options: Dict = None
|
259
|
+
script_pass_env_spec: Optional[bool] = False
|
258
260
|
abortable: Optional[bool] = False
|
259
261
|
save_files: Union[List[str], bool] = True
|
260
262
|
clean_up: Optional[List[str]] = None
|
@@ -342,14 +344,14 @@ class OutputFileParser(JSONLike):
|
|
342
344
|
out = out.format(script_str=script_str, main_block=main_block)
|
343
345
|
return out
|
344
346
|
|
345
|
-
def write_source(self, action):
|
347
|
+
def write_source(self, action, env_spec: Dict[str, Any]):
|
346
348
|
if self.output is None:
|
347
349
|
# might be used just for saving files:
|
348
350
|
return
|
349
351
|
|
350
352
|
# write the script if it is specified as a snippet script, otherwise we assume
|
351
353
|
# the script already exists in the working directory:
|
352
|
-
snip_path = action.get_snippet_script_path(self.script)
|
354
|
+
snip_path = action.get_snippet_script_path(self.script, env_spec)
|
353
355
|
if snip_path:
|
354
356
|
source_str = self.compose_source(snip_path)
|
355
357
|
with Path(snip_path.name).open("wt", newline="\n") as fp:
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from dataclasses import dataclass
|
1
|
+
from dataclasses import dataclass, field
|
2
2
|
from functools import partial
|
3
3
|
from pathlib import Path
|
4
4
|
import re
|
@@ -6,15 +6,24 @@ from typing import Dict, Iterable, List, Optional, Tuple, Union
|
|
6
6
|
|
7
7
|
import numpy as np
|
8
8
|
|
9
|
+
from hpcflow.sdk import app
|
9
10
|
from hpcflow.sdk.core.element import ElementResources
|
10
11
|
from hpcflow.sdk.core.errors import NoCLIFormatMethodError
|
11
|
-
from hpcflow.sdk.core.json_like import JSONLike
|
12
|
+
from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
|
12
13
|
from hpcflow.sdk.core.parameters import ParameterValue
|
13
14
|
|
14
15
|
|
15
16
|
@dataclass
|
16
17
|
class Command(JSONLike):
|
17
18
|
_app_attr = "app"
|
19
|
+
_child_objects = (
|
20
|
+
ChildObjectSpec(
|
21
|
+
name="rules",
|
22
|
+
class_name="ActionRule",
|
23
|
+
is_multiple=True,
|
24
|
+
parent_ref="command",
|
25
|
+
),
|
26
|
+
)
|
18
27
|
|
19
28
|
command: Optional[str] = None
|
20
29
|
executable: Optional[str] = None
|
@@ -23,6 +32,7 @@ class Command(JSONLike):
|
|
23
32
|
stdout: Optional[str] = None
|
24
33
|
stderr: Optional[str] = None
|
25
34
|
stdin: Optional[str] = None
|
35
|
+
rules: Optional[List[app.ActionRule]] = field(default_factory=lambda: [])
|
26
36
|
|
27
37
|
def __repr__(self) -> str:
|
28
38
|
out = []
|
@@ -40,6 +50,8 @@ class Command(JSONLike):
|
|
40
50
|
out.append(f"stderr={self.stderr!r}")
|
41
51
|
if self.stdin:
|
42
52
|
out.append(f"stdin={self.stdin!r}")
|
53
|
+
if self.rules:
|
54
|
+
out.append(f"rules={self.rules!r}")
|
43
55
|
|
44
56
|
return f"{self.__class__.__name__}({', '.join(out)})"
|
45
57
|
|
@@ -107,6 +119,7 @@ class Command(JSONLike):
|
|
107
119
|
|
108
120
|
file_regex = r"(\<\<file:{}\>\>?)"
|
109
121
|
exe_script_regex = r"\<\<(executable|script):(.*?)\>\>"
|
122
|
+
env_specs_regex = r"\<\<env:(.*?)\>\>"
|
110
123
|
|
111
124
|
# substitute executables:
|
112
125
|
cmd_str = re.sub(
|
@@ -118,6 +131,12 @@ class Command(JSONLike):
|
|
118
131
|
# executable command might itself contain variables defined in `variables`, and/or
|
119
132
|
# an `<<args>>` variable::
|
120
133
|
for var_key, var_val in (self.variables or {}).items():
|
134
|
+
# substitute any `<<env:>>` specifiers
|
135
|
+
var_val = re.sub(
|
136
|
+
pattern=env_specs_regex,
|
137
|
+
repl=lambda match_obj: EAR.env_spec[match_obj.group(1)],
|
138
|
+
string=var_val,
|
139
|
+
)
|
121
140
|
cmd_str = cmd_str.replace(f"<<{var_key}>>", var_val)
|
122
141
|
if "<<args>>" in cmd_str:
|
123
142
|
args_str = " ".join(self.arguments or [])
|
@@ -199,6 +199,7 @@ class ElementResources(JSONLike):
|
|
199
199
|
scheduler_args: Optional[Dict] = None
|
200
200
|
shell_args: Optional[Dict] = None
|
201
201
|
os_name: Optional[str] = None
|
202
|
+
environments: Optional[Dict] = None
|
202
203
|
|
203
204
|
# SGE scheduler specific:
|
204
205
|
SGE_parallel_env: str = None
|
@@ -241,16 +242,24 @@ class ElementResources(JSONLike):
|
|
241
242
|
return hash(tuple((keys, vals)))
|
242
243
|
|
243
244
|
exclude = ("time_limit",)
|
244
|
-
sub_dicts = ("scheduler_args", "shell_args")
|
245
245
|
dct = {k: copy.deepcopy(v) for k, v in self.__dict__.items() if k not in exclude}
|
246
|
-
if "options" in dct.get("scheduler_args", []):
|
247
|
-
dct["scheduler_args"]["options"] = _hash_dict(
|
248
|
-
dct["scheduler_args"]["options"]
|
249
|
-
)
|
250
246
|
|
251
|
-
|
252
|
-
|
253
|
-
|
247
|
+
scheduler_args = dct["scheduler_args"]
|
248
|
+
shell_args = dct["shell_args"]
|
249
|
+
envs = dct["environments"]
|
250
|
+
|
251
|
+
if isinstance(scheduler_args, dict):
|
252
|
+
if "options" in scheduler_args:
|
253
|
+
dct["scheduler_args"]["options"] = _hash_dict(scheduler_args["options"])
|
254
|
+
dct["scheduler_args"] = _hash_dict(dct["scheduler_args"])
|
255
|
+
|
256
|
+
if isinstance(shell_args, dict):
|
257
|
+
dct["shell_args"] = _hash_dict(shell_args)
|
258
|
+
|
259
|
+
if isinstance(envs, dict):
|
260
|
+
for k, v in envs.items():
|
261
|
+
dct["environments"][k] = _hash_dict(v)
|
262
|
+
dct["environments"] = _hash_dict(dct["environments"])
|
254
263
|
|
255
264
|
return _hash_dict(dct)
|
256
265
|
|
@@ -878,6 +887,28 @@ class ElementIteration:
|
|
878
887
|
# an EAR?" which would then allow us to test a resources-based action rule.
|
879
888
|
|
880
889
|
resource_specs = copy.deepcopy(self.get("resources"))
|
890
|
+
|
891
|
+
env_spec = action.get_environment_spec()
|
892
|
+
env_name = env_spec["name"]
|
893
|
+
|
894
|
+
# set default env specifiers, if none set:
|
895
|
+
if "any" not in resource_specs:
|
896
|
+
resource_specs["any"] = {}
|
897
|
+
if "environments" not in resource_specs["any"]:
|
898
|
+
resource_specs["any"]["environments"] = {env_name: copy.deepcopy(env_spec)}
|
899
|
+
|
900
|
+
for scope, dat in resource_specs.items():
|
901
|
+
if "environments" in dat:
|
902
|
+
# keep only relevant user-provided environment specifiers:
|
903
|
+
resource_specs[scope]["environments"] = {
|
904
|
+
k: v for k, v in dat["environments"].items() if k == env_name
|
905
|
+
}
|
906
|
+
# merge user-provided specifiers into action specifiers:
|
907
|
+
resource_specs[scope]["environments"][env_name] = {
|
908
|
+
**resource_specs[scope]["environments"].get(env_name, {}),
|
909
|
+
**copy.deepcopy(env_spec),
|
910
|
+
}
|
911
|
+
|
881
912
|
resources = {}
|
882
913
|
for scope in action.get_possible_scopes()[::-1]:
|
883
914
|
# loop in reverse so higher-specificity scopes take precedence:
|
@@ -95,6 +95,10 @@ class MissingActionEnvironment(Exception):
|
|
95
95
|
pass
|
96
96
|
|
97
97
|
|
98
|
+
class ActionEnvironmentMissingNameError(Exception):
|
99
|
+
pass
|
100
|
+
|
101
|
+
|
98
102
|
class FromSpecMissingObjectError(Exception):
|
99
103
|
pass
|
100
104
|
|
@@ -391,3 +395,15 @@ class UnknownScriptDataKey(ValueError):
|
|
391
395
|
|
392
396
|
class MissingVariableSubstitutionError(KeyError):
|
393
397
|
pass
|
398
|
+
|
399
|
+
|
400
|
+
class EnvironmentPresetUnknownEnvironmentError(ValueError):
|
401
|
+
pass
|
402
|
+
|
403
|
+
|
404
|
+
class UnknownEnvironmentPresetError(ValueError):
|
405
|
+
pass
|
406
|
+
|
407
|
+
|
408
|
+
class MultipleEnvironmentsError(ValueError):
|
409
|
+
pass
|