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
hpcflow/nesting.py
DELETED
hpcflow/profiles.py
DELETED
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
"""`hpcflow.profiles.py`
|
|
2
|
-
|
|
3
|
-
Module containing code associated with using YAML file job profiles to
|
|
4
|
-
construct a Workflow.
|
|
5
|
-
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
from ruamel import yaml
|
|
11
|
-
|
|
12
|
-
from hpcflow.config import Config as CONFIG
|
|
13
|
-
from hpcflow.validation import (
|
|
14
|
-
validate_task_multiplicity,
|
|
15
|
-
validate_job_profile,
|
|
16
|
-
validate_job_profile_list,
|
|
17
|
-
)
|
|
18
|
-
from hpcflow.variables import (
|
|
19
|
-
select_cmd_group_var_definitions,
|
|
20
|
-
get_all_var_defns_from_lookup,
|
|
21
|
-
get_variabled_filename_regex,
|
|
22
|
-
find_variabled_filenames,
|
|
23
|
-
)
|
|
24
|
-
from hpcflow.models import CommandGroup
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def resolve_root_archive(root_archive_name, archives):
|
|
28
|
-
"""Resolve archive definition from the config file for the root archive.
|
|
29
|
-
|
|
30
|
-
Returns
|
|
31
|
-
-------
|
|
32
|
-
arch_idx : int
|
|
33
|
-
The index of the root archive in the archives list.
|
|
34
|
-
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
try:
|
|
38
|
-
arch_defn = CONFIG.get('archive_locations')[root_archive_name]
|
|
39
|
-
except KeyError:
|
|
40
|
-
msg = ('An archive called "{}" was not found in the '
|
|
41
|
-
'configuration file. Available archives are: {}')
|
|
42
|
-
raise ValueError(msg.format(root_archive_name, CONFIG.get('archive_locations')))
|
|
43
|
-
|
|
44
|
-
existing_idx = None
|
|
45
|
-
for k_idx, k in enumerate(archives):
|
|
46
|
-
if k['name'] == root_archive_name:
|
|
47
|
-
existing_idx = k_idx
|
|
48
|
-
break
|
|
49
|
-
|
|
50
|
-
if existing_idx is not None:
|
|
51
|
-
arch_idx = existing_idx
|
|
52
|
-
else:
|
|
53
|
-
archives.append({
|
|
54
|
-
'name': root_archive_name,
|
|
55
|
-
**arch_defn,
|
|
56
|
-
})
|
|
57
|
-
arch_idx = len(archives) - 1
|
|
58
|
-
|
|
59
|
-
return arch_idx
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def resolve_archives(cmd_group, archives, profile_archives):
|
|
63
|
-
"""Resolve archive definition from the config file and add to the archives
|
|
64
|
-
list"""
|
|
65
|
-
|
|
66
|
-
arch_name = cmd_group.pop('archive', None)
|
|
67
|
-
if arch_name:
|
|
68
|
-
try:
|
|
69
|
-
arch_defn = {
|
|
70
|
-
**CONFIG.get('archive_locations'),
|
|
71
|
-
**(profile_archives or {}),
|
|
72
|
-
}[arch_name]
|
|
73
|
-
except KeyError:
|
|
74
|
-
msg = ('An archive called "{}" was not found in the '
|
|
75
|
-
'configuration file. Available archives are: {}')
|
|
76
|
-
raise ValueError(msg.format(arch_name, CONFIG.get('archive_locations')))
|
|
77
|
-
|
|
78
|
-
existing_idx = None
|
|
79
|
-
for k_idx, k in enumerate(archives):
|
|
80
|
-
if k['name'] == arch_name:
|
|
81
|
-
existing_idx = k_idx
|
|
82
|
-
break
|
|
83
|
-
|
|
84
|
-
if existing_idx is not None:
|
|
85
|
-
cmd_group['archive_idx'] = existing_idx
|
|
86
|
-
else:
|
|
87
|
-
archives.append({
|
|
88
|
-
'name': arch_name,
|
|
89
|
-
**arch_defn,
|
|
90
|
-
})
|
|
91
|
-
cmd_group['archive_idx'] = len(archives) - 1
|
|
92
|
-
|
|
93
|
-
else:
|
|
94
|
-
cmd_group.pop('archive_excludes', None)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def normalise_commands(commands):
|
|
98
|
-
"""Normalise command group commands to a list of dicts."""
|
|
99
|
-
|
|
100
|
-
if isinstance(commands, str):
|
|
101
|
-
commands = [{'line': i} for i in commands.splitlines()]
|
|
102
|
-
|
|
103
|
-
if not isinstance(commands, list):
|
|
104
|
-
msg = '`commands` must be a (optionally multiline) string or a list of dict.'
|
|
105
|
-
raise ValueError(msg)
|
|
106
|
-
|
|
107
|
-
for cmd_idx, cmd in enumerate(commands):
|
|
108
|
-
if not isinstance(cmd, dict):
|
|
109
|
-
msg = 'Each element in `commands` must be dict.'
|
|
110
|
-
raise ValueError(msg)
|
|
111
|
-
|
|
112
|
-
ALLOWED = ['line', 'parallel_mode', 'subshell']
|
|
113
|
-
ALLOWED_fmt = ', '.join([f'{i!r}' for i in ALLOWED])
|
|
114
|
-
bad_keys = set(cmd.keys()) - set(ALLOWED)
|
|
115
|
-
if bad_keys:
|
|
116
|
-
bad_keys_fmt = ', '.join([f'{i!r}' for i in bad_keys])
|
|
117
|
-
msg = (f'Each element in `commands` must be a dict with allowed keys: '
|
|
118
|
-
f'{ALLOWED_fmt}. Unknown keys specified: {bad_keys_fmt}.')
|
|
119
|
-
raise ValueError(msg)
|
|
120
|
-
|
|
121
|
-
if 'subshell' in cmd:
|
|
122
|
-
if ('line' or 'parallel_mode') in cmd:
|
|
123
|
-
msg = (f'If `subshell` is specified in a `commands` list element, no '
|
|
124
|
-
f'other keys are permitted in the list element.')
|
|
125
|
-
raise ValueError(msg)
|
|
126
|
-
commands[cmd_idx]['subshell'] = normalise_commands(cmd['subshell'])
|
|
127
|
-
|
|
128
|
-
elif 'line' in cmd:
|
|
129
|
-
if not cmd['line']:
|
|
130
|
-
raise ValueError(f'The `line` key in each element in `commands` must be '
|
|
131
|
-
f'non-null.')
|
|
132
|
-
|
|
133
|
-
return commands
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def prepare_workflow_dict(*profile_dicts):
|
|
137
|
-
"""Prepare the workflow dict for initialisation as a Workflow object."""
|
|
138
|
-
|
|
139
|
-
# Form command group list, where profile-level parameters are copied to the
|
|
140
|
-
# child command groups, but equivalent command group parameters take
|
|
141
|
-
# precedence. Also extract out variable definitions.
|
|
142
|
-
var_definitions = {}
|
|
143
|
-
command_groups = []
|
|
144
|
-
pre_commands = []
|
|
145
|
-
archives = []
|
|
146
|
-
root_arch_name = None
|
|
147
|
-
loop = None
|
|
148
|
-
parallel_modes = None
|
|
149
|
-
root_arch_exc = []
|
|
150
|
-
root_arch_num = 0
|
|
151
|
-
profile_files = []
|
|
152
|
-
for i in profile_dicts:
|
|
153
|
-
|
|
154
|
-
pre_commands.extend(i.get('pre_commands') or [])
|
|
155
|
-
|
|
156
|
-
# `root_archive` should be specified at most once across all profiles
|
|
157
|
-
# regardless of whether the value is set to `null`:
|
|
158
|
-
if 'root_archive' in i:
|
|
159
|
-
if root_arch_num == 1:
|
|
160
|
-
msg = ('`root_archive` must be specified at most once across '
|
|
161
|
-
'all job profiles.')
|
|
162
|
-
raise ValueError(msg)
|
|
163
|
-
else:
|
|
164
|
-
root_arch_num += 1
|
|
165
|
-
root_arch_name = i['root_archive']
|
|
166
|
-
|
|
167
|
-
if i.get('root_archive_excludes'):
|
|
168
|
-
root_arch_exc.extend(i['root_archive_excludes'])
|
|
169
|
-
|
|
170
|
-
if 'loop' in i:
|
|
171
|
-
loop = i['loop']
|
|
172
|
-
if 'parallel_modes' in i:
|
|
173
|
-
parallel_modes = i['parallel_modes']
|
|
174
|
-
|
|
175
|
-
profile_cmd_groups = []
|
|
176
|
-
# Form command group list:
|
|
177
|
-
exec_order_add = len(command_groups)
|
|
178
|
-
for cmd_group_idx, j in enumerate(i['command_groups']):
|
|
179
|
-
|
|
180
|
-
# Populate with defaults first:
|
|
181
|
-
cmd_group = {**CONFIG.get('cmd_group_defaults')}
|
|
182
|
-
|
|
183
|
-
# Overwrite with profile-level parameters:
|
|
184
|
-
SHARED_KEYS = (
|
|
185
|
-
set(CONFIG.get('profile_keys_allowed')) &
|
|
186
|
-
set(CONFIG.get('cmd_group_keys_allowed'))
|
|
187
|
-
)
|
|
188
|
-
for k in SHARED_KEYS:
|
|
189
|
-
if i.get(k) is not None:
|
|
190
|
-
cmd_group.update({k: i[k]})
|
|
191
|
-
# Overwrite with command-level parameters:
|
|
192
|
-
cmd_group.update(**j)
|
|
193
|
-
|
|
194
|
-
if 'exec_order' not in cmd_group:
|
|
195
|
-
cmd_group['exec_order'] = cmd_group_idx
|
|
196
|
-
|
|
197
|
-
cmd_group['exec_order'] += exec_order_add
|
|
198
|
-
|
|
199
|
-
# Combine scheduler options with scheduler name:
|
|
200
|
-
cmd_group['scheduler'] = {
|
|
201
|
-
'name': cmd_group['scheduler'],
|
|
202
|
-
'options': cmd_group.pop('scheduler_options'),
|
|
203
|
-
'output_dir': cmd_group.pop('output_dir'),
|
|
204
|
-
'error_dir': cmd_group.pop('error_dir'),
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
# Normalise env: if env is a string, split by newlines:
|
|
208
|
-
env = cmd_group.get('environment')
|
|
209
|
-
if isinstance(env, str):
|
|
210
|
-
cmd_group['environment'] = env.splitlines()
|
|
211
|
-
|
|
212
|
-
# Normalise commands:
|
|
213
|
-
cmd_group['commands'] = normalise_commands(cmd_group['commands'])
|
|
214
|
-
|
|
215
|
-
profile_cmd_groups.append(cmd_group)
|
|
216
|
-
|
|
217
|
-
resolve_archives(cmd_group, archives, i.get('archive_locations'))
|
|
218
|
-
|
|
219
|
-
command_groups.extend(profile_cmd_groups)
|
|
220
|
-
|
|
221
|
-
# Extract var_defs from profile and from var lookup given var scope:
|
|
222
|
-
var_scope = i.get('variable_scope')
|
|
223
|
-
var_defns_all = i.get('variables', {})
|
|
224
|
-
var_defns_all.update(get_all_var_defns_from_lookup(var_scope))
|
|
225
|
-
|
|
226
|
-
for j in profile_cmd_groups:
|
|
227
|
-
|
|
228
|
-
cmd_group_var_defns = select_cmd_group_var_definitions(
|
|
229
|
-
var_defns_all,
|
|
230
|
-
CommandGroup.get_command_lines(j['commands']),
|
|
231
|
-
j['directory']
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
var_definitions.update(cmd_group_var_defns)
|
|
235
|
-
|
|
236
|
-
if 'profile_file' in i:
|
|
237
|
-
profile_files.append(i['profile_file'])
|
|
238
|
-
|
|
239
|
-
root_arch_idx = None
|
|
240
|
-
if root_arch_name:
|
|
241
|
-
root_arch_idx = resolve_root_archive(root_arch_name, archives)
|
|
242
|
-
|
|
243
|
-
workflow = {
|
|
244
|
-
'command_groups': command_groups,
|
|
245
|
-
'var_definitions': var_definitions,
|
|
246
|
-
'pre_commands': pre_commands,
|
|
247
|
-
'archives': archives,
|
|
248
|
-
'root_archive_idx': root_arch_idx,
|
|
249
|
-
'root_archive_excludes': root_arch_exc,
|
|
250
|
-
'profile_files': profile_files,
|
|
251
|
-
'parallel_modes': parallel_modes,
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if loop:
|
|
255
|
-
workflow.update({'loop': loop})
|
|
256
|
-
|
|
257
|
-
return workflow
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def parse_job_profiles(dir_path=None, profile_list=None):
|
|
261
|
-
"""Parse YAML file profiles into a form suitable for `models.Workflow`
|
|
262
|
-
initialisation.
|
|
263
|
-
|
|
264
|
-
Parameters
|
|
265
|
-
----------
|
|
266
|
-
dir_path : Path
|
|
267
|
-
The directory in which the Workflow will be generated. In addition to
|
|
268
|
-
the profile file paths explicitly declared in the `profiles` parameter,
|
|
269
|
-
this directory (`dir_path`) will be searched for profile files.
|
|
270
|
-
profile_list : list of (str or Path), optional
|
|
271
|
-
List of YAML profile file paths to be used to construct the Workflow.
|
|
272
|
-
If `None` and if no profiles are found in `dir_path` according to the
|
|
273
|
-
global configuration for the expected format of profile files, then a
|
|
274
|
-
`ValueError` is raised.
|
|
275
|
-
|
|
276
|
-
Returns
|
|
277
|
-
-------
|
|
278
|
-
workflow_dict : dict
|
|
279
|
-
Dict representing the workflow, with two keys: `command_groups` and
|
|
280
|
-
`variable_definitions`.
|
|
281
|
-
|
|
282
|
-
"""
|
|
283
|
-
|
|
284
|
-
profile_matches = parse_profile_filenames(dir_path, profile_list)
|
|
285
|
-
|
|
286
|
-
print(f'profile_matches: {profile_matches}')
|
|
287
|
-
|
|
288
|
-
all_profiles = []
|
|
289
|
-
for job_profile_path, var_values in profile_matches.items():
|
|
290
|
-
all_profiles.append(
|
|
291
|
-
resolve_job_profile(job_profile_path, var_values)
|
|
292
|
-
)
|
|
293
|
-
|
|
294
|
-
all_profiles = validate_job_profile_list(all_profiles)
|
|
295
|
-
|
|
296
|
-
return prepare_workflow_dict(*all_profiles)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def parse_profile_filenames(dir_path, profile_list=None):
|
|
300
|
-
"""Resolve name and order variables embedded in profile filenames."""
|
|
301
|
-
|
|
302
|
-
fn_fmt = CONFIG.get('profile_filename_fmt')
|
|
303
|
-
var_delims = CONFIG.get('variable_delimiters')
|
|
304
|
-
|
|
305
|
-
# There are only two allowed profile filename-encodable variables:
|
|
306
|
-
var_value = {
|
|
307
|
-
'profile_name': {
|
|
308
|
-
'fmt': r'[A-Za-z0-9_\-]*',
|
|
309
|
-
'type': str,
|
|
310
|
-
},
|
|
311
|
-
'profile_order': {
|
|
312
|
-
'fmt': r'[0-9]+',
|
|
313
|
-
'type': int,
|
|
314
|
-
},
|
|
315
|
-
}
|
|
316
|
-
var_value_fmt = {k: v['fmt'] for k, v in var_value.items()}
|
|
317
|
-
var_value_type = {k: v['type'] for k, v in var_value.items()}
|
|
318
|
-
|
|
319
|
-
fn_regex, var_names = get_variabled_filename_regex(
|
|
320
|
-
fn_fmt, var_delims, var_value_fmt)
|
|
321
|
-
|
|
322
|
-
if profile_list:
|
|
323
|
-
# Check given profile file names exist and match expected format:
|
|
324
|
-
profile_matches = find_variabled_filenames(
|
|
325
|
-
profile_list,
|
|
326
|
-
fn_regex,
|
|
327
|
-
var_names,
|
|
328
|
-
var_value_type
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
else:
|
|
332
|
-
# Search `dir_path` for matching file names:
|
|
333
|
-
dir_files = [i for i in dir_path.iterdir() if i.is_file()]
|
|
334
|
-
profile_matches = find_variabled_filenames(
|
|
335
|
-
dir_files,
|
|
336
|
-
fn_regex,
|
|
337
|
-
var_names,
|
|
338
|
-
var_value_type,
|
|
339
|
-
all_must_match=False,
|
|
340
|
-
check_exists=False
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
if not profile_matches:
|
|
344
|
-
msg = ('No profiles found in directory: "{}"')
|
|
345
|
-
raise ValueError(msg.format(dir_path))
|
|
346
|
-
|
|
347
|
-
return profile_matches
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
def resolve_job_profile(job_profile_path, filename_var_values,
|
|
351
|
-
common_profiles_dir=None):
|
|
352
|
-
"""Resolve the inheritance tree of a job profile and do basic validation.
|
|
353
|
-
|
|
354
|
-
Parameters
|
|
355
|
-
----------
|
|
356
|
-
job_profile_path : str or Path
|
|
357
|
-
Path to the job profile file, which may inherit from common profiles.
|
|
358
|
-
filename_var_values : dict
|
|
359
|
-
Dictionary that maps variable names to their values as embedded in the
|
|
360
|
-
job profile file name.
|
|
361
|
-
common_profiles_dir : str or Path
|
|
362
|
-
Directory that contains common profiles. By default, set to `None`, in
|
|
363
|
-
which case, the default profile directory is used (which is within the
|
|
364
|
-
hpcflow configuration directory).
|
|
365
|
-
|
|
366
|
-
Returns
|
|
367
|
-
-------
|
|
368
|
-
merged_profile : dict
|
|
369
|
-
Dictionary of the resolved job profile.
|
|
370
|
-
|
|
371
|
-
"""
|
|
372
|
-
|
|
373
|
-
job_profile_path = Path(job_profile_path)
|
|
374
|
-
if common_profiles_dir:
|
|
375
|
-
common_profiles_dir = Path(common_profiles_dir)
|
|
376
|
-
else:
|
|
377
|
-
common_profiles_dir = CONFIG.get('profiles_dir')
|
|
378
|
-
|
|
379
|
-
# Get a list of all common profile file names (without any extension):
|
|
380
|
-
prof_names = []
|
|
381
|
-
for prof in common_profiles_dir.iterdir():
|
|
382
|
-
prof_names.append(prof.stem)
|
|
383
|
-
|
|
384
|
-
profile_inheritance = [job_profile_path.stem]
|
|
385
|
-
|
|
386
|
-
with job_profile_path.open() as handle:
|
|
387
|
-
job_profile = yaml.safe_load(handle)
|
|
388
|
-
|
|
389
|
-
# Form a list of data from each profile in the inheritance tree:
|
|
390
|
-
all_profiles = [job_profile]
|
|
391
|
-
parent_profile = job_profile
|
|
392
|
-
while 'inherits' in parent_profile:
|
|
393
|
-
|
|
394
|
-
parent_profile_name = parent_profile['inherits']
|
|
395
|
-
profile_inheritance.append(parent_profile_name)
|
|
396
|
-
|
|
397
|
-
if parent_profile_name not in prof_names:
|
|
398
|
-
msg = ('Cannot find profile "{}" in the profile directory "{}".')
|
|
399
|
-
raise ValueError(msg.format(
|
|
400
|
-
parent_profile_name, common_profiles_dir))
|
|
401
|
-
|
|
402
|
-
parent_spec_path = common_profiles_dir.joinpath(
|
|
403
|
-
parent_profile_name + CONFIG.get('profile_ext'))
|
|
404
|
-
|
|
405
|
-
with parent_spec_path.open() as handle:
|
|
406
|
-
parent_profile = yaml.safe_load(handle)
|
|
407
|
-
all_profiles.append(parent_profile)
|
|
408
|
-
|
|
409
|
-
# Merge inherited profile data:
|
|
410
|
-
merged_profile = {}
|
|
411
|
-
merged_vars = {}
|
|
412
|
-
merged_opts = {}
|
|
413
|
-
for i in all_profiles[::-1]:
|
|
414
|
-
|
|
415
|
-
# Merge variables and opts independently to allow variables to inherit:
|
|
416
|
-
if 'variables' in i:
|
|
417
|
-
merged_vars.update(i.pop('variables'))
|
|
418
|
-
|
|
419
|
-
if 'scheduler_options' in i:
|
|
420
|
-
merged_opts.update(i.pop('scheduler_options'))
|
|
421
|
-
|
|
422
|
-
merged_profile.update(**i)
|
|
423
|
-
|
|
424
|
-
if 'inherits' in merged_profile:
|
|
425
|
-
merged_profile.pop('inherits')
|
|
426
|
-
|
|
427
|
-
merged_profile['variables'] = merged_vars
|
|
428
|
-
merged_profile['scheduler_options'] = merged_opts
|
|
429
|
-
|
|
430
|
-
# Add variables embedded in file name:
|
|
431
|
-
allowed_filename_vars = ['profile_order', 'profile_name']
|
|
432
|
-
for i in allowed_filename_vars:
|
|
433
|
-
if i in filename_var_values:
|
|
434
|
-
if i in merged_profile:
|
|
435
|
-
msg = ('`{}` is set in the job profile file name and also in '
|
|
436
|
-
'the file YAML contents for job profile file "{}". It '
|
|
437
|
-
'must be specified only once.')
|
|
438
|
-
raise ValueError(msg.format(i, job_profile_path.name))
|
|
439
|
-
else:
|
|
440
|
-
merged_profile.update({i: filename_var_values[i]})
|
|
441
|
-
|
|
442
|
-
merged_profile = validate_job_profile(merged_profile)
|
|
443
|
-
|
|
444
|
-
cmd_groups = merged_profile['command_groups']
|
|
445
|
-
if not cmd_groups:
|
|
446
|
-
msg = ('At least one command group must be specified in the job '
|
|
447
|
-
'profile file named "{}".')
|
|
448
|
-
raise ValueError(msg.format(job_profile_path.name))
|
|
449
|
-
|
|
450
|
-
com_err_msg = '[Job profile name: "{}"]'.format(job_profile_path.name)
|
|
451
|
-
cmd_groups = validate_task_multiplicity(cmd_groups, com_err_msg)
|
|
452
|
-
merged_profile['command_groups'] = cmd_groups
|
|
453
|
-
merged_profile['profile_file'] = job_profile_path
|
|
454
|
-
|
|
455
|
-
return merged_profile
|
hpcflow/project.py
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
"""`hpcflow.project.py`
|
|
2
|
-
|
|
3
|
-
This module contains a class to represent the root directory of an hpcflow
|
|
4
|
-
project.
|
|
5
|
-
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import os
|
|
9
|
-
import shutil
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from subprocess import run
|
|
12
|
-
|
|
13
|
-
from hpcflow.config import Config
|
|
14
|
-
from hpcflow.utils import get_random_hex
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class Project(object):
|
|
18
|
-
|
|
19
|
-
def __init__(self, dir_path, config_dir=None, clean=False):
|
|
20
|
-
|
|
21
|
-
Config.set_config(config_dir)
|
|
22
|
-
self.dir_path = Path(dir_path or '').resolve()
|
|
23
|
-
self.hf_dir = self.dir_path.joinpath(Config.get('hpcflow_directory'))
|
|
24
|
-
|
|
25
|
-
if clean:
|
|
26
|
-
self.clean()
|
|
27
|
-
|
|
28
|
-
if not self.hf_dir.exists():
|
|
29
|
-
|
|
30
|
-
self.db_directory_name = get_random_hex(10)
|
|
31
|
-
self.db_path = self.project_db_dir.joinpath(Config.get('DB_name')).as_posix()
|
|
32
|
-
|
|
33
|
-
self.hf_dir.mkdir()
|
|
34
|
-
self.project_db_dir.mkdir()
|
|
35
|
-
|
|
36
|
-
with self.hf_dir.joinpath('db_path').open('w') as handle:
|
|
37
|
-
handle.write(self.db_path)
|
|
38
|
-
|
|
39
|
-
else:
|
|
40
|
-
|
|
41
|
-
with self.hf_dir.joinpath('db_path').open() as handle:
|
|
42
|
-
self.db_path = handle.read().strip()
|
|
43
|
-
self.db_directory_name = Path(
|
|
44
|
-
self.db_path).relative_to(
|
|
45
|
-
Config.get('projects_DB_dir')).parent
|
|
46
|
-
|
|
47
|
-
@property
|
|
48
|
-
def db_uri(self):
|
|
49
|
-
return 'sqlite:///' + self.db_path
|
|
50
|
-
|
|
51
|
-
@property
|
|
52
|
-
def project_db_dir(self):
|
|
53
|
-
return Config.get('projects_DB_dir').joinpath(self.db_directory_name)
|
|
54
|
-
|
|
55
|
-
@property
|
|
56
|
-
def db_dir_symlink(self):
|
|
57
|
-
return self.hf_dir.joinpath(self.db_directory_name)
|
|
58
|
-
|
|
59
|
-
def ensure_db_symlink(self):
|
|
60
|
-
"""Add a symlink to the DB for convenience (has to be done after the DB is created)"""
|
|
61
|
-
|
|
62
|
-
if not self.db_dir_symlink.exists():
|
|
63
|
-
target = str(self.project_db_dir)
|
|
64
|
-
link = str(self.db_dir_symlink)
|
|
65
|
-
if os.name == 'nt':
|
|
66
|
-
cmd = 'mklink /D "{}" "{}"'.format(link, target)
|
|
67
|
-
elif os.name == 'posix':
|
|
68
|
-
cmd = 'ln --symbolic "{}" "{}"'.format(target, link)
|
|
69
|
-
|
|
70
|
-
run(cmd, shell=True)
|
|
71
|
-
|
|
72
|
-
def clean(self):
|
|
73
|
-
"""Remove all hpcflow related files and directories associated with this project."""
|
|
74
|
-
|
|
75
|
-
if self.hf_dir.exists():
|
|
76
|
-
|
|
77
|
-
# Remove database:
|
|
78
|
-
shutil.rmtree(str(self.project_db_dir))
|
|
79
|
-
|
|
80
|
-
# Remove project directory:
|
|
81
|
-
shutil.rmtree(str(self.hf_dir))
|