toil 9.1.1__py3-none-any.whl → 9.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 (155) hide show
  1. toil/__init__.py +5 -9
  2. toil/batchSystems/abstractBatchSystem.py +23 -22
  3. toil/batchSystems/abstractGridEngineBatchSystem.py +17 -12
  4. toil/batchSystems/awsBatch.py +8 -8
  5. toil/batchSystems/cleanup_support.py +4 -4
  6. toil/batchSystems/contained_executor.py +3 -3
  7. toil/batchSystems/gridengine.py +3 -4
  8. toil/batchSystems/htcondor.py +5 -5
  9. toil/batchSystems/kubernetes.py +65 -63
  10. toil/batchSystems/local_support.py +2 -3
  11. toil/batchSystems/lsf.py +6 -7
  12. toil/batchSystems/mesos/batchSystem.py +11 -7
  13. toil/batchSystems/mesos/test/__init__.py +1 -2
  14. toil/batchSystems/options.py +9 -10
  15. toil/batchSystems/registry.py +3 -7
  16. toil/batchSystems/singleMachine.py +8 -11
  17. toil/batchSystems/slurm.py +49 -38
  18. toil/batchSystems/torque.py +3 -4
  19. toil/bus.py +36 -34
  20. toil/common.py +129 -89
  21. toil/cwl/cwltoil.py +857 -729
  22. toil/cwl/utils.py +44 -35
  23. toil/fileStores/__init__.py +3 -1
  24. toil/fileStores/abstractFileStore.py +28 -30
  25. toil/fileStores/cachingFileStore.py +8 -8
  26. toil/fileStores/nonCachingFileStore.py +10 -21
  27. toil/job.py +159 -158
  28. toil/jobStores/abstractJobStore.py +68 -69
  29. toil/jobStores/aws/jobStore.py +249 -213
  30. toil/jobStores/aws/utils.py +13 -24
  31. toil/jobStores/fileJobStore.py +28 -22
  32. toil/jobStores/googleJobStore.py +21 -17
  33. toil/jobStores/utils.py +3 -7
  34. toil/leader.py +17 -22
  35. toil/lib/accelerators.py +6 -4
  36. toil/lib/aws/__init__.py +9 -10
  37. toil/lib/aws/ami.py +33 -19
  38. toil/lib/aws/iam.py +6 -6
  39. toil/lib/aws/s3.py +259 -157
  40. toil/lib/aws/session.py +76 -76
  41. toil/lib/aws/utils.py +51 -43
  42. toil/lib/checksum.py +19 -15
  43. toil/lib/compatibility.py +3 -2
  44. toil/lib/conversions.py +45 -18
  45. toil/lib/directory.py +29 -26
  46. toil/lib/docker.py +93 -99
  47. toil/lib/dockstore.py +77 -50
  48. toil/lib/ec2.py +39 -38
  49. toil/lib/ec2nodes.py +11 -4
  50. toil/lib/exceptions.py +8 -5
  51. toil/lib/ftp_utils.py +9 -14
  52. toil/lib/generatedEC2Lists.py +161 -20
  53. toil/lib/history.py +141 -97
  54. toil/lib/history_submission.py +163 -72
  55. toil/lib/io.py +27 -17
  56. toil/lib/memoize.py +2 -1
  57. toil/lib/misc.py +15 -11
  58. toil/lib/pipes.py +40 -25
  59. toil/lib/plugins.py +12 -8
  60. toil/lib/resources.py +1 -0
  61. toil/lib/retry.py +32 -38
  62. toil/lib/threading.py +12 -12
  63. toil/lib/throttle.py +1 -2
  64. toil/lib/trs.py +113 -51
  65. toil/lib/url.py +14 -23
  66. toil/lib/web.py +7 -2
  67. toil/options/common.py +18 -15
  68. toil/options/cwl.py +2 -2
  69. toil/options/runner.py +9 -5
  70. toil/options/wdl.py +1 -3
  71. toil/provisioners/__init__.py +9 -9
  72. toil/provisioners/abstractProvisioner.py +22 -20
  73. toil/provisioners/aws/__init__.py +20 -14
  74. toil/provisioners/aws/awsProvisioner.py +10 -8
  75. toil/provisioners/clusterScaler.py +19 -18
  76. toil/provisioners/gceProvisioner.py +2 -3
  77. toil/provisioners/node.py +11 -13
  78. toil/realtimeLogger.py +4 -4
  79. toil/resource.py +5 -5
  80. toil/server/app.py +2 -2
  81. toil/server/cli/wes_cwl_runner.py +11 -11
  82. toil/server/utils.py +18 -21
  83. toil/server/wes/abstract_backend.py +9 -8
  84. toil/server/wes/amazon_wes_utils.py +3 -3
  85. toil/server/wes/tasks.py +3 -5
  86. toil/server/wes/toil_backend.py +17 -21
  87. toil/server/wsgi_app.py +3 -3
  88. toil/serviceManager.py +3 -4
  89. toil/statsAndLogging.py +12 -13
  90. toil/test/__init__.py +33 -24
  91. toil/test/batchSystems/batchSystemTest.py +12 -11
  92. toil/test/batchSystems/batch_system_plugin_test.py +3 -5
  93. toil/test/batchSystems/test_slurm.py +38 -24
  94. toil/test/cwl/conftest.py +5 -6
  95. toil/test/cwl/cwlTest.py +194 -78
  96. toil/test/cwl/download_file_uri.json +6 -0
  97. toil/test/cwl/download_file_uri_no_hostname.json +6 -0
  98. toil/test/docs/scripts/tutorial_staging.py +1 -0
  99. toil/test/jobStores/jobStoreTest.py +9 -7
  100. toil/test/lib/aws/test_iam.py +1 -3
  101. toil/test/lib/aws/test_s3.py +1 -1
  102. toil/test/lib/dockerTest.py +9 -9
  103. toil/test/lib/test_ec2.py +12 -11
  104. toil/test/lib/test_history.py +4 -4
  105. toil/test/lib/test_trs.py +16 -14
  106. toil/test/lib/test_url.py +7 -6
  107. toil/test/lib/url_plugin_test.py +12 -18
  108. toil/test/provisioners/aws/awsProvisionerTest.py +10 -8
  109. toil/test/provisioners/clusterScalerTest.py +2 -5
  110. toil/test/provisioners/clusterTest.py +1 -3
  111. toil/test/server/serverTest.py +13 -4
  112. toil/test/sort/restart_sort.py +2 -6
  113. toil/test/sort/sort.py +3 -8
  114. toil/test/src/deferredFunctionTest.py +7 -7
  115. toil/test/src/environmentTest.py +1 -2
  116. toil/test/src/fileStoreTest.py +5 -5
  117. toil/test/src/importExportFileTest.py +5 -6
  118. toil/test/src/jobServiceTest.py +22 -14
  119. toil/test/src/jobTest.py +121 -25
  120. toil/test/src/miscTests.py +5 -7
  121. toil/test/src/promisedRequirementTest.py +8 -7
  122. toil/test/src/regularLogTest.py +2 -3
  123. toil/test/src/resourceTest.py +5 -8
  124. toil/test/src/restartDAGTest.py +5 -6
  125. toil/test/src/resumabilityTest.py +2 -2
  126. toil/test/src/retainTempDirTest.py +3 -3
  127. toil/test/src/systemTest.py +3 -3
  128. toil/test/src/threadingTest.py +1 -1
  129. toil/test/src/workerTest.py +1 -2
  130. toil/test/utils/toilDebugTest.py +6 -4
  131. toil/test/utils/toilKillTest.py +1 -1
  132. toil/test/utils/utilsTest.py +15 -14
  133. toil/test/wdl/wdltoil_test.py +247 -124
  134. toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
  135. toil/toilState.py +2 -3
  136. toil/utils/toilDebugFile.py +3 -8
  137. toil/utils/toilDebugJob.py +1 -2
  138. toil/utils/toilLaunchCluster.py +1 -2
  139. toil/utils/toilSshCluster.py +2 -0
  140. toil/utils/toilStats.py +19 -24
  141. toil/utils/toilStatus.py +11 -14
  142. toil/version.py +10 -10
  143. toil/wdl/wdltoil.py +313 -209
  144. toil/worker.py +18 -12
  145. {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/METADATA +11 -14
  146. {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/RECORD +150 -153
  147. {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/WHEEL +1 -1
  148. toil/test/cwl/staging_cat.cwl +0 -27
  149. toil/test/cwl/staging_make_file.cwl +0 -25
  150. toil/test/cwl/staging_workflow.cwl +0 -43
  151. toil/test/cwl/zero_default.cwl +0 -61
  152. toil/test/utils/ABCWorkflowDebug/ABC.txt +0 -1
  153. {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/entry_points.txt +0 -0
  154. {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/licenses/LICENSE +0 -0
  155. {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/top_level.txt +0 -0
@@ -23,29 +23,24 @@ import io
23
23
  import json
24
24
  import logging
25
25
  import multiprocessing
26
- import os
27
- import pathlib
28
- import subprocess
29
26
  import sys
30
27
  import textwrap
31
- from typing import Any, Literal, Optional, TypeVar, Union
28
+ from typing import Literal, Optional, TypeVar, Union
32
29
 
33
30
  from toil.lib.dockstore import (
34
- send_metrics,
35
- get_metrics_url,
36
- pack_workflow_metrics,
31
+ RunExecution,
32
+ TaskExecutions,
37
33
  pack_single_task_metrics,
34
+ pack_workflow_metrics,
38
35
  pack_workflow_task_set_metrics,
39
- RunExecution,
40
- TaskExecutions
36
+ send_metrics,
41
37
  )
42
- from toil.lib.history import HistoryManager, WorkflowAttemptSummary, JobAttemptSummary
43
- from toil.lib.misc import unix_seconds_to_local_time
38
+ from toil.lib.history import HistoryManager, JobAttemptSummary, WorkflowAttemptSummary
44
39
  from toil.lib.trs import parse_trs_spec
45
- from toil.lib.io import get_toil_home
46
40
 
47
41
  logger = logging.getLogger(__name__)
48
42
 
43
+
49
44
  def workflow_execution_id(workflow_attempt: WorkflowAttemptSummary) -> str:
50
45
  """
51
46
  Get the execution ID for a workflow attempt.
@@ -55,7 +50,12 @@ def workflow_execution_id(workflow_attempt: WorkflowAttemptSummary) -> str:
55
50
  Deterministic.
56
51
  """
57
52
 
58
- return workflow_attempt.workflow_id.replace("-", "_") + "_attempt_" + str(workflow_attempt.attempt_number)
53
+ return (
54
+ workflow_attempt.workflow_id.replace("-", "_")
55
+ + "_attempt_"
56
+ + str(workflow_attempt.attempt_number)
57
+ )
58
+
59
59
 
60
60
  def workflow_task_set_execution_id(workflow_attempt: WorkflowAttemptSummary) -> str:
61
61
  """
@@ -68,6 +68,7 @@ def workflow_task_set_execution_id(workflow_attempt: WorkflowAttemptSummary) ->
68
68
 
69
69
  return workflow_execution_id(workflow_attempt) + "_tasks"
70
70
 
71
+
71
72
  def job_execution_id(job_attempt: JobAttemptSummary) -> str:
72
73
  """
73
74
  Get the execution ID for a job attempt.
@@ -79,6 +80,7 @@ def job_execution_id(job_attempt: JobAttemptSummary) -> str:
79
80
 
80
81
  return job_attempt.id.replace("-", "_")
81
82
 
83
+
82
84
  def get_parsed_trs_spec(workflow_attempt: WorkflowAttemptSummary) -> tuple[str, str]:
83
85
  """
84
86
  Get the TRS ID and version of the workflow, or raise an error.
@@ -92,9 +94,12 @@ def get_parsed_trs_spec(workflow_attempt: WorkflowAttemptSummary) -> tuple[str,
92
94
  raise ValueError("Workflow cannot be submitted without a TRS spec")
93
95
  trs_id, trs_version = parse_trs_spec(workflow_attempt.workflow_trs_spec)
94
96
  if trs_version is None:
95
- raise ValueError("Workflow stored in history with TRS ID but without TRS version")
97
+ raise ValueError(
98
+ "Workflow stored in history with TRS ID but without TRS version"
99
+ )
96
100
  return trs_id, trs_version
97
101
 
102
+
98
103
  class Submission:
99
104
  """
100
105
  Class holding a package of information to submit to Dockstore, and the
@@ -111,8 +116,13 @@ class Submission:
111
116
 
112
117
  # This collects everything by TRS ID and version, and keeps separate lists for workflows and for task sets.
113
118
  # Each thing to be submitted comes along with the reference to what in the database to makr submitted when it goes in.
114
- self.data: dict[tuple[str, str], tuple[list[tuple[RunExecution, str, int]], list[tuple[TaskExecutions, list[str]]]]] = collections.defaultdict(lambda: ([], []))
115
-
119
+ self.data: dict[
120
+ tuple[str, str],
121
+ tuple[
122
+ list[tuple[RunExecution, str, int]],
123
+ list[tuple[TaskExecutions, list[str]]],
124
+ ],
125
+ ] = collections.defaultdict(lambda: ([], []))
116
126
 
117
127
  def add_workflow_attempt(self, workflow_attempt: WorkflowAttemptSummary) -> None:
118
128
  """
@@ -123,9 +133,10 @@ class Submission:
123
133
  trs_id, trs_version = get_parsed_trs_spec(workflow_attempt)
124
134
 
125
135
  # Figure out what kind of job store was used.
126
- job_store_type: Optional[str] = None
136
+ job_store_type: str | None = None
127
137
  try:
128
138
  from toil.common import Toil
139
+
129
140
  job_store_type = Toil.parseLocator(workflow_attempt.workflow_job_store)[0]
130
141
  if job_store_type not in Toil.JOB_STORE_TYPES:
131
142
  # Make sure we don't send typo'd job store types in.
@@ -146,13 +157,23 @@ class Submission:
146
157
  python_version=workflow_attempt.python_version,
147
158
  platform_system=workflow_attempt.platform_system,
148
159
  platform_machine=workflow_attempt.platform_machine,
149
- job_store_type=job_store_type
160
+ job_store_type=job_store_type,
150
161
  )
151
162
 
152
163
  # Add it to the package
153
- self.data[(trs_id, trs_version)][0].append((workflow_metrics, workflow_attempt.workflow_id, workflow_attempt.attempt_number))
164
+ self.data[(trs_id, trs_version)][0].append(
165
+ (
166
+ workflow_metrics,
167
+ workflow_attempt.workflow_id,
168
+ workflow_attempt.attempt_number,
169
+ )
170
+ )
154
171
 
155
- def add_job_attempts(self, workflow_attempt: WorkflowAttemptSummary, job_attempts: list[JobAttemptSummary]) -> None:
172
+ def add_job_attempts(
173
+ self,
174
+ workflow_attempt: WorkflowAttemptSummary,
175
+ job_attempts: list[JobAttemptSummary],
176
+ ) -> None:
156
177
  """
157
178
  Add the job attempts for a workflow attempt to the submission.
158
179
  """
@@ -170,13 +191,20 @@ class Submission:
170
191
  cores=job_attempt.cores,
171
192
  cpu_seconds=job_attempt.cpu_seconds,
172
193
  memory_bytes=job_attempt.memory_bytes,
173
- disk_bytes=job_attempt.disk_bytes
174
- ) for job_attempt in job_attempts
194
+ disk_bytes=job_attempt.disk_bytes,
195
+ )
196
+ for job_attempt in job_attempts
175
197
  ]
176
- task_set_metrics = pack_workflow_task_set_metrics(workflow_task_set_execution_id(workflow_attempt), workflow_attempt.start_time, per_task_metrics)
198
+ task_set_metrics = pack_workflow_task_set_metrics(
199
+ workflow_task_set_execution_id(workflow_attempt),
200
+ workflow_attempt.start_time,
201
+ per_task_metrics,
202
+ )
177
203
 
178
204
  # Add it to the package
179
- self.data[(trs_id, trs_version)][1].append((task_set_metrics, [job_attempt.id for job_attempt in job_attempts]))
205
+ self.data[(trs_id, trs_version)][1].append(
206
+ (task_set_metrics, [job_attempt.id for job_attempt in job_attempts])
207
+ )
180
208
 
181
209
  def empty(self) -> bool:
182
210
  """
@@ -200,16 +228,23 @@ class Submission:
200
228
 
201
229
  stream = io.StringIO()
202
230
 
203
- for (trs_id, trs_version), (workflow_metrics, task_metrics) in self.data.items():
231
+ for (trs_id, trs_version), (
232
+ workflow_metrics,
233
+ task_metrics,
234
+ ) in self.data.items():
204
235
  stream.write(f"For workflow {trs_id} version {trs_version}:\n")
205
236
  if len(workflow_metrics) > 0:
206
237
  stream.write(" Workflow executions:\n")
207
238
  for workflow_metric, _, _ in workflow_metrics:
208
- stream.write(textwrap.indent(json.dumps(workflow_metric, indent=2), ' '))
239
+ stream.write(
240
+ textwrap.indent(json.dumps(workflow_metric, indent=2), " ")
241
+ )
209
242
  if len(task_metrics) > 0:
210
243
  stream.write(" Task execution sets:\n")
211
244
  for tasks_metric, _ in task_metrics:
212
- stream.write(textwrap.indent(json.dumps(tasks_metric, indent=2), ' '))
245
+ stream.write(
246
+ textwrap.indent(json.dumps(tasks_metric, indent=2), " ")
247
+ )
213
248
 
214
249
  return stream.getvalue()
215
250
 
@@ -228,10 +263,18 @@ class Submission:
228
263
 
229
264
  all_submitted_and_marked = True
230
265
 
231
- for (trs_id, trs_version), (workflow_metrics, task_metrics) in self.data.items():
266
+ for (trs_id, trs_version), (
267
+ workflow_metrics,
268
+ task_metrics,
269
+ ) in self.data.items():
232
270
  submitted = False
233
271
  try:
234
- send_metrics(trs_id, trs_version, [item[0] for item in workflow_metrics], [item[0] for item in task_metrics])
272
+ send_metrics(
273
+ trs_id,
274
+ trs_version,
275
+ [item[0] for item in workflow_metrics],
276
+ [item[0] for item in task_metrics],
277
+ )
235
278
  submitted = True
236
279
  except:
237
280
  logger.exception("Could not submit to Dockstore")
@@ -242,7 +285,9 @@ class Submission:
242
285
  for _, workflow_id, attempt_number in workflow_metrics:
243
286
  # For each workflow attempt of this TRS ID/version that we successfully submitted, mark it
244
287
  try:
245
- HistoryManager.mark_workflow_attempt_submitted(workflow_id, attempt_number)
288
+ HistoryManager.mark_workflow_attempt_submitted(
289
+ workflow_id, attempt_number
290
+ )
246
291
  except:
247
292
  logger.exception("Could not mark workflow attempt submitted")
248
293
  all_submitted_and_marked = False
@@ -259,7 +304,9 @@ class Submission:
259
304
  return all_submitted_and_marked
260
305
 
261
306
 
262
- def create_history_submission(batch_size: Optional[int] = None, desired_tasks: Optional[int] = None) -> Submission:
307
+ def create_history_submission(
308
+ batch_size: int | None = None, desired_tasks: int | None = None
309
+ ) -> Submission:
263
310
  """
264
311
  Make a package of data about recent workflow runs to send in.
265
312
 
@@ -284,7 +331,9 @@ def create_history_submission(batch_size: Optional[int] = None, desired_tasks: O
284
331
  # count how many tasks we've colected so far.
285
332
  total_tasks = 0
286
333
 
287
- for workflow_attempt in HistoryManager.get_submittable_workflow_attempts(limit=batch_size):
334
+ for workflow_attempt in HistoryManager.get_submittable_workflow_attempts(
335
+ limit=batch_size
336
+ ):
288
337
  try:
289
338
  submission.add_workflow_attempt(workflow_attempt)
290
339
  except:
@@ -303,8 +352,14 @@ def create_history_submission(batch_size: Optional[int] = None, desired_tasks: O
303
352
  # <https://ucsc-cgl.atlassian.net/browse/SEAB-6919>), set the default task
304
353
  # limit to submit to be nonzero.
305
354
 
306
- for workflow_attempt in HistoryManager.get_workflow_attempts_with_submittable_job_attempts(limit=batch_size):
307
- job_attempts = HistoryManager.get_unsubmitted_job_attempts(workflow_attempt.workflow_id, workflow_attempt.attempt_number)
355
+ for (
356
+ workflow_attempt
357
+ ) in HistoryManager.get_workflow_attempts_with_submittable_job_attempts(
358
+ limit=batch_size
359
+ ):
360
+ job_attempts = HistoryManager.get_unsubmitted_job_attempts(
361
+ workflow_attempt.workflow_id, workflow_attempt.attempt_number
362
+ )
308
363
 
309
364
  if desired_tasks == 0 or total_tasks + len(job_attempts) > desired_tasks:
310
365
  # Don't add any more task sets to the submission
@@ -321,6 +376,7 @@ def create_history_submission(batch_size: Optional[int] = None, desired_tasks: O
321
376
 
322
377
  return submission
323
378
 
379
+
324
380
  def create_current_submission(workflow_id: str, attempt_number: int) -> Submission:
325
381
  """
326
382
  Make a package of data about the current workflow attempt to send in.
@@ -330,16 +386,22 @@ def create_current_submission(workflow_id: str, attempt_number: int) -> Submissi
330
386
 
331
387
  submission = Submission()
332
388
  try:
333
- workflow_attempt = HistoryManager.get_workflow_attempt(workflow_id, attempt_number)
389
+ workflow_attempt = HistoryManager.get_workflow_attempt(
390
+ workflow_id, attempt_number
391
+ )
334
392
  if workflow_attempt is not None and HistoryManager.enabled():
335
393
  if not workflow_attempt.submitted_to_dockstore:
336
394
  submission.add_workflow_attempt(workflow_attempt)
337
395
  if HistoryManager.enabled_job():
338
396
  try:
339
- job_attempts = HistoryManager.get_unsubmitted_job_attempts(workflow_attempt.workflow_id, workflow_attempt.attempt_number)
397
+ job_attempts = HistoryManager.get_unsubmitted_job_attempts(
398
+ workflow_attempt.workflow_id, workflow_attempt.attempt_number
399
+ )
340
400
  submission.add_job_attempts(workflow_attempt, job_attempts)
341
401
  except:
342
- logger.exception("Could not compose metrics report for workflow task set")
402
+ logger.exception(
403
+ "Could not compose metrics report for workflow task set"
404
+ )
343
405
  # Keep going with just the workflow.
344
406
  except:
345
407
  logger.exception("Could not compose metrics report for workflow execution")
@@ -347,10 +409,14 @@ def create_current_submission(workflow_id: str, attempt_number: int) -> Submissi
347
409
 
348
410
  return submission
349
411
 
412
+
350
413
  # We have dialog functions that MyPy knows can return strings from a possibly restricted set
351
- KeyType = TypeVar('KeyType', bound=str)
414
+ KeyType = TypeVar("KeyType", bound=str)
352
415
 
353
- def dialog_tkinter(title: str, text: str, options: dict[KeyType, str], timeout: float) -> Optional[KeyType]:
416
+
417
+ def dialog_tkinter(
418
+ title: str, text: str, options: dict[KeyType, str], timeout: float
419
+ ) -> KeyType | None:
354
420
  """
355
421
  Display a dialog with tkinter.
356
422
 
@@ -366,8 +432,13 @@ def dialog_tkinter(title: str, text: str, options: dict[KeyType, str], timeout:
366
432
  """
367
433
 
368
434
  # Multiprocessing queues aren't actually generic, but MyPy requires them to be.
369
- result_queue: "multiprocessing.Queue[Union[Exception, Optional[KeyType]]]" = multiprocessing.Queue()
370
- process = multiprocessing.Process(target=display_dialog_tkinter, args=(title, text, options, timeout, result_queue))
435
+ result_queue: "multiprocessing.Queue[Union[Exception, Optional[KeyType]]]" = (
436
+ multiprocessing.Queue()
437
+ )
438
+ process = multiprocessing.Process(
439
+ target=display_dialog_tkinter,
440
+ args=(title, text, options, timeout, result_queue),
441
+ )
371
442
  process.start()
372
443
  result = result_queue.get()
373
444
  process.join()
@@ -378,8 +449,13 @@ def dialog_tkinter(title: str, text: str, options: dict[KeyType, str], timeout:
378
449
  return result
379
450
 
380
451
 
381
-
382
- def display_dialog_tkinter(title: str, text: str, options: dict[KeyType, str], timeout: float, result_queue: "multiprocessing.Queue[Union[Exception, Optional[KeyType]]]") -> None:
452
+ def display_dialog_tkinter(
453
+ title: str,
454
+ text: str,
455
+ options: dict[KeyType, str],
456
+ timeout: float,
457
+ result_queue: "multiprocessing.Queue[Union[Exception, Optional[KeyType]]]",
458
+ ) -> None:
383
459
  """
384
460
  Display a dialog with tkinter in the current process.
385
461
 
@@ -415,7 +491,6 @@ def display_dialog_tkinter(title: str, text: str, options: dict[KeyType, str], t
415
491
  # So we schedule the root to go away.
416
492
  root.after(100, root.destroy)
417
493
 
418
-
419
494
  # Make a frame
420
495
  frame = ttk.Frame(root, padding=FRAME_PADDING)
421
496
  # Put it on a grid in the parent
@@ -441,7 +516,10 @@ def display_dialog_tkinter(title: str, text: str, options: dict[KeyType, str], t
441
516
  result.append(set_to)
442
517
  # Close the window.
443
518
  close_root()
444
- ttk.Button(button_frame, text=v, command=setter).grid(column=button_column, row=0)
519
+
520
+ ttk.Button(button_frame, text=v, command=setter).grid(
521
+ column=button_column, row=0
522
+ )
445
523
  button_column += 1
446
524
 
447
525
  # Buttons do not grow as the window resizes.
@@ -465,7 +543,10 @@ def display_dialog_tkinter(title: str, text: str, options: dict[KeyType, str], t
465
543
  # contents require. We can't use the required width exactly because
466
544
  # that would never let us shrink and rewrap the label, so we use the
467
545
  # button frame instead and add padding ourselves.
468
- min_width = max(int(root.winfo_fpixels("2i")), button_frame.winfo_reqwidth() + BUTTON_FRAME_PADDING + FRAME_PADDING)
546
+ min_width = max(
547
+ int(root.winfo_fpixels("2i")),
548
+ button_frame.winfo_reqwidth() + BUTTON_FRAME_PADDING + FRAME_PADDING,
549
+ )
469
550
  min_height = max(int(root.winfo_fpixels("1i")), root.winfo_reqheight())
470
551
  root.minsize(min_width, min_height)
471
552
 
@@ -473,6 +554,7 @@ def display_dialog_tkinter(title: str, text: str, options: dict[KeyType, str], t
473
554
 
474
555
  # Rewrap the text as the window size changes
475
556
  setattr(label, "last_width", 100)
557
+
476
558
  # MyPy demands a generic argument here but Python won't be able to handle
477
559
  # it until
478
560
  # <https://github.com/python/cpython/commit/42a818912bdb367c4ec2b7d58c18db35f55ebe3b>
@@ -491,7 +573,8 @@ def display_dialog_tkinter(title: str, text: str, options: dict[KeyType, str], t
491
573
  label.update_idletasks()
492
574
  # Impose minimum height
493
575
  force_fit()
494
- label.bind('<Configure>', resize_label)
576
+
577
+ label.bind("<Configure>", resize_label)
495
578
 
496
579
  # Do root sizing
497
580
  force_fit()
@@ -503,8 +586,8 @@ def display_dialog_tkinter(title: str, text: str, options: dict[KeyType, str], t
503
586
  # To make the dialog pop up over the terminal instead of behind it, we
504
587
  # lift it and temporarily make it topmost. We don't keep it topmost
505
588
  # because we want to let the user switch away from it.
506
- root.attributes('-topmost', True)
507
- root.after(10, lambda: root.attributes('-topmost', False))
589
+ root.attributes("-topmost", True)
590
+ root.after(10, lambda: root.attributes("-topmost", False))
508
591
  root.lift()
509
592
 
510
593
  # Run the window's main loop
@@ -519,7 +602,10 @@ def display_dialog_tkinter(title: str, text: str, options: dict[KeyType, str], t
519
602
  except Exception as e:
520
603
  result_queue.put(e)
521
604
 
522
- def dialog_tui(title: str, text: str, options: dict[KeyType, str], timeout: float) -> Optional[KeyType]:
605
+
606
+ def dialog_tui(
607
+ title: str, text: str, options: dict[KeyType, str], timeout: float
608
+ ) -> KeyType | None:
523
609
  """
524
610
  Display a dialog in the terminal.
525
611
 
@@ -538,7 +624,6 @@ def dialog_tui(title: str, text: str, options: dict[KeyType, str], timeout: floa
538
624
  # for how we run concurrently with it.
539
625
 
540
626
  from prompt_toolkit.shortcuts import button_dialog
541
- from prompt_toolkit.eventloop.inputhook import set_eventloop_with_inputhook
542
627
 
543
628
  # We take button options in the reverse order from how prompt_toolkit does it.
544
629
  # TODO: This is not scrollable! What if there's more than 1 screen of text?
@@ -572,7 +657,9 @@ def dialog_tui(title: str, text: str, options: dict[KeyType, str], timeout: floa
572
657
  timeout_task = loop.create_task(timeout_coroutine)
573
658
 
574
659
  # This task finishes when the first task in the set finishes.
575
- race_task = asyncio.wait({application_task, timeout_task}, return_when=asyncio.FIRST_COMPLETED)
660
+ race_task = asyncio.wait(
661
+ {application_task, timeout_task}, return_when=asyncio.FIRST_COMPLETED
662
+ )
576
663
  # Run it until then
577
664
  loop.run_until_complete(race_task)
578
665
  # Maybe the application needs to finish its exit?
@@ -585,6 +672,7 @@ def dialog_tui(title: str, text: str, options: dict[KeyType, str], timeout: floa
585
672
 
586
673
  return application_task.result()
587
674
 
675
+
588
676
  # Define the dialog form in the abstract
589
677
  Decision = Union[Literal["all"], Literal["current"], Literal["no"], Literal["never"]]
590
678
 
@@ -614,20 +702,27 @@ DIALOG_OPTIONS: dict[Decision, str] = {
614
702
  "all": "All",
615
703
  "current": "Yes",
616
704
  "no": "No",
617
- "never": "Never"
705
+ "never": "Never",
618
706
  }
619
707
 
620
708
  # Make sure the option texts are short enough; prompt_toolkit can only handle 10 characters.
621
709
  for k, v in DIALOG_OPTIONS.items():
622
- assert len(v) <= 10, f"Label for \"{k}\" dialog option is too long to work on all backends!"
710
+ assert (
711
+ len(v) <= 10
712
+ ), f'Label for "{k}" dialog option is too long to work on all backends!'
623
713
 
624
714
  # Make sure the options all have unique labels
625
- assert len(set(DIALOG_OPTIONS.values())) == len(DIALOG_OPTIONS), "Labels for dialog options are not unique!"
715
+ assert len(set(DIALOG_OPTIONS.values())) == len(
716
+ DIALOG_OPTIONS
717
+ ), "Labels for dialog options are not unique!"
626
718
 
627
719
  # How many seconds should we show the dialog for before assuming the user means "no"?
628
720
  DIALOG_TIMEOUT = 120
629
721
 
630
- def ask_user_about_publishing_metrics() -> Union[Literal["all"], Literal["current"], Literal["no"]]:
722
+
723
+ def ask_user_about_publishing_metrics() -> (
724
+ Literal["all"] | Literal["current"] | Literal["no"]
725
+ ):
631
726
  """
632
727
  Ask the user to set standing workflow submission consent.
633
728
 
@@ -636,13 +731,13 @@ def ask_user_about_publishing_metrics() -> Union[Literal["all"], Literal["curren
636
731
  :returns: The user's decision about when to publish metrics.
637
732
  """
638
733
 
639
- from toil.common import update_config, get_default_config_path
734
+ from toil.common import get_default_config_path, update_config
640
735
 
641
736
  # Find the default config path to talk about or update
642
737
  default_config_path = get_default_config_path()
643
738
 
644
739
  # Actual chatting with the user will fill this in if possible
645
- decision: Optional[Decision] = None
740
+ decision: Decision | None = None
646
741
 
647
742
  if sys.stdin.isatty() and sys.stderr.isatty():
648
743
  # IO is not redirected (except maybe JSON to a file). We might be able to raise the user.
@@ -656,10 +751,15 @@ def ask_user_about_publishing_metrics() -> Union[Literal["all"], Literal["curren
656
751
  # dialogs can't hold enough text. We don't really want to bring in
657
752
  # e.g. QT for this.
658
753
  try:
659
- strategy_decision = strategy(DIALOG_TITLE, DIALOG_TEXT.format(default_config_path), DIALOG_OPTIONS, DIALOG_TIMEOUT)
754
+ strategy_decision = strategy(
755
+ DIALOG_TITLE,
756
+ DIALOG_TEXT.format(default_config_path),
757
+ DIALOG_OPTIONS,
758
+ DIALOG_TIMEOUT,
759
+ )
660
760
  if strategy_decision is None:
661
761
  # User declined to choose. Treat that as choosing "no".
662
- logger.warning("User did not make a selection. Assuming \"no\".")
762
+ logger.warning('User did not make a selection. Assuming "no".')
663
763
  strategy_decision = "no"
664
764
  decision = strategy_decision
665
765
  break
@@ -669,7 +769,9 @@ def ask_user_about_publishing_metrics() -> Union[Literal["all"], Literal["curren
669
769
  if decision is None:
670
770
  # If we think we should be able to reach the user, but we can't, fail and make them tell us via option
671
771
  # TODO: Provide a command (or a general toil config editing command) to manage this setting
672
- logger.critical("Decide whether to publish workflow metrics and pass --publishWorkflowMetrics=[all|current|no]")
772
+ logger.critical(
773
+ "Decide whether to publish workflow metrics and pass --publishWorkflowMetrics=[all|current|no]"
774
+ )
673
775
  sys.exit(1)
674
776
 
675
777
  if decision is None:
@@ -682,14 +784,3 @@ def ask_user_about_publishing_metrics() -> Union[Literal["all"], Literal["curren
682
784
  update_config(default_config_path, "publishWorkflowMetrics", result)
683
785
 
684
786
  return result
685
-
686
-
687
-
688
-
689
-
690
-
691
-
692
-
693
-
694
-
695
-