toil 7.0.0__py3-none-any.whl → 8.1.0b1__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 (197) hide show
  1. toil/__init__.py +124 -86
  2. toil/batchSystems/__init__.py +1 -0
  3. toil/batchSystems/abstractBatchSystem.py +137 -77
  4. toil/batchSystems/abstractGridEngineBatchSystem.py +211 -101
  5. toil/batchSystems/awsBatch.py +237 -128
  6. toil/batchSystems/cleanup_support.py +22 -16
  7. toil/batchSystems/contained_executor.py +30 -26
  8. toil/batchSystems/gridengine.py +85 -49
  9. toil/batchSystems/htcondor.py +164 -87
  10. toil/batchSystems/kubernetes.py +622 -386
  11. toil/batchSystems/local_support.py +17 -12
  12. toil/batchSystems/lsf.py +132 -79
  13. toil/batchSystems/lsfHelper.py +13 -11
  14. toil/batchSystems/mesos/__init__.py +41 -29
  15. toil/batchSystems/mesos/batchSystem.py +288 -149
  16. toil/batchSystems/mesos/executor.py +77 -49
  17. toil/batchSystems/mesos/test/__init__.py +31 -23
  18. toil/batchSystems/options.py +39 -29
  19. toil/batchSystems/registry.py +53 -19
  20. toil/batchSystems/singleMachine.py +293 -123
  21. toil/batchSystems/slurm.py +651 -155
  22. toil/batchSystems/torque.py +46 -32
  23. toil/bus.py +141 -73
  24. toil/common.py +784 -397
  25. toil/cwl/__init__.py +1 -1
  26. toil/cwl/cwltoil.py +1137 -534
  27. toil/cwl/utils.py +17 -22
  28. toil/deferred.py +62 -41
  29. toil/exceptions.py +5 -3
  30. toil/fileStores/__init__.py +5 -5
  31. toil/fileStores/abstractFileStore.py +88 -57
  32. toil/fileStores/cachingFileStore.py +711 -247
  33. toil/fileStores/nonCachingFileStore.py +113 -75
  34. toil/job.py +1031 -349
  35. toil/jobStores/abstractJobStore.py +387 -243
  36. toil/jobStores/aws/jobStore.py +772 -412
  37. toil/jobStores/aws/utils.py +161 -109
  38. toil/jobStores/conftest.py +1 -0
  39. toil/jobStores/fileJobStore.py +289 -151
  40. toil/jobStores/googleJobStore.py +137 -70
  41. toil/jobStores/utils.py +36 -15
  42. toil/leader.py +614 -269
  43. toil/lib/accelerators.py +115 -18
  44. toil/lib/aws/__init__.py +55 -28
  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 +204 -58
  49. toil/lib/aws/utils.py +290 -213
  50. toil/lib/bioio.py +13 -5
  51. toil/lib/compatibility.py +11 -6
  52. toil/lib/conversions.py +83 -49
  53. toil/lib/docker.py +131 -103
  54. toil/lib/dockstore.py +379 -0
  55. toil/lib/ec2.py +322 -209
  56. toil/lib/ec2nodes.py +174 -105
  57. toil/lib/encryption/_dummy.py +5 -3
  58. toil/lib/encryption/_nacl.py +10 -6
  59. toil/lib/encryption/conftest.py +1 -0
  60. toil/lib/exceptions.py +26 -7
  61. toil/lib/expando.py +4 -2
  62. toil/lib/ftp_utils.py +217 -0
  63. toil/lib/generatedEC2Lists.py +127 -19
  64. toil/lib/history.py +1271 -0
  65. toil/lib/history_submission.py +681 -0
  66. toil/lib/humanize.py +6 -2
  67. toil/lib/io.py +121 -12
  68. toil/lib/iterables.py +4 -2
  69. toil/lib/memoize.py +12 -8
  70. toil/lib/misc.py +83 -18
  71. toil/lib/objects.py +2 -2
  72. toil/lib/resources.py +19 -7
  73. toil/lib/retry.py +125 -87
  74. toil/lib/threading.py +282 -80
  75. toil/lib/throttle.py +15 -14
  76. toil/lib/trs.py +390 -0
  77. toil/lib/web.py +38 -0
  78. toil/options/common.py +850 -402
  79. toil/options/cwl.py +185 -90
  80. toil/options/runner.py +50 -0
  81. toil/options/wdl.py +70 -19
  82. toil/provisioners/__init__.py +111 -46
  83. toil/provisioners/abstractProvisioner.py +322 -157
  84. toil/provisioners/aws/__init__.py +62 -30
  85. toil/provisioners/aws/awsProvisioner.py +980 -627
  86. toil/provisioners/clusterScaler.py +541 -279
  87. toil/provisioners/gceProvisioner.py +283 -180
  88. toil/provisioners/node.py +147 -79
  89. toil/realtimeLogger.py +34 -22
  90. toil/resource.py +137 -75
  91. toil/server/app.py +127 -61
  92. toil/server/celery_app.py +3 -1
  93. toil/server/cli/wes_cwl_runner.py +84 -55
  94. toil/server/utils.py +56 -31
  95. toil/server/wes/abstract_backend.py +64 -26
  96. toil/server/wes/amazon_wes_utils.py +21 -15
  97. toil/server/wes/tasks.py +121 -63
  98. toil/server/wes/toil_backend.py +142 -107
  99. toil/server/wsgi_app.py +4 -3
  100. toil/serviceManager.py +58 -22
  101. toil/statsAndLogging.py +183 -65
  102. toil/test/__init__.py +263 -179
  103. toil/test/batchSystems/batchSystemTest.py +438 -195
  104. toil/test/batchSystems/batch_system_plugin_test.py +18 -7
  105. toil/test/batchSystems/test_gridengine.py +173 -0
  106. toil/test/batchSystems/test_lsf_helper.py +67 -58
  107. toil/test/batchSystems/test_slurm.py +265 -49
  108. toil/test/cactus/test_cactus_integration.py +20 -22
  109. toil/test/cwl/conftest.py +39 -0
  110. toil/test/cwl/cwlTest.py +375 -72
  111. toil/test/cwl/measure_default_memory.cwl +12 -0
  112. toil/test/cwl/not_run_required_input.cwl +29 -0
  113. toil/test/cwl/optional-file.cwl +18 -0
  114. toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
  115. toil/test/docs/scriptsTest.py +60 -34
  116. toil/test/jobStores/jobStoreTest.py +412 -235
  117. toil/test/lib/aws/test_iam.py +116 -48
  118. toil/test/lib/aws/test_s3.py +16 -9
  119. toil/test/lib/aws/test_utils.py +5 -6
  120. toil/test/lib/dockerTest.py +118 -141
  121. toil/test/lib/test_conversions.py +113 -115
  122. toil/test/lib/test_ec2.py +57 -49
  123. toil/test/lib/test_history.py +212 -0
  124. toil/test/lib/test_misc.py +12 -5
  125. toil/test/lib/test_trs.py +161 -0
  126. toil/test/mesos/MesosDataStructuresTest.py +23 -10
  127. toil/test/mesos/helloWorld.py +7 -6
  128. toil/test/mesos/stress.py +25 -20
  129. toil/test/options/options.py +7 -2
  130. toil/test/provisioners/aws/awsProvisionerTest.py +293 -140
  131. toil/test/provisioners/clusterScalerTest.py +440 -250
  132. toil/test/provisioners/clusterTest.py +81 -42
  133. toil/test/provisioners/gceProvisionerTest.py +174 -100
  134. toil/test/provisioners/provisionerTest.py +25 -13
  135. toil/test/provisioners/restartScript.py +5 -4
  136. toil/test/server/serverTest.py +188 -141
  137. toil/test/sort/restart_sort.py +137 -68
  138. toil/test/sort/sort.py +134 -66
  139. toil/test/sort/sortTest.py +91 -49
  140. toil/test/src/autoDeploymentTest.py +140 -100
  141. toil/test/src/busTest.py +20 -18
  142. toil/test/src/checkpointTest.py +8 -2
  143. toil/test/src/deferredFunctionTest.py +49 -35
  144. toil/test/src/dockerCheckTest.py +33 -26
  145. toil/test/src/environmentTest.py +20 -10
  146. toil/test/src/fileStoreTest.py +538 -271
  147. toil/test/src/helloWorldTest.py +7 -4
  148. toil/test/src/importExportFileTest.py +61 -31
  149. toil/test/src/jobDescriptionTest.py +32 -17
  150. toil/test/src/jobEncapsulationTest.py +2 -0
  151. toil/test/src/jobFileStoreTest.py +74 -50
  152. toil/test/src/jobServiceTest.py +187 -73
  153. toil/test/src/jobTest.py +120 -70
  154. toil/test/src/miscTests.py +19 -18
  155. toil/test/src/promisedRequirementTest.py +82 -36
  156. toil/test/src/promisesTest.py +7 -6
  157. toil/test/src/realtimeLoggerTest.py +6 -6
  158. toil/test/src/regularLogTest.py +71 -37
  159. toil/test/src/resourceTest.py +80 -49
  160. toil/test/src/restartDAGTest.py +36 -22
  161. toil/test/src/resumabilityTest.py +9 -2
  162. toil/test/src/retainTempDirTest.py +45 -14
  163. toil/test/src/systemTest.py +12 -8
  164. toil/test/src/threadingTest.py +44 -25
  165. toil/test/src/toilContextManagerTest.py +10 -7
  166. toil/test/src/userDefinedJobArgTypeTest.py +8 -5
  167. toil/test/src/workerTest.py +33 -16
  168. toil/test/utils/toilDebugTest.py +70 -58
  169. toil/test/utils/toilKillTest.py +4 -5
  170. toil/test/utils/utilsTest.py +239 -102
  171. toil/test/wdl/wdltoil_test.py +789 -148
  172. toil/test/wdl/wdltoil_test_kubernetes.py +37 -23
  173. toil/toilState.py +52 -26
  174. toil/utils/toilConfig.py +13 -4
  175. toil/utils/toilDebugFile.py +44 -27
  176. toil/utils/toilDebugJob.py +85 -25
  177. toil/utils/toilDestroyCluster.py +11 -6
  178. toil/utils/toilKill.py +8 -3
  179. toil/utils/toilLaunchCluster.py +251 -145
  180. toil/utils/toilMain.py +37 -16
  181. toil/utils/toilRsyncCluster.py +27 -14
  182. toil/utils/toilSshCluster.py +45 -22
  183. toil/utils/toilStats.py +75 -36
  184. toil/utils/toilStatus.py +226 -119
  185. toil/utils/toilUpdateEC2Instances.py +3 -1
  186. toil/version.py +6 -6
  187. toil/wdl/utils.py +5 -5
  188. toil/wdl/wdltoil.py +3528 -1053
  189. toil/worker.py +370 -149
  190. toil-8.1.0b1.dist-info/METADATA +178 -0
  191. toil-8.1.0b1.dist-info/RECORD +259 -0
  192. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/WHEEL +1 -1
  193. toil-7.0.0.dist-info/METADATA +0 -158
  194. toil-7.0.0.dist-info/RECORD +0 -244
  195. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/LICENSE +0 -0
  196. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/entry_points.txt +0 -0
  197. {toil-7.0.0.dist-info → toil-8.1.0b1.dist-info}/top_level.txt +0 -0
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
 
@@ -131,35 +131,30 @@ import sqlite3
131
131
  import time
132
132
  import traceback
133
133
  import urllib.error
134
+ from collections.abc import Generator, Iterable, Sequence
134
135
  from contextlib import contextmanager
135
- from typing import (Any,
136
- Callable,
137
- ContextManager,
138
- Generator,
139
- Iterable,
140
- List,
141
- Optional,
142
- Sequence,
143
- Tuple,
144
- Type,
145
- Union, TypeVar)
136
+ from typing import Any, Callable, ContextManager, Optional, TypeVar, Union
146
137
 
147
138
  import requests.exceptions
148
139
  import urllib3.exceptions
149
140
 
150
- SUPPORTED_HTTP_ERRORS = [http.client.HTTPException,
151
- urllib.error.HTTPError,
152
- urllib3.exceptions.HTTPError,
153
- requests.exceptions.HTTPError]
141
+ SUPPORTED_HTTP_ERRORS = [
142
+ http.client.HTTPException,
143
+ urllib.error.HTTPError,
144
+ urllib3.exceptions.HTTPError,
145
+ requests.exceptions.HTTPError,
146
+ ]
154
147
 
155
148
  try:
156
149
  import kubernetes.client.rest
150
+
157
151
  SUPPORTED_HTTP_ERRORS.append(kubernetes.client.rest.ApiException)
158
152
  except ModuleNotFoundError:
159
153
  kubernetes = None
160
154
 
161
155
  try:
162
156
  import botocore.exceptions
157
+
163
158
  SUPPORTED_HTTP_ERRORS.append(botocore.exceptions.ClientError)
164
159
  except ModuleNotFoundError:
165
160
  botocore = None
@@ -175,12 +170,14 @@ class ErrorCondition:
175
170
  whether to retry.
176
171
  """
177
172
 
178
- def __init__(self,
179
- error: Optional[Any] = None,
180
- error_codes: List[int] = None,
181
- boto_error_codes: List[str] = None,
182
- error_message_must_include: str = None,
183
- retry_on_this_condition: bool = True):
173
+ def __init__(
174
+ self,
175
+ error: Optional[Any] = None,
176
+ error_codes: list[int] = None,
177
+ boto_error_codes: list[str] = None,
178
+ error_message_must_include: str = None,
179
+ retry_on_this_condition: bool = True,
180
+ ):
184
181
  """
185
182
  Initialize this ErrorCondition.
186
183
 
@@ -227,12 +224,14 @@ class ErrorCondition:
227
224
  # There is a better way to type hint this with python 3.10
228
225
  # https://stackoverflow.com/a/68290080
229
226
  RT = TypeVar("RT")
227
+
228
+
230
229
  def retry(
231
- intervals: Optional[List] = None,
230
+ intervals: Optional[list] = None,
232
231
  infinite_retries: bool = False,
233
- errors: Optional[Sequence[Union[ErrorCondition, Type[Exception]]]] = None,
234
- log_message: Optional[Tuple[Callable, str]] = None,
235
- prepare: Optional[List[Callable]] = None,
232
+ errors: Optional[Sequence[Union[ErrorCondition, type[Exception]]]] = None,
233
+ log_message: Optional[tuple[Callable, str]] = None,
234
+ prepare: Optional[list[Callable]] = None,
236
235
  ) -> Callable[[Callable[..., RT]], Callable[..., RT]]:
237
236
  """
238
237
  Retry a function if it fails with any Exception defined in "errors".
@@ -266,7 +265,9 @@ def retry(
266
265
  errors = errors if errors else [Exception]
267
266
 
268
267
  error_conditions = {error for error in errors if isinstance(error, ErrorCondition)}
269
- retriable_errors = {error for error in errors if not isinstance(error, ErrorCondition)}
268
+ retriable_errors = {
269
+ error for error in errors if not isinstance(error, ErrorCondition)
270
+ }
270
271
 
271
272
  if log_message:
272
273
  post_message_function = log_message[0]
@@ -275,7 +276,10 @@ def retry(
275
276
  # if a generic error exists (with no restrictions),
276
277
  # delete more specific error_condition instances of it
277
278
  for error_condition in error_conditions:
278
- if error_condition.retry_on_this_condition and error_condition.error in retriable_errors:
279
+ if (
280
+ error_condition.retry_on_this_condition
281
+ and error_condition.error in retriable_errors
282
+ ):
279
283
  error_conditions.remove(error_condition)
280
284
 
281
285
  # if a more specific error exists that isn't in the general set,
@@ -306,13 +310,17 @@ def retry(
306
310
  raise
307
311
 
308
312
  interval = intervals_remaining.pop(0)
309
- logger.warning(f"Error in {func}: {e}. Retrying after {interval} s...")
313
+ logger.warning(
314
+ f"Error in {func}: {e}. Retrying after {interval} s..."
315
+ )
310
316
  time.sleep(interval)
311
317
  if prepare is not None:
312
318
  for prep_function in prepare:
313
319
  # Reset state for next attempt
314
320
  prep_function(*args, **kwargs)
321
+
315
322
  return call
323
+
316
324
  return decorate
317
325
 
318
326
 
@@ -323,17 +331,18 @@ def return_status_code(e):
323
331
 
324
332
  if botocore:
325
333
  if isinstance(e, botocore.exceptions.ClientError):
326
- return e.response.get('ResponseMetadata', {}).get('HTTPStatusCode')
334
+ return e.response.get("ResponseMetadata", {}).get("HTTPStatusCode")
327
335
 
328
336
  if isinstance(e, requests.exceptions.HTTPError):
329
337
  return e.response.status_code
330
- elif isinstance(e, http.client.HTTPException) or \
331
- isinstance(e, urllib3.exceptions.HTTPError):
338
+ elif isinstance(e, http.client.HTTPException) or isinstance(
339
+ e, urllib3.exceptions.HTTPError
340
+ ):
332
341
  return e.status
333
342
  elif isinstance(e, urllib.error.HTTPError):
334
343
  return e.code
335
344
  else:
336
- raise ValueError(f'Unsupported error type; cannot grok status code: {e}.')
345
+ raise ValueError(f"Unsupported error type; cannot grok status code: {e}.")
337
346
 
338
347
 
339
348
  def get_error_code(e: Exception) -> str:
@@ -342,21 +351,21 @@ def get_error_code(e: Exception) -> str:
342
351
 
343
352
  Returns empty string for other errors.
344
353
  """
345
- if hasattr(e, 'error_code') and isinstance(e.error_code, str):
354
+ if hasattr(e, "error_code") and isinstance(e.error_code, str):
346
355
  # A Boto 2 error
347
356
  return e.error_code
348
- if hasattr(e, 'code') and isinstance(e.code, str):
357
+ if hasattr(e, "code") and isinstance(e.code, str):
349
358
  # A (different?) Boto 2 error
350
359
  return e.code
351
- elif hasattr(e, 'response') and hasattr(e.response, 'get'):
360
+ elif hasattr(e, "response") and hasattr(e.response, "get"):
352
361
  # A Boto 3 error
353
- code = e.response.get('Error', {}).get('Code')
362
+ code = e.response.get("Error", {}).get("Code")
354
363
  if isinstance(code, str):
355
364
  return code
356
365
  else:
357
- return ''
366
+ return ""
358
367
  else:
359
- return ''
368
+ return ""
360
369
 
361
370
 
362
371
  def get_error_message(e: Exception) -> str:
@@ -366,18 +375,18 @@ def get_error_message(e: Exception) -> str:
366
375
  Note that error message conditions also check more than this; this function
367
376
  does not fall back to the traceback for incompatible types.
368
377
  """
369
- if hasattr(e, 'error_message') and isinstance(e.error_message, str):
378
+ if hasattr(e, "error_message") and isinstance(e.error_message, str):
370
379
  # A Boto 2 error
371
380
  return e.error_message
372
- elif hasattr(e, 'response') and hasattr(e.response, 'get'):
381
+ elif hasattr(e, "response") and hasattr(e.response, "get"):
373
382
  # A Boto 3 error
374
- message = e.response.get('Error', {}).get('Message')
383
+ message = e.response.get("Error", {}).get("Message")
375
384
  if isinstance(message, str):
376
385
  return message
377
386
  else:
378
- return ''
387
+ return ""
379
388
  else:
380
- return ''
389
+ return ""
381
390
 
382
391
 
383
392
  def get_error_status(e: Exception) -> int:
@@ -391,22 +400,23 @@ def get_error_status(e: Exception) -> int:
391
400
 
392
401
  Returns 0 from other errors.
393
402
  """
403
+
394
404
  def numify(x):
395
405
  """Make sure a value is an integer."""
396
406
  return int(str(x).strip())
397
407
 
398
- if hasattr(e, 'status'):
408
+ if hasattr(e, "status"):
399
409
  # A Boto 2 error, kubernetes.client.rest.ApiException,
400
410
  # http.client.HTTPException, or urllib3.exceptions.HTTPError
401
411
  return numify(e.status)
402
- elif hasattr(e, 'response'):
403
- if hasattr(e.response, 'status_code'):
412
+ elif hasattr(e, "response"):
413
+ if hasattr(e.response, "status_code"):
404
414
  # A requests.exceptions.HTTPError
405
415
  return numify(e.response.status_code)
406
- elif hasattr(e.response, 'get'):
416
+ elif hasattr(e.response, "get"):
407
417
  # A Boto 3 error
408
- return numify(e.response.get('ResponseMetadata', {}).get('HTTPStatusCode'))
409
- elif hasattr(e, 'code'):
418
+ return numify(e.response.get("ResponseMetadata", {}).get("HTTPStatusCode"))
419
+ elif hasattr(e, "code"):
410
420
  # A urllib.error.HTTPError
411
421
  return numify(e.code)
412
422
  else:
@@ -419,16 +429,17 @@ def get_error_body(e: Exception) -> str:
419
429
 
420
430
  Returns the code and message if the error does not have a body.
421
431
  """
422
- if hasattr(e, 'body'):
432
+ if hasattr(e, "body"):
423
433
  # A Boto 2 error
424
434
  if isinstance(e.body, bytes):
425
435
  # Decode the body first
426
- return e.body.decode('utf-8')
436
+ return e.body.decode("utf-8")
427
437
  elif isinstance(e.body, str):
428
438
  return e.body
429
439
 
430
440
  # Anything else
431
- return f'{get_error_code(e)}: {get_error_message(e)}'
441
+ return f"{get_error_code(e)}: {get_error_message(e)}"
442
+
432
443
 
433
444
  def meets_error_message_condition(e: Exception, error_message: Optional[str]):
434
445
  if error_message:
@@ -440,7 +451,9 @@ def meets_error_message_condition(e: Exception, error_message: Optional[str]):
440
451
  if isinstance(e, botocore.exceptions.ClientError):
441
452
  return error_message in str(e)
442
453
 
443
- if isinstance(e, http.client.HTTPException) or isinstance(e, urllib3.exceptions.HTTPError):
454
+ if isinstance(e, http.client.HTTPException) or isinstance(
455
+ e, urllib3.exceptions.HTTPError
456
+ ):
444
457
  return error_message in e.reason
445
458
  elif isinstance(e, sqlite3.OperationalError):
446
459
  return error_message in str(e)
@@ -448,7 +461,7 @@ def meets_error_message_condition(e: Exception, error_message: Optional[str]):
448
461
  return error_message in e.msg
449
462
  elif isinstance(e, requests.exceptions.HTTPError):
450
463
  return error_message in e.raw
451
- elif hasattr(e, 'msg'):
464
+ elif hasattr(e, "msg"):
452
465
  return error_message in e.msg
453
466
  else:
454
467
  return error_message in traceback.format_exc()
@@ -456,7 +469,7 @@ def meets_error_message_condition(e: Exception, error_message: Optional[str]):
456
469
  return True
457
470
 
458
471
 
459
- def meets_error_code_condition(e: Exception, error_codes: Optional[List[int]]):
472
+ def meets_error_code_condition(e: Exception, error_codes: Optional[list[int]]):
460
473
  """These are expected to be normal HTTP error codes, like 404 or 500."""
461
474
  if error_codes:
462
475
  status_code = get_error_status(e)
@@ -465,7 +478,9 @@ def meets_error_code_condition(e: Exception, error_codes: Optional[List[int]]):
465
478
  return True
466
479
 
467
480
 
468
- def meets_boto_error_code_condition(e: Exception, boto_error_codes: Optional[List[str]]):
481
+ def meets_boto_error_code_condition(
482
+ e: Exception, boto_error_codes: Optional[list[str]]
483
+ ):
469
484
  """These are expected to be AWS's custom error aliases, like 'BucketNotFound' or 'AccessDenied'."""
470
485
  if boto_error_codes:
471
486
  status_code = get_error_code(e)
@@ -478,21 +493,37 @@ def error_meets_conditions(e, error_conditions):
478
493
  condition_met = False
479
494
  for error in error_conditions:
480
495
  if isinstance(e, error.error):
481
- if error.error_codes or error.boto_error_codes or error.error_message_must_include:
482
- error_message_condition_met = meets_error_message_condition(e, error.error_message_must_include)
483
- error_code_condition_met = meets_error_code_condition(e, error.error_codes)
484
- boto_error_code_condition_met = meets_boto_error_code_condition(e, error.boto_error_codes)
485
- if error_message_condition_met and error_code_condition_met and boto_error_code_condition_met:
496
+ if (
497
+ error.error_codes
498
+ or error.boto_error_codes
499
+ or error.error_message_must_include
500
+ ):
501
+ error_message_condition_met = meets_error_message_condition(
502
+ e, error.error_message_must_include
503
+ )
504
+ error_code_condition_met = meets_error_code_condition(
505
+ e, error.error_codes
506
+ )
507
+ boto_error_code_condition_met = meets_boto_error_code_condition(
508
+ e, error.boto_error_codes
509
+ )
510
+ if (
511
+ error_message_condition_met
512
+ and error_code_condition_met
513
+ and boto_error_code_condition_met
514
+ ):
486
515
  if not error.retry_on_this_condition:
487
516
  return False
488
517
  condition_met = True
489
518
  return condition_met
490
519
 
520
+
491
521
  DEFAULT_DELAYS = (0, 1, 1, 4, 16, 64)
492
522
  DEFAULT_TIMEOUT = 300
493
523
 
494
524
  E = TypeVar("E", bound=Exception) # so mypy understands passed through types
495
525
 
526
+
496
527
  # TODO: Replace the use of this with retry()
497
528
  # The aws provisioner and jobstore need a large refactoring to be boto3 compliant, so this is
498
529
  # still used there to avoid the duplication of future work
@@ -575,38 +606,45 @@ def old_retry(
575
606
  if timeout is None:
576
607
  timeout = DEFAULT_TIMEOUT
577
608
  if timeout > 0:
578
- go = [ None ]
609
+ go = [None]
579
610
 
580
611
  @contextmanager
581
- def repeated_attempt( delay ):
612
+ def repeated_attempt(delay):
582
613
  try:
583
614
  yield
584
615
  except Exception as e:
585
- if time.time( ) + delay < expiration:
586
- if predicate( e ):
587
- logger.info('Got %s, trying again in %is.', e, delay)
588
- time.sleep( delay )
616
+ if time.time() + delay < expiration:
617
+ if predicate(e):
618
+ logger.info("Got %s, trying again in %is.", e, delay)
619
+ time.sleep(delay)
589
620
  else:
590
- logger.error('Got a %s: %s which is not retriable according to %s', type(e), e, predicate)
621
+ logger.error(
622
+ "Got a %s: %s which is not retriable according to %s",
623
+ type(e),
624
+ e,
625
+ predicate,
626
+ )
591
627
  raise
592
628
  else:
593
- logger.error('Got %s and no time is left to retry', e)
629
+ logger.error("Got %s and no time is left to retry", e)
594
630
  raise
595
631
  else:
596
- go.pop( )
632
+ go.pop()
597
633
 
598
- delays = iter( delays )
599
- expiration = time.time( ) + timeout
600
- delay = next( delays )
634
+ delays = iter(delays)
635
+ expiration = time.time() + timeout
636
+ delay = next(delays)
601
637
  while go:
602
- yield repeated_attempt( delay )
603
- delay = next( delays, delay )
638
+ yield repeated_attempt(delay)
639
+ delay = next(delays, delay)
604
640
  else:
641
+
605
642
  @contextmanager
606
- def single_attempt( ):
643
+ def single_attempt():
607
644
  yield
608
645
 
609
- yield single_attempt( )
646
+ yield single_attempt()
647
+
610
648
 
611
649
  # Decorator to retry tests that fail. Needs to be called with
612
650
  # prepare=[tearDown, setUp] if the test class has tear down and set up that