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
@@ -11,33 +11,22 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- import base64
15
- import bz2
16
14
  import logging
17
15
  import os
18
- import types
19
16
  from ssl import SSLError
20
- from typing import TYPE_CHECKING, IO, Optional, cast, Any
17
+ from typing import IO, TYPE_CHECKING, Any
21
18
 
22
19
  from boto3.s3.transfer import TransferConfig
23
20
  from botocore.client import Config
24
21
  from botocore.exceptions import ClientError
25
22
 
26
23
  from toil.lib.aws import AWSServerErrors, session
27
- from toil.lib.aws.utils import connection_error, get_bucket_region
24
+ from toil.lib.aws.utils import get_bucket_region
28
25
  from toil.lib.compatibility import compat_bytes
29
- from toil.lib.retry import (
30
- DEFAULT_DELAYS,
31
- DEFAULT_TIMEOUT,
32
- get_error_code,
33
- get_error_message,
34
- get_error_status,
35
- old_retry,
36
- retry,
37
- )
26
+ from toil.lib.retry import get_error_code, get_error_message, retry
38
27
 
39
28
  if TYPE_CHECKING:
40
- from mypy_boto3_s3 import S3Client, S3ServiceResource
29
+ from mypy_boto3_s3 import S3ServiceResource
41
30
  from mypy_boto3_s3.type_defs import CopySourceTypeDef
42
31
 
43
32
  logger = logging.getLogger(__name__)
@@ -66,9 +55,9 @@ def uploadFromPath(
66
55
  resource: "S3ServiceResource",
67
56
  bucketName: str,
68
57
  fileID: str,
69
- headerArgs: Optional[dict[str, Any]] = None,
58
+ headerArgs: dict[str, Any] | None = None,
70
59
  partSize: int = 50 << 20,
71
- ) -> Optional[str]:
60
+ ) -> str | None:
72
61
  """
73
62
  Uploads a file to s3, using multipart uploading if applicable
74
63
 
@@ -112,9 +101,9 @@ def uploadFile(
112
101
  resource: "S3ServiceResource",
113
102
  bucketName: str,
114
103
  fileID: str,
115
- headerArgs: Optional[dict[str, Any]] = None,
104
+ headerArgs: dict[str, Any] | None = None,
116
105
  partSize: int = 50 << 20,
117
- ) -> Optional[str]:
106
+ ) -> str | None:
118
107
  """
119
108
  Upload a readable object to s3, using multipart uploading if applicable.
120
109
  :param readable: a readable stream or a file path to upload to s3
@@ -172,11 +161,11 @@ def copyKeyMultipart(
172
161
  srcKeyVersion: str,
173
162
  dstBucketName: str,
174
163
  dstKeyName: str,
175
- sseAlgorithm: Optional[str] = None,
176
- sseKey: Optional[str] = None,
177
- copySourceSseAlgorithm: Optional[str] = None,
178
- copySourceSseKey: Optional[str] = None,
179
- ) -> Optional[str]:
164
+ sseAlgorithm: str | None = None,
165
+ sseKey: str | None = None,
166
+ copySourceSseAlgorithm: str | None = None,
167
+ copySourceSseKey: str | None = None,
168
+ ) -> str | None:
180
169
  """
181
170
  Copies a key from a source key to a destination key in multiple parts. Note that if the
182
171
  destination key exists it will be overwritten implicitly, and if it does not exist a new
@@ -23,7 +23,7 @@ import time
23
23
  import uuid
24
24
  from collections.abc import Iterable, Iterator
25
25
  from contextlib import contextmanager
26
- from typing import IO, Literal, Optional, Union, overload
26
+ from typing import IO, Literal, overload
27
27
  from urllib.parse import ParseResult, quote, unquote
28
28
 
29
29
  from toil.fileStores import FileID
@@ -327,7 +327,7 @@ class FileJobStore(AbstractJobStore, URLAccess):
327
327
  # linking is not done be default because of issue #1755
328
328
  # TODO: is hardlinking ever actually done?
329
329
  src_path = self._extract_path_from_url(src_path)
330
- if self.linkImports and not hardlink and symlink:
330
+ if self.linkImports and not hardlink and symlink and src_path != "/dev/null":
331
331
  os.symlink(os.path.realpath(src_path), dst_path)
332
332
  else:
333
333
  atomic_copy(src_path, dst_path)
@@ -703,33 +703,33 @@ class FileJobStore(AbstractJobStore, URLAccess):
703
703
  @overload
704
704
  def read_file_stream(
705
705
  self,
706
- file_id: Union[str, FileID],
706
+ file_id: str | FileID,
707
707
  encoding: Literal[None] = None,
708
- errors: Optional[str] = None,
708
+ errors: str | None = None,
709
709
  ) -> Iterator[IO[bytes]]: ...
710
710
 
711
711
  @contextmanager
712
712
  @overload
713
713
  def read_file_stream(
714
- self, file_id: Union[str, FileID], encoding: str, errors: Optional[str] = None
714
+ self, file_id: str | FileID, encoding: str, errors: str | None = None
715
715
  ) -> Iterator[IO[str]]: ...
716
716
 
717
717
  @contextmanager
718
718
  @overload
719
719
  def read_file_stream(
720
720
  self,
721
- file_id: Union[str, FileID],
722
- encoding: Optional[str] = None,
723
- errors: Optional[str] = None,
724
- ) -> Union[Iterator[IO[bytes]], Iterator[IO[str]]]: ...
721
+ file_id: str | FileID,
722
+ encoding: str | None = None,
723
+ errors: str | None = None,
724
+ ) -> Iterator[IO[bytes]] | Iterator[IO[str]]: ...
725
725
 
726
726
  @contextmanager
727
727
  def read_file_stream(
728
728
  self,
729
- file_id: Union[str, FileID],
730
- encoding: Optional[str] = None,
731
- errors: Optional[str] = None,
732
- ) -> Union[Iterator[IO[bytes]], Iterator[IO[str]]]:
729
+ file_id: str | FileID,
730
+ encoding: str | None = None,
731
+ errors: str | None = None,
732
+ ) -> Iterator[IO[bytes]] | Iterator[IO[str]]:
733
733
  self._check_job_store_file_id(file_id)
734
734
  if encoding is None:
735
735
  with open(
@@ -779,7 +779,7 @@ class FileJobStore(AbstractJobStore, URLAccess):
779
779
  self,
780
780
  shared_file_name: str,
781
781
  encoding: str,
782
- errors: Optional[str] = None,
782
+ errors: str | None = None,
783
783
  ) -> Iterator[IO[str]]: ...
784
784
 
785
785
  @overload
@@ -788,16 +788,16 @@ class FileJobStore(AbstractJobStore, URLAccess):
788
788
  self,
789
789
  shared_file_name: str,
790
790
  encoding: Literal[None] = None,
791
- errors: Optional[str] = None,
791
+ errors: str | None = None,
792
792
  ) -> Iterator[IO[bytes]]: ...
793
793
 
794
794
  @contextmanager
795
795
  def read_shared_file_stream(
796
796
  self,
797
797
  shared_file_name: str,
798
- encoding: Optional[str] = None,
799
- errors: Optional[str] = None,
800
- ) -> Union[Iterator[IO[bytes]], Iterator[IO[str]]]:
798
+ encoding: str | None = None,
799
+ errors: str | None = None,
800
+ ) -> Iterator[IO[bytes]] | Iterator[IO[str]]:
801
801
  self._requireValidSharedFileName(shared_file_name)
802
802
  try:
803
803
  with open(
@@ -814,7 +814,7 @@ class FileJobStore(AbstractJobStore, URLAccess):
814
814
  else:
815
815
  raise
816
816
 
817
- def list_all_file_names(self, for_job: Optional[str] = None) -> Iterable[str]:
817
+ def list_all_file_names(self, for_job: str | None = None) -> Iterable[str]:
818
818
  """
819
819
  Get all the file names (not file IDs) of files stored in the job store.
820
820
 
@@ -871,12 +871,18 @@ class FileJobStore(AbstractJobStore, URLAccess):
871
871
 
872
872
  def write_logs(self, msg):
873
873
  # Temporary files are placed in the stats directory tree
874
- tempStatsFileName = self.LOG_PREFIX + str(uuid.uuid4().hex) + self.LOG_TEMP_SUFFIX
875
- tempStatsFile = os.path.join(self._get_arbitrary_stats_inbox_dir(), tempStatsFileName)
874
+ tempStatsFileName = (
875
+ self.LOG_PREFIX + str(uuid.uuid4().hex) + self.LOG_TEMP_SUFFIX
876
+ )
877
+ tempStatsFile = os.path.join(
878
+ self._get_arbitrary_stats_inbox_dir(), tempStatsFileName
879
+ )
876
880
  writeFormat = "w" if isinstance(msg, str) else "wb"
877
881
  with open(tempStatsFile, writeFormat) as f:
878
882
  f.write(msg)
879
- os.rename(tempStatsFile, tempStatsFile[:-len(self.LOG_TEMP_SUFFIX)]) # This operation is atomic
883
+ os.rename(
884
+ tempStatsFile, tempStatsFile[: -len(self.LOG_TEMP_SUFFIX)]
885
+ ) # This operation is atomic
880
886
 
881
887
  def read_logs(self, callback, read_all=False):
882
888
  files_processed = 0
@@ -17,10 +17,11 @@ import pickle
17
17
  import stat
18
18
  import time
19
19
  import uuid
20
+ from collections.abc import Iterator
20
21
  from contextlib import contextmanager
21
22
  from functools import wraps
22
23
  from io import BytesIO
23
- from typing import Any, IO, Iterator, Optional
24
+ from typing import IO, Any
24
25
  from urllib.parse import ParseResult, urlunparse
25
26
 
26
27
  from google.api_core.exceptions import (
@@ -39,10 +40,10 @@ from toil.jobStores.abstractJobStore import (
39
40
  NoSuchJobException,
40
41
  NoSuchJobStoreException,
41
42
  )
42
- from toil.lib.pipes import ReadablePipe, WritablePipe
43
43
  from toil.lib.compatibility import compat_bytes
44
44
  from toil.lib.io import AtomicFileCreate
45
45
  from toil.lib.misc import truncExpBackoff
46
+ from toil.lib.pipes import ReadablePipe, WritablePipe
46
47
  from toil.lib.retry import old_retry
47
48
  from toil.lib.url import URLAccess
48
49
 
@@ -92,6 +93,7 @@ def google_retry(f):
92
93
 
93
94
  return wrapper
94
95
 
96
+
95
97
  @contextmanager
96
98
  def permission_error_reporter(url: ParseResult, notes: str) -> Iterator[None]:
97
99
  """
@@ -102,7 +104,7 @@ def permission_error_reporter(url: ParseResult, notes: str) -> Iterator[None]:
102
104
  behind the scenes. Then it will complain::
103
105
 
104
106
  <class 'google.auth.exceptions.InvalidOperation'>: Anonymous credentials cannot be refreshed.
105
-
107
+
106
108
  We need to detect this and report that the real problem is that the user
107
109
  has not set up any credentials. When you try to make the client
108
110
  non-anonymously and don't have credentials set up, you get a nice error
@@ -138,7 +140,6 @@ def permission_error_reporter(url: ParseResult, notes: str) -> Iterator[None]:
138
140
  raise
139
141
 
140
142
 
141
-
142
143
  class GoogleJobStore(AbstractJobStore, URLAccess):
143
144
 
144
145
  nodeServiceAccountJson = "/root/service_account.json"
@@ -182,6 +183,7 @@ class GoogleJobStore(AbstractJobStore, URLAccess):
182
183
  """
183
184
 
184
185
  notes: list[str] = []
186
+
185
187
  def add_note(message: str, *args: Any, warn: bool = False) -> None:
186
188
  """
187
189
  Add and possibly warn with a note about the client permissions.
@@ -190,6 +192,7 @@ class GoogleJobStore(AbstractJobStore, URLAccess):
190
192
  if warn:
191
193
  log.warning(note)
192
194
  notes.append(note)
195
+
193
196
  def compile_notes() -> str:
194
197
  """
195
198
  Make one string explainign why we might not have expected permissions.
@@ -202,9 +205,7 @@ class GoogleJobStore(AbstractJobStore, URLAccess):
202
205
  # Determine if we have an override environment variable for our credentials.
203
206
  # We get the path to check existence, but Google Storage works out what
204
207
  # to use later by looking at the environment again.
205
- credentials_path: Optional[str] = os.getenv(
206
- "GOOGLE_APPLICATION_CREDENTIALS", None
207
- )
208
+ credentials_path: str | None = os.getenv("GOOGLE_APPLICATION_CREDENTIALS", None)
208
209
  if credentials_path is not None and not os.path.exists(credentials_path):
209
210
  # If the file is missing, complain.
210
211
  # This variable holds a file name and not any sensitive data itself.
@@ -212,22 +213,25 @@ class GoogleJobStore(AbstractJobStore, URLAccess):
212
213
  "File '%s' from GOOGLE_APPLICATION_CREDENTIALS is unavailable! "
213
214
  "We may not be able to authenticate!",
214
215
  credentials_path,
215
- warn=True
216
+ warn=True,
216
217
  )
217
218
 
218
219
  if credentials_path is None and os.path.exists(cls.nodeServiceAccountJson):
219
220
  try:
220
221
  # load credentials from a particular file on GCE nodes if an
221
222
  # override path is not set
222
- return storage.Client.from_service_account_json(
223
- cls.nodeServiceAccountJson
224
- ), compile_notes()
223
+ return (
224
+ storage.Client.from_service_account_json(
225
+ cls.nodeServiceAccountJson
226
+ ),
227
+ compile_notes(),
228
+ )
225
229
  except OSError:
226
230
  # Probably we don't have permission to use the file.
227
231
  add_note(
228
232
  "File '%s' exists but didn't work to authenticate!",
229
233
  cls.nodeServiceAccountJson,
230
- warn=True
234
+ warn=True,
231
235
  )
232
236
 
233
237
  # Either a filename is specified, or our fallback file isn't there.
@@ -366,9 +370,7 @@ class GoogleJobStore(AbstractJobStore, URLAccess):
366
370
 
367
371
  env = {}
368
372
 
369
- credentials_path: Optional[str] = os.getenv(
370
- "GOOGLE_APPLICATION_CREDENTIALS", None
371
- )
373
+ credentials_path: str | None = os.getenv("GOOGLE_APPLICATION_CREDENTIALS", None)
372
374
  if credentials_path is not None:
373
375
  # Send along the environment variable that points to the credentials file.
374
376
  # It must be available in the same place on all nodes.
@@ -486,7 +488,9 @@ class GoogleJobStore(AbstractJobStore, URLAccess):
486
488
 
487
489
  @classmethod
488
490
  @google_retry
489
- def _get_blob_from_url(cls, client: storage.Client, url: ParseResult, exists: bool = False) -> storage.blob.Blob:
491
+ def _get_blob_from_url(
492
+ cls, client: storage.Client, url: ParseResult, exists: bool = False
493
+ ) -> storage.blob.Blob:
490
494
  """
491
495
  Gets the blob specified by the url.
492
496
 
@@ -521,7 +525,7 @@ class GoogleJobStore(AbstractJobStore, URLAccess):
521
525
  @classmethod
522
526
  def _url_exists(cls, url: ParseResult) -> bool:
523
527
  client, auth_notes = cls.create_client()
524
- with permission_error_reporter(url, auth_notes):
528
+ with permission_error_reporter(url, auth_notes):
525
529
  try:
526
530
  cls._get_blob_from_url(client, url, exists=True)
527
531
  return True
toil/jobStores/utils.py CHANGED
@@ -1,15 +1,11 @@
1
- import errno
2
1
  import logging
3
2
  import os
4
3
  import tempfile
5
4
  import uuid
6
- from abc import ABC, abstractmethod
7
- from typing import Optional
8
-
9
- from toil.lib.threading import ExceptionalThread
10
5
 
11
6
  log = logging.getLogger(__name__)
12
7
 
8
+
13
9
  class JobStoreUnavailableException(RuntimeError):
14
10
  """
15
11
  Raised when a particular type of job store is requested but can't be used.
@@ -18,8 +14,8 @@ class JobStoreUnavailableException(RuntimeError):
18
14
 
19
15
  def generate_locator(
20
16
  job_store_type: str,
21
- local_suggestion: Optional[str] = None,
22
- decoration: Optional[str] = None,
17
+ local_suggestion: str | None = None,
18
+ decoration: str | None = None,
23
19
  ) -> str:
24
20
  """
25
21
  Generate a random locator for a job store of the given type. Raises an
toil/leader.py CHANGED
@@ -21,7 +21,7 @@ import os
21
21
  import pickle
22
22
  import sys
23
23
  import time
24
- from typing import Any, Optional, Union
24
+ from typing import Any
25
25
 
26
26
  import enlighten
27
27
 
@@ -89,10 +89,10 @@ class Leader:
89
89
  self,
90
90
  config: Config,
91
91
  batchSystem: AbstractBatchSystem,
92
- provisioner: Optional[AbstractProvisioner],
92
+ provisioner: AbstractProvisioner | None,
93
93
  jobStore: AbstractJobStore,
94
94
  rootJob: JobDescription,
95
- jobCache: Optional[dict[Union[str, TemporaryID], JobDescription]] = None,
95
+ jobCache: dict[str | TemporaryID, JobDescription] | None = None,
96
96
  ) -> None:
97
97
  """
98
98
  Create a Toil Leader object.
@@ -201,7 +201,7 @@ class Leader:
201
201
 
202
202
  # A dashboard that runs on the leader node in AWS clusters to track the state
203
203
  # of the cluster
204
- self.toilMetrics: Optional[ToilMetrics] = None
204
+ self.toilMetrics: ToilMetrics | None = None
205
205
 
206
206
  # internal jobs we should not expose at top level debugging
207
207
  self.debugJobNames = (
@@ -349,14 +349,9 @@ class Leader:
349
349
  def create_status_sentinel_file(self, fail: bool) -> None:
350
350
  """Create a file in the jobstore indicating failure or success."""
351
351
  logName = "failed.log" if fail else "succeeded.log"
352
- localLog = os.path.join(os.getcwd(), logName)
353
- open(localLog, "w").close()
354
- self.jobStore.import_file("file://" + localLog, logName, hardlink=True)
355
-
356
- if os.path.exists(
357
- localLog
358
- ): # Bandaid for Jenkins tests failing stochastically and unexplainably.
359
- os.remove(localLog)
352
+ with self.jobStore.write_shared_file_stream(logName) as file_handle:
353
+ # We just need an empty file, so don't write any content
354
+ pass
360
355
 
361
356
  def _handledFailedSuccessor(self, successor_id: str, predecessor_id: str) -> bool:
362
357
  """
@@ -852,15 +847,15 @@ class Leader:
852
847
  )
853
848
  message = [
854
849
  f"Job failed with exit value {status_string}: {updatedJob}",
855
- f"Exit reason: {BatchJobExitReason.to_string(update.exitReason)}"
850
+ f"Exit reason: {BatchJobExitReason.to_string(update.exitReason)}",
856
851
  ]
857
852
  if update.backing_id is not None:
858
853
  # Report the job in the backing scheduler in case the user
859
854
  # needs to follow it down a level.
860
- message.append(f"Failed job in backing scheduler: {update.backing_id}")
861
- logger.warning(
862
- "\n".join(message)
863
- )
855
+ message.append(
856
+ f"Failed job in backing scheduler: {update.backing_id}"
857
+ )
858
+ logger.warning("\n".join(message))
864
859
  # This logic is undefined for which of the failing jobs will send its exit code
865
860
  # when there are multiple failing jobs with different exit statuses
866
861
  self.recommended_fail_exit_code = update.exitStatus
@@ -1188,7 +1183,7 @@ class Leader:
1188
1183
  )
1189
1184
  self.preemptibleServiceJobsIssued += 1
1190
1185
 
1191
- def getNumberOfJobsIssued(self, preemptible: Optional[bool] = None) -> int:
1186
+ def getNumberOfJobsIssued(self, preemptible: bool | None = None) -> int:
1192
1187
  """
1193
1188
  Get number of jobs that have been added by issueJob(s) and not removed by removeJob.
1194
1189
 
@@ -1267,7 +1262,7 @@ class Leader:
1267
1262
 
1268
1263
  return issuedDesc
1269
1264
 
1270
- def getJobs(self, preemptible: Optional[bool] = None) -> list[JobDescription]:
1265
+ def getJobs(self, preemptible: bool | None = None) -> list[JobDescription]:
1271
1266
  """
1272
1267
  Get all issued jobs.
1273
1268
 
@@ -1427,9 +1422,9 @@ class Leader:
1427
1422
  self,
1428
1423
  finished_job: JobDescription,
1429
1424
  result_status: int,
1430
- wall_time: Optional[float] = None,
1431
- exit_reason: Optional[BatchJobExitReason] = None,
1432
- batch_system_id: Optional[int] = None,
1425
+ wall_time: float | None = None,
1426
+ exit_reason: BatchJobExitReason | None = None,
1427
+ batch_system_id: int | None = None,
1433
1428
  ) -> bool:
1434
1429
  """
1435
1430
  Process a finished JobDescription based upon its success or failure.
toil/lib/accelerators.py CHANGED
@@ -17,7 +17,7 @@
17
17
  import os
18
18
  import string
19
19
  import subprocess
20
- from typing import Union, cast
20
+ from typing import cast
21
21
  from xml.dom import minidom
22
22
 
23
23
  from toil.job import AcceleratorRequirement
@@ -34,7 +34,9 @@ def have_working_nvidia_smi() -> bool:
34
34
  it can fulfill a CUDARequirement.
35
35
  """
36
36
  try:
37
- subprocess.check_call(["nvidia-smi"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
37
+ subprocess.check_call(
38
+ ["nvidia-smi"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
39
+ )
38
40
  except (
39
41
  FileNotFoundError,
40
42
  PermissionError,
@@ -105,7 +107,7 @@ def have_working_nvidia_docker_runtime() -> bool:
105
107
  "nvidia-smi",
106
108
  ],
107
109
  stdout=subprocess.DEVNULL,
108
- stderr=subprocess.DEVNULL
110
+ stderr=subprocess.DEVNULL,
109
111
  )
110
112
  except (
111
113
  FileNotFoundError,
@@ -217,7 +219,7 @@ def get_individual_local_accelerators() -> list[AcceleratorRequirement]:
217
219
 
218
220
 
219
221
  def get_restrictive_environment_for_local_accelerators(
220
- accelerator_numbers: Union[set[int], list[int]]
222
+ accelerator_numbers: set[int] | list[int],
221
223
  ) -> dict[str, str]:
222
224
  """
223
225
  Get environment variables which can be applied to a process to restrict it
toil/lib/aws/__init__.py CHANGED
@@ -15,10 +15,9 @@ import json
15
15
  import logging
16
16
  import os
17
17
  import re
18
- import socket
19
18
  from collections.abc import MutableMapping
20
19
  from http.client import HTTPException
21
- from typing import TYPE_CHECKING, Literal, Optional, Union
20
+ from typing import TYPE_CHECKING, Literal, Union
22
21
  from urllib.error import URLError
23
22
  from urllib.request import urlopen
24
23
 
@@ -43,7 +42,7 @@ logger = logging.getLogger(__name__)
43
42
  # which may not be installed, because it has to be importable everywhere.
44
43
 
45
44
 
46
- def get_current_aws_region() -> Optional[str]:
45
+ def get_current_aws_region() -> str | None:
47
46
  """
48
47
  Return the AWS region that the currently configured AWS zone (see
49
48
  get_current_aws_zone()) is in.
@@ -53,14 +52,14 @@ def get_current_aws_region() -> Optional[str]:
53
52
  return zone_to_region(aws_zone) if aws_zone else None
54
53
 
55
54
 
56
- def get_aws_zone_from_environment() -> Optional[str]:
55
+ def get_aws_zone_from_environment() -> str | None:
57
56
  """
58
57
  Get the AWS zone from TOIL_AWS_ZONE if set.
59
58
  """
60
59
  return os.environ.get("TOIL_AWS_ZONE", None)
61
60
 
62
61
 
63
- def get_aws_zone_from_metadata() -> Optional[str]:
62
+ def get_aws_zone_from_metadata() -> str | None:
64
63
  """
65
64
  Get the AWS zone from instance metadata, if on EC2 and the boto module is
66
65
  available. Otherwise, gets the AWS zone from ECS task metadata, if on ECS.
@@ -103,7 +102,7 @@ def get_aws_zone_from_metadata() -> Optional[str]:
103
102
  return None
104
103
 
105
104
 
106
- def get_aws_zone_from_boto() -> Optional[str]:
105
+ def get_aws_zone_from_boto() -> str | None:
107
106
  """
108
107
  Get the AWS zone from the Boto3 config file or from AWS_DEFAULT_REGION, if it is configured and the
109
108
  boto3 module is available.
@@ -122,7 +121,7 @@ def get_aws_zone_from_boto() -> Optional[str]:
122
121
  return None
123
122
 
124
123
 
125
- def get_aws_zone_from_environment_region() -> Optional[str]:
124
+ def get_aws_zone_from_environment_region() -> str | None:
126
125
  """
127
126
  Pick an AWS zone in the region defined by TOIL_AWS_REGION, if it is set.
128
127
  """
@@ -134,7 +133,7 @@ def get_aws_zone_from_environment_region() -> Optional[str]:
134
133
  return None
135
134
 
136
135
 
137
- def get_current_aws_zone() -> Optional[str]:
136
+ def get_current_aws_zone() -> str | None:
138
137
  """
139
138
  Get the currently configured or occupied AWS zone to use.
140
139
 
@@ -191,7 +190,7 @@ def running_on_ec2() -> bool:
191
190
  timeout=1,
192
191
  )
193
192
  return True
194
- except (URLError, socket.timeout, HTTPException):
193
+ except (URLError, TimeoutError, HTTPException):
195
194
  return False
196
195
 
197
196
 
@@ -204,7 +203,7 @@ def running_on_ecs() -> bool:
204
203
 
205
204
 
206
205
  def build_tag_dict_from_env(
207
- environment: MutableMapping[str, str] = os.environ
206
+ environment: MutableMapping[str, str] = os.environ,
208
207
  ) -> dict[str, str]:
209
208
  tags = dict()
210
209
  owner_tag = environment.get("TOIL_OWNER_TAG")