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
toil/lib/retry.py CHANGED
@@ -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)
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
 
@@ -224,13 +221,18 @@ class ErrorCondition:
224
221
  )
225
222
 
226
223
 
224
+ # There is a better way to type hint this with python 3.10
225
+ # https://stackoverflow.com/a/68290080
226
+ RT = TypeVar("RT")
227
+
228
+
227
229
  def retry(
228
- intervals: Optional[List] = None,
230
+ intervals: Optional[list] = None,
229
231
  infinite_retries: bool = False,
230
- errors: Optional[Sequence[Union[ErrorCondition, Type[Exception]]]] = None,
231
- log_message: Optional[Tuple[Callable, str]] = None,
232
- prepare: Optional[List[Callable]] = None,
233
- ) -> Callable[[Any], Any]:
232
+ errors: Optional[Sequence[Union[ErrorCondition, type[Exception]]]] = None,
233
+ log_message: Optional[tuple[Callable, str]] = None,
234
+ prepare: Optional[list[Callable]] = None,
235
+ ) -> Callable[[Callable[..., RT]], Callable[..., RT]]:
234
236
  """
235
237
  Retry a function if it fails with any Exception defined in "errors".
236
238
 
@@ -263,7 +265,9 @@ def retry(
263
265
  errors = errors if errors else [Exception]
264
266
 
265
267
  error_conditions = {error for error in errors if isinstance(error, ErrorCondition)}
266
- 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
+ }
267
271
 
268
272
  if log_message:
269
273
  post_message_function = log_message[0]
@@ -272,7 +276,10 @@ def retry(
272
276
  # if a generic error exists (with no restrictions),
273
277
  # delete more specific error_condition instances of it
274
278
  for error_condition in error_conditions:
275
- 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
+ ):
276
283
  error_conditions.remove(error_condition)
277
284
 
278
285
  # if a more specific error exists that isn't in the general set,
@@ -281,9 +288,9 @@ def retry(
281
288
  if error_condition.retry_on_this_condition:
282
289
  retriable_errors.add(error_condition.error)
283
290
 
284
- def decorate(func):
291
+ def decorate(func: Callable[..., RT]) -> Callable[..., RT]:
285
292
  @functools.wraps(func)
286
- def call(*args, **kwargs):
293
+ def call(*args, **kwargs) -> RT:
287
294
  intervals_remaining = copy.deepcopy(intervals)
288
295
  while True:
289
296
  try:
@@ -303,13 +310,17 @@ def retry(
303
310
  raise
304
311
 
305
312
  interval = intervals_remaining.pop(0)
306
- 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
+ )
307
316
  time.sleep(interval)
308
317
  if prepare is not None:
309
318
  for prep_function in prepare:
310
319
  # Reset state for next attempt
311
320
  prep_function(*args, **kwargs)
321
+
312
322
  return call
323
+
313
324
  return decorate
314
325
 
315
326
 
@@ -320,17 +331,18 @@ def return_status_code(e):
320
331
 
321
332
  if botocore:
322
333
  if isinstance(e, botocore.exceptions.ClientError):
323
- return e.response.get('ResponseMetadata', {}).get('HTTPStatusCode')
334
+ return e.response.get("ResponseMetadata", {}).get("HTTPStatusCode")
324
335
 
325
336
  if isinstance(e, requests.exceptions.HTTPError):
326
337
  return e.response.status_code
327
- elif isinstance(e, http.client.HTTPException) or \
328
- isinstance(e, urllib3.exceptions.HTTPError):
338
+ elif isinstance(e, http.client.HTTPException) or isinstance(
339
+ e, urllib3.exceptions.HTTPError
340
+ ):
329
341
  return e.status
330
342
  elif isinstance(e, urllib.error.HTTPError):
331
343
  return e.code
332
344
  else:
333
- raise ValueError(f'Unsupported error type; cannot grok status code: {e}.')
345
+ raise ValueError(f"Unsupported error type; cannot grok status code: {e}.")
334
346
 
335
347
 
336
348
  def get_error_code(e: Exception) -> str:
@@ -339,21 +351,21 @@ def get_error_code(e: Exception) -> str:
339
351
 
340
352
  Returns empty string for other errors.
341
353
  """
342
- if hasattr(e, 'error_code') and isinstance(e.error_code, str):
354
+ if hasattr(e, "error_code") and isinstance(e.error_code, str):
343
355
  # A Boto 2 error
344
356
  return e.error_code
345
- if hasattr(e, 'code') and isinstance(e.code, str):
357
+ if hasattr(e, "code") and isinstance(e.code, str):
346
358
  # A (different?) Boto 2 error
347
359
  return e.code
348
- elif hasattr(e, 'response') and hasattr(e.response, 'get'):
360
+ elif hasattr(e, "response") and hasattr(e.response, "get"):
349
361
  # A Boto 3 error
350
- code = e.response.get('Error', {}).get('Code')
362
+ code = e.response.get("Error", {}).get("Code")
351
363
  if isinstance(code, str):
352
364
  return code
353
365
  else:
354
- return ''
366
+ return ""
355
367
  else:
356
- return ''
368
+ return ""
357
369
 
358
370
 
359
371
  def get_error_message(e: Exception) -> str:
@@ -363,18 +375,18 @@ def get_error_message(e: Exception) -> str:
363
375
  Note that error message conditions also check more than this; this function
364
376
  does not fall back to the traceback for incompatible types.
365
377
  """
366
- if hasattr(e, 'error_message') and isinstance(e.error_message, str):
378
+ if hasattr(e, "error_message") and isinstance(e.error_message, str):
367
379
  # A Boto 2 error
368
380
  return e.error_message
369
- elif hasattr(e, 'response') and hasattr(e.response, 'get'):
381
+ elif hasattr(e, "response") and hasattr(e.response, "get"):
370
382
  # A Boto 3 error
371
- message = e.response.get('Error', {}).get('Message')
383
+ message = e.response.get("Error", {}).get("Message")
372
384
  if isinstance(message, str):
373
385
  return message
374
386
  else:
375
- return ''
387
+ return ""
376
388
  else:
377
- return ''
389
+ return ""
378
390
 
379
391
 
380
392
  def get_error_status(e: Exception) -> int:
@@ -388,22 +400,23 @@ def get_error_status(e: Exception) -> int:
388
400
 
389
401
  Returns 0 from other errors.
390
402
  """
403
+
391
404
  def numify(x):
392
405
  """Make sure a value is an integer."""
393
406
  return int(str(x).strip())
394
407
 
395
- if hasattr(e, 'status'):
408
+ if hasattr(e, "status"):
396
409
  # A Boto 2 error, kubernetes.client.rest.ApiException,
397
410
  # http.client.HTTPException, or urllib3.exceptions.HTTPError
398
411
  return numify(e.status)
399
- elif hasattr(e, 'response'):
400
- if hasattr(e.response, 'status_code'):
412
+ elif hasattr(e, "response"):
413
+ if hasattr(e.response, "status_code"):
401
414
  # A requests.exceptions.HTTPError
402
415
  return numify(e.response.status_code)
403
- elif hasattr(e.response, 'get'):
416
+ elif hasattr(e.response, "get"):
404
417
  # A Boto 3 error
405
- return numify(e.response.get('ResponseMetadata', {}).get('HTTPStatusCode'))
406
- elif hasattr(e, 'code'):
418
+ return numify(e.response.get("ResponseMetadata", {}).get("HTTPStatusCode"))
419
+ elif hasattr(e, "code"):
407
420
  # A urllib.error.HTTPError
408
421
  return numify(e.code)
409
422
  else:
@@ -416,16 +429,17 @@ def get_error_body(e: Exception) -> str:
416
429
 
417
430
  Returns the code and message if the error does not have a body.
418
431
  """
419
- if hasattr(e, 'body'):
432
+ if hasattr(e, "body"):
420
433
  # A Boto 2 error
421
434
  if isinstance(e.body, bytes):
422
435
  # Decode the body first
423
- return e.body.decode('utf-8')
436
+ return e.body.decode("utf-8")
424
437
  elif isinstance(e.body, str):
425
438
  return e.body
426
439
 
427
440
  # Anything else
428
- return f'{get_error_code(e)}: {get_error_message(e)}'
441
+ return f"{get_error_code(e)}: {get_error_message(e)}"
442
+
429
443
 
430
444
  def meets_error_message_condition(e: Exception, error_message: Optional[str]):
431
445
  if error_message:
@@ -437,7 +451,9 @@ def meets_error_message_condition(e: Exception, error_message: Optional[str]):
437
451
  if isinstance(e, botocore.exceptions.ClientError):
438
452
  return error_message in str(e)
439
453
 
440
- 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
+ ):
441
457
  return error_message in e.reason
442
458
  elif isinstance(e, sqlite3.OperationalError):
443
459
  return error_message in str(e)
@@ -445,7 +461,7 @@ def meets_error_message_condition(e: Exception, error_message: Optional[str]):
445
461
  return error_message in e.msg
446
462
  elif isinstance(e, requests.exceptions.HTTPError):
447
463
  return error_message in e.raw
448
- elif hasattr(e, 'msg'):
464
+ elif hasattr(e, "msg"):
449
465
  return error_message in e.msg
450
466
  else:
451
467
  return error_message in traceback.format_exc()
@@ -453,7 +469,7 @@ def meets_error_message_condition(e: Exception, error_message: Optional[str]):
453
469
  return True
454
470
 
455
471
 
456
- 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]]):
457
473
  """These are expected to be normal HTTP error codes, like 404 or 500."""
458
474
  if error_codes:
459
475
  status_code = get_error_status(e)
@@ -462,7 +478,9 @@ def meets_error_code_condition(e: Exception, error_codes: Optional[List[int]]):
462
478
  return True
463
479
 
464
480
 
465
- 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
+ ):
466
484
  """These are expected to be AWS's custom error aliases, like 'BucketNotFound' or 'AccessDenied'."""
467
485
  if boto_error_codes:
468
486
  status_code = get_error_code(e)
@@ -475,26 +493,44 @@ def error_meets_conditions(e, error_conditions):
475
493
  condition_met = False
476
494
  for error in error_conditions:
477
495
  if isinstance(e, error.error):
478
- if error.error_codes or error.boto_error_codes or error.error_message_must_include:
479
- error_message_condition_met = meets_error_message_condition(e, error.error_message_must_include)
480
- error_code_condition_met = meets_error_code_condition(e, error.error_codes)
481
- boto_error_code_condition_met = meets_boto_error_code_condition(e, error.boto_error_codes)
482
- 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
+ ):
483
515
  if not error.retry_on_this_condition:
484
516
  return False
485
517
  condition_met = True
486
518
  return condition_met
487
519
 
520
+
488
521
  DEFAULT_DELAYS = (0, 1, 1, 4, 16, 64)
489
522
  DEFAULT_TIMEOUT = 300
490
523
 
524
+ E = TypeVar("E", bound=Exception) # so mypy understands passed through types
525
+
526
+
491
527
  # TODO: Replace the use of this with retry()
492
528
  # The aws provisioner and jobstore need a large refactoring to be boto3 compliant, so this is
493
529
  # still used there to avoid the duplication of future work
494
530
  def old_retry(
495
531
  delays: Iterable[float] = DEFAULT_DELAYS,
496
532
  timeout: float = DEFAULT_TIMEOUT,
497
- predicate: Callable[[Exception], bool] = lambda e: False,
533
+ predicate: Callable[[E], bool] = lambda e: False,
498
534
  ) -> Generator[ContextManager, None, None]:
499
535
  """
500
536
  Deprecated.
@@ -567,39 +603,48 @@ def old_retry(
567
603
  >>> i
568
604
  1
569
605
  """
606
+ if timeout is None:
607
+ timeout = DEFAULT_TIMEOUT
570
608
  if timeout > 0:
571
- go = [ None ]
609
+ go = [None]
572
610
 
573
611
  @contextmanager
574
- def repeated_attempt( delay ):
612
+ def repeated_attempt(delay):
575
613
  try:
576
614
  yield
577
615
  except Exception as e:
578
- if time.time( ) + delay < expiration:
579
- if predicate( e ):
580
- logger.info('Got %s, trying again in %is.', e, delay)
581
- 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)
582
620
  else:
583
- 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
+ )
584
627
  raise
585
628
  else:
586
- 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)
587
630
  raise
588
631
  else:
589
- go.pop( )
632
+ go.pop()
590
633
 
591
- delays = iter( delays )
592
- expiration = time.time( ) + timeout
593
- delay = next( delays )
634
+ delays = iter(delays)
635
+ expiration = time.time() + timeout
636
+ delay = next(delays)
594
637
  while go:
595
- yield repeated_attempt( delay )
596
- delay = next( delays, delay )
638
+ yield repeated_attempt(delay)
639
+ delay = next(delays, delay)
597
640
  else:
641
+
598
642
  @contextmanager
599
- def single_attempt( ):
643
+ def single_attempt():
600
644
  yield
601
645
 
602
- yield single_attempt( )
646
+ yield single_attempt()
647
+
603
648
 
604
649
  # Decorator to retry tests that fail. Needs to be called with
605
650
  # prepare=[tearDown, setUp] if the test class has tear down and set up that