hpcflow 0.1.15__py3-none-any.whl → 0.2.0a271__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hpcflow/__init__.py +2 -11
- hpcflow/__pyinstaller/__init__.py +5 -0
- hpcflow/__pyinstaller/hook-hpcflow.py +40 -0
- hpcflow/_version.py +1 -1
- hpcflow/app.py +43 -0
- hpcflow/cli.py +2 -461
- hpcflow/data/demo_data_manifest/__init__.py +3 -0
- hpcflow/data/demo_data_manifest/demo_data_manifest.json +6 -0
- hpcflow/data/jinja_templates/test/test_template.txt +8 -0
- hpcflow/data/programs/hello_world/README.md +1 -0
- hpcflow/data/programs/hello_world/hello_world.c +87 -0
- hpcflow/data/programs/hello_world/linux/hello_world +0 -0
- hpcflow/data/programs/hello_world/macos/hello_world +0 -0
- hpcflow/data/programs/hello_world/win/hello_world.exe +0 -0
- hpcflow/data/scripts/__init__.py +1 -0
- hpcflow/data/scripts/bad_script.py +2 -0
- hpcflow/data/scripts/demo_task_1_generate_t1_infile_1.py +8 -0
- hpcflow/data/scripts/demo_task_1_generate_t1_infile_2.py +8 -0
- hpcflow/data/scripts/demo_task_1_parse_p3.py +7 -0
- hpcflow/data/scripts/do_nothing.py +2 -0
- hpcflow/data/scripts/env_specifier_test/input_file_generator_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/main_script_test_pass_env_spec.py +8 -0
- hpcflow/data/scripts/env_specifier_test/output_file_parser_pass_env_spec.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/input_file_generator_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v1/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/env_specifier_test/v1/output_file_parser_basic.py +4 -0
- hpcflow/data/scripts/env_specifier_test/v2/main_script_test_direct_in_direct_out.py +7 -0
- hpcflow/data/scripts/generate_t1_file_01.py +7 -0
- hpcflow/data/scripts/import_future_script.py +7 -0
- hpcflow/data/scripts/input_file_generator_basic.py +3 -0
- hpcflow/data/scripts/input_file_generator_basic_FAIL.py +3 -0
- hpcflow/data/scripts/input_file_generator_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in.py +3 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_2_fail_allowed_group.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_all_iters_test.py +15 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_env_spec.py +7 -0
- hpcflow/data/scripts/main_script_test_direct_in_direct_out_labels.py +8 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_in_group_one_fail_direct_out_3.py +6 -0
- hpcflow/data/scripts/main_script_test_direct_sub_param_in_direct_out.py +6 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_2.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_in_obj_group.py +12 -0
- hpcflow/data/scripts/main_script_test_hdf5_out_obj.py +11 -0
- hpcflow/data/scripts/main_script_test_json_and_direct_in_json_out.py +14 -0
- hpcflow/data/scripts/main_script_test_json_in_json_and_direct_out.py +17 -0
- hpcflow/data/scripts/main_script_test_json_in_json_out.py +14 -0
- hpcflow/data/scripts/main_script_test_json_in_json_out_labels.py +16 -0
- hpcflow/data/scripts/main_script_test_json_in_obj.py +12 -0
- hpcflow/data/scripts/main_script_test_json_out_FAIL.py +3 -0
- hpcflow/data/scripts/main_script_test_json_out_obj.py +10 -0
- hpcflow/data/scripts/main_script_test_json_sub_param_in_json_out_labels.py +16 -0
- hpcflow/data/scripts/main_script_test_shell_env_vars.py +12 -0
- hpcflow/data/scripts/main_script_test_std_out_std_err.py +6 -0
- hpcflow/data/scripts/output_file_parser_basic.py +3 -0
- hpcflow/data/scripts/output_file_parser_basic_FAIL.py +7 -0
- hpcflow/data/scripts/output_file_parser_test_stdout_stderr.py +8 -0
- hpcflow/data/scripts/parse_t1_file_01.py +4 -0
- hpcflow/data/scripts/script_exit_test.py +5 -0
- hpcflow/data/template_components/__init__.py +1 -0
- hpcflow/data/template_components/command_files.yaml +26 -0
- hpcflow/data/template_components/environments.yaml +13 -0
- hpcflow/data/template_components/parameters.yaml +14 -0
- hpcflow/data/template_components/task_schemas.yaml +139 -0
- hpcflow/data/workflows/workflow_1.yaml +5 -0
- hpcflow/examples.ipynb +1037 -0
- hpcflow/sdk/__init__.py +149 -0
- hpcflow/sdk/app.py +4266 -0
- hpcflow/sdk/cli.py +1479 -0
- hpcflow/sdk/cli_common.py +385 -0
- hpcflow/sdk/config/__init__.py +5 -0
- hpcflow/sdk/config/callbacks.py +246 -0
- hpcflow/sdk/config/cli.py +388 -0
- hpcflow/sdk/config/config.py +1410 -0
- hpcflow/sdk/config/config_file.py +501 -0
- hpcflow/sdk/config/errors.py +272 -0
- hpcflow/sdk/config/types.py +150 -0
- hpcflow/sdk/core/__init__.py +38 -0
- hpcflow/sdk/core/actions.py +3857 -0
- hpcflow/sdk/core/app_aware.py +25 -0
- hpcflow/sdk/core/cache.py +224 -0
- hpcflow/sdk/core/command_files.py +814 -0
- hpcflow/sdk/core/commands.py +424 -0
- hpcflow/sdk/core/element.py +2071 -0
- hpcflow/sdk/core/enums.py +221 -0
- hpcflow/sdk/core/environment.py +256 -0
- hpcflow/sdk/core/errors.py +1043 -0
- hpcflow/sdk/core/execute.py +207 -0
- hpcflow/sdk/core/json_like.py +809 -0
- hpcflow/sdk/core/loop.py +1320 -0
- hpcflow/sdk/core/loop_cache.py +282 -0
- hpcflow/sdk/core/object_list.py +933 -0
- hpcflow/sdk/core/parameters.py +3371 -0
- hpcflow/sdk/core/rule.py +196 -0
- hpcflow/sdk/core/run_dir_files.py +57 -0
- hpcflow/sdk/core/skip_reason.py +7 -0
- hpcflow/sdk/core/task.py +3792 -0
- hpcflow/sdk/core/task_schema.py +993 -0
- hpcflow/sdk/core/test_utils.py +538 -0
- hpcflow/sdk/core/types.py +447 -0
- hpcflow/sdk/core/utils.py +1207 -0
- hpcflow/sdk/core/validation.py +87 -0
- hpcflow/sdk/core/values.py +477 -0
- hpcflow/sdk/core/workflow.py +4820 -0
- hpcflow/sdk/core/zarr_io.py +206 -0
- hpcflow/sdk/data/__init__.py +13 -0
- hpcflow/sdk/data/config_file_schema.yaml +34 -0
- hpcflow/sdk/data/config_schema.yaml +260 -0
- hpcflow/sdk/data/environments_spec_schema.yaml +21 -0
- hpcflow/sdk/data/files_spec_schema.yaml +5 -0
- hpcflow/sdk/data/parameters_spec_schema.yaml +7 -0
- hpcflow/sdk/data/task_schema_spec_schema.yaml +3 -0
- hpcflow/sdk/data/workflow_spec_schema.yaml +22 -0
- hpcflow/sdk/demo/__init__.py +3 -0
- hpcflow/sdk/demo/cli.py +242 -0
- hpcflow/sdk/helper/__init__.py +3 -0
- hpcflow/sdk/helper/cli.py +137 -0
- hpcflow/sdk/helper/helper.py +300 -0
- hpcflow/sdk/helper/watcher.py +192 -0
- hpcflow/sdk/log.py +288 -0
- hpcflow/sdk/persistence/__init__.py +18 -0
- hpcflow/sdk/persistence/base.py +2817 -0
- hpcflow/sdk/persistence/defaults.py +6 -0
- hpcflow/sdk/persistence/discovery.py +39 -0
- hpcflow/sdk/persistence/json.py +954 -0
- hpcflow/sdk/persistence/pending.py +948 -0
- hpcflow/sdk/persistence/store_resource.py +203 -0
- hpcflow/sdk/persistence/types.py +309 -0
- hpcflow/sdk/persistence/utils.py +73 -0
- hpcflow/sdk/persistence/zarr.py +2388 -0
- hpcflow/sdk/runtime.py +320 -0
- hpcflow/sdk/submission/__init__.py +3 -0
- hpcflow/sdk/submission/enums.py +70 -0
- hpcflow/sdk/submission/jobscript.py +2379 -0
- hpcflow/sdk/submission/schedulers/__init__.py +281 -0
- hpcflow/sdk/submission/schedulers/direct.py +233 -0
- hpcflow/sdk/submission/schedulers/sge.py +376 -0
- hpcflow/sdk/submission/schedulers/slurm.py +598 -0
- hpcflow/sdk/submission/schedulers/utils.py +25 -0
- hpcflow/sdk/submission/shells/__init__.py +52 -0
- hpcflow/sdk/submission/shells/base.py +229 -0
- hpcflow/sdk/submission/shells/bash.py +504 -0
- hpcflow/sdk/submission/shells/os_version.py +115 -0
- hpcflow/sdk/submission/shells/powershell.py +352 -0
- hpcflow/sdk/submission/submission.py +1402 -0
- hpcflow/sdk/submission/types.py +140 -0
- hpcflow/sdk/typing.py +194 -0
- hpcflow/sdk/utils/arrays.py +69 -0
- hpcflow/sdk/utils/deferred_file.py +55 -0
- hpcflow/sdk/utils/hashing.py +16 -0
- hpcflow/sdk/utils/patches.py +31 -0
- hpcflow/sdk/utils/strings.py +69 -0
- hpcflow/tests/api/test_api.py +32 -0
- hpcflow/tests/conftest.py +123 -0
- hpcflow/tests/data/__init__.py +0 -0
- hpcflow/tests/data/benchmark_N_elements.yaml +6 -0
- hpcflow/tests/data/benchmark_script_runner.yaml +26 -0
- hpcflow/tests/data/multi_path_sequences.yaml +29 -0
- hpcflow/tests/data/workflow_1.json +10 -0
- hpcflow/tests/data/workflow_1.yaml +5 -0
- hpcflow/tests/data/workflow_1_slurm.yaml +8 -0
- hpcflow/tests/data/workflow_1_wsl.yaml +8 -0
- hpcflow/tests/data/workflow_test_run_abort.yaml +42 -0
- hpcflow/tests/jinja_templates/test_jinja_templates.py +161 -0
- hpcflow/tests/programs/test_programs.py +180 -0
- hpcflow/tests/schedulers/direct_linux/test_direct_linux_submission.py +12 -0
- hpcflow/tests/schedulers/sge/test_sge_submission.py +36 -0
- hpcflow/tests/schedulers/slurm/test_slurm_submission.py +14 -0
- hpcflow/tests/scripts/test_input_file_generators.py +282 -0
- hpcflow/tests/scripts/test_main_scripts.py +1361 -0
- hpcflow/tests/scripts/test_non_snippet_script.py +46 -0
- hpcflow/tests/scripts/test_ouput_file_parsers.py +353 -0
- hpcflow/tests/shells/wsl/test_wsl_submission.py +14 -0
- hpcflow/tests/unit/test_action.py +1066 -0
- hpcflow/tests/unit/test_action_rule.py +24 -0
- hpcflow/tests/unit/test_app.py +132 -0
- hpcflow/tests/unit/test_cache.py +46 -0
- hpcflow/tests/unit/test_cli.py +172 -0
- hpcflow/tests/unit/test_command.py +377 -0
- hpcflow/tests/unit/test_config.py +195 -0
- hpcflow/tests/unit/test_config_file.py +162 -0
- hpcflow/tests/unit/test_element.py +666 -0
- hpcflow/tests/unit/test_element_iteration.py +88 -0
- hpcflow/tests/unit/test_element_set.py +158 -0
- hpcflow/tests/unit/test_group.py +115 -0
- hpcflow/tests/unit/test_input_source.py +1479 -0
- hpcflow/tests/unit/test_input_value.py +398 -0
- hpcflow/tests/unit/test_jobscript_unit.py +757 -0
- hpcflow/tests/unit/test_json_like.py +1247 -0
- hpcflow/tests/unit/test_loop.py +2674 -0
- hpcflow/tests/unit/test_meta_task.py +325 -0
- hpcflow/tests/unit/test_multi_path_sequences.py +259 -0
- hpcflow/tests/unit/test_object_list.py +116 -0
- hpcflow/tests/unit/test_parameter.py +243 -0
- hpcflow/tests/unit/test_persistence.py +664 -0
- hpcflow/tests/unit/test_resources.py +243 -0
- hpcflow/tests/unit/test_run.py +286 -0
- hpcflow/tests/unit/test_run_directories.py +29 -0
- hpcflow/tests/unit/test_runtime.py +9 -0
- hpcflow/tests/unit/test_schema_input.py +372 -0
- hpcflow/tests/unit/test_shell.py +129 -0
- hpcflow/tests/unit/test_slurm.py +39 -0
- hpcflow/tests/unit/test_submission.py +502 -0
- hpcflow/tests/unit/test_task.py +2560 -0
- hpcflow/tests/unit/test_task_schema.py +182 -0
- hpcflow/tests/unit/test_utils.py +616 -0
- hpcflow/tests/unit/test_value_sequence.py +549 -0
- hpcflow/tests/unit/test_values.py +91 -0
- hpcflow/tests/unit/test_workflow.py +827 -0
- hpcflow/tests/unit/test_workflow_template.py +186 -0
- hpcflow/tests/unit/utils/test_arrays.py +40 -0
- hpcflow/tests/unit/utils/test_deferred_file_writer.py +34 -0
- hpcflow/tests/unit/utils/test_hashing.py +65 -0
- hpcflow/tests/unit/utils/test_patches.py +5 -0
- hpcflow/tests/unit/utils/test_redirect_std.py +50 -0
- hpcflow/tests/unit/utils/test_strings.py +97 -0
- hpcflow/tests/workflows/__init__.py +0 -0
- hpcflow/tests/workflows/test_directory_structure.py +31 -0
- hpcflow/tests/workflows/test_jobscript.py +355 -0
- hpcflow/tests/workflows/test_run_status.py +198 -0
- hpcflow/tests/workflows/test_skip_downstream.py +696 -0
- hpcflow/tests/workflows/test_submission.py +140 -0
- hpcflow/tests/workflows/test_workflows.py +564 -0
- hpcflow/tests/workflows/test_zip.py +18 -0
- hpcflow/viz_demo.ipynb +6794 -0
- hpcflow-0.2.0a271.dist-info/LICENSE +375 -0
- hpcflow-0.2.0a271.dist-info/METADATA +65 -0
- hpcflow-0.2.0a271.dist-info/RECORD +237 -0
- {hpcflow-0.1.15.dist-info → hpcflow-0.2.0a271.dist-info}/WHEEL +4 -5
- hpcflow-0.2.0a271.dist-info/entry_points.txt +6 -0
- hpcflow/api.py +0 -490
- hpcflow/archive/archive.py +0 -307
- hpcflow/archive/cloud/cloud.py +0 -45
- hpcflow/archive/cloud/errors.py +0 -9
- hpcflow/archive/cloud/providers/dropbox.py +0 -427
- hpcflow/archive/errors.py +0 -5
- hpcflow/base_db.py +0 -4
- hpcflow/config.py +0 -233
- hpcflow/copytree.py +0 -66
- hpcflow/data/examples/_config.yml +0 -14
- hpcflow/data/examples/damask/demo/1.run.yml +0 -4
- hpcflow/data/examples/damask/demo/2.process.yml +0 -29
- hpcflow/data/examples/damask/demo/geom.geom +0 -2052
- hpcflow/data/examples/damask/demo/load.load +0 -1
- hpcflow/data/examples/damask/demo/material.config +0 -185
- hpcflow/data/examples/damask/inputs/geom.geom +0 -2052
- hpcflow/data/examples/damask/inputs/load.load +0 -1
- hpcflow/data/examples/damask/inputs/material.config +0 -185
- hpcflow/data/examples/damask/profiles/_variable_lookup.yml +0 -21
- hpcflow/data/examples/damask/profiles/damask.yml +0 -4
- hpcflow/data/examples/damask/profiles/damask_process.yml +0 -8
- hpcflow/data/examples/damask/profiles/damask_run.yml +0 -5
- hpcflow/data/examples/damask/profiles/default.yml +0 -6
- hpcflow/data/examples/thinking.yml +0 -177
- hpcflow/errors.py +0 -2
- hpcflow/init_db.py +0 -37
- hpcflow/models.py +0 -2595
- hpcflow/nesting.py +0 -9
- hpcflow/profiles.py +0 -455
- hpcflow/project.py +0 -81
- hpcflow/scheduler.py +0 -322
- hpcflow/utils.py +0 -103
- hpcflow/validation.py +0 -166
- hpcflow/variables.py +0 -543
- hpcflow-0.1.15.dist-info/METADATA +0 -168
- hpcflow-0.1.15.dist-info/RECORD +0 -45
- hpcflow-0.1.15.dist-info/entry_points.txt +0 -8
- hpcflow-0.1.15.dist-info/top_level.txt +0 -1
- /hpcflow/{archive → data/jinja_templates}/__init__.py +0 -0
- /hpcflow/{archive/cloud → data/programs}/__init__.py +0 -0
- /hpcflow/{archive/cloud/providers → data/workflows}/__init__.py +0 -0
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Model of a command run in an action.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from functools import partial
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import re
|
|
10
|
+
from typing import Any, ClassVar, TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from hpcflow.sdk.log import TimeIt
|
|
15
|
+
from hpcflow.sdk.typing import hydrate
|
|
16
|
+
from hpcflow.sdk.core.element import ElementResources
|
|
17
|
+
from hpcflow.sdk.core.errors import NoCLIFormatMethodError
|
|
18
|
+
from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
|
|
19
|
+
from hpcflow.sdk.core.parameters import ParameterValue
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Callable, Iterable, Mapping, Sequence
|
|
23
|
+
from re import Pattern
|
|
24
|
+
from .actions import ActionRule, Action
|
|
25
|
+
from .element import ElementActionRun
|
|
26
|
+
from .environment import Environment
|
|
27
|
+
from ..submission.shells import Shell
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
@hydrate
|
|
32
|
+
class Command(JSONLike):
|
|
33
|
+
"""
|
|
34
|
+
A command that may be run within a workflow action.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
command: str
|
|
39
|
+
The actual command.
|
|
40
|
+
executable: str
|
|
41
|
+
The executable to run,
|
|
42
|
+
from the set of executable managed by the environment.
|
|
43
|
+
arguments: list[str]
|
|
44
|
+
The arguments to pass in.
|
|
45
|
+
variables: dict[str, str]
|
|
46
|
+
Values that may be substituted when preparing the arguments.
|
|
47
|
+
stdout: str
|
|
48
|
+
The name of a file to write standard output to.
|
|
49
|
+
stderr: str
|
|
50
|
+
The name of a file to write standard error to.
|
|
51
|
+
stdin: str
|
|
52
|
+
The name of a file to read standard input from.
|
|
53
|
+
rules: list[~hpcflow.app.ActionRule]
|
|
54
|
+
Rules that state whether this command is eligible to run.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
|
58
|
+
ChildObjectSpec(
|
|
59
|
+
name="rules",
|
|
60
|
+
class_name="ActionRule",
|
|
61
|
+
is_multiple=True,
|
|
62
|
+
parent_ref="command",
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
#: The actual command.
|
|
67
|
+
#: Overrides :py:attr:`executable`.
|
|
68
|
+
command: str | None = None
|
|
69
|
+
#: The executable to run,
|
|
70
|
+
#: from the set of executable managed by the environment.
|
|
71
|
+
executable: str | None = None
|
|
72
|
+
#: The arguments to pass in.
|
|
73
|
+
arguments: list[str] | None = None
|
|
74
|
+
#: Values that may be substituted when preparing the arguments.
|
|
75
|
+
variables: dict[str, str] | None = None
|
|
76
|
+
#: The name of a file to write standard output to.
|
|
77
|
+
stdout: str | None = None
|
|
78
|
+
#: The name of a file to write standard error to.
|
|
79
|
+
stderr: str | None = None
|
|
80
|
+
#: The name of a file to read standard input from.
|
|
81
|
+
stdin: str | None = None
|
|
82
|
+
#: Rules that state whether this command is eligible to run.
|
|
83
|
+
rules: list[ActionRule] = field(default_factory=list)
|
|
84
|
+
|
|
85
|
+
action: Action | None = None # assigned by parent Action
|
|
86
|
+
|
|
87
|
+
def __post_init__(self):
|
|
88
|
+
self._set_parent_refs()
|
|
89
|
+
|
|
90
|
+
def __repr__(self) -> str:
|
|
91
|
+
out = []
|
|
92
|
+
if self.command:
|
|
93
|
+
out.append(f"command={self.command!r}")
|
|
94
|
+
if self.executable:
|
|
95
|
+
out.append(f"executable={self.executable!r}")
|
|
96
|
+
if self.arguments:
|
|
97
|
+
out.append(f"arguments={self.arguments!r}")
|
|
98
|
+
if self.variables:
|
|
99
|
+
out.append(f"variables={self.variables!r}")
|
|
100
|
+
if self.stdout:
|
|
101
|
+
out.append(f"stdout={self.stdout!r}")
|
|
102
|
+
if self.stderr:
|
|
103
|
+
out.append(f"stderr={self.stderr!r}")
|
|
104
|
+
if self.stdin:
|
|
105
|
+
out.append(f"stdin={self.stdin!r}")
|
|
106
|
+
if self.rules:
|
|
107
|
+
out.append(f"rules={self.rules!r}")
|
|
108
|
+
|
|
109
|
+
return f"{self.__class__.__name__}({', '.join(out)})"
|
|
110
|
+
|
|
111
|
+
def __eq__(self, other):
|
|
112
|
+
if not isinstance(other, self.__class__):
|
|
113
|
+
return False
|
|
114
|
+
return (
|
|
115
|
+
self.command == other.command
|
|
116
|
+
and self.executable == other.executable
|
|
117
|
+
and self.arguments == other.arguments
|
|
118
|
+
and self.variables == other.variables
|
|
119
|
+
and self.stdout == other.stdout
|
|
120
|
+
and self.stderr == other.stderr
|
|
121
|
+
and self.stdin == other.stdin
|
|
122
|
+
and self.rules == other.rules
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def __get_initial_command_line(self) -> str:
|
|
126
|
+
if self.command:
|
|
127
|
+
return self.command
|
|
128
|
+
else:
|
|
129
|
+
return self.executable or ""
|
|
130
|
+
|
|
131
|
+
__EXE_SCRIPT_RE: ClassVar[Pattern] = re.compile(r"\<\<(executable|script):(.*?)\>\>")
|
|
132
|
+
__ENV_SPEC_RE: ClassVar[Pattern] = re.compile(r"\<\<env:(.*?)\>\>")
|
|
133
|
+
|
|
134
|
+
def get_command_line(
|
|
135
|
+
self, EAR: ElementActionRun, shell: Shell, env: Environment
|
|
136
|
+
) -> tuple[str, list[tuple[str, ...]]]:
|
|
137
|
+
"""Return the resolved command line.
|
|
138
|
+
|
|
139
|
+
This is ordinarily called at run-time by `Workflow.write_commands`.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
self._app.persistence_logger.debug("Command.get_command_line")
|
|
143
|
+
cmd_str = self.__get_initial_command_line()
|
|
144
|
+
|
|
145
|
+
def _format_sum(iterable: Iterable) -> str:
|
|
146
|
+
return str(sum(iterable))
|
|
147
|
+
|
|
148
|
+
def _join(iterable: Iterable, delim: str) -> str:
|
|
149
|
+
return delim.join(map(str, iterable))
|
|
150
|
+
|
|
151
|
+
parse_types: dict[str, Callable[..., str]] = {
|
|
152
|
+
"sum": _format_sum,
|
|
153
|
+
"join": _join,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
def exec_script_repl(match_obj: re.Match[str]) -> str:
|
|
157
|
+
typ, val = match_obj.groups()
|
|
158
|
+
if typ == "executable":
|
|
159
|
+
executable = env.executables.get(val)
|
|
160
|
+
filterable = ElementResources.get_env_instance_filterable_attributes()
|
|
161
|
+
filter_exec = {attr: EAR.get_resources().get(attr) for attr in filterable}
|
|
162
|
+
exec_cmd = executable.filter_instances(**filter_exec)[0].command
|
|
163
|
+
return exec_cmd.replace("<<num_cores>>", str(EAR.resources.num_cores))
|
|
164
|
+
elif typ == "script":
|
|
165
|
+
# TODO: is this needed? we have <<script_name>> <<script_path>> etc as command variables
|
|
166
|
+
return EAR.action.get_script_name(val)
|
|
167
|
+
else:
|
|
168
|
+
raise ValueError("impossible match occurred")
|
|
169
|
+
|
|
170
|
+
def input_param_repl(match_obj: re.Match[str], inp_val) -> str:
|
|
171
|
+
_, func, func_kwargs, method, method_kwargs = match_obj.groups()
|
|
172
|
+
|
|
173
|
+
if isinstance(inp_val, ParameterValue):
|
|
174
|
+
if not method:
|
|
175
|
+
method = "CLI_format"
|
|
176
|
+
if not hasattr(inp_val, method):
|
|
177
|
+
raise NoCLIFormatMethodError(method, inp_val)
|
|
178
|
+
kwargs = self.__prepare_kwargs_from_string(args_str=method_kwargs)
|
|
179
|
+
inp_val = getattr(inp_val, method)(**kwargs)
|
|
180
|
+
|
|
181
|
+
if func:
|
|
182
|
+
kwargs = self.__prepare_kwargs_from_string(
|
|
183
|
+
args_str=func_kwargs,
|
|
184
|
+
doubled_quoted_args=["delim"],
|
|
185
|
+
)
|
|
186
|
+
inp_val = parse_types[func](inp_val, **kwargs)
|
|
187
|
+
|
|
188
|
+
return str(inp_val)
|
|
189
|
+
|
|
190
|
+
file_regex = r"(\<\<file:{}\>\>?)"
|
|
191
|
+
|
|
192
|
+
# substitute executables:
|
|
193
|
+
cmd_str = self.__EXE_SCRIPT_RE.sub(
|
|
194
|
+
repl=exec_script_repl,
|
|
195
|
+
string=cmd_str,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# executable command might itself contain variables defined in `variables`, and/or
|
|
199
|
+
# an `<<args>>` variable::
|
|
200
|
+
for var_key, var_val in (self.variables or {}).items():
|
|
201
|
+
# substitute any `<<env:>>` specifiers
|
|
202
|
+
var_val = self.__ENV_SPEC_RE.sub(
|
|
203
|
+
repl=lambda match_obj: EAR.env_spec[match_obj[1]],
|
|
204
|
+
string=var_val,
|
|
205
|
+
)
|
|
206
|
+
cmd_str = cmd_str.replace(f"<<{var_key}>>", var_val)
|
|
207
|
+
if "<<args>>" in cmd_str:
|
|
208
|
+
args_str = " ".join(self.arguments or ())
|
|
209
|
+
ends_in_args = cmd_str.endswith("<<args>>")
|
|
210
|
+
cmd_str = cmd_str.replace("<<args>>", args_str)
|
|
211
|
+
if ends_in_args and not args_str:
|
|
212
|
+
cmd_str = cmd_str.rstrip()
|
|
213
|
+
|
|
214
|
+
# remove any left over "<<args>>" and "<<script_name>>"s:
|
|
215
|
+
cmd_str = (
|
|
216
|
+
cmd_str.replace("<<args>>", "")
|
|
217
|
+
.replace("<<script_name>>", "")
|
|
218
|
+
.replace("<<script_path>>", "")
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# substitute input parameters in command:
|
|
222
|
+
types_pattern = "|".join(parse_types)
|
|
223
|
+
pattern = (
|
|
224
|
+
r"(\<\<(?:({types_pattern})(?:\[(.*)\])?\()?parameter:{name}(?:\.(\w+)"
|
|
225
|
+
r"\((.*?)\))?\)?\>\>?)"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
for cmd_inp_full in EAR.action.get_command_input_types(sub_parameters=True):
|
|
229
|
+
# remove any CLI formatting method, which will be the final component and will
|
|
230
|
+
# include parentheses:
|
|
231
|
+
cmd_inp_parts = cmd_inp_full.split(".")
|
|
232
|
+
if "(" in cmd_inp_parts[-1]:
|
|
233
|
+
cmd_inp = ".".join(cmd_inp_parts[:-1])
|
|
234
|
+
else:
|
|
235
|
+
cmd_inp = cmd_inp_full
|
|
236
|
+
inp_val = EAR.get(
|
|
237
|
+
f"inputs.{cmd_inp}",
|
|
238
|
+
raise_on_unset=True,
|
|
239
|
+
) # TODO: what if schema output?
|
|
240
|
+
pattern_i = pattern.format(
|
|
241
|
+
types_pattern=types_pattern,
|
|
242
|
+
name=re.escape(cmd_inp),
|
|
243
|
+
)
|
|
244
|
+
cmd_str = re.sub(
|
|
245
|
+
pattern=pattern_i,
|
|
246
|
+
repl=partial(input_param_repl, inp_val=inp_val),
|
|
247
|
+
string=cmd_str,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# substitute input/output files in command:
|
|
251
|
+
for cmd_file in EAR.action.get_command_file_labels():
|
|
252
|
+
file_path = EAR.get(
|
|
253
|
+
f"input_files.{cmd_file}", raise_on_unset=True
|
|
254
|
+
) or EAR.get(f"output_files.{cmd_file}", raise_on_unset=True)
|
|
255
|
+
# assuming we have copied this file to the EAR directory, then we just
|
|
256
|
+
# need the file name:
|
|
257
|
+
file_name = Path(file_path).name
|
|
258
|
+
cmd_str = re.sub(
|
|
259
|
+
pattern=file_regex.format(cmd_file),
|
|
260
|
+
repl=file_name,
|
|
261
|
+
string=cmd_str,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
shell_vars: list[tuple[str, ...]] = []
|
|
265
|
+
out_types = self.get_output_types()
|
|
266
|
+
if out_types["stdout"]:
|
|
267
|
+
# TODO: also map stderr/both if possible
|
|
268
|
+
# assign stdout to a shell variable if required:
|
|
269
|
+
param_name = f"outputs.{out_types['stdout']}"
|
|
270
|
+
shell_var_name = f"parameter_{out_types['stdout']}"
|
|
271
|
+
shell_vars.append((param_name, shell_var_name, "stdout"))
|
|
272
|
+
cmd_str = shell.format_stream_assignment(
|
|
273
|
+
shell_var_name=shell_var_name,
|
|
274
|
+
command=cmd_str,
|
|
275
|
+
)
|
|
276
|
+
elif self.stdout:
|
|
277
|
+
cmd_str += f" 1>> {self.stdout}"
|
|
278
|
+
|
|
279
|
+
if self.stderr:
|
|
280
|
+
cmd_str += f" 2>> {self.stderr}"
|
|
281
|
+
|
|
282
|
+
return cmd_str, shell_vars
|
|
283
|
+
|
|
284
|
+
# note: we use "parameter" rather than "output", because it could be a schema
|
|
285
|
+
# output or schema input.
|
|
286
|
+
__PARAM_RE: ClassVar[Pattern] = re.compile(
|
|
287
|
+
r"(?:\<\<(?:\w+(?:\[(?:.*)\])?\()?parameter:(\w+)"
|
|
288
|
+
r"(?:\.(?:\w+)\((?:.*?)\))?\)?\>\>?)"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def get_output_types(self) -> Mapping[str, str | None]:
|
|
292
|
+
"""
|
|
293
|
+
Get whether stdout and stderr are workflow parameters.
|
|
294
|
+
"""
|
|
295
|
+
out: dict[str, str | None] = {"stdout": None, "stderr": None}
|
|
296
|
+
for i, label in zip((self.stdout, self.stderr), ("stdout", "stderr")):
|
|
297
|
+
if i and (match := self.__PARAM_RE.search(i)):
|
|
298
|
+
param_typ: str = match[1]
|
|
299
|
+
if match.span(0) != (0, len(i)):
|
|
300
|
+
raise ValueError(
|
|
301
|
+
f"If specified as a parameter, `{label}` must not include"
|
|
302
|
+
f" any characters other than the parameter "
|
|
303
|
+
f"specification, but this was given: {i!r}."
|
|
304
|
+
)
|
|
305
|
+
out[label] = param_typ
|
|
306
|
+
return out
|
|
307
|
+
|
|
308
|
+
@staticmethod
|
|
309
|
+
def __prepare_kwargs_from_string(
|
|
310
|
+
args_str: str | None, doubled_quoted_args: list[str] | None = None
|
|
311
|
+
) -> dict[str, str]:
|
|
312
|
+
if args_str is None:
|
|
313
|
+
return {}
|
|
314
|
+
|
|
315
|
+
kwargs: dict[str, str] = {}
|
|
316
|
+
# deal with specified double-quoted arguments first if it exists:
|
|
317
|
+
for quote_arg in doubled_quoted_args or ():
|
|
318
|
+
quote_pat = r'.*({quote_arg}="(.*)").*'.format(quote_arg=quote_arg)
|
|
319
|
+
if match := re.match(quote_pat, args_str):
|
|
320
|
+
quote_str, quote_contents = match.groups()
|
|
321
|
+
args_str = args_str.replace(quote_str, "")
|
|
322
|
+
kwargs[quote_arg] = quote_contents
|
|
323
|
+
|
|
324
|
+
if args_str := args_str.strip().strip(","):
|
|
325
|
+
for arg_part in args_str.split(","):
|
|
326
|
+
name_i, value_i = map(str.strip, arg_part.split("="))
|
|
327
|
+
kwargs[name_i] = value_i
|
|
328
|
+
return kwargs
|
|
329
|
+
|
|
330
|
+
def process_std_stream(self, name: str, value: str, stderr: bool) -> Any:
|
|
331
|
+
"""
|
|
332
|
+
Process a description of a standard stread from a command to get how it becomes
|
|
333
|
+
a workflow parameter for later actions.
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
---------
|
|
337
|
+
name:
|
|
338
|
+
The name of the output, describing how to process things.
|
|
339
|
+
value:
|
|
340
|
+
The actual value read from the stream.
|
|
341
|
+
stderr:
|
|
342
|
+
If true, this is handling the stderr stream. If false, the stdout stream.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
def _parse_list(
|
|
346
|
+
lst_str: str, item_type: str = "str", delim: str = " "
|
|
347
|
+
) -> list[Any]:
|
|
348
|
+
return [parse_types[item_type](i) for i in lst_str.split(delim)]
|
|
349
|
+
|
|
350
|
+
def _parse_array(
|
|
351
|
+
arr_str: str, item_type: str = "float", delim: str = " "
|
|
352
|
+
) -> np.ndarray[Any, np.dtype[Any]]:
|
|
353
|
+
return np.array(
|
|
354
|
+
_parse_list(lst_str=arr_str, item_type=item_type, delim=delim)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
def _parse_bool(bool_str: str) -> bool:
|
|
358
|
+
bool_str = bool_str.lower()
|
|
359
|
+
if bool_str in ("true", "1"):
|
|
360
|
+
return True
|
|
361
|
+
elif bool_str in ("false", "0"):
|
|
362
|
+
return False
|
|
363
|
+
else:
|
|
364
|
+
raise ValueError(
|
|
365
|
+
f"Cannot parse value {bool_str!r} as a boolean in command "
|
|
366
|
+
f"{'stderr' if stderr else 'stdout'}: "
|
|
367
|
+
f"{self.stderr if stderr else self.stdout!r}."
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
parse_types: dict[str, Callable[[str], Any]] = {
|
|
371
|
+
"str": str,
|
|
372
|
+
"int": int,
|
|
373
|
+
"float": float,
|
|
374
|
+
"bool": _parse_bool,
|
|
375
|
+
"list": _parse_list,
|
|
376
|
+
"array": _parse_array,
|
|
377
|
+
}
|
|
378
|
+
types_pattern = "|".join(parse_types)
|
|
379
|
+
|
|
380
|
+
# TODO: use str.removeprefix in 3.9 onwards
|
|
381
|
+
out_name = name.replace("outputs.", "")
|
|
382
|
+
pattern = (
|
|
383
|
+
r"(\<\<(?:({types_pattern})(?:\[(.*)\])?\()?parameter:{name}(?:\.(\w+)"
|
|
384
|
+
r"\((.*?)\))?\)?\>\>?)"
|
|
385
|
+
)
|
|
386
|
+
pattern = pattern.format(types_pattern=types_pattern, name=out_name)
|
|
387
|
+
spec = self.stderr if stderr else self.stdout
|
|
388
|
+
assert spec is not None
|
|
389
|
+
self._app.submission_logger.info(
|
|
390
|
+
f"processing shell standard stream according to spec: {spec!r}"
|
|
391
|
+
)
|
|
392
|
+
param = self._app.Parameter(out_name)
|
|
393
|
+
if (match := re.match(pattern, spec)) is None:
|
|
394
|
+
return value
|
|
395
|
+
groups = match.groups()
|
|
396
|
+
parse_type, parse_args_str = groups[1:3]
|
|
397
|
+
parse_args = self.__prepare_kwargs_from_string(
|
|
398
|
+
args_str=parse_args_str,
|
|
399
|
+
doubled_quoted_args=["delim"],
|
|
400
|
+
)
|
|
401
|
+
if param._value_class:
|
|
402
|
+
method, method_args_str = groups[3:5]
|
|
403
|
+
method_args = self.__prepare_kwargs_from_string(
|
|
404
|
+
args_str=method_args_str,
|
|
405
|
+
doubled_quoted_args=["delim"],
|
|
406
|
+
)
|
|
407
|
+
method = method or "CLI_parse"
|
|
408
|
+
value = getattr(param._value_class, method)(value, **method_args)
|
|
409
|
+
if parse_type:
|
|
410
|
+
value = parse_types[parse_type](value, **parse_args)
|
|
411
|
+
|
|
412
|
+
return value
|
|
413
|
+
|
|
414
|
+
__EXE_RE: ClassVar[Pattern] = re.compile(r"\<\<(?:executable):(.*?)\>\>")
|
|
415
|
+
|
|
416
|
+
@classmethod
|
|
417
|
+
def _extract_executable_labels(cls, cmd_str: str) -> Sequence[str]:
|
|
418
|
+
return cls.__EXE_RE.findall(cmd_str)
|
|
419
|
+
|
|
420
|
+
@TimeIt.decorator
|
|
421
|
+
def get_required_executables(self) -> Sequence[str]:
|
|
422
|
+
"""Return executable labels required by this command."""
|
|
423
|
+
# an executable label might appear in the `command` or `executable` attribute:
|
|
424
|
+
return self._extract_executable_labels(self.__get_initial_command_line())
|