toil 8.0.0__py3-none-any.whl → 8.2.0__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.
- toil/__init__.py +4 -39
- toil/batchSystems/abstractBatchSystem.py +1 -1
- toil/batchSystems/abstractGridEngineBatchSystem.py +1 -1
- toil/batchSystems/awsBatch.py +1 -1
- toil/batchSystems/cleanup_support.py +1 -1
- toil/batchSystems/kubernetes.py +53 -7
- toil/batchSystems/local_support.py +1 -1
- toil/batchSystems/mesos/batchSystem.py +13 -8
- toil/batchSystems/mesos/test/__init__.py +3 -2
- toil/batchSystems/options.py +1 -0
- toil/batchSystems/singleMachine.py +1 -1
- toil/batchSystems/slurm.py +229 -84
- toil/bus.py +5 -3
- toil/common.py +198 -54
- toil/cwl/cwltoil.py +32 -11
- toil/job.py +110 -86
- toil/jobStores/abstractJobStore.py +24 -3
- toil/jobStores/aws/jobStore.py +46 -10
- toil/jobStores/fileJobStore.py +25 -1
- toil/jobStores/googleJobStore.py +104 -30
- toil/leader.py +9 -0
- toil/lib/accelerators.py +3 -1
- toil/lib/aws/session.py +14 -3
- toil/lib/aws/utils.py +92 -35
- toil/lib/aws/utils.py.orig +504 -0
- toil/lib/bioio.py +1 -1
- toil/lib/docker.py +252 -91
- toil/lib/dockstore.py +387 -0
- toil/lib/ec2nodes.py +3 -2
- toil/lib/exceptions.py +5 -3
- toil/lib/history.py +1345 -0
- toil/lib/history_submission.py +695 -0
- toil/lib/io.py +56 -23
- toil/lib/misc.py +25 -1
- toil/lib/resources.py +2 -1
- toil/lib/retry.py +10 -10
- toil/lib/threading.py +11 -10
- toil/lib/{integration.py → trs.py} +95 -46
- toil/lib/web.py +38 -0
- toil/options/common.py +25 -2
- toil/options/cwl.py +10 -0
- toil/options/wdl.py +11 -0
- toil/provisioners/gceProvisioner.py +4 -4
- toil/server/api_spec/LICENSE +201 -0
- toil/server/api_spec/README.rst +5 -0
- toil/server/cli/wes_cwl_runner.py +5 -4
- toil/server/utils.py +2 -3
- toil/statsAndLogging.py +35 -1
- toil/test/__init__.py +275 -115
- toil/test/batchSystems/batchSystemTest.py +227 -205
- toil/test/batchSystems/test_slurm.py +199 -2
- toil/test/cactus/pestis.tar.gz +0 -0
- toil/test/conftest.py +7 -0
- toil/test/cwl/2.fasta +11 -0
- toil/test/cwl/2.fastq +12 -0
- toil/test/cwl/conftest.py +39 -0
- toil/test/cwl/cwlTest.py +1015 -780
- toil/test/cwl/directory/directory/file.txt +15 -0
- toil/test/cwl/download_directory_file.json +4 -0
- toil/test/cwl/download_directory_s3.json +4 -0
- toil/test/cwl/download_file.json +6 -0
- toil/test/cwl/download_http.json +6 -0
- toil/test/cwl/download_https.json +6 -0
- toil/test/cwl/download_s3.json +6 -0
- toil/test/cwl/download_subdirectory_file.json +5 -0
- toil/test/cwl/download_subdirectory_s3.json +5 -0
- toil/test/cwl/empty.json +1 -0
- toil/test/cwl/mock_mpi/fake_mpi.yml +8 -0
- toil/test/cwl/mock_mpi/fake_mpi_run.py +42 -0
- toil/test/cwl/optional-file-exists.json +6 -0
- toil/test/cwl/optional-file-missing.json +6 -0
- toil/test/cwl/optional-file.cwl +18 -0
- toil/test/cwl/preemptible_expression.json +1 -0
- toil/test/cwl/revsort-job-missing.json +6 -0
- toil/test/cwl/revsort-job.json +6 -0
- toil/test/cwl/s3_secondary_file.json +16 -0
- toil/test/cwl/seqtk_seq_job.json +6 -0
- toil/test/cwl/stream.json +6 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.dat +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f1 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f1i +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f2 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f2_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f3 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f3_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f4 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f4_TSM0 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.f5 +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.info +0 -0
- toil/test/cwl/test_filename_conflict_resolution.ms/table.lock +0 -0
- toil/test/cwl/whale.txt +16 -0
- toil/test/docs/scripts/example_alwaysfail.py +38 -0
- toil/test/docs/scripts/example_alwaysfail_with_files.wdl +33 -0
- toil/test/docs/scripts/example_cachingbenchmark.py +117 -0
- toil/test/docs/scripts/stagingExampleFiles/in.txt +1 -0
- toil/test/docs/scripts/stagingExampleFiles/out.txt +2 -0
- toil/test/docs/scripts/tutorial_arguments.py +23 -0
- toil/test/docs/scripts/tutorial_debugging.patch +12 -0
- toil/test/docs/scripts/tutorial_debugging_hangs.wdl +126 -0
- toil/test/docs/scripts/tutorial_debugging_works.wdl +129 -0
- toil/test/docs/scripts/tutorial_docker.py +20 -0
- toil/test/docs/scripts/tutorial_dynamic.py +24 -0
- toil/test/docs/scripts/tutorial_encapsulation.py +28 -0
- toil/test/docs/scripts/tutorial_encapsulation2.py +29 -0
- toil/test/docs/scripts/tutorial_helloworld.py +15 -0
- toil/test/docs/scripts/tutorial_invokeworkflow.py +27 -0
- toil/test/docs/scripts/tutorial_invokeworkflow2.py +30 -0
- toil/test/docs/scripts/tutorial_jobfunctions.py +22 -0
- toil/test/docs/scripts/tutorial_managing.py +29 -0
- toil/test/docs/scripts/tutorial_managing2.py +56 -0
- toil/test/docs/scripts/tutorial_multiplejobs.py +25 -0
- toil/test/docs/scripts/tutorial_multiplejobs2.py +21 -0
- toil/test/docs/scripts/tutorial_multiplejobs3.py +22 -0
- toil/test/docs/scripts/tutorial_promises.py +25 -0
- toil/test/docs/scripts/tutorial_promises2.py +30 -0
- toil/test/docs/scripts/tutorial_quickstart.py +22 -0
- toil/test/docs/scripts/tutorial_requirements.py +44 -0
- toil/test/docs/scripts/tutorial_services.py +45 -0
- toil/test/docs/scripts/tutorial_staging.py +45 -0
- toil/test/docs/scripts/tutorial_stats.py +64 -0
- toil/test/lib/aws/test_iam.py +3 -1
- toil/test/lib/dockerTest.py +205 -122
- toil/test/lib/test_history.py +236 -0
- toil/test/lib/test_trs.py +161 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +12 -9
- toil/test/provisioners/clusterTest.py +4 -4
- toil/test/provisioners/gceProvisionerTest.py +16 -14
- toil/test/sort/sort.py +4 -1
- toil/test/src/busTest.py +17 -17
- toil/test/src/deferredFunctionTest.py +145 -132
- toil/test/src/importExportFileTest.py +71 -63
- toil/test/src/jobEncapsulationTest.py +27 -28
- toil/test/src/jobServiceTest.py +149 -133
- toil/test/src/jobTest.py +219 -211
- toil/test/src/miscTests.py +66 -60
- toil/test/src/promisedRequirementTest.py +163 -169
- toil/test/src/regularLogTest.py +24 -24
- toil/test/src/resourceTest.py +82 -76
- toil/test/src/restartDAGTest.py +51 -47
- toil/test/src/resumabilityTest.py +24 -19
- toil/test/src/retainTempDirTest.py +60 -57
- toil/test/src/systemTest.py +17 -13
- toil/test/src/threadingTest.py +29 -32
- toil/test/utils/ABCWorkflowDebug/B_file.txt +1 -0
- toil/test/utils/ABCWorkflowDebug/debugWorkflow.py +204 -0
- toil/test/utils/ABCWorkflowDebug/mkFile.py +16 -0
- toil/test/utils/ABCWorkflowDebug/sleep.cwl +12 -0
- toil/test/utils/ABCWorkflowDebug/sleep.yaml +1 -0
- toil/test/utils/toilDebugTest.py +117 -102
- toil/test/utils/toilKillTest.py +54 -53
- toil/test/utils/utilsTest.py +303 -229
- toil/test/wdl/lint_error.wdl +9 -0
- toil/test/wdl/md5sum/empty_file.json +1 -0
- toil/test/wdl/md5sum/md5sum-gs.json +1 -0
- toil/test/wdl/md5sum/md5sum.1.0.wdl +32 -0
- toil/test/wdl/md5sum/md5sum.input +1 -0
- toil/test/wdl/md5sum/md5sum.json +1 -0
- toil/test/wdl/md5sum/md5sum.wdl +25 -0
- toil/test/wdl/miniwdl_self_test/inputs-namespaced.json +1 -0
- toil/test/wdl/miniwdl_self_test/inputs.json +1 -0
- toil/test/wdl/miniwdl_self_test/self_test.wdl +40 -0
- toil/test/wdl/standard_library/as_map.json +16 -0
- toil/test/wdl/standard_library/as_map_as_input.wdl +23 -0
- toil/test/wdl/standard_library/as_pairs.json +7 -0
- toil/test/wdl/standard_library/as_pairs_as_input.wdl +23 -0
- toil/test/wdl/standard_library/ceil.json +3 -0
- toil/test/wdl/standard_library/ceil_as_command.wdl +16 -0
- toil/test/wdl/standard_library/ceil_as_input.wdl +16 -0
- toil/test/wdl/standard_library/collect_by_key.json +1 -0
- toil/test/wdl/standard_library/collect_by_key_as_input.wdl +23 -0
- toil/test/wdl/standard_library/cross.json +11 -0
- toil/test/wdl/standard_library/cross_as_input.wdl +19 -0
- toil/test/wdl/standard_library/flatten.json +7 -0
- toil/test/wdl/standard_library/flatten_as_input.wdl +18 -0
- toil/test/wdl/standard_library/floor.json +3 -0
- toil/test/wdl/standard_library/floor_as_command.wdl +16 -0
- toil/test/wdl/standard_library/floor_as_input.wdl +16 -0
- toil/test/wdl/standard_library/keys.json +8 -0
- toil/test/wdl/standard_library/keys_as_input.wdl +24 -0
- toil/test/wdl/standard_library/length.json +7 -0
- toil/test/wdl/standard_library/length_as_input.wdl +16 -0
- toil/test/wdl/standard_library/length_as_input_with_map.json +7 -0
- toil/test/wdl/standard_library/length_as_input_with_map.wdl +17 -0
- toil/test/wdl/standard_library/length_invalid.json +3 -0
- toil/test/wdl/standard_library/range.json +3 -0
- toil/test/wdl/standard_library/range_0.json +3 -0
- toil/test/wdl/standard_library/range_as_input.wdl +17 -0
- toil/test/wdl/standard_library/range_invalid.json +3 -0
- toil/test/wdl/standard_library/read_boolean.json +3 -0
- toil/test/wdl/standard_library/read_boolean_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_float.json +3 -0
- toil/test/wdl/standard_library/read_float_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_int.json +3 -0
- toil/test/wdl/standard_library/read_int_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_json.json +3 -0
- toil/test/wdl/standard_library/read_json_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_lines.json +3 -0
- toil/test/wdl/standard_library/read_lines_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_map.json +3 -0
- toil/test/wdl/standard_library/read_map_as_output.wdl +31 -0
- toil/test/wdl/standard_library/read_string.json +3 -0
- toil/test/wdl/standard_library/read_string_as_command.wdl +17 -0
- toil/test/wdl/standard_library/read_tsv.json +3 -0
- toil/test/wdl/standard_library/read_tsv_as_output.wdl +31 -0
- toil/test/wdl/standard_library/round.json +3 -0
- toil/test/wdl/standard_library/round_as_command.wdl +16 -0
- toil/test/wdl/standard_library/round_as_input.wdl +16 -0
- toil/test/wdl/standard_library/size.json +3 -0
- toil/test/wdl/standard_library/size_as_command.wdl +17 -0
- toil/test/wdl/standard_library/size_as_output.wdl +36 -0
- toil/test/wdl/standard_library/stderr.json +3 -0
- toil/test/wdl/standard_library/stderr_as_output.wdl +30 -0
- toil/test/wdl/standard_library/stdout.json +3 -0
- toil/test/wdl/standard_library/stdout_as_output.wdl +30 -0
- toil/test/wdl/standard_library/sub.json +3 -0
- toil/test/wdl/standard_library/sub_as_input.wdl +17 -0
- toil/test/wdl/standard_library/sub_as_input_with_file.wdl +17 -0
- toil/test/wdl/standard_library/transpose.json +6 -0
- toil/test/wdl/standard_library/transpose_as_input.wdl +18 -0
- toil/test/wdl/standard_library/write_json.json +6 -0
- toil/test/wdl/standard_library/write_json_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_lines.json +7 -0
- toil/test/wdl/standard_library/write_lines_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_map.json +6 -0
- toil/test/wdl/standard_library/write_map_as_command.wdl +17 -0
- toil/test/wdl/standard_library/write_tsv.json +6 -0
- toil/test/wdl/standard_library/write_tsv_as_command.wdl +17 -0
- toil/test/wdl/standard_library/zip.json +12 -0
- toil/test/wdl/standard_library/zip_as_input.wdl +19 -0
- toil/test/wdl/test.csv +3 -0
- toil/test/wdl/test.tsv +3 -0
- toil/test/wdl/testfiles/croo.wdl +38 -0
- toil/test/wdl/testfiles/drop_files.wdl +62 -0
- toil/test/wdl/testfiles/drop_files_subworkflow.wdl +13 -0
- toil/test/wdl/testfiles/empty.txt +0 -0
- toil/test/wdl/testfiles/not_enough_outputs.wdl +33 -0
- toil/test/wdl/testfiles/random.wdl +66 -0
- toil/test/wdl/testfiles/string_file_coercion.json +1 -0
- toil/test/wdl/testfiles/string_file_coercion.wdl +35 -0
- toil/test/wdl/testfiles/test.json +4 -0
- toil/test/wdl/testfiles/test_boolean.txt +1 -0
- toil/test/wdl/testfiles/test_float.txt +1 -0
- toil/test/wdl/testfiles/test_int.txt +1 -0
- toil/test/wdl/testfiles/test_lines.txt +5 -0
- toil/test/wdl/testfiles/test_map.txt +2 -0
- toil/test/wdl/testfiles/test_string.txt +1 -0
- toil/test/wdl/testfiles/url_to_file.wdl +13 -0
- toil/test/wdl/testfiles/url_to_optional_file.wdl +13 -0
- toil/test/wdl/testfiles/vocab.json +1 -0
- toil/test/wdl/testfiles/vocab.wdl +66 -0
- toil/test/wdl/testfiles/wait.wdl +34 -0
- toil/test/wdl/wdl_specification/type_pair.json +23 -0
- toil/test/wdl/wdl_specification/type_pair_basic.wdl +36 -0
- toil/test/wdl/wdl_specification/type_pair_with_files.wdl +36 -0
- toil/test/wdl/wdl_specification/v1_spec.json +1 -0
- toil/test/wdl/wdl_specification/v1_spec_declaration.wdl +39 -0
- toil/test/wdl/wdltoil_test.py +681 -408
- toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
- toil/version.py +10 -10
- toil/wdl/wdltoil.py +350 -123
- toil/worker.py +113 -33
- {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/METADATA +13 -7
- toil-8.2.0.dist-info/RECORD +439 -0
- {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/WHEEL +1 -1
- toil/test/lib/test_integration.py +0 -104
- toil-8.0.0.dist-info/RECORD +0 -253
- {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/entry_points.txt +0 -0
- {toil-8.0.0.dist-info → toil-8.2.0.dist-info/licenses}/LICENSE +0 -0
- {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/top_level.txt +0 -0
toil/common.py
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import json
|
|
15
15
|
import logging
|
|
16
16
|
import os
|
|
17
|
+
import platform
|
|
17
18
|
import pickle
|
|
18
19
|
import re
|
|
19
20
|
import signal
|
|
@@ -53,6 +54,7 @@ import requests
|
|
|
53
54
|
from configargparse import ArgParser, YAMLConfigFileParser
|
|
54
55
|
from ruamel.yaml import YAML
|
|
55
56
|
from ruamel.yaml.comments import CommentedMap
|
|
57
|
+
from ruamel.yaml.scalarstring import DoubleQuotedScalarString
|
|
56
58
|
|
|
57
59
|
from toil import logProcessContext, lookupEnvVar
|
|
58
60
|
from toil.batchSystems.options import set_batchsystem_options
|
|
@@ -69,7 +71,11 @@ from toil.bus import (
|
|
|
69
71
|
)
|
|
70
72
|
from toil.fileStores import FileID
|
|
71
73
|
from toil.lib.compatibility import deprecated
|
|
72
|
-
from toil.lib.
|
|
74
|
+
from toil.lib.history import HistoryManager
|
|
75
|
+
from toil.lib.history_submission import ask_user_about_publishing_metrics, create_history_submission, create_current_submission
|
|
76
|
+
from toil.lib.io import AtomicFileCreate, try_path, get_toil_home
|
|
77
|
+
from toil.lib.misc import StrPath
|
|
78
|
+
from toil.lib.memoize import memoize
|
|
73
79
|
from toil.lib.retry import retry
|
|
74
80
|
from toil.lib.threading import ensure_filesystem_lockable
|
|
75
81
|
from toil.options.common import JOBSTORE_HELP, add_base_toil_options
|
|
@@ -79,7 +85,7 @@ from toil.options.wdl import add_wdl_options
|
|
|
79
85
|
from toil.provisioners import add_provisioner_options, cluster_factory
|
|
80
86
|
from toil.realtimeLogger import RealtimeLogger
|
|
81
87
|
from toil.statsAndLogging import add_logging_options, set_logging_from_options
|
|
82
|
-
from toil.version import dockerRegistry, dockerTag, version
|
|
88
|
+
from toil.version import dockerRegistry, dockerTag, version, baseVersion
|
|
83
89
|
|
|
84
90
|
if TYPE_CHECKING:
|
|
85
91
|
from toil.batchSystems.abstractBatchSystem import AbstractBatchSystem
|
|
@@ -92,11 +98,14 @@ if TYPE_CHECKING:
|
|
|
92
98
|
UUID_LENGTH = 32
|
|
93
99
|
logger = logging.getLogger(__name__)
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
@memoize
|
|
102
|
+
def get_default_config_path() -> str:
|
|
103
|
+
"""
|
|
104
|
+
Get the default path where the Toil configuration file lives.
|
|
99
105
|
|
|
106
|
+
The file at the path will not necessarily exist.
|
|
107
|
+
"""
|
|
108
|
+
return os.path.join(get_toil_home(), "default.yaml")
|
|
100
109
|
|
|
101
110
|
class Config:
|
|
102
111
|
"""Class to represent configuration operations for a toil workflow run."""
|
|
@@ -118,6 +127,8 @@ class Config:
|
|
|
118
127
|
kubernetes_service_account: Optional[str]
|
|
119
128
|
kubernetes_pod_timeout: float
|
|
120
129
|
kubernetes_privileged: bool
|
|
130
|
+
kubernetes_pod_security_context: Optional[str]
|
|
131
|
+
kubernetes_security_context: Optional[str]
|
|
121
132
|
tes_endpoint: str
|
|
122
133
|
tes_user: str
|
|
123
134
|
tes_password: str
|
|
@@ -130,7 +141,7 @@ class Config:
|
|
|
130
141
|
batch_logs_dir: Optional[str]
|
|
131
142
|
"""The backing scheduler will be instructed, if possible, to save logs
|
|
132
143
|
to this directory, where the leader can read them."""
|
|
133
|
-
statePollingWait:
|
|
144
|
+
statePollingWait: float
|
|
134
145
|
state_polling_timeout: int
|
|
135
146
|
disableAutoDeployment: bool
|
|
136
147
|
|
|
@@ -200,6 +211,7 @@ class Config:
|
|
|
200
211
|
|
|
201
212
|
# Retrying/rescuing jobs
|
|
202
213
|
retryCount: int
|
|
214
|
+
stop_on_first_failure: bool
|
|
203
215
|
enableUnlimitedPreemptibleRetries: bool
|
|
204
216
|
doubleMem: bool
|
|
205
217
|
maxJobDuration: int
|
|
@@ -214,6 +226,9 @@ class Config:
|
|
|
214
226
|
write_messages: Optional[str]
|
|
215
227
|
realTimeLogging: bool
|
|
216
228
|
|
|
229
|
+
# Data publishing
|
|
230
|
+
publish_workflow_metrics: Union[Literal["all"], Literal["current"], Literal["no"], None]
|
|
231
|
+
|
|
217
232
|
# Misc
|
|
218
233
|
environment: dict[str, str]
|
|
219
234
|
disableChaining: bool
|
|
@@ -375,6 +390,7 @@ class Config:
|
|
|
375
390
|
|
|
376
391
|
# Retrying/rescuing jobs
|
|
377
392
|
set_option("retryCount")
|
|
393
|
+
set_option("stop_on_first_failure")
|
|
378
394
|
set_option("enableUnlimitedPreemptibleRetries")
|
|
379
395
|
set_option("doubleMem")
|
|
380
396
|
set_option("maxJobDuration")
|
|
@@ -388,6 +404,9 @@ class Config:
|
|
|
388
404
|
set_option("writeLogsFromAllJobs")
|
|
389
405
|
set_option("write_messages")
|
|
390
406
|
|
|
407
|
+
# Data Publishing Options
|
|
408
|
+
set_option("publish_workflow_metrics")
|
|
409
|
+
|
|
391
410
|
if self.write_messages is None:
|
|
392
411
|
# The user hasn't specified a place for the message bus so we
|
|
393
412
|
# should make one.
|
|
@@ -463,44 +482,20 @@ class Config:
|
|
|
463
482
|
def __hash__(self) -> int:
|
|
464
483
|
return self.__dict__.__hash__() # type: ignore
|
|
465
484
|
|
|
466
|
-
|
|
467
|
-
def check_and_create_toil_home_dir() -> None:
|
|
468
|
-
"""
|
|
469
|
-
Ensure that TOIL_HOME_DIR exists.
|
|
470
|
-
|
|
471
|
-
Raises an error if it does not exist and cannot be created. Safe to run
|
|
472
|
-
simultaneously in multiple processes.
|
|
485
|
+
def ensure_config(filepath: str) -> None:
|
|
473
486
|
"""
|
|
487
|
+
If the config file at the filepath does not exist, create it.
|
|
488
|
+
The parent directory should be created prior to calling this.
|
|
474
489
|
|
|
475
|
-
|
|
476
|
-
if dir_path is None:
|
|
477
|
-
raise RuntimeError(
|
|
478
|
-
f"Cannot create or access Toil configuration directory {TOIL_HOME_DIR}"
|
|
479
|
-
)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
def check_and_create_default_config_file() -> None:
|
|
483
|
-
"""
|
|
484
|
-
If the default config file does not exist, create it in the Toil home directory. Create the Toil home directory
|
|
485
|
-
if needed
|
|
486
|
-
|
|
487
|
-
Raises an error if the default config file cannot be created.
|
|
490
|
+
Raises an error if the config file cannot be created.
|
|
488
491
|
Safe to run simultaneously in multiple processes. If this process runs
|
|
489
|
-
this function, it will always see the
|
|
492
|
+
this function, it will always see the config file existing with
|
|
490
493
|
parseable contents, even if other processes are racing to create it.
|
|
491
494
|
|
|
492
|
-
No process will see
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
# The default config file did not appear to exist when we checked.
|
|
496
|
-
# It might exist now, though. Try creating it.
|
|
497
|
-
check_and_create_config_file(DEFAULT_CONFIG_FILE)
|
|
498
|
-
|
|
495
|
+
No process will see a new empty or partially-written config file. The
|
|
496
|
+
caller should still check to make sure there isn't a preexisting empty file
|
|
497
|
+
here.
|
|
499
498
|
|
|
500
|
-
def check_and_create_config_file(filepath: str) -> None:
|
|
501
|
-
"""
|
|
502
|
-
If the config file at the filepath does not exist, try creating it.
|
|
503
|
-
The parent directory should be created prior to calling this
|
|
504
499
|
:param filepath: path to config file
|
|
505
500
|
:return: None
|
|
506
501
|
"""
|
|
@@ -648,9 +643,39 @@ def generate_config(filepath: str) -> None:
|
|
|
648
643
|
yaml.dump(
|
|
649
644
|
data,
|
|
650
645
|
f,
|
|
646
|
+
# Comment everything out, Unix config file style, to show defaults
|
|
651
647
|
transform=lambda s: re.sub(r"^(.)", r"#\1", s, flags=re.MULTILINE),
|
|
652
648
|
)
|
|
653
649
|
|
|
650
|
+
def update_config(filepath: str, key: str, new_value: Union[str, bool, int, float]) -> None:
|
|
651
|
+
"""
|
|
652
|
+
Set the given top-level key to the given value in the given YAML config
|
|
653
|
+
file.
|
|
654
|
+
|
|
655
|
+
Does not dramatically alter comments or formatting, and does not make a
|
|
656
|
+
partially-written file visible.
|
|
657
|
+
|
|
658
|
+
:param key: Setting to set. Must be the command-line option name, not the
|
|
659
|
+
destination variable name.
|
|
660
|
+
"""
|
|
661
|
+
|
|
662
|
+
yaml = YAML(typ="rt")
|
|
663
|
+
data = yaml.load(open(filepath))
|
|
664
|
+
|
|
665
|
+
logger.info("Change config field %s from %s to %s", key, repr(data.get(key, None)), repr(new_value))
|
|
666
|
+
|
|
667
|
+
if isinstance(new_value, str):
|
|
668
|
+
# Strings with some values (no, yes) will be interpreted as booleans on
|
|
669
|
+
# load if not quoted. But ruamel is not determining that this is needed
|
|
670
|
+
# on serialization for newly-added values. So if we set something to a
|
|
671
|
+
# string we always quote it.
|
|
672
|
+
data[key] = DoubleQuotedScalarString(new_value)
|
|
673
|
+
else:
|
|
674
|
+
data[key] = new_value
|
|
675
|
+
|
|
676
|
+
with AtomicFileCreate(filepath) as temp_path:
|
|
677
|
+
with open(temp_path, "w") as f:
|
|
678
|
+
yaml.dump(data, f)
|
|
654
679
|
|
|
655
680
|
def parser_with_common_options(
|
|
656
681
|
provisioner_options: bool = False,
|
|
@@ -658,6 +683,17 @@ def parser_with_common_options(
|
|
|
658
683
|
prog: Optional[str] = None,
|
|
659
684
|
default_log_level: Optional[int] = None,
|
|
660
685
|
) -> ArgParser:
|
|
686
|
+
"""
|
|
687
|
+
Get a command-line option parser for a Toil subcommand.
|
|
688
|
+
|
|
689
|
+
The returned parser just has basic options (like version reporting and
|
|
690
|
+
logging) used by all Toil subcommands.
|
|
691
|
+
|
|
692
|
+
Toil Python workflows should use
|
|
693
|
+
:meth:`toil.job.Job.Runner.getDefaultArgumentParser` instead, which makes
|
|
694
|
+
sure to add all the important options for actually running a workflow.
|
|
695
|
+
"""
|
|
696
|
+
|
|
661
697
|
parser = ArgParser(
|
|
662
698
|
prog=prog or "Toil", formatter_class=ArgumentDefaultsHelpFormatter
|
|
663
699
|
)
|
|
@@ -708,11 +744,13 @@ def addOptions(
|
|
|
708
744
|
f"Unanticipated class: {parser.__class__}. Must be: argparse.ArgumentParser or ArgumentGroup."
|
|
709
745
|
)
|
|
710
746
|
|
|
747
|
+
config_path = get_default_config_path()
|
|
748
|
+
|
|
711
749
|
if isinstance(parser, ArgParser):
|
|
712
750
|
# in case the user passes in their own configargparse instance instead of calling getDefaultArgumentParser()
|
|
713
751
|
# this forces configargparser to process the config file in YAML rather than in it's own format
|
|
714
752
|
parser._config_file_parser = YAMLConfigFileParser() # type: ignore[misc]
|
|
715
|
-
parser._default_config_files = [
|
|
753
|
+
parser._default_config_files = [config_path] # type: ignore[misc]
|
|
716
754
|
else:
|
|
717
755
|
# configargparse advertises itself as a drag and drop replacement, and running the normal argparse ArgumentParser
|
|
718
756
|
# through this code still seems to work (with the exception of --config and environmental variables)
|
|
@@ -723,24 +761,24 @@ def addOptions(
|
|
|
723
761
|
DeprecationWarning,
|
|
724
762
|
)
|
|
725
763
|
|
|
726
|
-
|
|
764
|
+
ensure_config(config_path)
|
|
727
765
|
# Check on the config file to make sure it is sensible
|
|
728
|
-
config_status = os.stat(
|
|
766
|
+
config_status = os.stat(config_path)
|
|
729
767
|
if config_status.st_size == 0:
|
|
730
768
|
# If we have an empty config file, someone has to manually delete
|
|
731
769
|
# it before we will work again.
|
|
732
770
|
raise RuntimeError(
|
|
733
|
-
f"Config file {
|
|
771
|
+
f"Config file {config_path} exists but is empty. Delete it! Stat says: {config_status}"
|
|
734
772
|
)
|
|
735
773
|
try:
|
|
736
|
-
with open(
|
|
774
|
+
with open(config_path) as f:
|
|
737
775
|
yaml = YAML(typ="safe")
|
|
738
776
|
s = yaml.load(f)
|
|
739
777
|
logger.debug("Initialized default configuration: %s", json.dumps(s))
|
|
740
778
|
except:
|
|
741
779
|
# Something went wrong reading the default config, so dump its
|
|
742
780
|
# contents to the log.
|
|
743
|
-
logger.info("Configuration file contents: %s", open(
|
|
781
|
+
logger.info("Configuration file contents: %s", open(config_path).read())
|
|
744
782
|
raise
|
|
745
783
|
|
|
746
784
|
# Add base toil options
|
|
@@ -759,7 +797,7 @@ def addOptions(
|
|
|
759
797
|
:param typ: string of either "cwl" or "wdl" to specify which runner to check against
|
|
760
798
|
:return: None, raise parser error if option is found
|
|
761
799
|
"""
|
|
762
|
-
check_parser = ArgParser()
|
|
800
|
+
check_parser = ArgParser(allow_abbrev=False)
|
|
763
801
|
if typ == "wdl":
|
|
764
802
|
add_cwl_options(check_parser)
|
|
765
803
|
if typ == "cwl":
|
|
@@ -902,8 +940,9 @@ class Toil(ContextManager["Toil"]):
|
|
|
902
940
|
_jobStore: "AbstractJobStore"
|
|
903
941
|
_batchSystem: "AbstractBatchSystem"
|
|
904
942
|
_provisioner: Optional["AbstractProvisioner"]
|
|
943
|
+
_start_time: float
|
|
905
944
|
|
|
906
|
-
def __init__(self, options: Namespace) -> None:
|
|
945
|
+
def __init__(self, options: Namespace, workflow_name: Optional[str] = None, trs_spec: Optional[str] = None) -> None:
|
|
907
946
|
"""
|
|
908
947
|
Initialize a Toil object from the given options.
|
|
909
948
|
|
|
@@ -911,6 +950,12 @@ class Toil(ContextManager["Toil"]):
|
|
|
911
950
|
done when the context is entered.
|
|
912
951
|
|
|
913
952
|
:param options: command line options specified by the user
|
|
953
|
+
:param workflow_name: A human-readable name (probably a filename, URL,
|
|
954
|
+
or TRS specifier) for the workflow being run. Used for Toil history
|
|
955
|
+
storage.
|
|
956
|
+
:param trs_spec: A TRS id:version string for the workflow being run, if
|
|
957
|
+
any. Used for Toil history storage and publishing workflow
|
|
958
|
+
execution metrics to Dockstore.
|
|
914
959
|
"""
|
|
915
960
|
super().__init__()
|
|
916
961
|
self.options = options
|
|
@@ -918,6 +963,17 @@ class Toil(ContextManager["Toil"]):
|
|
|
918
963
|
self._inContextManager: bool = False
|
|
919
964
|
self._inRestart: bool = False
|
|
920
965
|
|
|
966
|
+
if workflow_name is None:
|
|
967
|
+
# Try to use the entrypoint file.
|
|
968
|
+
import __main__
|
|
969
|
+
if hasattr(__main__, '__file__'):
|
|
970
|
+
workflow_name = __main__.__file__
|
|
971
|
+
if workflow_name is None:
|
|
972
|
+
# If there's no file, say this is an interactive usage of Toil.
|
|
973
|
+
workflow_name = "<interactive>"
|
|
974
|
+
self._workflow_name: str = workflow_name
|
|
975
|
+
self._trs_spec = trs_spec
|
|
976
|
+
|
|
921
977
|
def __enter__(self) -> "Toil":
|
|
922
978
|
"""
|
|
923
979
|
Derive configuration from the command line options.
|
|
@@ -937,9 +993,16 @@ class Toil(ContextManager["Toil"]):
|
|
|
937
993
|
# Set the caching option because it wasn't set originally, resuming jobstore rebuilds config from CLI options
|
|
938
994
|
self.options.caching = config.caching
|
|
939
995
|
|
|
996
|
+
if self._trs_spec and config.publish_workflow_metrics is None:
|
|
997
|
+
# We could potentially publish this workflow run. Get a call from the user.
|
|
998
|
+
config.publish_workflow_metrics = ask_user_about_publishing_metrics()
|
|
999
|
+
|
|
940
1000
|
if not config.restart:
|
|
941
1001
|
config.prepare_start()
|
|
942
1002
|
jobStore.initialize(config)
|
|
1003
|
+
assert config.workflowID is not None
|
|
1004
|
+
# Record that there is a workflow beign run
|
|
1005
|
+
HistoryManager.record_workflow_creation(config.workflowID, self.canonical_locator(config.jobStore))
|
|
943
1006
|
else:
|
|
944
1007
|
jobStore.resume()
|
|
945
1008
|
# Merge configuration from job store with command line options
|
|
@@ -949,6 +1012,7 @@ class Toil(ContextManager["Toil"]):
|
|
|
949
1012
|
jobStore.write_config()
|
|
950
1013
|
self.config = config
|
|
951
1014
|
self._jobStore = jobStore
|
|
1015
|
+
self._start_time = time.time()
|
|
952
1016
|
self._inContextManager = True
|
|
953
1017
|
|
|
954
1018
|
# This will make sure `self.__exit__()` is called when we get a SIGTERM signal.
|
|
@@ -968,6 +1032,50 @@ class Toil(ContextManager["Toil"]):
|
|
|
968
1032
|
Depending on the configuration, delete the job store.
|
|
969
1033
|
"""
|
|
970
1034
|
try:
|
|
1035
|
+
if self.config.workflowID is not None:
|
|
1036
|
+
# Record that this attempt to run the workflow succeeded or failed.
|
|
1037
|
+
# TODO: Get ahold of the timing from statsAndLogging instead of redoing it here!
|
|
1038
|
+
# To record the batch system, we need to avoid capturing typos/random text the user types instead of a real batch system.
|
|
1039
|
+
batch_system_type="<Not Initialized>"
|
|
1040
|
+
if hasattr(self, "_batchSystem"):
|
|
1041
|
+
batch_system_type = type(self._batchSystem).__module__ + "." + type(self._batchSystem).__qualname__
|
|
1042
|
+
HistoryManager.record_workflow_attempt(
|
|
1043
|
+
self.config.workflowID,
|
|
1044
|
+
self.config.workflowAttemptNumber,
|
|
1045
|
+
exc_type is None,
|
|
1046
|
+
self._start_time,
|
|
1047
|
+
time.time() - self._start_time,
|
|
1048
|
+
batch_system=batch_system_type,
|
|
1049
|
+
caching=self.config.caching,
|
|
1050
|
+
# Use the git-hash-free Toil version which should not be unique
|
|
1051
|
+
toil_version=baseVersion,
|
|
1052
|
+
# This should always be major.minor.patch.
|
|
1053
|
+
python_version=platform.python_version(),
|
|
1054
|
+
platform_system=platform.system(),
|
|
1055
|
+
platform_machine=platform.machine()
|
|
1056
|
+
)
|
|
1057
|
+
|
|
1058
|
+
if self.config.publish_workflow_metrics == "all":
|
|
1059
|
+
# Publish metrics for all workflows, including previous ones.
|
|
1060
|
+
submission = create_history_submission()
|
|
1061
|
+
while not submission.empty():
|
|
1062
|
+
if not submission.submit():
|
|
1063
|
+
# Submitting this batch failed. An item might be broken
|
|
1064
|
+
# and we don't want to get stuck making no progress on
|
|
1065
|
+
# a batch of stuff that can't really be submitted.
|
|
1066
|
+
break
|
|
1067
|
+
# Keep making submissions until we've uploaded the whole
|
|
1068
|
+
# history or something goes wrong.
|
|
1069
|
+
submission = create_history_submission()
|
|
1070
|
+
|
|
1071
|
+
elif self.config.publish_workflow_metrics == "current" and self.config.workflowID is not None:
|
|
1072
|
+
# Publish metrics for this run only. Might be empty if we had no TRS ID.
|
|
1073
|
+
create_current_submission(self.config.workflowID, self.config.workflowAttemptNumber).submit()
|
|
1074
|
+
|
|
1075
|
+
# Make sure the history doesn't stay too big
|
|
1076
|
+
HistoryManager.enforce_byte_size_limit()
|
|
1077
|
+
|
|
1078
|
+
|
|
971
1079
|
if (
|
|
972
1080
|
exc_type is not None
|
|
973
1081
|
and self.config.clean == "onError"
|
|
@@ -1012,6 +1120,9 @@ class Toil(ContextManager["Toil"]):
|
|
|
1012
1120
|
"""
|
|
1013
1121
|
self._assertContextManagerUsed()
|
|
1014
1122
|
|
|
1123
|
+
assert self.config.workflowID is not None
|
|
1124
|
+
HistoryManager.record_workflow_metadata(self.config.workflowID, self._workflow_name, self._trs_spec)
|
|
1125
|
+
|
|
1015
1126
|
from toil.job import Job
|
|
1016
1127
|
|
|
1017
1128
|
# Check that the rootJob is an instance of the Job class
|
|
@@ -1110,6 +1221,8 @@ class Toil(ContextManager["Toil"]):
|
|
|
1110
1221
|
)
|
|
1111
1222
|
self._provisioner.setAutoscaledNodeTypes(self.config.nodeTypes)
|
|
1112
1223
|
|
|
1224
|
+
JOB_STORE_TYPES = ["file", "aws", "google"]
|
|
1225
|
+
|
|
1113
1226
|
@classmethod
|
|
1114
1227
|
def getJobStore(cls, locator: str) -> "AbstractJobStore":
|
|
1115
1228
|
"""
|
|
@@ -1137,6 +1250,14 @@ class Toil(ContextManager["Toil"]):
|
|
|
1137
1250
|
|
|
1138
1251
|
@staticmethod
|
|
1139
1252
|
def parseLocator(locator: str) -> tuple[str, str]:
|
|
1253
|
+
"""
|
|
1254
|
+
Parse a job store locator to a type string and the data needed for that
|
|
1255
|
+
implementation to connect to it.
|
|
1256
|
+
|
|
1257
|
+
Does not validate the set of possible job store types.
|
|
1258
|
+
|
|
1259
|
+
:raises RuntimeError: if the locator is not in the approproate syntax.
|
|
1260
|
+
"""
|
|
1140
1261
|
if locator[0] in "/." or ":" not in locator:
|
|
1141
1262
|
return "file", locator
|
|
1142
1263
|
else:
|
|
@@ -1153,6 +1274,17 @@ class Toil(ContextManager["Toil"]):
|
|
|
1153
1274
|
raise ValueError(f"Can't have a ':' in the name: '{name}'.")
|
|
1154
1275
|
return f"{name}:{rest}"
|
|
1155
1276
|
|
|
1277
|
+
@classmethod
|
|
1278
|
+
def canonical_locator(cls, locator: str) -> str:
|
|
1279
|
+
"""
|
|
1280
|
+
Turn a job store locator into one that will work from any directory and
|
|
1281
|
+
always includes the explicit type of job store.
|
|
1282
|
+
"""
|
|
1283
|
+
job_store_type, rest = cls.parseLocator(locator)
|
|
1284
|
+
if job_store_type == "file":
|
|
1285
|
+
rest = os.path.abspath(rest)
|
|
1286
|
+
return cls.buildLocator(job_store_type, rest)
|
|
1287
|
+
|
|
1156
1288
|
@classmethod
|
|
1157
1289
|
def resumeJobStore(cls, locator: str) -> "AbstractJobStore":
|
|
1158
1290
|
jobStore = cls.getJobStore(locator)
|
|
@@ -1373,21 +1505,33 @@ class Toil(ContextManager["Toil"]):
|
|
|
1373
1505
|
self._jobStore.export_file(file_id, dst_uri)
|
|
1374
1506
|
|
|
1375
1507
|
@staticmethod
|
|
1376
|
-
def normalize_uri(uri: str, check_existence: bool = False) -> str:
|
|
1508
|
+
def normalize_uri(uri: str, check_existence: bool = False, dir_path: Optional[str] = None) -> str:
|
|
1377
1509
|
"""
|
|
1378
|
-
Given a URI, if it has no scheme,
|
|
1510
|
+
Given a URI, if it has no scheme, make it a properly quoted file: URI.
|
|
1379
1511
|
|
|
1380
1512
|
:param check_existence: If set, raise FileNotFoundError if a URI points to
|
|
1381
1513
|
a local file that does not exist.
|
|
1514
|
+
|
|
1515
|
+
:param dir_path: If specified, interpret relative paths relative to the
|
|
1516
|
+
given directory path instead of the current one.
|
|
1382
1517
|
"""
|
|
1383
|
-
|
|
1518
|
+
|
|
1519
|
+
parsed = urlparse(uri)
|
|
1520
|
+
if parsed.scheme == "file":
|
|
1384
1521
|
uri = unquote(
|
|
1385
|
-
|
|
1522
|
+
parsed.path
|
|
1386
1523
|
) # this should strip off the local file scheme; it will be added back
|
|
1524
|
+
parsed = urlparse(uri)
|
|
1387
1525
|
|
|
1388
1526
|
# account for the scheme-less case, which should be coerced to a local absolute path
|
|
1389
|
-
if
|
|
1390
|
-
|
|
1527
|
+
if parsed.scheme == "":
|
|
1528
|
+
if dir_path is not None:
|
|
1529
|
+
# To support relative paths from a particular directory, join
|
|
1530
|
+
# the directory on. If uri is already an abs path, join() will
|
|
1531
|
+
# not do anything
|
|
1532
|
+
abs_path = os.path.join(dir_path, uri)
|
|
1533
|
+
else:
|
|
1534
|
+
abs_path = os.path.abspath(uri)
|
|
1391
1535
|
if not os.path.exists(abs_path) and check_existence:
|
|
1392
1536
|
raise FileNotFoundError(
|
|
1393
1537
|
f'Could not find local file "{abs_path}" when importing "{uri}".\n'
|
|
@@ -1903,7 +2047,7 @@ def cacheDirName(workflowID: str) -> str:
|
|
|
1903
2047
|
return f"cache-{workflowID}"
|
|
1904
2048
|
|
|
1905
2049
|
|
|
1906
|
-
def getDirSizeRecursively(dirPath:
|
|
2050
|
+
def getDirSizeRecursively(dirPath: StrPath) -> int:
|
|
1907
2051
|
"""
|
|
1908
2052
|
This method will return the cumulative number of bytes occupied by the files
|
|
1909
2053
|
on disk in the directory and its subdirectories.
|
toil/cwl/cwltoil.py
CHANGED
|
@@ -111,7 +111,7 @@ from toil.batchSystems.abstractBatchSystem import InsufficientSystemResources
|
|
|
111
111
|
from toil.batchSystems.registry import DEFAULT_BATCH_SYSTEM
|
|
112
112
|
from toil.common import Config, Toil, addOptions
|
|
113
113
|
from toil.cwl import check_cwltool_version
|
|
114
|
-
from toil.lib.
|
|
114
|
+
from toil.lib.trs import resolve_workflow
|
|
115
115
|
from toil.lib.misc import call_command
|
|
116
116
|
from toil.provisioners.clusterScaler import JobTooBigError
|
|
117
117
|
|
|
@@ -1214,7 +1214,7 @@ def toil_make_tool(
|
|
|
1214
1214
|
return cwltool.workflow.default_make_tool(toolpath_object, loadingContext)
|
|
1215
1215
|
|
|
1216
1216
|
|
|
1217
|
-
# When a file we want to have is missing, we can give it this
|
|
1217
|
+
# When a file we want to have is missing, we can give it this sentinel location
|
|
1218
1218
|
# URI instead of raising an error right away, in case it is optional.
|
|
1219
1219
|
MISSING_FILE = "missing://"
|
|
1220
1220
|
|
|
@@ -1812,6 +1812,9 @@ def convert_file_uri_to_toil_uri(
|
|
|
1812
1812
|
# with unsupportedRequirement when retrieving later with getFile
|
|
1813
1813
|
elif file_uri.startswith("_:"):
|
|
1814
1814
|
return file_uri
|
|
1815
|
+
elif file_uri.startswith(MISSING_FILE):
|
|
1816
|
+
# We cannot import a missing file
|
|
1817
|
+
raise FileNotFoundError(f"Could not find {file_uri[len(MISSING_FILE):]}")
|
|
1815
1818
|
else:
|
|
1816
1819
|
file_uri = existing.get(file_uri, file_uri)
|
|
1817
1820
|
if file_uri not in index:
|
|
@@ -1876,7 +1879,7 @@ def extract_file_uri_once(
|
|
|
1876
1879
|
):
|
|
1877
1880
|
if mark_broken:
|
|
1878
1881
|
logger.debug("File %s is missing", file_metadata)
|
|
1879
|
-
file_metadata["location"] = location = MISSING_FILE
|
|
1882
|
+
file_metadata["location"] = location = MISSING_FILE + location
|
|
1880
1883
|
else:
|
|
1881
1884
|
raise cwl_utils.errors.WorkflowException(
|
|
1882
1885
|
"File is missing: %s" % file_metadata
|
|
@@ -2976,7 +2979,7 @@ def makeRootJob(
|
|
|
2976
2979
|
# This will consist of files that we were not able to get a file size for
|
|
2977
2980
|
leader_metadata = dict()
|
|
2978
2981
|
for filename, file_data in metadata.items():
|
|
2979
|
-
if file_data
|
|
2982
|
+
if file_data[2] is None: # size
|
|
2980
2983
|
leader_metadata[filename] = file_data
|
|
2981
2984
|
else:
|
|
2982
2985
|
worker_metadata[filename] = file_data
|
|
@@ -3599,6 +3602,7 @@ class CWLInstallImportsJob(Job):
|
|
|
3599
3602
|
"""
|
|
3600
3603
|
Given a mapping of filenames to Toil file IDs, replace the filename with the file IDs throughout the CWL object.
|
|
3601
3604
|
"""
|
|
3605
|
+
|
|
3602
3606
|
def fill_in_file(filename: str) -> FileID:
|
|
3603
3607
|
"""
|
|
3604
3608
|
Return the file name's associated Toil file ID
|
|
@@ -3954,10 +3958,10 @@ def filtered_secondary_files(
|
|
|
3954
3958
|
sf,
|
|
3955
3959
|
)
|
|
3956
3960
|
# remove secondary files that are not present in the filestore or pointing
|
|
3957
|
-
# to
|
|
3961
|
+
# to existent things on disk
|
|
3958
3962
|
for sf in intermediate_secondary_files:
|
|
3959
3963
|
sf_loc = cast(str, sf.get("location", ""))
|
|
3960
|
-
if sf_loc
|
|
3964
|
+
if not sf_loc.startswith(MISSING_FILE) or sf.get("class", "") == "Directory":
|
|
3961
3965
|
# Pass imported files, and all Directories
|
|
3962
3966
|
final_secondary_files.append(sf)
|
|
3963
3967
|
else:
|
|
@@ -4166,15 +4170,15 @@ def get_options(args: list[str]) -> Namespace:
|
|
|
4166
4170
|
description=textwrap.dedent(
|
|
4167
4171
|
"""
|
|
4168
4172
|
positional arguments:
|
|
4169
|
-
|
|
4173
|
+
|
|
4170
4174
|
WORKFLOW CWL file to run.
|
|
4171
4175
|
|
|
4172
4176
|
INFILE YAML or JSON file of workflow inputs.
|
|
4173
|
-
|
|
4177
|
+
|
|
4174
4178
|
WF_OPTIONS Additional inputs to the workflow as command-line
|
|
4175
4179
|
flags. If CWL workflow takes an input, the name of the
|
|
4176
4180
|
input can be used as an option. For example:
|
|
4177
|
-
|
|
4181
|
+
|
|
4178
4182
|
%(prog)s workflow.cwl --file1 file
|
|
4179
4183
|
|
|
4180
4184
|
If an input has the same name as a Toil option, pass
|
|
@@ -4261,8 +4265,18 @@ def main(args: Optional[list[str]] = None, stdout: TextIO = sys.stdout) -> int:
|
|
|
4261
4265
|
runtime_context.move_outputs = "leave"
|
|
4262
4266
|
runtime_context.rm_tmpdir = False
|
|
4263
4267
|
runtime_context.streaming_allowed = not options.disable_streaming
|
|
4268
|
+
if options.cachedir is not None:
|
|
4269
|
+
runtime_context.cachedir = os.path.abspath(options.cachedir)
|
|
4270
|
+
# Automatically bypass the file store to be compatible with cwltool caching
|
|
4271
|
+
# Otherwise, the CWL caching code makes links to temporary local copies
|
|
4272
|
+
# of filestore files and caches those.
|
|
4273
|
+
logger.debug("CWL task caching is turned on. Bypassing file store.")
|
|
4274
|
+
options.bypass_file_store = True
|
|
4264
4275
|
if options.mpi_config_file is not None:
|
|
4265
4276
|
runtime_context.mpi_config = MpiConfig.load(options.mpi_config_file)
|
|
4277
|
+
if cwltool.main.check_working_directories(runtime_context) is not None:
|
|
4278
|
+
logger.error("Failed to create directory. If using tmpdir_prefix, tmpdir_outdir_prefix, or cachedir, consider changing directory locations.")
|
|
4279
|
+
return 1
|
|
4266
4280
|
setattr(runtime_context, "bypass_file_store", options.bypass_file_store)
|
|
4267
4281
|
if options.bypass_file_store and options.destBucket:
|
|
4268
4282
|
# We use the file store to write to buckets, so we can't do this (yet?)
|
|
@@ -4293,6 +4307,10 @@ def main(args: Optional[list[str]] = None, stdout: TextIO = sys.stdout) -> int:
|
|
|
4293
4307
|
|
|
4294
4308
|
try:
|
|
4295
4309
|
|
|
4310
|
+
# We might have workflow metadata to pass to Toil
|
|
4311
|
+
workflow_name=None
|
|
4312
|
+
trs_spec = None
|
|
4313
|
+
|
|
4296
4314
|
if not options.restart:
|
|
4297
4315
|
# Make a version of the config based on the initial options, for
|
|
4298
4316
|
# setting up CWL option stuff
|
|
@@ -4302,7 +4320,9 @@ def main(args: Optional[list[str]] = None, stdout: TextIO = sys.stdout) -> int:
|
|
|
4302
4320
|
# Before showing the options to any cwltool stuff that wants to
|
|
4303
4321
|
# load the workflow, transform options.cwltool, where our
|
|
4304
4322
|
# argument for what to run is, to handle Dockstore workflows.
|
|
4305
|
-
options.cwltool = resolve_workflow(options.cwltool)
|
|
4323
|
+
options.cwltool, trs_spec = resolve_workflow(options.cwltool)
|
|
4324
|
+
# Figure out what to call the workflow
|
|
4325
|
+
workflow_name = trs_spec or options.cwltool
|
|
4306
4326
|
|
|
4307
4327
|
# TODO: why are we doing this? Does this get applied to all
|
|
4308
4328
|
# tools as a default or something?
|
|
@@ -4474,7 +4494,7 @@ def main(args: Optional[list[str]] = None, stdout: TextIO = sys.stdout) -> int:
|
|
|
4474
4494
|
logger.debug("Root tool: %s", tool)
|
|
4475
4495
|
tool = remove_pickle_problems(tool)
|
|
4476
4496
|
|
|
4477
|
-
with Toil(options) as toil:
|
|
4497
|
+
with Toil(options, workflow_name=workflow_name, trs_spec=trs_spec) as toil:
|
|
4478
4498
|
if options.restart:
|
|
4479
4499
|
outobj = toil.restart()
|
|
4480
4500
|
else:
|
|
@@ -4575,6 +4595,7 @@ def main(args: Optional[list[str]] = None, stdout: TextIO = sys.stdout) -> int:
|
|
|
4575
4595
|
InvalidImportExportUrlException,
|
|
4576
4596
|
UnimplementedURLException,
|
|
4577
4597
|
JobTooBigError,
|
|
4598
|
+
FileNotFoundError
|
|
4578
4599
|
) as err:
|
|
4579
4600
|
logging.error(err)
|
|
4580
4601
|
return 1
|