toil 6.1.0a1__py3-none-any.whl → 8.0.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 (193) hide show
  1. toil/__init__.py +122 -315
  2. toil/batchSystems/__init__.py +1 -0
  3. toil/batchSystems/abstractBatchSystem.py +173 -89
  4. toil/batchSystems/abstractGridEngineBatchSystem.py +272 -148
  5. toil/batchSystems/awsBatch.py +244 -135
  6. toil/batchSystems/cleanup_support.py +26 -16
  7. toil/batchSystems/contained_executor.py +31 -28
  8. toil/batchSystems/gridengine.py +86 -50
  9. toil/batchSystems/htcondor.py +166 -89
  10. toil/batchSystems/kubernetes.py +632 -382
  11. toil/batchSystems/local_support.py +20 -15
  12. toil/batchSystems/lsf.py +134 -81
  13. toil/batchSystems/lsfHelper.py +13 -11
  14. toil/batchSystems/mesos/__init__.py +41 -29
  15. toil/batchSystems/mesos/batchSystem.py +290 -151
  16. toil/batchSystems/mesos/executor.py +79 -50
  17. toil/batchSystems/mesos/test/__init__.py +31 -23
  18. toil/batchSystems/options.py +46 -28
  19. toil/batchSystems/registry.py +53 -19
  20. toil/batchSystems/singleMachine.py +296 -125
  21. toil/batchSystems/slurm.py +603 -138
  22. toil/batchSystems/torque.py +47 -33
  23. toil/bus.py +186 -76
  24. toil/common.py +664 -368
  25. toil/cwl/__init__.py +1 -1
  26. toil/cwl/cwltoil.py +1136 -483
  27. toil/cwl/utils.py +17 -22
  28. toil/deferred.py +63 -42
  29. toil/exceptions.py +5 -3
  30. toil/fileStores/__init__.py +5 -5
  31. toil/fileStores/abstractFileStore.py +140 -60
  32. toil/fileStores/cachingFileStore.py +717 -269
  33. toil/fileStores/nonCachingFileStore.py +116 -87
  34. toil/job.py +1225 -368
  35. toil/jobStores/abstractJobStore.py +416 -266
  36. toil/jobStores/aws/jobStore.py +863 -477
  37. toil/jobStores/aws/utils.py +201 -120
  38. toil/jobStores/conftest.py +3 -2
  39. toil/jobStores/fileJobStore.py +292 -154
  40. toil/jobStores/googleJobStore.py +140 -74
  41. toil/jobStores/utils.py +36 -15
  42. toil/leader.py +668 -272
  43. toil/lib/accelerators.py +115 -18
  44. toil/lib/aws/__init__.py +74 -31
  45. toil/lib/aws/ami.py +122 -87
  46. toil/lib/aws/iam.py +284 -108
  47. toil/lib/aws/s3.py +31 -0
  48. toil/lib/aws/session.py +214 -39
  49. toil/lib/aws/utils.py +287 -231
  50. toil/lib/bioio.py +13 -5
  51. toil/lib/compatibility.py +11 -6
  52. toil/lib/conversions.py +104 -47
  53. toil/lib/docker.py +131 -103
  54. toil/lib/ec2.py +361 -199
  55. toil/lib/ec2nodes.py +174 -106
  56. toil/lib/encryption/_dummy.py +5 -3
  57. toil/lib/encryption/_nacl.py +10 -6
  58. toil/lib/encryption/conftest.py +1 -0
  59. toil/lib/exceptions.py +26 -7
  60. toil/lib/expando.py +5 -3
  61. toil/lib/ftp_utils.py +217 -0
  62. toil/lib/generatedEC2Lists.py +127 -19
  63. toil/lib/humanize.py +6 -2
  64. toil/lib/integration.py +341 -0
  65. toil/lib/io.py +141 -15
  66. toil/lib/iterables.py +4 -2
  67. toil/lib/memoize.py +12 -8
  68. toil/lib/misc.py +66 -21
  69. toil/lib/objects.py +2 -2
  70. toil/lib/resources.py +68 -15
  71. toil/lib/retry.py +126 -81
  72. toil/lib/threading.py +299 -82
  73. toil/lib/throttle.py +16 -15
  74. toil/options/common.py +843 -409
  75. toil/options/cwl.py +175 -90
  76. toil/options/runner.py +50 -0
  77. toil/options/wdl.py +73 -17
  78. toil/provisioners/__init__.py +117 -46
  79. toil/provisioners/abstractProvisioner.py +332 -157
  80. toil/provisioners/aws/__init__.py +70 -33
  81. toil/provisioners/aws/awsProvisioner.py +1145 -715
  82. toil/provisioners/clusterScaler.py +541 -279
  83. toil/provisioners/gceProvisioner.py +282 -179
  84. toil/provisioners/node.py +155 -79
  85. toil/realtimeLogger.py +34 -22
  86. toil/resource.py +137 -75
  87. toil/server/app.py +128 -62
  88. toil/server/celery_app.py +3 -1
  89. toil/server/cli/wes_cwl_runner.py +82 -53
  90. toil/server/utils.py +54 -28
  91. toil/server/wes/abstract_backend.py +64 -26
  92. toil/server/wes/amazon_wes_utils.py +21 -15
  93. toil/server/wes/tasks.py +121 -63
  94. toil/server/wes/toil_backend.py +142 -107
  95. toil/server/wsgi_app.py +4 -3
  96. toil/serviceManager.py +58 -22
  97. toil/statsAndLogging.py +224 -70
  98. toil/test/__init__.py +282 -183
  99. toil/test/batchSystems/batchSystemTest.py +460 -210
  100. toil/test/batchSystems/batch_system_plugin_test.py +90 -0
  101. toil/test/batchSystems/test_gridengine.py +173 -0
  102. toil/test/batchSystems/test_lsf_helper.py +67 -58
  103. toil/test/batchSystems/test_slurm.py +110 -49
  104. toil/test/cactus/__init__.py +0 -0
  105. toil/test/cactus/test_cactus_integration.py +56 -0
  106. toil/test/cwl/cwlTest.py +496 -287
  107. toil/test/cwl/measure_default_memory.cwl +12 -0
  108. toil/test/cwl/not_run_required_input.cwl +29 -0
  109. toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
  110. toil/test/cwl/seqtk_seq.cwl +1 -1
  111. toil/test/docs/scriptsTest.py +69 -46
  112. toil/test/jobStores/jobStoreTest.py +427 -264
  113. toil/test/lib/aws/test_iam.py +118 -50
  114. toil/test/lib/aws/test_s3.py +16 -9
  115. toil/test/lib/aws/test_utils.py +5 -6
  116. toil/test/lib/dockerTest.py +118 -141
  117. toil/test/lib/test_conversions.py +113 -115
  118. toil/test/lib/test_ec2.py +58 -50
  119. toil/test/lib/test_integration.py +104 -0
  120. toil/test/lib/test_misc.py +12 -5
  121. toil/test/mesos/MesosDataStructuresTest.py +23 -10
  122. toil/test/mesos/helloWorld.py +7 -6
  123. toil/test/mesos/stress.py +25 -20
  124. toil/test/options/__init__.py +13 -0
  125. toil/test/options/options.py +42 -0
  126. toil/test/provisioners/aws/awsProvisionerTest.py +320 -150
  127. toil/test/provisioners/clusterScalerTest.py +440 -250
  128. toil/test/provisioners/clusterTest.py +166 -44
  129. toil/test/provisioners/gceProvisionerTest.py +174 -100
  130. toil/test/provisioners/provisionerTest.py +25 -13
  131. toil/test/provisioners/restartScript.py +5 -4
  132. toil/test/server/serverTest.py +188 -141
  133. toil/test/sort/restart_sort.py +137 -68
  134. toil/test/sort/sort.py +134 -66
  135. toil/test/sort/sortTest.py +91 -49
  136. toil/test/src/autoDeploymentTest.py +141 -101
  137. toil/test/src/busTest.py +20 -18
  138. toil/test/src/checkpointTest.py +8 -2
  139. toil/test/src/deferredFunctionTest.py +49 -35
  140. toil/test/src/dockerCheckTest.py +32 -24
  141. toil/test/src/environmentTest.py +135 -0
  142. toil/test/src/fileStoreTest.py +539 -272
  143. toil/test/src/helloWorldTest.py +7 -4
  144. toil/test/src/importExportFileTest.py +61 -31
  145. toil/test/src/jobDescriptionTest.py +46 -21
  146. toil/test/src/jobEncapsulationTest.py +2 -0
  147. toil/test/src/jobFileStoreTest.py +74 -50
  148. toil/test/src/jobServiceTest.py +187 -73
  149. toil/test/src/jobTest.py +121 -71
  150. toil/test/src/miscTests.py +19 -18
  151. toil/test/src/promisedRequirementTest.py +82 -36
  152. toil/test/src/promisesTest.py +7 -6
  153. toil/test/src/realtimeLoggerTest.py +10 -6
  154. toil/test/src/regularLogTest.py +71 -37
  155. toil/test/src/resourceTest.py +80 -49
  156. toil/test/src/restartDAGTest.py +36 -22
  157. toil/test/src/resumabilityTest.py +9 -2
  158. toil/test/src/retainTempDirTest.py +45 -14
  159. toil/test/src/systemTest.py +12 -8
  160. toil/test/src/threadingTest.py +44 -25
  161. toil/test/src/toilContextManagerTest.py +10 -7
  162. toil/test/src/userDefinedJobArgTypeTest.py +8 -5
  163. toil/test/src/workerTest.py +73 -23
  164. toil/test/utils/toilDebugTest.py +103 -33
  165. toil/test/utils/toilKillTest.py +4 -5
  166. toil/test/utils/utilsTest.py +245 -106
  167. toil/test/wdl/wdltoil_test.py +818 -149
  168. toil/test/wdl/wdltoil_test_kubernetes.py +91 -0
  169. toil/toilState.py +120 -35
  170. toil/utils/toilConfig.py +13 -4
  171. toil/utils/toilDebugFile.py +44 -27
  172. toil/utils/toilDebugJob.py +214 -27
  173. toil/utils/toilDestroyCluster.py +11 -6
  174. toil/utils/toilKill.py +8 -3
  175. toil/utils/toilLaunchCluster.py +256 -140
  176. toil/utils/toilMain.py +37 -16
  177. toil/utils/toilRsyncCluster.py +32 -14
  178. toil/utils/toilSshCluster.py +49 -22
  179. toil/utils/toilStats.py +356 -273
  180. toil/utils/toilStatus.py +292 -139
  181. toil/utils/toilUpdateEC2Instances.py +3 -1
  182. toil/version.py +12 -12
  183. toil/wdl/utils.py +5 -5
  184. toil/wdl/wdltoil.py +3913 -1033
  185. toil/worker.py +367 -184
  186. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/LICENSE +25 -0
  187. toil-8.0.0.dist-info/METADATA +173 -0
  188. toil-8.0.0.dist-info/RECORD +253 -0
  189. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
  190. toil-6.1.0a1.dist-info/METADATA +0 -125
  191. toil-6.1.0a1.dist-info/RECORD +0 -237
  192. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
  193. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
@@ -17,18 +17,9 @@ import os
17
17
  import shutil
18
18
  import uuid
19
19
  from collections import Counter
20
+ from collections.abc import Generator
20
21
  from contextlib import contextmanager
21
- from typing import (Any,
22
- Callable,
23
- Dict,
24
- Generator,
25
- List,
26
- Optional,
27
- TextIO,
28
- Tuple,
29
- Type,
30
- Union,
31
- overload)
22
+ from typing import Any, Callable, Optional, TextIO, Union, overload
32
23
 
33
24
  from flask import send_from_directory
34
25
  from werkzeug.utils import redirect
@@ -38,22 +29,24 @@ import toil.server.wes.amazon_wes_utils as amazon_wes_utils
38
29
  from toil.bus import JobStatus, replay_message_bus
39
30
  from toil.lib.io import AtomicFileCreate
40
31
  from toil.lib.threading import global_mutex
41
- from toil.server.utils import (WorkflowStateMachine,
42
- connect_to_workflow_state_store)
43
- from toil.server.wes.abstract_backend import (OperationForbidden,
44
- TaskLog,
45
- VersionNotImplementedException,
46
- WESBackend,
47
- WorkflowConflictException,
48
- WorkflowExecutionException,
49
- WorkflowNotFoundException,
50
- handle_errors)
32
+ from toil.server.utils import WorkflowStateMachine, connect_to_workflow_state_store
33
+ from toil.server.wes.abstract_backend import (
34
+ OperationForbidden,
35
+ TaskLog,
36
+ VersionNotImplementedException,
37
+ WESBackend,
38
+ WorkflowConflictException,
39
+ WorkflowExecutionException,
40
+ WorkflowNotFoundException,
41
+ handle_errors,
42
+ )
51
43
  from toil.server.wes.tasks import MultiprocessingTaskRunner, TaskRunner
52
44
  from toil.version import baseVersion
53
45
 
54
46
  logger = logging.getLogger(__name__)
55
47
  logging.basicConfig(level=logging.INFO)
56
48
 
49
+
57
50
  class ToilWorkflow:
58
51
  def __init__(self, base_work_dir: str, state_store_url: str, run_id: str):
59
52
  """
@@ -115,24 +108,31 @@ class ToilWorkflow:
115
108
  yield None
116
109
 
117
110
  def exists(self) -> bool:
118
- """ Return True if the workflow run exists."""
111
+ """Return True if the workflow run exists."""
119
112
  return self.get_state() != "UNKNOWN"
120
113
 
121
114
  def get_state(self) -> str:
122
- """ Return the state of the current run."""
115
+ """Return the state of the current run."""
123
116
  return self.state_machine.get_current_state()
124
117
 
125
- def check_on_run(self, task_runner: Type[TaskRunner]) -> None:
118
+ def check_on_run(self, task_runner: type[TaskRunner]) -> None:
126
119
  """
127
120
  Check to make sure nothing has gone wrong in the task runner for this
128
121
  workflow. If something has, log, and fail the workflow with an error.
129
122
  """
130
- if not task_runner.is_ok(self.run_id) and self.get_state() not in ['SYSTEM_ERROR', 'EXECUTOR_ERROR', 'COMPLETE', 'CANCELED']:
131
- logger.error('Failing run %s because the task to run its leader crashed', self.run_id)
123
+ if not task_runner.is_ok(self.run_id) and self.get_state() not in [
124
+ "SYSTEM_ERROR",
125
+ "EXECUTOR_ERROR",
126
+ "COMPLETE",
127
+ "CANCELED",
128
+ ]:
129
+ logger.error(
130
+ "Failing run %s because the task to run its leader crashed", self.run_id
131
+ )
132
132
  self.state_machine.send_system_error()
133
133
 
134
134
  def set_up_run(self) -> None:
135
- """ Set up necessary directories for the run."""
135
+ """Set up necessary directories for the run."""
136
136
  # Go to queued state
137
137
  self.state_machine.send_enqueue()
138
138
 
@@ -140,11 +140,13 @@ class ToilWorkflow:
140
140
  os.makedirs(self.exec_dir, exist_ok=True)
141
141
 
142
142
  def clean_up(self) -> None:
143
- """ Clean directory and files related to the run."""
143
+ """Clean directory and files related to the run."""
144
144
  shutil.rmtree(self.scratch_dir)
145
145
  # Don't remove state; state needs to persist forever.
146
146
 
147
- def queue_run(self, task_runner: Type[TaskRunner], request: Dict[str, Any], options: List[str]) -> None:
147
+ def queue_run(
148
+ self, task_runner: type[TaskRunner], request: dict[str, Any], options: list[str]
149
+ ) -> None:
148
150
  """This workflow should be ready to run. Hand this to the task system."""
149
151
  with open(os.path.join(self.scratch_dir, "request.json"), "w") as f:
150
152
  # Save the request to disk for get_run_log()
@@ -152,8 +154,16 @@ class ToilWorkflow:
152
154
 
153
155
  try:
154
156
  # Run the task. Set the task ID the same as our run ID
155
- task_runner.run(args=(self.base_scratch_dir, self.state_store_url, self.run_id, request, options),
156
- task_id=self.run_id)
157
+ task_runner.run(
158
+ args=(
159
+ self.base_scratch_dir,
160
+ self.state_store_url,
161
+ self.run_id,
162
+ request,
163
+ options,
164
+ ),
165
+ task_id=self.run_id,
166
+ )
157
167
  except Exception:
158
168
  # Celery or the broker might be down
159
169
  self.state_machine.send_system_error()
@@ -185,23 +195,28 @@ class ToilWorkflow:
185
195
  Return the path to the standard output log, relative to the run's
186
196
  scratch_dir, or None if it doesn't exist.
187
197
  """
188
- return self._get_scratch_file_path('stdout')
198
+ return self._get_scratch_file_path("stdout")
189
199
 
190
200
  def get_stderr_path(self) -> Optional[str]:
191
201
  """
192
202
  Return the path to the standard output log, relative to the run's
193
203
  scratch_dir, or None if it doesn't exist.
194
204
  """
195
- return self._get_scratch_file_path('stderr')
205
+ return self._get_scratch_file_path("stderr")
196
206
 
197
207
  def get_messages_path(self) -> Optional[str]:
198
208
  """
199
209
  Return the path to the bus message log, relative to the run's
200
210
  scratch_dir, or None if it doesn't exist.
201
211
  """
202
- return self._get_scratch_file_path('bus_messages')
212
+ return self._get_scratch_file_path("bus_messages")
203
213
 
204
- def get_task_logs(self, filter_function: Optional[Callable[[TaskLog, JobStatus], Optional[TaskLog]]] = None) -> List[Dict[str, Union[str, int, None]]]:
214
+ def get_task_logs(
215
+ self,
216
+ filter_function: Optional[
217
+ Callable[[TaskLog, JobStatus], Optional[TaskLog]]
218
+ ] = None,
219
+ ) -> list[dict[str, Union[str, int, None]]]:
205
220
  """
206
221
  Return all the task log objects for the individual tasks in the workflow.
207
222
 
@@ -226,9 +241,12 @@ class ToilWorkflow:
226
241
  abs_path = os.path.join(self.scratch_dir, path)
227
242
  job_statuses = replay_message_bus(abs_path)
228
243
  # Compose log objects from recovered job info.
229
- logs: List[TaskLog] = []
244
+ logs: list[TaskLog] = []
230
245
  for job_status in job_statuses.values():
231
- task: Optional[TaskLog] = {"name": job_status.name, "exit_code": job_status.exit_code}
246
+ task: Optional[TaskLog] = {
247
+ "name": job_status.name,
248
+ "exit_code": job_status.exit_code,
249
+ }
232
250
  if filter_function is not None:
233
251
  # Convince MyPy the task is set
234
252
  assert task is not None
@@ -236,22 +254,26 @@ class ToilWorkflow:
236
254
  task = filter_function(task, job_status)
237
255
  if task is not None:
238
256
  logs.append(task)
239
- logger.info('Recovered task logs: %s', logs)
257
+ logger.info("Recovered task logs: %s", logs)
240
258
  return logs
241
259
  # TODO: times, log files, AWS Batch IDs if any, names from the workflow instead of IDs, commands
242
260
 
243
261
 
244
-
245
-
246
-
247
262
  class ToilBackend(WESBackend):
248
263
  """
249
264
  WES backend implemented for Toil to run CWL, WDL, or Toil workflows. This
250
265
  class is responsible for validating and executing submitted workflows.
251
266
  """
252
267
 
253
- def __init__(self, work_dir: str, state_store: Optional[str], options: List[str],
254
- dest_bucket_base: Optional[str], bypass_celery: bool = False, wes_dialect: str = "standard") -> None:
268
+ def __init__(
269
+ self,
270
+ work_dir: str,
271
+ state_store: Optional[str],
272
+ options: list[str],
273
+ dest_bucket_base: Optional[str],
274
+ bypass_celery: bool = False,
275
+ wes_dialect: str = "standard",
276
+ ) -> None:
255
277
  """
256
278
  Make a new ToilBackend for serving WES.
257
279
 
@@ -274,19 +296,21 @@ class ToilBackend(WESBackend):
274
296
  acceptable in any dialect.
275
297
  """
276
298
  for opt in options:
277
- if not opt.startswith('-'):
299
+ if not opt.startswith("-"):
278
300
  # We don't allow a value to be set across multiple arguments
279
301
  # that would need to remain in the same order.
280
- raise ValueError(f'Option {opt} does not begin with -')
302
+ raise ValueError(f"Option {opt} does not begin with -")
281
303
  super().__init__(options)
282
304
 
283
305
  # How should we generate run IDs? We apply a prefix so that we can tell
284
306
  # what things in our work directory suggest that runs exist and what
285
307
  # things don't.
286
- self.run_id_prefix = 'run-'
308
+ self.run_id_prefix = "run-"
287
309
 
288
310
  # Use this to run Celery tasks so we can swap it out for testing.
289
- self.task_runner = TaskRunner if not bypass_celery else MultiprocessingTaskRunner
311
+ self.task_runner = (
312
+ TaskRunner if not bypass_celery else MultiprocessingTaskRunner
313
+ )
290
314
  logger.info("Using task runner: %s", self.task_runner)
291
315
 
292
316
  # Record if we need to limit our WES responses for a particular
@@ -304,7 +328,7 @@ class ToilBackend(WESBackend):
304
328
 
305
329
  if state_store is None:
306
330
  # Store workflow metadata under the work_dir.
307
- self.state_store_url = os.path.join(self.work_dir, 'state_store')
331
+ self.state_store_url = os.path.join(self.work_dir, "state_store")
308
332
  else:
309
333
  # Use the provided value
310
334
  self.state_store_url = state_store
@@ -331,14 +355,14 @@ class ToilBackend(WESBackend):
331
355
  pass
332
356
  # Assign an ID to the work directory storage.
333
357
  work_dir_id = None
334
- work_dir_id_file = os.path.join(self.work_dir, 'id.txt')
358
+ work_dir_id_file = os.path.join(self.work_dir, "id.txt")
335
359
  if os.path.exists(work_dir_id_file):
336
360
  # An ID is assigned already
337
361
  with open(work_dir_id_file) as f:
338
362
  work_dir_id = uuid.UUID(f.readline().strip())
339
363
  else:
340
364
  # We need to try and assign an ID.
341
- with global_mutex(self.work_dir, 'id-assignment'):
365
+ with global_mutex(self.work_dir, "id-assignment"):
342
366
  # We need to synchronize with other processes starting up to
343
367
  # make sure we agree on an ID.
344
368
  if os.path.exists(work_dir_id_file):
@@ -350,7 +374,7 @@ class ToilBackend(WESBackend):
350
374
  with AtomicFileCreate(work_dir_id_file) as temp_file:
351
375
  # Still need to be atomic here or people not locking
352
376
  # will see an incomplete file.
353
- with open(temp_file, 'w') as f:
377
+ with open(temp_file, "w") as f:
354
378
  f.write(str(work_dir_id))
355
379
  # Now combine into one ID
356
380
  if boot_id is not None:
@@ -359,14 +383,15 @@ class ToilBackend(WESBackend):
359
383
  self.server_id = str(work_dir_id)
360
384
  logger.info("Using server ID: %s", self.server_id)
361
385
 
362
-
363
386
  self.supported_versions = {
364
387
  "py": ["3.7", "3.8", "3.9"],
365
388
  "cwl": ["v1.0", "v1.1", "v1.2"],
366
- "wdl": ["draft-2", "1.0"]
389
+ "wdl": ["draft-2", "1.0"],
367
390
  }
368
391
 
369
- def _get_run(self, run_id: str, should_exists: Optional[bool] = None) -> ToilWorkflow:
392
+ def _get_run(
393
+ self, run_id: str, should_exists: Optional[bool] = None
394
+ ) -> ToilWorkflow:
370
395
  """
371
396
  Helper method to instantiate a ToilWorkflow object.
372
397
 
@@ -387,24 +412,32 @@ class ToilBackend(WESBackend):
387
412
  # TODO: Implement multiple servers working together.
388
413
  owning_server = run.fetch_state("server_id")
389
414
  apparent_state = run.get_state()
390
- if (apparent_state not in ("UNKNOWN", "COMPLETE", "EXECUTOR_ERROR", "SYSTEM_ERROR", "CANCELED") and
391
- owning_server != self.server_id):
415
+ if (
416
+ apparent_state
417
+ not in ("UNKNOWN", "COMPLETE", "EXECUTOR_ERROR", "SYSTEM_ERROR", "CANCELED")
418
+ and owning_server != self.server_id
419
+ ):
392
420
 
393
421
  # This workflow is in a state that suggests it is doing something
394
422
  # but it appears to belong to a previous incarnation of the server,
395
423
  # and so its Celery is probably gone. Put it into system error
396
424
  # state if possible.
397
- logger.warning("Run %s in state %s appears to belong to server %s and not us, server %s. "
398
- "Its server is probably gone. Failing the workflow!",
399
- run_id, apparent_state, owning_server, self.server_id)
425
+ logger.warning(
426
+ "Run %s in state %s appears to belong to server %s and not us, server %s. "
427
+ "Its server is probably gone. Failing the workflow!",
428
+ run_id,
429
+ apparent_state,
430
+ owning_server,
431
+ self.server_id,
432
+ )
400
433
  run.state_machine.send_system_error()
401
434
 
402
435
  # Poll to make sure the run is not broken
403
436
  run.check_on_run(self.task_runner)
404
437
  return run
405
438
 
406
- def get_runs(self) -> Generator[Tuple[str, str], None, None]:
407
- """ A generator of a list of run ids and their state."""
439
+ def get_runs(self) -> Generator[tuple[str, str], None, None]:
440
+ """A generator of a list of run ids and their state."""
408
441
  if not os.path.exists(self.work_dir):
409
442
  return
410
443
 
@@ -423,25 +456,24 @@ class ToilBackend(WESBackend):
423
456
  return self._get_run(run_id, should_exists=True).get_state()
424
457
 
425
458
  @handle_errors
426
- def get_service_info(self) -> Dict[str, Any]:
427
- """ Get information about the Workflow Execution Service."""
459
+ def get_service_info(self) -> dict[str, Any]:
460
+ """Get information about the Workflow Execution Service."""
428
461
 
429
462
  state_counts = Counter(state for _, state in self.get_runs())
430
463
 
431
464
  engine_parameters = []
432
465
  for option in self.options:
433
- if '=' not in option: # flags like "--logDebug"
466
+ if "=" not in option: # flags like "--logDebug"
434
467
  k, v = option, None
435
468
  else:
436
- k, v = option.split('=', 1)
469
+ k, v = option.split("=", 1)
437
470
  engine_parameters.append((k, v))
438
471
 
439
472
  return {
440
473
  "version": baseVersion,
441
474
  "workflow_type_versions": {
442
- k: {
443
- "workflow_type_version": v
444
- } for k, v in self.supported_versions.items()
475
+ k: {"workflow_type_version": v}
476
+ for k, v in self.supported_versions.items()
445
477
  },
446
478
  "supported_wes_versions": ["1.0.0"],
447
479
  "supported_filesystem_protocols": ["file", "http", "https"],
@@ -449,10 +481,7 @@ class ToilBackend(WESBackend):
449
481
  # TODO: How can we report --destBucket here, since we pass it only
450
482
  # for CWL workflows?
451
483
  "default_workflow_engine_parameters": [
452
- {
453
- "name": key,
454
- "default_value": value
455
- }
484
+ {"name": key, "default_value": value}
456
485
  for key, value in engine_parameters
457
486
  ],
458
487
  "system_state_counts": state_counts,
@@ -460,22 +489,21 @@ class ToilBackend(WESBackend):
460
489
  }
461
490
 
462
491
  @handle_errors
463
- def list_runs(self, page_size: Optional[int] = None, page_token: Optional[str] = None) -> Dict[str, Any]:
464
- """ List the workflow runs."""
492
+ def list_runs(
493
+ self, page_size: Optional[int] = None, page_token: Optional[str] = None
494
+ ) -> dict[str, Any]:
495
+ """List the workflow runs."""
465
496
  # TODO: implement pagination
466
497
  return {
467
498
  "workflows": [
468
- {
469
- "run_id": run_id,
470
- "state": state
471
- } for run_id, state in self.get_runs()
499
+ {"run_id": run_id, "state": state} for run_id, state in self.get_runs()
472
500
  ],
473
- "next_page_token": ""
501
+ "next_page_token": "",
474
502
  }
475
503
 
476
504
  @handle_errors
477
- def run_workflow(self) -> Dict[str, str]:
478
- """ Run a workflow."""
505
+ def run_workflow(self) -> dict[str, str]:
506
+ """Run a workflow."""
479
507
  run_id = self.run_id_prefix + uuid.uuid4().hex
480
508
  run = self._get_run(run_id, should_exists=False)
481
509
 
@@ -491,7 +519,11 @@ class ToilBackend(WESBackend):
491
519
  run.clean_up()
492
520
  raise
493
521
 
494
- logger.info("Received workflow run request %s with parameters: %s", run_id, list(request.keys()))
522
+ logger.info(
523
+ "Received workflow run request %s with parameters: %s",
524
+ run_id,
525
+ list(request.keys()),
526
+ )
495
527
 
496
528
  wf_type = request["workflow_type"].lower().strip()
497
529
  version = request["workflow_type_version"]
@@ -514,21 +546,25 @@ class ToilBackend(WESBackend):
514
546
  workflow_options = list(self.options)
515
547
  if wf_type == "cwl" and self.dest_bucket_base:
516
548
  # Output to a directory under out base destination bucket URL.
517
- workflow_options.append('--destBucket=' + os.path.join(self.dest_bucket_base, run_id))
549
+ workflow_options.append(
550
+ "--destBucket=" + os.path.join(self.dest_bucket_base, run_id)
551
+ )
518
552
  # Tell it to dump its messages to a file.
519
553
  # TODO: automatically sync file names with accessors somehow.
520
- workflow_options.append('--writeMessages=' + os.path.join(run.scratch_dir, 'bus_messages'))
554
+ workflow_options.append(
555
+ "--writeMessages=" + os.path.join(run.scratch_dir, "bus_messages")
556
+ )
521
557
 
522
- logger.info(f"Putting workflow {run_id} into the queue. Waiting to be picked up...")
558
+ logger.info(
559
+ f"Putting workflow {run_id} into the queue. Waiting to be picked up..."
560
+ )
523
561
  run.queue_run(self.task_runner, request, options=workflow_options)
524
562
 
525
- return {
526
- "run_id": run_id
527
- }
563
+ return {"run_id": run_id}
528
564
 
529
565
  @handle_errors
530
- def get_run_log(self, run_id: str) -> Dict[str, Any]:
531
- """ Get detailed info about a workflow run."""
566
+ def get_run_log(self, run_id: str) -> dict[str, Any]:
567
+ """Get detailed info about a workflow run."""
532
568
  run = self._get_run(run_id, should_exists=True)
533
569
  state = run.get_state()
534
570
 
@@ -550,7 +586,7 @@ class ToilBackend(WESBackend):
550
586
  # path under that hostname. So we need to use a relative URL to the
551
587
  # logs.
552
588
  stdout = f"../../../../toil/wes/v1/logs/{run_id}/stdout"
553
- stderr =""
589
+ stderr = ""
554
590
  if run.get_stderr_path() is not None:
555
591
  # We have a standard error link.
556
592
  stderr = f"../../../../toil/wes/v1/logs/{run_id}/stderr"
@@ -564,7 +600,9 @@ class ToilBackend(WESBackend):
564
600
  filter_function = amazon_wes_utils.task_filter
565
601
  else:
566
602
  # We can emit any standard-compliant WES tasks
567
- logger.info("WES dialect %s does not require transforming tasks", self.wes_dialect)
603
+ logger.info(
604
+ "WES dialect %s does not require transforming tasks", self.wes_dialect
605
+ )
568
606
  filter_function = None
569
607
  task_logs = run.get_task_logs(filter_function=filter_function)
570
608
 
@@ -589,8 +627,8 @@ class ToilBackend(WESBackend):
589
627
  }
590
628
 
591
629
  @handle_errors
592
- def cancel_run(self, run_id: str) -> Dict[str, str]:
593
- """ Cancel a running workflow."""
630
+ def cancel_run(self, run_id: str) -> dict[str, str]:
631
+ """Cancel a running workflow."""
594
632
  run = self._get_run(run_id, should_exists=True)
595
633
 
596
634
  # Do some preflight checks on the current state.
@@ -598,31 +636,30 @@ class ToilBackend(WESBackend):
598
636
  state = run.get_state()
599
637
  if state in ("CANCELING", "CANCELED", "COMPLETE"):
600
638
  # We don't need to do anything.
601
- logger.warning(f"A user is attempting to cancel a workflow in state: '{state}'.")
639
+ logger.warning(
640
+ f"A user is attempting to cancel a workflow in state: '{state}'."
641
+ )
602
642
  elif state in ("EXECUTOR_ERROR", "SYSTEM_ERROR"):
603
643
  # Something went wrong. Let the user know.
604
- raise OperationForbidden(f"Workflow is in state: '{state}', which cannot be cancelled.")
644
+ raise OperationForbidden(
645
+ f"Workflow is in state: '{state}', which cannot be cancelled."
646
+ )
605
647
  else:
606
648
  # Go to canceling state if allowed
607
649
  run.state_machine.send_cancel()
608
650
  # Stop the run task if it is there.
609
651
  self.task_runner.cancel(run_id)
610
652
 
611
- return {
612
- "run_id": run_id
613
- }
653
+ return {"run_id": run_id}
614
654
 
615
655
  @handle_errors
616
- def get_run_status(self, run_id: str) -> Dict[str, str]:
656
+ def get_run_status(self, run_id: str) -> dict[str, str]:
617
657
  """
618
658
  Get quick status info about a workflow run, returning a simple result
619
659
  with the overall state of the workflow run.
620
660
  """
621
661
 
622
- return {
623
- "run_id": run_id,
624
- "state": self.get_state(run_id)
625
- }
662
+ return {"run_id": run_id, "state": self.get_state(run_id)}
626
663
 
627
664
  # Toil custom endpoints that are not part of the GA4GH WES spec
628
665
 
@@ -665,6 +702,4 @@ class ToilBackend(WESBackend):
665
702
  Provide a sensible result for / other than 404.
666
703
  """
667
704
  # For now just go to the service info endpoint
668
- return redirect('ga4gh/wes/v1/service-info', code=302)
669
-
670
-
705
+ return redirect("ga4gh/wes/v1/service-info", code=302)
toil/server/wsgi_app.py CHANGED
@@ -11,7 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- from typing import Any, Dict, Optional
14
+ from typing import Any, Optional
15
15
 
16
16
  from gunicorn.app.base import BaseApplication # type: ignore
17
17
 
@@ -29,7 +29,8 @@ class GunicornApplication(BaseApplication): # type: ignore
29
29
 
30
30
  For more details, see: https://docs.gunicorn.org/en/latest/custom.html
31
31
  """
32
- def __init__(self, app: object, options: Optional[Dict[str, Any]] = None):
32
+
33
+ def __init__(self, app: object, options: Optional[dict[str, Any]] = None):
33
34
  self.options = options or {}
34
35
  self.application = app
35
36
  super().__init__()
@@ -51,7 +52,7 @@ class GunicornApplication(BaseApplication): # type: ignore
51
52
  return self.application
52
53
 
53
54
 
54
- def run_app(app: object, options: Optional[Dict[str, Any]] = None) -> None:
55
+ def run_app(app: object, options: Optional[dict[str, Any]] = None) -> None:
55
56
  """
56
57
  Run a Gunicorn WSGI server.
57
58
  """