toil 9.1.2__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 +14 -14
  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.2.dist-info → toil-9.2.0.dist-info}/METADATA +11 -14
  146. {toil-9.1.2.dist-info → toil-9.2.0.dist-info}/RECORD +150 -153
  147. {toil-9.1.2.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.2.dist-info → toil-9.2.0.dist-info}/entry_points.txt +0 -0
  154. {toil-9.1.2.dist-info → toil-9.2.0.dist-info}/licenses/LICENSE +0 -0
  155. {toil-9.1.2.dist-info → toil-9.2.0.dist-info}/top_level.txt +0 -0
toil/lib/io.py CHANGED
@@ -6,15 +6,15 @@ import stat
6
6
  import sys
7
7
  import tempfile
8
8
  import uuid
9
- from collections.abc import Iterator, Iterable
9
+ from collections.abc import Callable, Iterable, Iterator
10
10
  from contextlib import contextmanager
11
11
  from io import BytesIO
12
- from typing import IO, Any, Callable, Optional, Protocol, Union
12
+ from typing import IO, Any, Protocol
13
13
 
14
- from toil.lib.directory import get_directory_item, TOIL_DIR_URI_SCHEME
15
- from toil.lib.url import URLAccess
14
+ from toil.lib.directory import TOIL_DIR_URI_SCHEME, get_directory_item
16
15
  from toil.lib.memoize import memoize
17
16
  from toil.lib.misc import StrPath
17
+ from toil.lib.url import URLAccess
18
18
 
19
19
  logger = logging.getLogger(__name__)
20
20
 
@@ -27,7 +27,7 @@ def get_toil_home() -> str:
27
27
  Raises an error if it does not exist and cannot be created. Safe to run
28
28
  simultaneously in multiple processes.
29
29
  """
30
-
30
+
31
31
  # TODO: should this use an XDG config directory or ~/.config to not clutter the
32
32
  # base home directory?
33
33
  toil_home_dir = os.path.join(os.path.expanduser("~"), ".toil")
@@ -39,24 +39,28 @@ def get_toil_home() -> str:
39
39
  )
40
40
  return dir_path
41
41
 
42
+
42
43
  TOIL_URI_SCHEME = "toilfile:"
43
44
 
44
45
  STANDARD_SCHEMES = ["http:", "https:", "s3:", "gs:", "ftp:"]
45
46
  REMOTE_SCHEMES = STANDARD_SCHEMES + [TOIL_URI_SCHEME, TOIL_DIR_URI_SCHEME]
46
47
  ALL_SCHEMES = REMOTE_SCHEMES + ["file:"]
47
48
 
49
+
48
50
  def is_standard_url(filename: str) -> bool:
49
51
  """
50
52
  Return True if the given URL is a non-Toil, non-file: URL.
51
53
  """
52
54
  return is_url_with_scheme(filename, STANDARD_SCHEMES)
53
55
 
56
+
54
57
  def is_remote_url(filename: str) -> bool:
55
58
  """
56
59
  Decide if a filename is a known, non-file kind of URL
57
60
  """
58
61
  return is_url_with_scheme(filename, REMOTE_SCHEMES)
59
62
 
63
+
60
64
  def is_any_url(filename: str) -> bool:
61
65
  """
62
66
  Decide if a string is a URI like http:// or file://.
@@ -65,6 +69,7 @@ def is_any_url(filename: str) -> bool:
65
69
  """
66
70
  return is_url_with_scheme(filename, ALL_SCHEMES)
67
71
 
72
+
68
73
  def is_url_with_scheme(filename: str, schemes: list[str]) -> bool:
69
74
  """
70
75
  Return True if filename is a URL with any of the given schemes and False otherwise.
@@ -75,26 +80,30 @@ def is_url_with_scheme(filename: str, schemes: list[str]) -> bool:
75
80
  return True
76
81
  return False
77
82
 
83
+
78
84
  def is_toil_url(filename: str) -> bool:
79
85
  """
80
86
  Return True if a URL is a toilfile: or toildir: URL.
81
87
  """
82
88
  return is_url_with_scheme(filename, [TOIL_URI_SCHEME, TOIL_DIR_URI_SCHEME])
83
89
 
90
+
84
91
  def is_toil_file_url(filename: str) -> bool:
85
92
  """
86
93
  Return True if a URL is a toilfile: URL.
87
94
  """
88
95
  return is_url_with_scheme(filename, [TOIL_URI_SCHEME])
89
96
 
97
+
90
98
  def is_toil_dir_url(filename: str) -> bool:
91
99
  """
92
100
  Return True if a URL is a toildir: URL.
93
101
 
94
- Note that this may point to either a direcotry or a leaf file.
102
+ Note that this may point to either a directory or a leaf file.
95
103
  """
96
104
  return is_url_with_scheme(filename, [TOIL_DIR_URI_SCHEME])
97
105
 
106
+
98
107
  def is_file_url(filename: str) -> bool:
99
108
  """
100
109
  Return True if a URL is a file: URL.
@@ -103,6 +112,7 @@ def is_file_url(filename: str) -> bool:
103
112
  """
104
113
  return is_url_with_scheme(filename, ["file:"])
105
114
 
115
+
106
116
  def is_directory_url(filename: str) -> bool:
107
117
  """
108
118
  Return True if a URL points to a directory.
@@ -119,10 +129,11 @@ def is_directory_url(filename: str) -> bool:
119
129
  return not isinstance(get_directory_item(filename), str)
120
130
  return URLAccess.get_is_directory(filename)
121
131
 
132
+
122
133
  def mkdtemp(
123
- suffix: Optional[str] = None,
124
- prefix: Optional[str] = None,
125
- dir: Optional[StrPath] = None,
134
+ suffix: str | None = None,
135
+ prefix: str | None = None,
136
+ dir: StrPath | None = None,
126
137
  ) -> str:
127
138
  """
128
139
  Make a temporary directory like tempfile.mkdtemp, but with relaxed permissions.
@@ -147,7 +158,7 @@ def mkdtemp(
147
158
  return result
148
159
 
149
160
 
150
- def robust_rmtree(path: Union[str, bytes]) -> None:
161
+ def robust_rmtree(path: str | bytes) -> None:
151
162
  """
152
163
  Robustly tries to delete paths.
153
164
 
@@ -257,9 +268,7 @@ def AtomicFileCreate(final_path: StrPath, keep: bool = False) -> Iterator[str]:
257
268
  raise
258
269
 
259
270
 
260
- def atomic_copy(
261
- src_path: str, dest_path: str, executable: Optional[bool] = None
262
- ) -> None:
271
+ def atomic_copy(src_path: str, dest_path: str, executable: bool | None = None) -> None:
263
272
  """Copy a file using posix atomic creations semantics."""
264
273
  if executable is None:
265
274
  executable = os.stat(src_path).st_mode & stat.S_IXUSR != 0
@@ -280,7 +289,7 @@ def atomic_copyobj(
280
289
  os.chmod(dest_path_tmp, os.stat(dest_path_tmp).st_mode | stat.S_IXUSR)
281
290
 
282
291
 
283
- def make_public_dir(in_directory: str, suggested_name: Optional[str] = None) -> str:
292
+ def make_public_dir(in_directory: str, suggested_name: str | None = None) -> str:
284
293
  """
285
294
  Make a publicly-accessible directory in the given directory.
286
295
 
@@ -318,7 +327,7 @@ def make_public_dir(in_directory: str, suggested_name: Optional[str] = None) ->
318
327
  return this_should_never_happen
319
328
 
320
329
 
321
- def try_path(path: str, min_size: int = 100 * 1024 * 1024) -> Optional[str]:
330
+ def try_path(path: str, min_size: int = 100 * 1024 * 1024) -> str | None:
322
331
  """
323
332
  Try to use the given path. Return it if it exists or can be made,
324
333
  and we can make things within it, or None otherwise.
@@ -416,6 +425,7 @@ class WriteWatchingStream:
416
425
 
417
426
  self.backingStream.close()
418
427
 
428
+
419
429
  class ReadableFileObj(Protocol):
420
430
  """
421
431
  Protocol that is more specific than what file_digest takes as an argument.
@@ -423,10 +433,12 @@ class ReadableFileObj(Protocol):
423
433
  Would extend the protocol from Typeshed for hashlib but those are only
424
434
  declared for 3.11+.
425
435
  """
436
+
426
437
  def readinto(self, buf: bytearray, /) -> int: ...
427
438
  def readable(self) -> bool: ...
428
439
  def read(self, number: int) -> bytes: ...
429
440
 
441
+
430
442
  # hashlib._Hash seems to not appear at runtime
431
443
  def file_digest(f: ReadableFileObj, alg_name: str) -> "hashlib._Hash":
432
444
  """
@@ -441,5 +453,3 @@ def file_digest(f: ReadableFileObj, alg_name: str) -> "hashlib._Hash":
441
453
  hasher.update(buffer)
442
454
  buffer = f.read(BUFFER_SIZE)
443
455
  return hasher
444
-
445
-
toil/lib/memoize.py CHANGED
@@ -15,9 +15,10 @@
15
15
  # 5.14.2018: copied into Toil from https://github.com/BD2KGenomics/bd2k-python-lib
16
16
  import datetime
17
17
  import re
18
+ from collections.abc import Callable
18
19
  from functools import lru_cache, wraps
19
20
  from threading import Lock
20
- from typing import Any, Callable, TypeVar
21
+ from typing import Any, TypeVar
21
22
 
22
23
  memoize = lru_cache(maxsize=None)
23
24
  """
toil/lib/misc.py CHANGED
@@ -9,17 +9,14 @@ import sys
9
9
  import time
10
10
  from collections.abc import Iterator
11
11
  from contextlib import closing
12
- from typing import Optional, Union
13
- if sys.version_info >= (3, 10):
14
- from typing import TypeAlias
15
- else:
16
- from typing_extensions import TypeAlias
12
+ from typing import TypeAlias, Union
17
13
 
18
14
  logger = logging.getLogger(__name__)
19
15
 
20
16
  StrPath: TypeAlias = Union[str, os.PathLike[str]]
21
17
  FileDescriptorOrPath: TypeAlias = Union[int, bytes, os.PathLike[bytes], StrPath]
22
18
 
19
+
23
20
  def get_public_ip() -> str:
24
21
  """Get the IP that this machine uses to contact the internet.
25
22
 
@@ -69,17 +66,24 @@ def unix_now_ms() -> float:
69
66
  """Return the current time in milliseconds since the Unix epoch."""
70
67
  return time.time() * 1000
71
68
 
69
+
72
70
  def unix_seconds_to_timestamp(timestamp: float) -> str:
73
71
  """
74
72
  Convert a time in seconds since the Unix epoch to an ISO 8601 string.
75
73
  """
76
- return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc).isoformat()
74
+ return datetime.datetime.fromtimestamp(
75
+ timestamp, tz=datetime.timezone.utc
76
+ ).isoformat()
77
+
77
78
 
78
79
  def unix_seconds_to_local_time(timestamp: float) -> datetime.datetime:
79
80
  """
80
81
  Returns a local time corresponding to the given number of seconds since the Unix epoch.
81
82
  """
82
- return datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc).astimezone()
83
+ return datetime.datetime.fromtimestamp(
84
+ timestamp, tz=datetime.timezone.utc
85
+ ).astimezone()
86
+
83
87
 
84
88
  def seconds_to_duration(time_difference: float) -> str:
85
89
  """
@@ -157,11 +161,11 @@ class CalledProcessErrorStderr(subprocess.CalledProcessError):
157
161
  def call_command(
158
162
  cmd: list[str],
159
163
  *args: str,
160
- input: Optional[str] = None,
161
- timeout: Optional[float] = None,
164
+ input: str | None = None,
165
+ timeout: float | None = None,
162
166
  useCLocale: bool = True,
163
- env: Optional[dict[str, str]] = None,
164
- quiet: Optional[bool] = False
167
+ env: dict[str, str] | None = None,
168
+ quiet: bool | None = False,
165
169
  ) -> str:
166
170
  """
167
171
  Simplified calling of external commands.
toil/lib/pipes.py CHANGED
@@ -1,11 +1,9 @@
1
1
  import errno
2
+ import hashlib
2
3
  import logging
3
4
  import os
4
- import hashlib
5
- import threading
6
-
7
5
  from abc import ABC, abstractmethod
8
- from typing import Optional, TextIO, BinaryIO, IO, Any
6
+ from typing import IO, Any
9
7
 
10
8
  from toil.lib.checksum import ChecksumError
11
9
  from toil.lib.threading import ExceptionalThread
@@ -75,7 +73,7 @@ class WritablePipe(ABC):
75
73
  True
76
74
  """
77
75
 
78
- def __init__(self, encoding: Optional[str] = None, errors: Optional[str] = None) -> None:
76
+ def __init__(self, encoding: str | None = None, errors: str | None = None) -> None:
79
77
  """
80
78
  The specified encoding and errors apply to the writable end of the pipe.
81
79
 
@@ -86,11 +84,11 @@ class WritablePipe(ABC):
86
84
  are the same as for open(). Defaults to 'strict' when an encoding is specified.
87
85
  """
88
86
  super().__init__()
89
- self.encoding: Optional[str] = encoding
90
- self.errors: Optional[str] = errors
91
- self.readable_fh: Optional[int] = None
92
- self.writable: Optional[IO[Any]] = None
93
- self.thread: Optional[ExceptionalThread] = None
87
+ self.encoding: str | None = encoding
88
+ self.errors: str | None = errors
89
+ self.readable_fh: int | None = None
90
+ self.writable: IO[Any] | None = None
91
+ self.thread: ExceptionalThread | None = None
94
92
  self.reader_done: bool = False
95
93
 
96
94
  def __enter__(self) -> IO[Any]:
@@ -105,7 +103,9 @@ class WritablePipe(ABC):
105
103
  self.thread.start()
106
104
  return self.writable
107
105
 
108
- def __exit__(self, exc_type: Optional[str], exc_val: Optional[str], exc_tb: Optional[str]) -> None:
106
+ def __exit__(
107
+ self, exc_type: str | None, exc_val: str | None, exc_tb: str | None
108
+ ) -> None:
109
109
  # Closing the writable end will send EOF to the readable and cause the reader thread
110
110
  # to finish.
111
111
  # TODO: Can close() fail? If so, would we try and clean up after the reader?
@@ -160,7 +160,6 @@ class WritablePipe(ABC):
160
160
  self.reader_done = True
161
161
 
162
162
 
163
-
164
163
  class ReadablePipe(ABC):
165
164
  """
166
165
  An object-oriented wrapper for os.pipe. Clients should subclass it, implement
@@ -245,7 +244,7 @@ class ReadablePipe(ABC):
245
244
  if e.errno != errno.EPIPE:
246
245
  raise
247
246
 
248
- def __init__(self, encoding: Optional[str] = None, errors: Optional[str] = None) -> None:
247
+ def __init__(self, encoding: str | None = None, errors: str | None = None) -> None:
249
248
  """
250
249
  The specified encoding and errors apply to the readable end of the pipe.
251
250
 
@@ -256,11 +255,11 @@ class ReadablePipe(ABC):
256
255
  are the same as for open(). Defaults to 'strict' when an encoding is specified.
257
256
  """
258
257
  super().__init__()
259
- self.encoding: Optional[str] = encoding
260
- self.errors: Optional[str] = errors
261
- self.writable_fh: Optional[int] = None
262
- self.readable: Optional[IO[Any]] = None
263
- self.thread: Optional[ExceptionalThread] = None
258
+ self.encoding: str | None = encoding
259
+ self.errors: str | None = errors
260
+ self.writable_fh: int | None = None
261
+ self.readable: IO[Any] | None = None
262
+ self.thread: ExceptionalThread | None = None
264
263
 
265
264
  def __enter__(self) -> IO[Any]:
266
265
  readable_fh, self.writable_fh = os.pipe()
@@ -274,7 +273,9 @@ class ReadablePipe(ABC):
274
273
  self.thread.start()
275
274
  return self.readable
276
275
 
277
- def __exit__(self, exc_type: Optional[str], exc_val: Optional[str], exc_tb: Optional[str]) -> None:
276
+ def __exit__(
277
+ self, exc_type: str | None, exc_val: str | None, exc_tb: str | None
278
+ ) -> None:
278
279
  # Close the read end of the pipe. The writing thread may
279
280
  # still be writing to the other end, but this will wake it up
280
281
  # if that's the case.
@@ -323,7 +324,12 @@ class ReadableTransformingPipe(ReadablePipe):
323
324
 
324
325
  """
325
326
 
326
- def __init__(self, source: IO[Any], encoding: Optional[str] = None, errors: Optional[str] = None) -> None:
327
+ def __init__(
328
+ self,
329
+ source: IO[Any],
330
+ encoding: str | None = None,
331
+ errors: str | None = None,
332
+ ) -> None:
327
333
  """
328
334
  :param str encoding: the name of the encoding used to encode the file. Encodings are the same
329
335
  as for encode(). Defaults to None which represents binary mode.
@@ -357,7 +363,14 @@ class HashingPipe(ReadableTransformingPipe):
357
363
 
358
364
  Assumes info actually has a checksum.
359
365
  """
360
- def __init__(self, source: IO[Any], encoding: Optional[str] = None, errors: Optional[str] = None, checksum_to_verify: Optional[str] = None) -> None:
366
+
367
+ def __init__(
368
+ self,
369
+ source: IO[Any],
370
+ encoding: str | None = None,
371
+ errors: str | None = None,
372
+ checksum_to_verify: str | None = None,
373
+ ) -> None:
361
374
  """
362
375
  :param str encoding: the name of the encoding used to encode the file. Encodings are the same
363
376
  as for encode(). Defaults to None which represents binary mode.
@@ -365,13 +378,13 @@ class HashingPipe(ReadableTransformingPipe):
365
378
  :param str errors: an optional string that specifies how encoding errors are to be handled. Errors
366
379
  are the same as for open(). Defaults to 'strict' when an encoding is specified.
367
380
  """
368
- super(HashingPipe, self).__init__(source=source, encoding=encoding, errors=errors)
381
+ super().__init__(source=source, encoding=encoding, errors=errors)
369
382
  self.checksum_to_verify = checksum_to_verify
370
383
 
371
384
  def transform(self, readable: IO[Any], writable: IO[Any]) -> None:
372
385
  hash_object = hashlib.sha1()
373
386
  contents = readable.read(1024 * 1024)
374
- while contents != b'':
387
+ while contents != b"":
375
388
  hash_object.update(contents)
376
389
  try:
377
390
  writable.write(contents)
@@ -380,6 +393,8 @@ class HashingPipe(ReadableTransformingPipe):
380
393
  # Can't check the checksum.
381
394
  return
382
395
  contents = readable.read(1024 * 1024)
383
- final_computed_checksum = f'sha1${hash_object.hexdigest()}'
396
+ final_computed_checksum = f"sha1${hash_object.hexdigest()}"
384
397
  if not self.checksum_to_verify == final_computed_checksum:
385
- raise ChecksumError(f'Checksum mismatch. Expected: {self.checksum_to_verify} Actual: {final_computed_checksum}')
398
+ raise ChecksumError(
399
+ f"Checksum mismatch. Expected: {self.checksum_to_verify} Actual: {final_computed_checksum}"
400
+ )
toil/lib/plugins.py CHANGED
@@ -26,16 +26,17 @@ themselves.
26
26
  """
27
27
 
28
28
  import importlib
29
- from typing import Any, Literal, Union
30
29
  import pkgutil
31
- from toil.lib.memoize import memoize
30
+ from typing import Any, Literal, Union
32
31
 
32
+ from toil.lib.memoize import memoize
33
33
 
34
34
  PluginType = Union[Literal["batch_system"], Literal["url_access"]]
35
35
  plugin_types: list[PluginType] = ["batch_system", "url_access"]
36
36
 
37
37
  _registry: dict[str, dict[str, Any]] = {k: {} for k in plugin_types}
38
38
 
39
+
39
40
  def register_plugin(
40
41
  plugin_type: PluginType, plugin_name: str, plugin_being_registered: Any
41
42
  ) -> None:
@@ -51,13 +52,13 @@ def register_plugin(
51
52
  the resulting type must extend
52
53
  :class:`toil.batchSystems.abstractBatchSystem.AbstractBatchSystem`. For
53
54
  URL access plugins, it must extend :class:`toil.lib.url.URLAccess`.
54
- Note that the function used here should return the class itslef; it
55
+ Note that the function used here should return the class itself; it
55
56
  should not construct an instance of the class.
56
57
  """
57
58
  _registry[plugin_type][plugin_name] = plugin_being_registered
58
59
 
59
- def remove_plugin(
60
- plugin_type: PluginType, plugin_name: str) -> None:
60
+
61
+ def remove_plugin(plugin_type: PluginType, plugin_name: str) -> None:
61
62
  """
62
63
  Removes a plugin from the registry for the given type of plugin.
63
64
  """
@@ -67,13 +68,15 @@ def remove_plugin(
67
68
  # If the plugin does not exist, it can be ignored
68
69
  pass
69
70
 
70
- def get_plugin_names(plugin_type:PluginType) -> list[str]:
71
+
72
+ def get_plugin_names(plugin_type: PluginType) -> list[str]:
71
73
  """
72
74
  Get the names of all the available plugins of the given type.
73
75
  """
74
76
  _load_all_plugins(plugin_type)
75
77
  return list(_registry[plugin_type].keys())
76
78
 
79
+
77
80
  def get_plugin(plugin_type: PluginType, plugin_name: str) -> Any:
78
81
  """
79
82
  Get a plugin class factory function by name.
@@ -87,12 +90,13 @@ def get_plugin(plugin_type: PluginType, plugin_name: str) -> Any:
87
90
 
88
91
  def _plugin_name_prefix(plugin_type: PluginType) -> str:
89
92
  """
90
- Get prefix for plugin type.
91
-
93
+ Get prefix for plugin type.
94
+
92
95
  Any packages with prefix will count as toil plugins of that type.
93
96
  """
94
97
  return f"toil_{plugin_type}_"
95
98
 
99
+
96
100
  @memoize
97
101
  def _load_all_plugins(plugin_type: PluginType) -> None:
98
102
  """
toil/lib/resources.py CHANGED
@@ -19,6 +19,7 @@ import sys
19
19
 
20
20
  from toil.lib.misc import StrPath
21
21
 
22
+
22
23
  class ResourceMonitor:
23
24
  """
24
25
  Global resource monitoring widget.
toil/lib/retry.py CHANGED
@@ -131,9 +131,9 @@ import sqlite3
131
131
  import time
132
132
  import traceback
133
133
  import urllib.error
134
- from collections.abc import Generator, Iterable, Sequence
134
+ from collections.abc import Callable, Generator, Iterable, Sequence
135
135
  from contextlib import contextmanager
136
- from typing import Any, Callable, ContextManager, Optional, TypeVar, Union
136
+ from typing import ContextManager, TypeVar
137
137
 
138
138
  import requests.exceptions
139
139
  import urllib3.exceptions
@@ -172,7 +172,7 @@ class ErrorCondition:
172
172
 
173
173
  def __init__(
174
174
  self,
175
- error: Optional[type[BaseException]] = None,
175
+ error: type[BaseException] | None = None,
176
176
  error_codes: list[int] = None,
177
177
  boto_error_codes: list[str] = None,
178
178
  error_message_must_include: str = None,
@@ -227,11 +227,11 @@ RT = TypeVar("RT")
227
227
 
228
228
 
229
229
  def retry(
230
- intervals: Optional[list] = None,
230
+ intervals: list | None = None,
231
231
  infinite_retries: bool = False,
232
- errors: Optional[Sequence[Union[ErrorCondition, type[Exception]]]] = None,
233
- log_message: Optional[tuple[Callable, str]] = None,
234
- prepare: Optional[list[Callable]] = None,
232
+ errors: Sequence[ErrorCondition | type[Exception]] | None = None,
233
+ log_message: tuple[Callable, str] | None = None,
234
+ prepare: list[Callable] | None = None,
235
235
  ) -> Callable[[Callable[..., RT]], Callable[..., RT]]:
236
236
  """
237
237
  Retry a function if it fails with any Exception defined in "errors".
@@ -332,17 +332,14 @@ def return_status_code(e):
332
332
  if botocore:
333
333
  if isinstance(e, botocore.exceptions.ClientError):
334
334
  return e.response.get("ResponseMetadata", {}).get("HTTPStatusCode")
335
-
336
- if isinstance(e, requests.exceptions.HTTPError):
337
- return e.response.status_code
338
- elif isinstance(e, http.client.HTTPException) or isinstance(
339
- e, urllib3.exceptions.HTTPError
340
- ):
341
- return e.status
342
- elif isinstance(e, urllib.error.HTTPError):
343
- return e.code
344
- else:
345
- raise ValueError(f"Unsupported error type; cannot grok status code: {e}.")
335
+ match e:
336
+ case requests.exceptions.HTTPError():
337
+ return e.response.status_code
338
+ case http.client.HTTPException() | urllib3.exceptions.HTTPError():
339
+ return e.status
340
+ case urllib.error.HTTPError():
341
+ return e.code
342
+ raise ValueError(f"Unsupported error type; cannot grok status code: {e}.")
346
343
 
347
344
 
348
345
  def get_error_code(e: Exception) -> str:
@@ -441,7 +438,7 @@ def get_error_body(e: Exception) -> str:
441
438
  return f"{get_error_code(e)}: {get_error_message(e)}"
442
439
 
443
440
 
444
- def meets_error_message_condition(e: Exception, error_message: Optional[str]):
441
+ def meets_error_message_condition(e: Exception, error_message: str | None):
445
442
  if error_message:
446
443
  if kubernetes:
447
444
  if isinstance(e, kubernetes.client.rest.ApiException):
@@ -450,26 +447,25 @@ def meets_error_message_condition(e: Exception, error_message: Optional[str]):
450
447
  if botocore:
451
448
  if isinstance(e, botocore.exceptions.ClientError):
452
449
  return error_message in str(e)
453
-
454
- if isinstance(e, http.client.HTTPException) or isinstance(
455
- e, urllib3.exceptions.HTTPError
456
- ):
457
- return error_message in e.reason
458
- elif isinstance(e, sqlite3.OperationalError):
459
- return error_message in str(e)
460
- elif isinstance(e, urllib.error.HTTPError):
461
- return error_message in e.msg
462
- elif isinstance(e, requests.exceptions.HTTPError):
463
- return error_message in e.raw
464
- elif hasattr(e, "msg"):
465
- return error_message in e.msg
466
- else:
467
- return error_message in traceback.format_exc()
450
+ match e:
451
+ case (
452
+ http.client.HTTPException() | urllib3.exceptions.HTTPError()
453
+ ) as c_exc:
454
+ return error_message in c_exc.reason
455
+ case sqlite3.OperationalError():
456
+ return error_message in str(e)
457
+ case urllib.error.HTTPError() as u_err:
458
+ return error_message in u_err.msg
459
+ case requests.exceptions.HTTPError() as r_err:
460
+ return error_message in r_err.raw
461
+ case Exception() as exc if hasattr(exc, "msg"):
462
+ return error_message in exc.msg
463
+ return error_message in traceback.format_exc()
468
464
  else: # there is no error message, so the user is not expecting it to be present
469
465
  return True
470
466
 
471
467
 
472
- def meets_error_code_condition(e: Exception, error_codes: Optional[list[int]]):
468
+ def meets_error_code_condition(e: Exception, error_codes: list[int] | None):
473
469
  """These are expected to be normal HTTP error codes, like 404 or 500."""
474
470
  if error_codes:
475
471
  status_code = get_error_status(e)
@@ -478,9 +474,7 @@ def meets_error_code_condition(e: Exception, error_codes: Optional[list[int]]):
478
474
  return True
479
475
 
480
476
 
481
- def meets_boto_error_code_condition(
482
- e: Exception, boto_error_codes: Optional[list[str]]
483
- ):
477
+ def meets_boto_error_code_condition(e: Exception, boto_error_codes: list[str] | None):
484
478
  """These are expected to be AWS's custom error aliases, like 'BucketNotFound' or 'AccessDenied'."""
485
479
  if boto_error_codes:
486
480
  status_code = get_error_code(e)