hpcflow 0.1.9__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 -462
- 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.9.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 -458
- hpcflow/archive/archive.py +0 -308
- hpcflow/archive/cloud/cloud.py +0 -47
- hpcflow/archive/cloud/errors.py +0 -9
- hpcflow/archive/cloud/providers/dropbox.py +0 -432
- hpcflow/archive/errors.py +0 -5
- hpcflow/base_db.py +0 -4
- hpcflow/config.py +0 -232
- 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 -2549
- hpcflow/nesting.py +0 -9
- hpcflow/profiles.py +0 -455
- hpcflow/project.py +0 -81
- hpcflow/scheduler.py +0 -323
- hpcflow/utils.py +0 -103
- hpcflow/validation.py +0 -167
- hpcflow/variables.py +0 -544
- hpcflow-0.1.9.dist-info/METADATA +0 -168
- hpcflow-0.1.9.dist-info/RECORD +0 -45
- hpcflow-0.1.9.dist-info/entry_points.txt +0 -8
- hpcflow-0.1.9.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,814 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Model of files that hold commands.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import copy
|
|
7
|
+
from dataclasses import dataclass, field, InitVar
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from textwrap import dedent
|
|
10
|
+
from typing import Protocol, cast, overload, TYPE_CHECKING
|
|
11
|
+
from typing_extensions import Final, override
|
|
12
|
+
|
|
13
|
+
from hpcflow.sdk.core.types import ActionData
|
|
14
|
+
from hpcflow.sdk.typing import PathLike, hydrate, ParamSource
|
|
15
|
+
from hpcflow.sdk.core.json_like import ChildObjectSpec, JSONLike
|
|
16
|
+
from hpcflow.sdk.core.utils import search_dir_files_by_regex
|
|
17
|
+
from hpcflow.sdk.core.zarr_io import zarr_decode
|
|
18
|
+
from hpcflow.sdk.core.values import process_demo_data_strings
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
import os
|
|
22
|
+
from collections.abc import Mapping
|
|
23
|
+
from typing import Any, ClassVar
|
|
24
|
+
from typing_extensions import Self
|
|
25
|
+
from .actions import Action, ActionRule
|
|
26
|
+
from .environment import Environment
|
|
27
|
+
from .object_list import CommandFilesList
|
|
28
|
+
from .parameters import Parameter
|
|
29
|
+
from .task import ElementSet
|
|
30
|
+
from .workflow import Workflow
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FileNamePart(Protocol):
|
|
34
|
+
"""
|
|
35
|
+
A filename or piece of filename that can be expanded.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def value(self, directory: str | os.PathLike = ".") -> str | list[str]:
|
|
39
|
+
"""
|
|
40
|
+
Get the part of the file, possibly with directory specified.
|
|
41
|
+
Implementations of this may ignore the directory.
|
|
42
|
+
If a pattern, the expanded value may be a list of strings.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(init=False)
|
|
47
|
+
@hydrate
|
|
48
|
+
class FileSpec(JSONLike):
|
|
49
|
+
"""
|
|
50
|
+
A specification of a file handled by a workflow.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
_validation_schema: ClassVar[str] = "files_spec_schema.yaml"
|
|
54
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
|
55
|
+
ChildObjectSpec(name="name", class_name="FileNameSpec"),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
#: Label for this file specification.
|
|
59
|
+
label: Final[str]
|
|
60
|
+
#: The name of the file.
|
|
61
|
+
name: Final[FileNameSpec]
|
|
62
|
+
#: Documentation for the file specification.
|
|
63
|
+
doc: Final[str]
|
|
64
|
+
_hash_value: str | None = field(default=None, repr=False)
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
label: str,
|
|
69
|
+
name: str | FileNameSpec,
|
|
70
|
+
doc: str = "",
|
|
71
|
+
_hash_value: str | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
self.label = label
|
|
74
|
+
self.name = self._app.FileNameSpec(name) if isinstance(name, str) else name
|
|
75
|
+
self.doc = doc
|
|
76
|
+
self._hash_value = _hash_value
|
|
77
|
+
self.__hash = hash((label, self.name))
|
|
78
|
+
|
|
79
|
+
def value(self, directory: str | os.PathLike = ".") -> str:
|
|
80
|
+
"""
|
|
81
|
+
The path to a file, optionally resolved with respect to a particular directory.
|
|
82
|
+
"""
|
|
83
|
+
return cast("str", self.name.value(directory))
|
|
84
|
+
|
|
85
|
+
def __eq__(self, other: object) -> bool:
|
|
86
|
+
if not isinstance(other, self.__class__):
|
|
87
|
+
return False
|
|
88
|
+
return self.label == other.label and self.name == other.name
|
|
89
|
+
|
|
90
|
+
def __hash__(self) -> int:
|
|
91
|
+
return self.__hash
|
|
92
|
+
|
|
93
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
|
94
|
+
d.pop("_FileSpec__hash")
|
|
95
|
+
return d
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def stem(self) -> FileNameStem:
|
|
99
|
+
"""
|
|
100
|
+
The stem of the file name.
|
|
101
|
+
"""
|
|
102
|
+
return self.name.stem
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def ext(self) -> FileNameExt:
|
|
106
|
+
"""
|
|
107
|
+
The extension of the file name.
|
|
108
|
+
"""
|
|
109
|
+
return self.name.ext
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def documentation(self) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Documentation for rendering via Jinja.
|
|
115
|
+
"""
|
|
116
|
+
if self.doc:
|
|
117
|
+
import markupsafe
|
|
118
|
+
|
|
119
|
+
return markupsafe.Markup(self.doc)
|
|
120
|
+
return repr(self)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@hydrate
|
|
124
|
+
class FileNameSpec(JSONLike):
|
|
125
|
+
"""
|
|
126
|
+
The name of a file handled by a workflow, or a pattern that matches multiple files.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
name: str
|
|
131
|
+
The name or pattern.
|
|
132
|
+
args: list
|
|
133
|
+
Positional arguments to use when formatting the name.
|
|
134
|
+
Can be omitted if the name does not contain a Python formatting pattern.
|
|
135
|
+
is_regex: bool
|
|
136
|
+
If true, the name is used as a regex to search for actual files.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def __init__(
|
|
140
|
+
self,
|
|
141
|
+
name: str,
|
|
142
|
+
args: list[FileNamePart] | None = None,
|
|
143
|
+
is_regex: bool = False,
|
|
144
|
+
) -> None:
|
|
145
|
+
#: The name or pattern.
|
|
146
|
+
self.name: Final[str] = name
|
|
147
|
+
#: Positional arguments to use when formatting the name.
|
|
148
|
+
self.args: Final[tuple[FileNamePart, ...]] = tuple(args or [])
|
|
149
|
+
#: Whether the name is used as a regex to search for actual files.
|
|
150
|
+
self.is_regex: Final[bool] = is_regex
|
|
151
|
+
self.__hash = hash((name, self.args, is_regex))
|
|
152
|
+
|
|
153
|
+
def __eq__(self, other: object) -> bool:
|
|
154
|
+
if not isinstance(other, self.__class__):
|
|
155
|
+
return False
|
|
156
|
+
return (
|
|
157
|
+
self.name == other.name
|
|
158
|
+
and self.args == other.args
|
|
159
|
+
and self.is_regex == other.is_regex
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def __hash__(self) -> int:
|
|
163
|
+
return self.__hash
|
|
164
|
+
|
|
165
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
|
166
|
+
d.pop("_FileNameSpec__hash")
|
|
167
|
+
return d
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def stem(self) -> FileNameStem:
|
|
171
|
+
"""
|
|
172
|
+
The stem of the name or pattern.
|
|
173
|
+
"""
|
|
174
|
+
return self._app.FileNameStem(self)
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def ext(self) -> FileNameExt:
|
|
178
|
+
"""
|
|
179
|
+
The extension of the name or pattern.
|
|
180
|
+
"""
|
|
181
|
+
return self._app.FileNameExt(self)
|
|
182
|
+
|
|
183
|
+
def value(self, directory: str | os.PathLike = ".") -> list[str] | str:
|
|
184
|
+
"""
|
|
185
|
+
Get the template-resolved name of the file
|
|
186
|
+
(or files matched if the name is a regex pattern).
|
|
187
|
+
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
directory: PathLike
|
|
191
|
+
Where to resolve values with respect to.
|
|
192
|
+
"""
|
|
193
|
+
format_args = [arg.value(Path(directory)) for arg in self.args]
|
|
194
|
+
value = self.name.format(*format_args)
|
|
195
|
+
if self.is_regex:
|
|
196
|
+
return search_dir_files_by_regex(value, directory=directory)
|
|
197
|
+
return value
|
|
198
|
+
|
|
199
|
+
def __repr__(self) -> str:
|
|
200
|
+
return f"{self.__class__.__name__}({self.name})"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@dataclass
|
|
204
|
+
class FileNameStem(JSONLike):
|
|
205
|
+
"""
|
|
206
|
+
The stem of a file name.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
#: The file specification this is derived from.
|
|
210
|
+
file_name: FileNameSpec
|
|
211
|
+
|
|
212
|
+
def value(self, directory: str | os.PathLike = ".") -> str:
|
|
213
|
+
"""
|
|
214
|
+
Get the stem, possibly with directory specified.
|
|
215
|
+
"""
|
|
216
|
+
d = self.file_name.value(directory)
|
|
217
|
+
if self.file_name.is_regex:
|
|
218
|
+
raise ValueError("cannot get the stem of a regex match")
|
|
219
|
+
assert not isinstance(d, list)
|
|
220
|
+
return Path(d).stem
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@dataclass
|
|
224
|
+
class FileNameExt(JSONLike):
|
|
225
|
+
"""
|
|
226
|
+
The extension of a file name.
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
#: The file specification this is derived from.
|
|
230
|
+
file_name: FileNameSpec
|
|
231
|
+
|
|
232
|
+
def value(self, directory: str | os.PathLike = ".") -> str:
|
|
233
|
+
"""
|
|
234
|
+
Get the extension.
|
|
235
|
+
"""
|
|
236
|
+
d = self.file_name.value(directory)
|
|
237
|
+
if self.file_name.is_regex:
|
|
238
|
+
raise ValueError("cannot get the extension of a regex match")
|
|
239
|
+
assert not isinstance(d, list)
|
|
240
|
+
return Path(d).suffix
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@dataclass
|
|
244
|
+
@hydrate
|
|
245
|
+
class InputFileGenerator(JSONLike):
|
|
246
|
+
"""
|
|
247
|
+
Represents a script that is run to generate input files for an action.
|
|
248
|
+
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
input_file:
|
|
252
|
+
The file to generate.
|
|
253
|
+
inputs: list[~hpcflow.app.Parameter]
|
|
254
|
+
The input parameters to the generator.
|
|
255
|
+
script:
|
|
256
|
+
The script that generates the input.
|
|
257
|
+
environment:
|
|
258
|
+
The environment in which to run the generator.
|
|
259
|
+
script_pass_env_spec:
|
|
260
|
+
Whether to pass in the environment.
|
|
261
|
+
abortable:
|
|
262
|
+
Whether the generator can be stopped early.
|
|
263
|
+
Quick-running scripts tend to not need this.
|
|
264
|
+
rules: list[~hpcflow.app.ActionRule]
|
|
265
|
+
User-specified rules for whether to run the generator.
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
|
269
|
+
ChildObjectSpec(
|
|
270
|
+
name="input_file",
|
|
271
|
+
class_name="FileSpec",
|
|
272
|
+
shared_data_primary_key="label",
|
|
273
|
+
shared_data_name="command_files",
|
|
274
|
+
),
|
|
275
|
+
ChildObjectSpec(
|
|
276
|
+
name="inputs",
|
|
277
|
+
class_name="Parameter",
|
|
278
|
+
is_multiple=True,
|
|
279
|
+
json_like_name="from_inputs",
|
|
280
|
+
shared_data_primary_key="typ",
|
|
281
|
+
shared_data_name="parameters",
|
|
282
|
+
),
|
|
283
|
+
ChildObjectSpec(
|
|
284
|
+
name="rules",
|
|
285
|
+
class_name="ActionRule",
|
|
286
|
+
is_multiple=True,
|
|
287
|
+
parent_ref="input_file_generator",
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
#: The file to generate.
|
|
292
|
+
input_file: FileSpec
|
|
293
|
+
#: The input parameters to the generator.
|
|
294
|
+
inputs: list[Parameter]
|
|
295
|
+
#: The script that generates the inputs.
|
|
296
|
+
script: str | None = None
|
|
297
|
+
#: Information about data input to the script.
|
|
298
|
+
script_data_in: str | Mapping[str, str | ActionData] | None = None
|
|
299
|
+
#: Information about data output from the script.
|
|
300
|
+
script_data_out: str | Mapping[str, str | ActionData] | None = None
|
|
301
|
+
#: The environment in which to run the generator.
|
|
302
|
+
environment: Environment | None = None
|
|
303
|
+
#: Whether to pass in the environment.
|
|
304
|
+
script_pass_env_spec: bool = False
|
|
305
|
+
#: The builtin path to a template that generates the input file.
|
|
306
|
+
jinja_template: str | None = None
|
|
307
|
+
#: The external path to a template that generates the input file.
|
|
308
|
+
jinja_template_path: str | None = None
|
|
309
|
+
#: Whether the generator can be stopped early.
|
|
310
|
+
#: Quick-running scripts tend to not need this.
|
|
311
|
+
abortable: bool = False
|
|
312
|
+
#: User-specified rules for whether to run the generator.
|
|
313
|
+
rules: list[ActionRule] = field(default_factory=list)
|
|
314
|
+
#: Whether the generator requires a working directory.
|
|
315
|
+
requires_dir: bool = True
|
|
316
|
+
|
|
317
|
+
def get_action_rules(self) -> list[ActionRule]:
|
|
318
|
+
"""
|
|
319
|
+
Get the rules that allow testing if this input file generator must be run or
|
|
320
|
+
not for a given element.
|
|
321
|
+
"""
|
|
322
|
+
return [
|
|
323
|
+
self._app.ActionRule.check_missing(f"input_files.{self.input_file.label}")
|
|
324
|
+
] + self.rules
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@dataclass
|
|
328
|
+
@hydrate
|
|
329
|
+
class OutputFileParser(JSONLike):
|
|
330
|
+
"""
|
|
331
|
+
Represents a script that is run to parse output files from an action and create outputs.
|
|
332
|
+
|
|
333
|
+
Parameters
|
|
334
|
+
----------
|
|
335
|
+
output_files: list[FileSpec]
|
|
336
|
+
The output files that this parser will parse.
|
|
337
|
+
output: ~hpcflow.app.Parameter
|
|
338
|
+
The singular output parsed by this parser. Not to be confused with `outputs` (plural).
|
|
339
|
+
script: str
|
|
340
|
+
The name of the file containing the output file parser source.
|
|
341
|
+
environment: ~hpcflow.app.Environment
|
|
342
|
+
The environment to use to run the parser.
|
|
343
|
+
inputs: list[str]
|
|
344
|
+
The other inputs to the parser.
|
|
345
|
+
outputs: list[str]
|
|
346
|
+
Optional multiple outputs from the upstream actions of the schema that are
|
|
347
|
+
required to parametrise this parser.
|
|
348
|
+
options: dict
|
|
349
|
+
Miscellaneous options.
|
|
350
|
+
script_pass_env_spec: bool
|
|
351
|
+
Whether to pass the environment specifier to the script.
|
|
352
|
+
abortable: bool
|
|
353
|
+
Whether this script can be aborted.
|
|
354
|
+
save_files: list[str]
|
|
355
|
+
The files that should be saved to the persistent store for the workflow.
|
|
356
|
+
clean_up: list[str]
|
|
357
|
+
The files that should be immediately removed.
|
|
358
|
+
rules: list[~hpcflow.app.ActionRule]
|
|
359
|
+
Rules for whether to enable this parser.
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
|
363
|
+
ChildObjectSpec(
|
|
364
|
+
name="output",
|
|
365
|
+
class_name="Parameter",
|
|
366
|
+
shared_data_name="parameters",
|
|
367
|
+
shared_data_primary_key="typ",
|
|
368
|
+
),
|
|
369
|
+
ChildObjectSpec(
|
|
370
|
+
name="output_files",
|
|
371
|
+
json_like_name="from_files",
|
|
372
|
+
class_name="FileSpec",
|
|
373
|
+
is_multiple=True,
|
|
374
|
+
shared_data_primary_key="label",
|
|
375
|
+
shared_data_name="command_files",
|
|
376
|
+
),
|
|
377
|
+
ChildObjectSpec(
|
|
378
|
+
name="save_files",
|
|
379
|
+
class_name="FileSpec",
|
|
380
|
+
is_multiple=True,
|
|
381
|
+
shared_data_primary_key="label",
|
|
382
|
+
shared_data_name="command_files",
|
|
383
|
+
),
|
|
384
|
+
ChildObjectSpec(
|
|
385
|
+
name="clean_up",
|
|
386
|
+
class_name="FileSpec",
|
|
387
|
+
is_multiple=True,
|
|
388
|
+
shared_data_primary_key="label",
|
|
389
|
+
shared_data_name="command_files",
|
|
390
|
+
),
|
|
391
|
+
ChildObjectSpec(
|
|
392
|
+
name="rules",
|
|
393
|
+
class_name="ActionRule",
|
|
394
|
+
is_multiple=True,
|
|
395
|
+
parent_ref="output_file_parser",
|
|
396
|
+
),
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
#: The output files that this parser will parse.
|
|
400
|
+
output_files: list[FileSpec]
|
|
401
|
+
#: The singular output parsed by this parser.
|
|
402
|
+
#: Not to be confused with :py:attr:`outputs` (plural).
|
|
403
|
+
output: Parameter | None = None
|
|
404
|
+
#: The name of the file containing the output file parser source.
|
|
405
|
+
script: str | None = None
|
|
406
|
+
#: The environment to use to run the parser.
|
|
407
|
+
environment: Environment | None = None
|
|
408
|
+
#: The other inputs to the parser.
|
|
409
|
+
inputs: list[str] | None = None
|
|
410
|
+
#: Optional multiple outputs from the upstream actions of the schema that are
|
|
411
|
+
#: required to parametrise this parser.
|
|
412
|
+
#: Not to be confused with :py:attr:`output` (singular).
|
|
413
|
+
outputs: list[str] | None = None
|
|
414
|
+
#: Miscellaneous options.
|
|
415
|
+
options: dict[str, Any] | None = None
|
|
416
|
+
#: Whether to pass the environment specifier to the script.
|
|
417
|
+
script_pass_env_spec: bool = False
|
|
418
|
+
#: Whether this script can be aborted.
|
|
419
|
+
abortable: bool = False
|
|
420
|
+
#: The files that should be saved to the persistent store for the workflow.
|
|
421
|
+
save_files: InitVar[list[FileSpec] | bool] = True
|
|
422
|
+
_save_files: list[FileSpec] = field(init=False)
|
|
423
|
+
#: The files that should be immediately removed.
|
|
424
|
+
clean_up: list[str] = field(default_factory=list)
|
|
425
|
+
#: Rules for whether to enable this parser.
|
|
426
|
+
rules: list[ActionRule] = field(default_factory=list)
|
|
427
|
+
#: Whether the parser requires a working directory.
|
|
428
|
+
requires_dir: bool = True
|
|
429
|
+
|
|
430
|
+
def __post_init__(self, save_files: list[FileSpec] | bool) -> None:
|
|
431
|
+
if not save_files:
|
|
432
|
+
# save no files
|
|
433
|
+
self._save_files = []
|
|
434
|
+
elif save_files is True:
|
|
435
|
+
# save all output files
|
|
436
|
+
self._save_files = [out_f for out_f in self.output_files]
|
|
437
|
+
else:
|
|
438
|
+
self._save_files = save_files
|
|
439
|
+
|
|
440
|
+
@override
|
|
441
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
|
442
|
+
d = super()._postprocess_to_dict(d)
|
|
443
|
+
if "_save_files" in d:
|
|
444
|
+
d["save_files"] = d.pop("_save_files")
|
|
445
|
+
return d
|
|
446
|
+
|
|
447
|
+
@classmethod
|
|
448
|
+
def from_json_like( # type: ignore[override]
|
|
449
|
+
cls, json_like: dict[str, Any], shared_data: Mapping | None = None
|
|
450
|
+
) -> Self:
|
|
451
|
+
if "save_files" in json_like:
|
|
452
|
+
if not json_like["save_files"]:
|
|
453
|
+
json_like["save_files"] = []
|
|
454
|
+
elif json_like["save_files"] is True:
|
|
455
|
+
json_like["save_files"] = [i for i in json_like["output_files"]]
|
|
456
|
+
return super().from_json_like(json_like, shared_data)
|
|
457
|
+
|
|
458
|
+
def get_action_rules(self) -> list[ActionRule]:
|
|
459
|
+
"""Get the rules that allow testing if this output file parser must be run or not
|
|
460
|
+
for a given element."""
|
|
461
|
+
return [
|
|
462
|
+
self._app.ActionRule.check_missing(f"output_files.{out_f.label}")
|
|
463
|
+
for out_f in self.output_files
|
|
464
|
+
] + self.rules
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
@hydrate
|
|
468
|
+
class _FileContentsSpecifier(JSONLike):
|
|
469
|
+
"""Class to represent the contents of a file, either via a file-system path or
|
|
470
|
+
directly."""
|
|
471
|
+
|
|
472
|
+
#: What file is this? Only if known.
|
|
473
|
+
file: FileSpec
|
|
474
|
+
|
|
475
|
+
def __init__(
|
|
476
|
+
self,
|
|
477
|
+
path: Path | str | None = None,
|
|
478
|
+
contents: str | None = None,
|
|
479
|
+
extension: str = "",
|
|
480
|
+
store_contents: bool = True,
|
|
481
|
+
) -> None:
|
|
482
|
+
if path is not None and contents is not None:
|
|
483
|
+
raise ValueError("Specify exactly one of `path` and `contents`.")
|
|
484
|
+
|
|
485
|
+
if contents is not None and not store_contents:
|
|
486
|
+
raise ValueError(
|
|
487
|
+
"`store_contents` cannot be set to False if `contents` was specified."
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
self._path = process_demo_data_strings(self._app, path)
|
|
491
|
+
self._contents = contents
|
|
492
|
+
self._extension = extension
|
|
493
|
+
self._store_contents = store_contents
|
|
494
|
+
|
|
495
|
+
# assigned by `make_persistent`
|
|
496
|
+
self._workflow: Workflow | None = None
|
|
497
|
+
self._value_group_idx: int | None = None
|
|
498
|
+
|
|
499
|
+
# assigned by parent `ElementSet`
|
|
500
|
+
self._element_set: ElementSet | None = None
|
|
501
|
+
|
|
502
|
+
def __deepcopy__(self, memo: dict | None) -> Self:
|
|
503
|
+
kwargs = self.to_dict()
|
|
504
|
+
value_group_idx = kwargs.pop("value_group_idx")
|
|
505
|
+
obj = self.__class__(**copy.deepcopy(kwargs, memo))
|
|
506
|
+
obj._value_group_idx = value_group_idx
|
|
507
|
+
obj._workflow = self._workflow
|
|
508
|
+
obj._element_set = self._element_set
|
|
509
|
+
return obj
|
|
510
|
+
|
|
511
|
+
@property
|
|
512
|
+
def normalised_path(self) -> str:
|
|
513
|
+
"""
|
|
514
|
+
Full workflow value path to the file.
|
|
515
|
+
|
|
516
|
+
Note
|
|
517
|
+
----
|
|
518
|
+
This is not the same as the path in the filesystem, but is closely
|
|
519
|
+
related.
|
|
520
|
+
"""
|
|
521
|
+
return str(self._path) if self._path else "."
|
|
522
|
+
|
|
523
|
+
@override
|
|
524
|
+
def _postprocess_to_dict(self, d: dict[str, Any]) -> dict[str, Any]:
|
|
525
|
+
out = super()._postprocess_to_dict(d)
|
|
526
|
+
if "_workflow" in out:
|
|
527
|
+
del out["_workflow"]
|
|
528
|
+
return {k.lstrip("_"): v for k, v in out.items()}
|
|
529
|
+
|
|
530
|
+
@classmethod
|
|
531
|
+
def _json_like_constructor(cls, json_like: dict[str, Any]) -> Self:
|
|
532
|
+
"""Invoked by `JSONLike.from_json_like` instead of `__init__`."""
|
|
533
|
+
|
|
534
|
+
_value_group_idx = json_like.pop("value_group_idx", None)
|
|
535
|
+
obj = cls(**json_like)
|
|
536
|
+
obj._value_group_idx = _value_group_idx
|
|
537
|
+
|
|
538
|
+
return obj
|
|
539
|
+
|
|
540
|
+
def _get_members(self, ensure_contents: bool = False) -> dict[str, Any]:
|
|
541
|
+
out = self.to_dict()
|
|
542
|
+
del out["value_group_idx"]
|
|
543
|
+
|
|
544
|
+
if ensure_contents and self._store_contents and self._contents is None:
|
|
545
|
+
out["contents"] = self.read_contents()
|
|
546
|
+
|
|
547
|
+
return out
|
|
548
|
+
|
|
549
|
+
def make_persistent(
|
|
550
|
+
self,
|
|
551
|
+
workflow: Workflow,
|
|
552
|
+
source: ParamSource,
|
|
553
|
+
) -> tuple[str, list[int], bool]:
|
|
554
|
+
"""Save to a persistent workflow.
|
|
555
|
+
|
|
556
|
+
Returns
|
|
557
|
+
-------
|
|
558
|
+
String is the data path for this task input and integer list
|
|
559
|
+
contains the indices of the parameter data Zarr groups where the data is
|
|
560
|
+
stored.
|
|
561
|
+
|
|
562
|
+
"""
|
|
563
|
+
|
|
564
|
+
if self._value_group_idx is not None:
|
|
565
|
+
data_ref = self._value_group_idx
|
|
566
|
+
is_new = False
|
|
567
|
+
if not workflow.check_parameters_exist(data_ref):
|
|
568
|
+
raise RuntimeError(
|
|
569
|
+
f"{self.__class__.__name__} has a data reference "
|
|
570
|
+
f"({data_ref}), but does not exist in the workflow."
|
|
571
|
+
)
|
|
572
|
+
# TODO: log if already persistent.
|
|
573
|
+
else:
|
|
574
|
+
data_ref = workflow._add_file(
|
|
575
|
+
store_contents=self.store_contents,
|
|
576
|
+
is_input=True,
|
|
577
|
+
source=source,
|
|
578
|
+
path=self.path,
|
|
579
|
+
contents=self.contents,
|
|
580
|
+
filename=self.file.name.name,
|
|
581
|
+
)
|
|
582
|
+
# data_ref = workflow._add_parameter_data(
|
|
583
|
+
# data=self._get_members(ensure_contents=True, use_file_label=True),
|
|
584
|
+
# source=source,
|
|
585
|
+
# )
|
|
586
|
+
is_new = True
|
|
587
|
+
self._value_group_idx = data_ref
|
|
588
|
+
self._workflow = workflow
|
|
589
|
+
self._path = None
|
|
590
|
+
self._contents = None
|
|
591
|
+
self._extension = ""
|
|
592
|
+
self._store_contents = True
|
|
593
|
+
|
|
594
|
+
return (self.normalised_path, [data_ref], is_new)
|
|
595
|
+
|
|
596
|
+
@overload
|
|
597
|
+
def _get_value(self, value_name: None = None) -> dict[str, Any]: ...
|
|
598
|
+
|
|
599
|
+
@overload
|
|
600
|
+
def _get_value(self, value_name: str) -> Any: ...
|
|
601
|
+
|
|
602
|
+
def _get_value(self, value_name: str | None = None) -> Any:
|
|
603
|
+
# TODO: fix
|
|
604
|
+
assert self._value_group_idx is None
|
|
605
|
+
if self._value_group_idx is not None:
|
|
606
|
+
from ..persistence.zarr import ZarrPersistentStore
|
|
607
|
+
|
|
608
|
+
assert isinstance(self.workflow._store, ZarrPersistentStore)
|
|
609
|
+
# FIXME: Next two lines are both thoroughly broken, but at least resolve to something
|
|
610
|
+
grp = self.workflow._store._get_parameter_group(self._value_group_idx)
|
|
611
|
+
val = zarr_decode(grp)
|
|
612
|
+
else:
|
|
613
|
+
val = self._get_members(ensure_contents=(value_name == "contents"))
|
|
614
|
+
if value_name:
|
|
615
|
+
return val.get(value_name)
|
|
616
|
+
|
|
617
|
+
return val
|
|
618
|
+
|
|
619
|
+
def read_contents(self) -> str:
|
|
620
|
+
"""
|
|
621
|
+
Get the actual contents of the file.
|
|
622
|
+
"""
|
|
623
|
+
with self.__path.open("r") as fh:
|
|
624
|
+
return fh.read()
|
|
625
|
+
|
|
626
|
+
@property
|
|
627
|
+
def __path(self) -> Path:
|
|
628
|
+
path = self._get_value("path")
|
|
629
|
+
assert path is not None
|
|
630
|
+
return Path(path)
|
|
631
|
+
|
|
632
|
+
@property
|
|
633
|
+
def path(self) -> Path | None:
|
|
634
|
+
"""
|
|
635
|
+
The path to the file.
|
|
636
|
+
"""
|
|
637
|
+
path = self._get_value("path")
|
|
638
|
+
return Path(path) if path else None
|
|
639
|
+
|
|
640
|
+
@property
|
|
641
|
+
def store_contents(self) -> Any:
|
|
642
|
+
"""
|
|
643
|
+
Whether the file's contents are stored in the workflow's persistent store.
|
|
644
|
+
"""
|
|
645
|
+
return self._get_value("store_contents")
|
|
646
|
+
|
|
647
|
+
@property
|
|
648
|
+
def contents(self) -> str:
|
|
649
|
+
"""
|
|
650
|
+
The contents of the file.
|
|
651
|
+
"""
|
|
652
|
+
if self.store_contents:
|
|
653
|
+
return self._get_value("contents")
|
|
654
|
+
else:
|
|
655
|
+
return self.read_contents()
|
|
656
|
+
|
|
657
|
+
@property
|
|
658
|
+
def extension(self) -> str:
|
|
659
|
+
"""
|
|
660
|
+
The extension of the file.
|
|
661
|
+
"""
|
|
662
|
+
return self._get_value("extension")
|
|
663
|
+
|
|
664
|
+
@property
|
|
665
|
+
def workflow(self) -> Workflow:
|
|
666
|
+
"""
|
|
667
|
+
The owning workflow.
|
|
668
|
+
"""
|
|
669
|
+
if self._workflow:
|
|
670
|
+
return self._workflow
|
|
671
|
+
elif self._element_set:
|
|
672
|
+
w_tmpl = self._element_set.task_template.workflow_template
|
|
673
|
+
if w_tmpl and w_tmpl.workflow:
|
|
674
|
+
return w_tmpl.workflow
|
|
675
|
+
raise NotImplementedError
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
@hydrate
|
|
679
|
+
class InputFile(_FileContentsSpecifier):
|
|
680
|
+
"""
|
|
681
|
+
An input file.
|
|
682
|
+
|
|
683
|
+
Parameters
|
|
684
|
+
----------
|
|
685
|
+
file:
|
|
686
|
+
What file is this?
|
|
687
|
+
path: Path
|
|
688
|
+
Where is the (original) file?
|
|
689
|
+
contents: str
|
|
690
|
+
What is the contents of the file (if already known)?
|
|
691
|
+
extension: str
|
|
692
|
+
What is the extension of the file?
|
|
693
|
+
store_contents: bool
|
|
694
|
+
Are the file's contents to be cached in the workflow persistent store?
|
|
695
|
+
"""
|
|
696
|
+
|
|
697
|
+
_child_objects: ClassVar[tuple[ChildObjectSpec, ...]] = (
|
|
698
|
+
ChildObjectSpec(
|
|
699
|
+
name="file",
|
|
700
|
+
class_name="FileSpec",
|
|
701
|
+
shared_data_name="command_files",
|
|
702
|
+
shared_data_primary_key="label",
|
|
703
|
+
),
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
def __init__(
|
|
707
|
+
self,
|
|
708
|
+
file: FileSpec | str,
|
|
709
|
+
path: Path | str | None = None,
|
|
710
|
+
contents: str | None = None,
|
|
711
|
+
extension: str = "",
|
|
712
|
+
store_contents: bool = True,
|
|
713
|
+
) -> None:
|
|
714
|
+
if not isinstance(file, FileSpec):
|
|
715
|
+
files: CommandFilesList = self._app.command_files
|
|
716
|
+
self.file = files.get(file)
|
|
717
|
+
else:
|
|
718
|
+
self.file = file
|
|
719
|
+
|
|
720
|
+
super().__init__(path, contents, extension, store_contents)
|
|
721
|
+
|
|
722
|
+
def _get_members(
|
|
723
|
+
self, ensure_contents: bool = False, use_file_label: bool = False
|
|
724
|
+
) -> dict[str, Any]:
|
|
725
|
+
out = super()._get_members(ensure_contents)
|
|
726
|
+
if use_file_label:
|
|
727
|
+
out["file"] = self.file.label
|
|
728
|
+
return out
|
|
729
|
+
|
|
730
|
+
def __repr__(self) -> str:
|
|
731
|
+
val_grp_idx = ""
|
|
732
|
+
if self._value_group_idx is not None:
|
|
733
|
+
val_grp_idx = f", value_group_idx={self._value_group_idx}"
|
|
734
|
+
|
|
735
|
+
path_str = ""
|
|
736
|
+
if self.path is not None:
|
|
737
|
+
path_str = f", path={self.path!r}"
|
|
738
|
+
|
|
739
|
+
return (
|
|
740
|
+
f"{self.__class__.__name__}("
|
|
741
|
+
f"file={self.file.label!r}"
|
|
742
|
+
f"{path_str}"
|
|
743
|
+
f"{val_grp_idx}"
|
|
744
|
+
f")"
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
@property
|
|
748
|
+
def normalised_files_path(self) -> str:
|
|
749
|
+
"""
|
|
750
|
+
Standard name for the file within the workflow.
|
|
751
|
+
"""
|
|
752
|
+
return self.file.label
|
|
753
|
+
|
|
754
|
+
@property
|
|
755
|
+
def normalised_path(self) -> str:
|
|
756
|
+
return f"input_files.{self.normalised_files_path}"
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
@hydrate
|
|
760
|
+
class InputFileGeneratorSource(_FileContentsSpecifier):
|
|
761
|
+
"""
|
|
762
|
+
The source of code for use in an input file generator.
|
|
763
|
+
|
|
764
|
+
Parameters
|
|
765
|
+
----------
|
|
766
|
+
generator:
|
|
767
|
+
How to generate the file.
|
|
768
|
+
path:
|
|
769
|
+
Path to the file to generate.
|
|
770
|
+
contents:
|
|
771
|
+
Contents of the file. Only used when recreating this object.
|
|
772
|
+
extension:
|
|
773
|
+
File name extension.
|
|
774
|
+
"""
|
|
775
|
+
|
|
776
|
+
def __init__(
|
|
777
|
+
self,
|
|
778
|
+
generator: InputFileGenerator,
|
|
779
|
+
path: Path | str | None = None,
|
|
780
|
+
contents: str | None = None,
|
|
781
|
+
extension: str = "",
|
|
782
|
+
):
|
|
783
|
+
#: How to generate the file.
|
|
784
|
+
self.generator = generator
|
|
785
|
+
super().__init__(path, contents, extension)
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
@hydrate
|
|
789
|
+
class OutputFileParserSource(_FileContentsSpecifier):
|
|
790
|
+
"""
|
|
791
|
+
The source of code for use in an output file parser.
|
|
792
|
+
|
|
793
|
+
Parameters
|
|
794
|
+
----------
|
|
795
|
+
parser:
|
|
796
|
+
How to parse the file.
|
|
797
|
+
path: Path
|
|
798
|
+
Path to the file to parse.
|
|
799
|
+
contents:
|
|
800
|
+
Contents of the file. Only used when recreating this object.
|
|
801
|
+
extension:
|
|
802
|
+
File name extension.
|
|
803
|
+
"""
|
|
804
|
+
|
|
805
|
+
def __init__(
|
|
806
|
+
self,
|
|
807
|
+
parser: OutputFileParser,
|
|
808
|
+
path: Path | str | None = None,
|
|
809
|
+
contents: str | None = None,
|
|
810
|
+
extension: str = "",
|
|
811
|
+
):
|
|
812
|
+
#: How to parse the file.
|
|
813
|
+
self.parser = parser
|
|
814
|
+
super().__init__(path, contents, extension)
|