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.
Files changed (270) hide show
  1. toil/__init__.py +4 -39
  2. toil/batchSystems/abstractBatchSystem.py +1 -1
  3. toil/batchSystems/abstractGridEngineBatchSystem.py +1 -1
  4. toil/batchSystems/awsBatch.py +1 -1
  5. toil/batchSystems/cleanup_support.py +1 -1
  6. toil/batchSystems/kubernetes.py +53 -7
  7. toil/batchSystems/local_support.py +1 -1
  8. toil/batchSystems/mesos/batchSystem.py +13 -8
  9. toil/batchSystems/mesos/test/__init__.py +3 -2
  10. toil/batchSystems/options.py +1 -0
  11. toil/batchSystems/singleMachine.py +1 -1
  12. toil/batchSystems/slurm.py +229 -84
  13. toil/bus.py +5 -3
  14. toil/common.py +198 -54
  15. toil/cwl/cwltoil.py +32 -11
  16. toil/job.py +110 -86
  17. toil/jobStores/abstractJobStore.py +24 -3
  18. toil/jobStores/aws/jobStore.py +46 -10
  19. toil/jobStores/fileJobStore.py +25 -1
  20. toil/jobStores/googleJobStore.py +104 -30
  21. toil/leader.py +9 -0
  22. toil/lib/accelerators.py +3 -1
  23. toil/lib/aws/session.py +14 -3
  24. toil/lib/aws/utils.py +92 -35
  25. toil/lib/aws/utils.py.orig +504 -0
  26. toil/lib/bioio.py +1 -1
  27. toil/lib/docker.py +252 -91
  28. toil/lib/dockstore.py +387 -0
  29. toil/lib/ec2nodes.py +3 -2
  30. toil/lib/exceptions.py +5 -3
  31. toil/lib/history.py +1345 -0
  32. toil/lib/history_submission.py +695 -0
  33. toil/lib/io.py +56 -23
  34. toil/lib/misc.py +25 -1
  35. toil/lib/resources.py +2 -1
  36. toil/lib/retry.py +10 -10
  37. toil/lib/threading.py +11 -10
  38. toil/lib/{integration.py → trs.py} +95 -46
  39. toil/lib/web.py +38 -0
  40. toil/options/common.py +25 -2
  41. toil/options/cwl.py +10 -0
  42. toil/options/wdl.py +11 -0
  43. toil/provisioners/gceProvisioner.py +4 -4
  44. toil/server/api_spec/LICENSE +201 -0
  45. toil/server/api_spec/README.rst +5 -0
  46. toil/server/cli/wes_cwl_runner.py +5 -4
  47. toil/server/utils.py +2 -3
  48. toil/statsAndLogging.py +35 -1
  49. toil/test/__init__.py +275 -115
  50. toil/test/batchSystems/batchSystemTest.py +227 -205
  51. toil/test/batchSystems/test_slurm.py +199 -2
  52. toil/test/cactus/pestis.tar.gz +0 -0
  53. toil/test/conftest.py +7 -0
  54. toil/test/cwl/2.fasta +11 -0
  55. toil/test/cwl/2.fastq +12 -0
  56. toil/test/cwl/conftest.py +39 -0
  57. toil/test/cwl/cwlTest.py +1015 -780
  58. toil/test/cwl/directory/directory/file.txt +15 -0
  59. toil/test/cwl/download_directory_file.json +4 -0
  60. toil/test/cwl/download_directory_s3.json +4 -0
  61. toil/test/cwl/download_file.json +6 -0
  62. toil/test/cwl/download_http.json +6 -0
  63. toil/test/cwl/download_https.json +6 -0
  64. toil/test/cwl/download_s3.json +6 -0
  65. toil/test/cwl/download_subdirectory_file.json +5 -0
  66. toil/test/cwl/download_subdirectory_s3.json +5 -0
  67. toil/test/cwl/empty.json +1 -0
  68. toil/test/cwl/mock_mpi/fake_mpi.yml +8 -0
  69. toil/test/cwl/mock_mpi/fake_mpi_run.py +42 -0
  70. toil/test/cwl/optional-file-exists.json +6 -0
  71. toil/test/cwl/optional-file-missing.json +6 -0
  72. toil/test/cwl/optional-file.cwl +18 -0
  73. toil/test/cwl/preemptible_expression.json +1 -0
  74. toil/test/cwl/revsort-job-missing.json +6 -0
  75. toil/test/cwl/revsort-job.json +6 -0
  76. toil/test/cwl/s3_secondary_file.json +16 -0
  77. toil/test/cwl/seqtk_seq_job.json +6 -0
  78. toil/test/cwl/stream.json +6 -0
  79. toil/test/cwl/test_filename_conflict_resolution.ms/table.dat +0 -0
  80. toil/test/cwl/test_filename_conflict_resolution.ms/table.f0 +0 -0
  81. toil/test/cwl/test_filename_conflict_resolution.ms/table.f1 +0 -0
  82. toil/test/cwl/test_filename_conflict_resolution.ms/table.f1i +0 -0
  83. toil/test/cwl/test_filename_conflict_resolution.ms/table.f2 +0 -0
  84. toil/test/cwl/test_filename_conflict_resolution.ms/table.f2_TSM0 +0 -0
  85. toil/test/cwl/test_filename_conflict_resolution.ms/table.f3 +0 -0
  86. toil/test/cwl/test_filename_conflict_resolution.ms/table.f3_TSM0 +0 -0
  87. toil/test/cwl/test_filename_conflict_resolution.ms/table.f4 +0 -0
  88. toil/test/cwl/test_filename_conflict_resolution.ms/table.f4_TSM0 +0 -0
  89. toil/test/cwl/test_filename_conflict_resolution.ms/table.f5 +0 -0
  90. toil/test/cwl/test_filename_conflict_resolution.ms/table.info +0 -0
  91. toil/test/cwl/test_filename_conflict_resolution.ms/table.lock +0 -0
  92. toil/test/cwl/whale.txt +16 -0
  93. toil/test/docs/scripts/example_alwaysfail.py +38 -0
  94. toil/test/docs/scripts/example_alwaysfail_with_files.wdl +33 -0
  95. toil/test/docs/scripts/example_cachingbenchmark.py +117 -0
  96. toil/test/docs/scripts/stagingExampleFiles/in.txt +1 -0
  97. toil/test/docs/scripts/stagingExampleFiles/out.txt +2 -0
  98. toil/test/docs/scripts/tutorial_arguments.py +23 -0
  99. toil/test/docs/scripts/tutorial_debugging.patch +12 -0
  100. toil/test/docs/scripts/tutorial_debugging_hangs.wdl +126 -0
  101. toil/test/docs/scripts/tutorial_debugging_works.wdl +129 -0
  102. toil/test/docs/scripts/tutorial_docker.py +20 -0
  103. toil/test/docs/scripts/tutorial_dynamic.py +24 -0
  104. toil/test/docs/scripts/tutorial_encapsulation.py +28 -0
  105. toil/test/docs/scripts/tutorial_encapsulation2.py +29 -0
  106. toil/test/docs/scripts/tutorial_helloworld.py +15 -0
  107. toil/test/docs/scripts/tutorial_invokeworkflow.py +27 -0
  108. toil/test/docs/scripts/tutorial_invokeworkflow2.py +30 -0
  109. toil/test/docs/scripts/tutorial_jobfunctions.py +22 -0
  110. toil/test/docs/scripts/tutorial_managing.py +29 -0
  111. toil/test/docs/scripts/tutorial_managing2.py +56 -0
  112. toil/test/docs/scripts/tutorial_multiplejobs.py +25 -0
  113. toil/test/docs/scripts/tutorial_multiplejobs2.py +21 -0
  114. toil/test/docs/scripts/tutorial_multiplejobs3.py +22 -0
  115. toil/test/docs/scripts/tutorial_promises.py +25 -0
  116. toil/test/docs/scripts/tutorial_promises2.py +30 -0
  117. toil/test/docs/scripts/tutorial_quickstart.py +22 -0
  118. toil/test/docs/scripts/tutorial_requirements.py +44 -0
  119. toil/test/docs/scripts/tutorial_services.py +45 -0
  120. toil/test/docs/scripts/tutorial_staging.py +45 -0
  121. toil/test/docs/scripts/tutorial_stats.py +64 -0
  122. toil/test/lib/aws/test_iam.py +3 -1
  123. toil/test/lib/dockerTest.py +205 -122
  124. toil/test/lib/test_history.py +236 -0
  125. toil/test/lib/test_trs.py +161 -0
  126. toil/test/provisioners/aws/awsProvisionerTest.py +12 -9
  127. toil/test/provisioners/clusterTest.py +4 -4
  128. toil/test/provisioners/gceProvisionerTest.py +16 -14
  129. toil/test/sort/sort.py +4 -1
  130. toil/test/src/busTest.py +17 -17
  131. toil/test/src/deferredFunctionTest.py +145 -132
  132. toil/test/src/importExportFileTest.py +71 -63
  133. toil/test/src/jobEncapsulationTest.py +27 -28
  134. toil/test/src/jobServiceTest.py +149 -133
  135. toil/test/src/jobTest.py +219 -211
  136. toil/test/src/miscTests.py +66 -60
  137. toil/test/src/promisedRequirementTest.py +163 -169
  138. toil/test/src/regularLogTest.py +24 -24
  139. toil/test/src/resourceTest.py +82 -76
  140. toil/test/src/restartDAGTest.py +51 -47
  141. toil/test/src/resumabilityTest.py +24 -19
  142. toil/test/src/retainTempDirTest.py +60 -57
  143. toil/test/src/systemTest.py +17 -13
  144. toil/test/src/threadingTest.py +29 -32
  145. toil/test/utils/ABCWorkflowDebug/B_file.txt +1 -0
  146. toil/test/utils/ABCWorkflowDebug/debugWorkflow.py +204 -0
  147. toil/test/utils/ABCWorkflowDebug/mkFile.py +16 -0
  148. toil/test/utils/ABCWorkflowDebug/sleep.cwl +12 -0
  149. toil/test/utils/ABCWorkflowDebug/sleep.yaml +1 -0
  150. toil/test/utils/toilDebugTest.py +117 -102
  151. toil/test/utils/toilKillTest.py +54 -53
  152. toil/test/utils/utilsTest.py +303 -229
  153. toil/test/wdl/lint_error.wdl +9 -0
  154. toil/test/wdl/md5sum/empty_file.json +1 -0
  155. toil/test/wdl/md5sum/md5sum-gs.json +1 -0
  156. toil/test/wdl/md5sum/md5sum.1.0.wdl +32 -0
  157. toil/test/wdl/md5sum/md5sum.input +1 -0
  158. toil/test/wdl/md5sum/md5sum.json +1 -0
  159. toil/test/wdl/md5sum/md5sum.wdl +25 -0
  160. toil/test/wdl/miniwdl_self_test/inputs-namespaced.json +1 -0
  161. toil/test/wdl/miniwdl_self_test/inputs.json +1 -0
  162. toil/test/wdl/miniwdl_self_test/self_test.wdl +40 -0
  163. toil/test/wdl/standard_library/as_map.json +16 -0
  164. toil/test/wdl/standard_library/as_map_as_input.wdl +23 -0
  165. toil/test/wdl/standard_library/as_pairs.json +7 -0
  166. toil/test/wdl/standard_library/as_pairs_as_input.wdl +23 -0
  167. toil/test/wdl/standard_library/ceil.json +3 -0
  168. toil/test/wdl/standard_library/ceil_as_command.wdl +16 -0
  169. toil/test/wdl/standard_library/ceil_as_input.wdl +16 -0
  170. toil/test/wdl/standard_library/collect_by_key.json +1 -0
  171. toil/test/wdl/standard_library/collect_by_key_as_input.wdl +23 -0
  172. toil/test/wdl/standard_library/cross.json +11 -0
  173. toil/test/wdl/standard_library/cross_as_input.wdl +19 -0
  174. toil/test/wdl/standard_library/flatten.json +7 -0
  175. toil/test/wdl/standard_library/flatten_as_input.wdl +18 -0
  176. toil/test/wdl/standard_library/floor.json +3 -0
  177. toil/test/wdl/standard_library/floor_as_command.wdl +16 -0
  178. toil/test/wdl/standard_library/floor_as_input.wdl +16 -0
  179. toil/test/wdl/standard_library/keys.json +8 -0
  180. toil/test/wdl/standard_library/keys_as_input.wdl +24 -0
  181. toil/test/wdl/standard_library/length.json +7 -0
  182. toil/test/wdl/standard_library/length_as_input.wdl +16 -0
  183. toil/test/wdl/standard_library/length_as_input_with_map.json +7 -0
  184. toil/test/wdl/standard_library/length_as_input_with_map.wdl +17 -0
  185. toil/test/wdl/standard_library/length_invalid.json +3 -0
  186. toil/test/wdl/standard_library/range.json +3 -0
  187. toil/test/wdl/standard_library/range_0.json +3 -0
  188. toil/test/wdl/standard_library/range_as_input.wdl +17 -0
  189. toil/test/wdl/standard_library/range_invalid.json +3 -0
  190. toil/test/wdl/standard_library/read_boolean.json +3 -0
  191. toil/test/wdl/standard_library/read_boolean_as_command.wdl +17 -0
  192. toil/test/wdl/standard_library/read_float.json +3 -0
  193. toil/test/wdl/standard_library/read_float_as_command.wdl +17 -0
  194. toil/test/wdl/standard_library/read_int.json +3 -0
  195. toil/test/wdl/standard_library/read_int_as_command.wdl +17 -0
  196. toil/test/wdl/standard_library/read_json.json +3 -0
  197. toil/test/wdl/standard_library/read_json_as_output.wdl +31 -0
  198. toil/test/wdl/standard_library/read_lines.json +3 -0
  199. toil/test/wdl/standard_library/read_lines_as_output.wdl +31 -0
  200. toil/test/wdl/standard_library/read_map.json +3 -0
  201. toil/test/wdl/standard_library/read_map_as_output.wdl +31 -0
  202. toil/test/wdl/standard_library/read_string.json +3 -0
  203. toil/test/wdl/standard_library/read_string_as_command.wdl +17 -0
  204. toil/test/wdl/standard_library/read_tsv.json +3 -0
  205. toil/test/wdl/standard_library/read_tsv_as_output.wdl +31 -0
  206. toil/test/wdl/standard_library/round.json +3 -0
  207. toil/test/wdl/standard_library/round_as_command.wdl +16 -0
  208. toil/test/wdl/standard_library/round_as_input.wdl +16 -0
  209. toil/test/wdl/standard_library/size.json +3 -0
  210. toil/test/wdl/standard_library/size_as_command.wdl +17 -0
  211. toil/test/wdl/standard_library/size_as_output.wdl +36 -0
  212. toil/test/wdl/standard_library/stderr.json +3 -0
  213. toil/test/wdl/standard_library/stderr_as_output.wdl +30 -0
  214. toil/test/wdl/standard_library/stdout.json +3 -0
  215. toil/test/wdl/standard_library/stdout_as_output.wdl +30 -0
  216. toil/test/wdl/standard_library/sub.json +3 -0
  217. toil/test/wdl/standard_library/sub_as_input.wdl +17 -0
  218. toil/test/wdl/standard_library/sub_as_input_with_file.wdl +17 -0
  219. toil/test/wdl/standard_library/transpose.json +6 -0
  220. toil/test/wdl/standard_library/transpose_as_input.wdl +18 -0
  221. toil/test/wdl/standard_library/write_json.json +6 -0
  222. toil/test/wdl/standard_library/write_json_as_command.wdl +17 -0
  223. toil/test/wdl/standard_library/write_lines.json +7 -0
  224. toil/test/wdl/standard_library/write_lines_as_command.wdl +17 -0
  225. toil/test/wdl/standard_library/write_map.json +6 -0
  226. toil/test/wdl/standard_library/write_map_as_command.wdl +17 -0
  227. toil/test/wdl/standard_library/write_tsv.json +6 -0
  228. toil/test/wdl/standard_library/write_tsv_as_command.wdl +17 -0
  229. toil/test/wdl/standard_library/zip.json +12 -0
  230. toil/test/wdl/standard_library/zip_as_input.wdl +19 -0
  231. toil/test/wdl/test.csv +3 -0
  232. toil/test/wdl/test.tsv +3 -0
  233. toil/test/wdl/testfiles/croo.wdl +38 -0
  234. toil/test/wdl/testfiles/drop_files.wdl +62 -0
  235. toil/test/wdl/testfiles/drop_files_subworkflow.wdl +13 -0
  236. toil/test/wdl/testfiles/empty.txt +0 -0
  237. toil/test/wdl/testfiles/not_enough_outputs.wdl +33 -0
  238. toil/test/wdl/testfiles/random.wdl +66 -0
  239. toil/test/wdl/testfiles/string_file_coercion.json +1 -0
  240. toil/test/wdl/testfiles/string_file_coercion.wdl +35 -0
  241. toil/test/wdl/testfiles/test.json +4 -0
  242. toil/test/wdl/testfiles/test_boolean.txt +1 -0
  243. toil/test/wdl/testfiles/test_float.txt +1 -0
  244. toil/test/wdl/testfiles/test_int.txt +1 -0
  245. toil/test/wdl/testfiles/test_lines.txt +5 -0
  246. toil/test/wdl/testfiles/test_map.txt +2 -0
  247. toil/test/wdl/testfiles/test_string.txt +1 -0
  248. toil/test/wdl/testfiles/url_to_file.wdl +13 -0
  249. toil/test/wdl/testfiles/url_to_optional_file.wdl +13 -0
  250. toil/test/wdl/testfiles/vocab.json +1 -0
  251. toil/test/wdl/testfiles/vocab.wdl +66 -0
  252. toil/test/wdl/testfiles/wait.wdl +34 -0
  253. toil/test/wdl/wdl_specification/type_pair.json +23 -0
  254. toil/test/wdl/wdl_specification/type_pair_basic.wdl +36 -0
  255. toil/test/wdl/wdl_specification/type_pair_with_files.wdl +36 -0
  256. toil/test/wdl/wdl_specification/v1_spec.json +1 -0
  257. toil/test/wdl/wdl_specification/v1_spec_declaration.wdl +39 -0
  258. toil/test/wdl/wdltoil_test.py +681 -408
  259. toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
  260. toil/version.py +10 -10
  261. toil/wdl/wdltoil.py +350 -123
  262. toil/worker.py +113 -33
  263. {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/METADATA +13 -7
  264. toil-8.2.0.dist-info/RECORD +439 -0
  265. {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/WHEEL +1 -1
  266. toil/test/lib/test_integration.py +0 -104
  267. toil-8.0.0.dist-info/RECORD +0 -253
  268. {toil-8.0.0.dist-info → toil-8.2.0.dist-info}/entry_points.txt +0 -0
  269. {toil-8.0.0.dist-info → toil-8.2.0.dist-info/licenses}/LICENSE +0 -0
  270. {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
- return is_url_with_scheme(filename, ["file:"])
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[str] = None,
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: str) -> str:
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: str, keep: bool = False) -> Iterator[str]:
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 actually name. If an error
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
- generated_dir_path: str = os.path.join(in_directory, suggested_name)
265
+ generated_dir_path1 = os.path.join(in_directory, suggested_name)
233
266
  try:
234
- os.mkdir(generated_dir_path)
235
- os.chmod(generated_dir_path, 0o777)
236
- return generated_dir_path
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: str = str(uuid.uuid4()).replace("-", "")[:i]
244
- generated_dir_path: str = os.path.join(in_directory, truncated_uuid)
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(generated_dir_path)
247
- os.chmod(generated_dir_path, 0o777)
248
- return generated_dir_path
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: str) -> list[str]:
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 requests import get
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 requests import get
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 requests import get
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 requests.get('https://www.deviantart.com/')
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 requests import get
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 requests.get('https://www.deviantart.com/')
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 requests import get
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 requests.get('https://www.deviantart.com/')
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: str, timeout: float = 30, hint: Optional[str] = None
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: str, mutex: str) -> Iterator[None]:
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 str base_dir: Base directory to work in. Defines the shared namespace.
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: str, name: str) -> None:
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 str base_dir: Base directory to work in. Defines the shared namespace.
687
- :param str name: Name of the arena. Must be a permissible path component.
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.