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/lib/io.py
CHANGED
|
@@ -6,14 +6,37 @@ import stat
|
|
|
6
6
|
import sys
|
|
7
7
|
import tempfile
|
|
8
8
|
import uuid
|
|
9
|
-
from collections.abc import Iterator
|
|
9
|
+
from collections.abc import Iterator, Iterable
|
|
10
10
|
from contextlib import contextmanager
|
|
11
11
|
from io import BytesIO
|
|
12
12
|
from typing import IO, Any, Callable, Optional, Protocol, Union
|
|
13
13
|
|
|
14
|
+
from toil.lib.memoize import memoize
|
|
15
|
+
from toil.lib.misc import StrPath
|
|
16
|
+
|
|
14
17
|
logger = logging.getLogger(__name__)
|
|
15
18
|
|
|
16
19
|
|
|
20
|
+
@memoize
|
|
21
|
+
def get_toil_home() -> str:
|
|
22
|
+
"""
|
|
23
|
+
Get the Toil home directory for storing configuration and global state.
|
|
24
|
+
|
|
25
|
+
Raises an error if it does not exist and cannot be created. Safe to run
|
|
26
|
+
simultaneously in multiple processes.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# TODO: should this use an XDG config directory or ~/.config to not clutter the
|
|
30
|
+
# base home directory?
|
|
31
|
+
toil_home_dir = os.path.join(os.path.expanduser("~"), ".toil")
|
|
32
|
+
|
|
33
|
+
dir_path = try_path(toil_home_dir)
|
|
34
|
+
if dir_path is None:
|
|
35
|
+
raise RuntimeError(
|
|
36
|
+
f"Cannot create or access Toil configuration directory {toil_home_dir}"
|
|
37
|
+
)
|
|
38
|
+
return dir_path
|
|
39
|
+
|
|
17
40
|
TOIL_URI_SCHEME = "toilfile:"
|
|
18
41
|
|
|
19
42
|
|
|
@@ -22,6 +45,9 @@ REMOTE_SCHEMES = STANDARD_SCHEMES + [TOIL_URI_SCHEME]
|
|
|
22
45
|
ALL_SCHEMES = REMOTE_SCHEMES + ["file:"]
|
|
23
46
|
|
|
24
47
|
def is_standard_url(filename: str) -> bool:
|
|
48
|
+
"""
|
|
49
|
+
Return True if the given URL is a non-Toil, non-file: URL.
|
|
50
|
+
"""
|
|
25
51
|
return is_url_with_scheme(filename, STANDARD_SCHEMES)
|
|
26
52
|
|
|
27
53
|
def is_remote_url(filename: str) -> bool:
|
|
@@ -49,16 +75,23 @@ def is_url_with_scheme(filename: str, schemes: list[str]) -> bool:
|
|
|
49
75
|
return False
|
|
50
76
|
|
|
51
77
|
def is_toil_url(filename: str) -> bool:
|
|
78
|
+
"""
|
|
79
|
+
Return True if a URL is a toilfile: URL.
|
|
80
|
+
"""
|
|
52
81
|
return is_url_with_scheme(filename, [TOIL_URI_SCHEME])
|
|
53
82
|
|
|
54
83
|
def is_file_url(filename: str) -> bool:
|
|
55
|
-
|
|
84
|
+
"""
|
|
85
|
+
Return True if a URL is a file: URL.
|
|
56
86
|
|
|
87
|
+
Will return False for bare paths.
|
|
88
|
+
"""
|
|
89
|
+
return is_url_with_scheme(filename, ["file:"])
|
|
57
90
|
|
|
58
91
|
def mkdtemp(
|
|
59
92
|
suffix: Optional[str] = None,
|
|
60
93
|
prefix: Optional[str] = None,
|
|
61
|
-
dir: Optional[
|
|
94
|
+
dir: Optional[StrPath] = None,
|
|
62
95
|
) -> str:
|
|
63
96
|
"""
|
|
64
97
|
Make a temporary directory like tempfile.mkdtemp, but with relaxed permissions.
|
|
@@ -153,31 +186,31 @@ def robust_rmtree(path: Union[str, bytes]) -> None:
|
|
|
153
186
|
raise
|
|
154
187
|
|
|
155
188
|
|
|
156
|
-
def atomic_tmp_file(final_path:
|
|
189
|
+
def atomic_tmp_file(final_path: StrPath) -> str:
|
|
157
190
|
"""Return a tmp file name to use with atomic_install. This will be in the
|
|
158
191
|
same directory as final_path. The temporary file will have the same extension
|
|
159
192
|
as finalPath. It the final path is in /dev (/dev/null, /dev/stdout), it is
|
|
160
193
|
returned unchanged and atomic_tmp_install will do nothing."""
|
|
161
194
|
final_dir = os.path.dirname(os.path.normpath(final_path)) # can be empty
|
|
162
195
|
if final_dir == "/dev":
|
|
163
|
-
return final_path
|
|
196
|
+
return str(final_path)
|
|
164
197
|
final_basename = os.path.basename(final_path)
|
|
165
198
|
final_ext = os.path.splitext(final_path)[1]
|
|
166
|
-
base_name = f"{final_basename}.{uuid.uuid4()}.tmp{final_ext}"
|
|
199
|
+
base_name = f"{final_basename}.{str(uuid.uuid4())}.tmp{final_ext}"
|
|
167
200
|
return os.path.join(final_dir, base_name)
|
|
168
201
|
|
|
169
202
|
|
|
170
|
-
def atomic_install(tmp_path, final_path) -> None:
|
|
203
|
+
def atomic_install(tmp_path: StrPath, final_path: StrPath) -> None:
|
|
171
204
|
"""atomic install of tmp_path as final_path"""
|
|
172
205
|
if os.path.dirname(os.path.normpath(final_path)) != "/dev":
|
|
173
206
|
os.rename(tmp_path, final_path)
|
|
174
207
|
|
|
175
208
|
|
|
176
209
|
@contextmanager
|
|
177
|
-
def AtomicFileCreate(final_path:
|
|
210
|
+
def AtomicFileCreate(final_path: StrPath, keep: bool = False) -> Iterator[str]:
|
|
178
211
|
"""Context manager to create a temporary file. Entering returns path to
|
|
179
212
|
the temporary file in the same directory as finalPath. If the code in
|
|
180
|
-
context succeeds, the file renamed to its
|
|
213
|
+
context succeeds, the file renamed to its actual name. If an error
|
|
181
214
|
occurs, the file is not installed and is removed unless keep is specified.
|
|
182
215
|
"""
|
|
183
216
|
tmp_path = atomic_tmp_file(final_path)
|
|
@@ -229,23 +262,23 @@ def make_public_dir(in_directory: str, suggested_name: Optional[str] = None) ->
|
|
|
229
262
|
our old default.
|
|
230
263
|
"""
|
|
231
264
|
if suggested_name is not None:
|
|
232
|
-
|
|
265
|
+
generated_dir_path1 = os.path.join(in_directory, suggested_name)
|
|
233
266
|
try:
|
|
234
|
-
os.mkdir(
|
|
235
|
-
os.chmod(
|
|
236
|
-
return
|
|
267
|
+
os.mkdir(generated_dir_path1)
|
|
268
|
+
os.chmod(generated_dir_path1, 0o777)
|
|
269
|
+
return generated_dir_path1
|
|
237
270
|
except FileExistsError:
|
|
238
271
|
pass
|
|
239
272
|
for i in range(
|
|
240
273
|
4, 32 + 1
|
|
241
274
|
): # make random uuids and truncate to lengths starting at 4 and working up to max 32
|
|
242
275
|
for _ in range(10): # make 10 attempts for each length
|
|
243
|
-
truncated_uuid
|
|
244
|
-
|
|
276
|
+
truncated_uuid = str(uuid.uuid4()).replace("-", "")[:i]
|
|
277
|
+
generated_dir_path2 = os.path.join(in_directory, truncated_uuid)
|
|
245
278
|
try:
|
|
246
|
-
os.mkdir(
|
|
247
|
-
os.chmod(
|
|
248
|
-
return
|
|
279
|
+
os.mkdir(generated_dir_path2)
|
|
280
|
+
os.chmod(generated_dir_path2, 0o777)
|
|
281
|
+
return generated_dir_path2
|
|
249
282
|
except FileExistsError:
|
|
250
283
|
pass
|
|
251
284
|
this_should_never_happen: str = os.path.join(in_directory, str(uuid.uuid4()))
|
|
@@ -307,7 +340,7 @@ class WriteWatchingStream:
|
|
|
307
340
|
|
|
308
341
|
self.backingStream = backingStream
|
|
309
342
|
# We have no write listeners yet
|
|
310
|
-
self.writeListeners = []
|
|
343
|
+
self.writeListeners: list[Callable[[int], None]] = []
|
|
311
344
|
|
|
312
345
|
def onWrite(self, listener: Callable[[int], None]) -> None:
|
|
313
346
|
"""
|
|
@@ -318,7 +351,7 @@ class WriteWatchingStream:
|
|
|
318
351
|
|
|
319
352
|
# Implement the file API from https://docs.python.org/2.4/lib/bltin-file-objects.html
|
|
320
353
|
|
|
321
|
-
def write(self, data):
|
|
354
|
+
def write(self, data: bytes) -> None:
|
|
322
355
|
"""
|
|
323
356
|
Write the given data to the file.
|
|
324
357
|
"""
|
|
@@ -330,7 +363,7 @@ class WriteWatchingStream:
|
|
|
330
363
|
# Send out notifications
|
|
331
364
|
listener(len(data))
|
|
332
365
|
|
|
333
|
-
def writelines(self, datas):
|
|
366
|
+
def writelines(self, datas: Iterable[bytes]) -> None:
|
|
334
367
|
"""
|
|
335
368
|
Write each string from the given iterable, without newlines.
|
|
336
369
|
"""
|
|
@@ -338,14 +371,14 @@ class WriteWatchingStream:
|
|
|
338
371
|
for data in datas:
|
|
339
372
|
self.write(data)
|
|
340
373
|
|
|
341
|
-
def flush(self):
|
|
374
|
+
def flush(self) -> None:
|
|
342
375
|
"""
|
|
343
376
|
Flush the backing stream.
|
|
344
377
|
"""
|
|
345
378
|
|
|
346
379
|
self.backingStream.flush()
|
|
347
380
|
|
|
348
|
-
def close(self):
|
|
381
|
+
def close(self) -> None:
|
|
349
382
|
"""
|
|
350
383
|
Close the backing stream.
|
|
351
384
|
"""
|
toil/lib/misc.py
CHANGED
|
@@ -9,10 +9,16 @@ import sys
|
|
|
9
9
|
import time
|
|
10
10
|
from collections.abc import Iterator
|
|
11
11
|
from contextlib import closing
|
|
12
|
-
from typing import Optional
|
|
12
|
+
from typing import Optional, Union
|
|
13
|
+
if sys.version_info >= (3, 10):
|
|
14
|
+
from typing import TypeAlias
|
|
15
|
+
else:
|
|
16
|
+
from typing_extensions import TypeAlias
|
|
13
17
|
|
|
14
18
|
logger = logging.getLogger(__name__)
|
|
15
19
|
|
|
20
|
+
StrPath: TypeAlias = Union[str, os.PathLike[str]]
|
|
21
|
+
FileDescriptorOrPath: TypeAlias = Union[int, bytes, os.PathLike[bytes], StrPath]
|
|
16
22
|
|
|
17
23
|
def get_public_ip() -> str:
|
|
18
24
|
"""Get the IP that this machine uses to contact the internet.
|
|
@@ -63,6 +69,24 @@ def unix_now_ms() -> float:
|
|
|
63
69
|
"""Return the current time in milliseconds since the Unix epoch."""
|
|
64
70
|
return time.time() * 1000
|
|
65
71
|
|
|
72
|
+
def unix_seconds_to_timestamp(timestamp: float) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Convert a time in seconds since the Unix epoch to an ISO 8601 string.
|
|
75
|
+
"""
|
|
76
|
+
return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc).isoformat()
|
|
77
|
+
|
|
78
|
+
def unix_seconds_to_local_time(timestamp: float) -> datetime.datetime:
|
|
79
|
+
"""
|
|
80
|
+
Returns a local time corresponding to the given number of seconds since the Unix epoch.
|
|
81
|
+
"""
|
|
82
|
+
return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc).astimezone()
|
|
83
|
+
|
|
84
|
+
def seconds_to_duration(time_difference: float) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Convert a time difference in seconds to an ISO 8601 duration string.
|
|
87
|
+
"""
|
|
88
|
+
return f"PT{time_difference:.3f}S"
|
|
89
|
+
|
|
66
90
|
|
|
67
91
|
def slow_down(seconds: float) -> float:
|
|
68
92
|
"""
|
toil/lib/resources.py
CHANGED
|
@@ -17,6 +17,7 @@ import os
|
|
|
17
17
|
import resource
|
|
18
18
|
import sys
|
|
19
19
|
|
|
20
|
+
from toil.lib.misc import StrPath
|
|
20
21
|
|
|
21
22
|
class ResourceMonitor:
|
|
22
23
|
"""
|
|
@@ -89,7 +90,7 @@ class ResourceMonitor:
|
|
|
89
90
|
)
|
|
90
91
|
|
|
91
92
|
|
|
92
|
-
def glob(glob_pattern: str, directoryname:
|
|
93
|
+
def glob(glob_pattern: str, directoryname: StrPath) -> list[str]:
|
|
93
94
|
"""
|
|
94
95
|
Walks through a directory and its subdirectories looking for files matching
|
|
95
96
|
the glob_pattern and returns a list=[].
|
toil/lib/retry.py
CHANGED
|
@@ -24,21 +24,21 @@ objects wrapping Exceptions to include additional conditions.
|
|
|
24
24
|
|
|
25
25
|
For example, retrying on a one Exception (HTTPError)::
|
|
26
26
|
|
|
27
|
-
from
|
|
27
|
+
from toil.lib.web import web_session
|
|
28
28
|
from requests.exceptions import HTTPError
|
|
29
29
|
|
|
30
30
|
@retry(errors=[HTTPError])
|
|
31
31
|
def update_my_wallpaper():
|
|
32
|
-
return get('https://www.deviantart.com/')
|
|
32
|
+
return web_session.get('https://www.deviantart.com/')
|
|
33
33
|
|
|
34
34
|
Or::
|
|
35
35
|
|
|
36
|
-
from
|
|
36
|
+
from toil.lib.web import web_session
|
|
37
37
|
from requests.exceptions import HTTPError
|
|
38
38
|
|
|
39
39
|
@retry(errors=[HTTPError, ValueError])
|
|
40
40
|
def update_my_wallpaper():
|
|
41
|
-
return get('https://www.deviantart.com/')
|
|
41
|
+
return web_session.get('https://www.deviantart.com/')
|
|
42
42
|
|
|
43
43
|
The examples above will retry for the default interval on any errors specified
|
|
44
44
|
the "errors=" arg list.
|
|
@@ -46,7 +46,7 @@ the "errors=" arg list.
|
|
|
46
46
|
To retry on specifically 500/502/503/504 errors, you could specify an ErrorCondition
|
|
47
47
|
object instead, for example::
|
|
48
48
|
|
|
49
|
-
from
|
|
49
|
+
from toil.lib.web import web_session
|
|
50
50
|
from requests.exceptions import HTTPError
|
|
51
51
|
|
|
52
52
|
@retry(errors=[
|
|
@@ -55,11 +55,11 @@ object instead, for example::
|
|
|
55
55
|
error_codes=[500, 502, 503, 504]
|
|
56
56
|
)])
|
|
57
57
|
def update_my_wallpaper():
|
|
58
|
-
return
|
|
58
|
+
return web_session.get('https://www.deviantart.com/')
|
|
59
59
|
|
|
60
60
|
To retry on specifically errors containing the phrase "NotFound"::
|
|
61
61
|
|
|
62
|
-
from
|
|
62
|
+
from toil.lib.web import web_session
|
|
63
63
|
from requests.exceptions import HTTPError
|
|
64
64
|
|
|
65
65
|
@retry(errors=[
|
|
@@ -68,11 +68,11 @@ To retry on specifically errors containing the phrase "NotFound"::
|
|
|
68
68
|
error_message_must_include="NotFound"
|
|
69
69
|
)])
|
|
70
70
|
def update_my_wallpaper():
|
|
71
|
-
return
|
|
71
|
+
return web_session.get('https://www.deviantart.com/')
|
|
72
72
|
|
|
73
73
|
To retry on all HTTPError errors EXCEPT an HTTPError containing the phrase "NotFound"::
|
|
74
74
|
|
|
75
|
-
from
|
|
75
|
+
from toil.lib.web import web_session
|
|
76
76
|
from requests.exceptions import HTTPError
|
|
77
77
|
|
|
78
78
|
@retry(errors=[
|
|
@@ -83,7 +83,7 @@ To retry on all HTTPError errors EXCEPT an HTTPError containing the phrase "NotF
|
|
|
83
83
|
retry_on_this_condition=False
|
|
84
84
|
)])
|
|
85
85
|
def update_my_wallpaper():
|
|
86
|
-
return
|
|
86
|
+
return web_session.get('https://www.deviantart.com/')
|
|
87
87
|
|
|
88
88
|
To retry on boto3's specific status errors, an example of the implementation is::
|
|
89
89
|
|
toil/lib/threading.py
CHANGED
|
@@ -36,12 +36,13 @@ import psutil
|
|
|
36
36
|
|
|
37
37
|
from toil.lib.exceptions import raise_
|
|
38
38
|
from toil.lib.io import robust_rmtree
|
|
39
|
+
from toil.lib.misc import StrPath
|
|
39
40
|
|
|
40
41
|
logger = logging.getLogger(__name__)
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
def ensure_filesystem_lockable(
|
|
44
|
-
path:
|
|
45
|
+
path: StrPath, timeout: float = 30, hint: Optional[str] = None
|
|
45
46
|
) -> None:
|
|
46
47
|
"""
|
|
47
48
|
Make sure that the filesystem used at the given path is one where locks are safe to use.
|
|
@@ -71,7 +72,7 @@ def ensure_filesystem_lockable(
|
|
|
71
72
|
# Start a child process to stat the path. See <https://unix.stackexchange.com/a/402236>.
|
|
72
73
|
# We really should call statfs but no bindings for it are in PyPI.
|
|
73
74
|
completed = subprocess.run(
|
|
74
|
-
["stat", "-f", "-c", "%T", path],
|
|
75
|
+
["stat", "-f", "-c", "%T", str(path)],
|
|
75
76
|
check=True,
|
|
76
77
|
capture_output=True,
|
|
77
78
|
timeout=timeout,
|
|
@@ -85,7 +86,7 @@ def ensure_filesystem_lockable(
|
|
|
85
86
|
# Stat didn't work. Maybe we don't have the right version of stat installed?
|
|
86
87
|
logger.warning(
|
|
87
88
|
"Could not determine filesystem type at %s because of: %s",
|
|
88
|
-
path,
|
|
89
|
+
str(path),
|
|
89
90
|
e.stderr.decode("utf-8", errors="replace").strip(),
|
|
90
91
|
)
|
|
91
92
|
# If we don't know the filesystem type, keep going anyway.
|
|
@@ -107,7 +108,7 @@ def ensure_filesystem_lockable(
|
|
|
107
108
|
# flaky with regard to locks actually locking anything).
|
|
108
109
|
logger.debug(
|
|
109
110
|
"Detected that %s has lockable filesystem type: %s",
|
|
110
|
-
path,
|
|
111
|
+
str(path),
|
|
111
112
|
filesystem_type,
|
|
112
113
|
)
|
|
113
114
|
|
|
@@ -518,7 +519,7 @@ def process_name_exists(base_dir: str, name: str) -> bool:
|
|
|
518
519
|
# Similar to the process naming system above, we define a global mutex system
|
|
519
520
|
# for critical sections, based just around file locks.
|
|
520
521
|
@contextmanager
|
|
521
|
-
def global_mutex(base_dir:
|
|
522
|
+
def global_mutex(base_dir: StrPath, mutex: str) -> Iterator[None]:
|
|
522
523
|
"""
|
|
523
524
|
Context manager that locks a mutex. The mutex is identified by the given
|
|
524
525
|
name, and scoped to the given directory. Works across all containers that
|
|
@@ -527,7 +528,7 @@ def global_mutex(base_dir: str, mutex: str) -> Iterator[None]:
|
|
|
527
528
|
|
|
528
529
|
Only works between processes, NOT between threads.
|
|
529
530
|
|
|
530
|
-
:param
|
|
531
|
+
:param base_dir: Base directory to work in. Defines the shared namespace.
|
|
531
532
|
:param str mutex: Mutex to lock. Must be a permissible path component.
|
|
532
533
|
"""
|
|
533
534
|
|
|
@@ -674,7 +675,7 @@ class LastProcessStandingArena:
|
|
|
674
675
|
Consider using a try/finally; this class is not a context manager.
|
|
675
676
|
"""
|
|
676
677
|
|
|
677
|
-
def __init__(self, base_dir:
|
|
678
|
+
def __init__(self, base_dir: StrPath, name: str) -> None:
|
|
678
679
|
"""
|
|
679
680
|
Connect to the arena specified by the given base_dir and name.
|
|
680
681
|
|
|
@@ -683,12 +684,12 @@ class LastProcessStandingArena:
|
|
|
683
684
|
|
|
684
685
|
Doesn't enter or leave the arena.
|
|
685
686
|
|
|
686
|
-
:param
|
|
687
|
-
:param
|
|
687
|
+
:param base_dir: Base directory to work in. Defines the shared namespace.
|
|
688
|
+
:param name: Name of the arena. Must be a permissible path component.
|
|
688
689
|
"""
|
|
689
690
|
|
|
690
691
|
# Save the base_dir which namespaces everything
|
|
691
|
-
self.base_dir = base_dir
|
|
692
|
+
self.base_dir = str(base_dir)
|
|
692
693
|
|
|
693
694
|
# We need a mutex name to allow only one process to be entering or
|
|
694
695
|
# leaving at a time.
|