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
@@ -20,20 +20,24 @@ import uuid
20
20
  from contextlib import contextmanager
21
21
  from functools import wraps
22
22
  from io import BytesIO
23
- from typing import IO, List, Optional
23
+ from typing import IO, Optional
24
24
  from urllib.parse import ParseResult
25
25
 
26
- from google.api_core.exceptions import (GoogleAPICallError,
27
- InternalServerError,
28
- ServiceUnavailable)
26
+ from google.api_core.exceptions import (
27
+ GoogleAPICallError,
28
+ InternalServerError,
29
+ ServiceUnavailable,
30
+ )
29
31
  from google.auth.exceptions import DefaultCredentialsError
30
32
  from google.cloud import exceptions, storage
31
33
 
32
- from toil.jobStores.abstractJobStore import (AbstractJobStore,
33
- JobStoreExistsException,
34
- NoSuchFileException,
35
- NoSuchJobException,
36
- NoSuchJobStoreException)
34
+ from toil.jobStores.abstractJobStore import (
35
+ AbstractJobStore,
36
+ JobStoreExistsException,
37
+ NoSuchFileException,
38
+ NoSuchJobException,
39
+ NoSuchJobStoreException,
40
+ )
37
41
  from toil.jobStores.utils import ReadablePipe, WritablePipe
38
42
  from toil.lib.compatibility import compat_bytes
39
43
  from toil.lib.io import AtomicFileCreate
@@ -42,7 +46,7 @@ from toil.lib.retry import old_retry
42
46
 
43
47
  log = logging.getLogger(__name__)
44
48
 
45
- GOOGLE_STORAGE = 'gs'
49
+ GOOGLE_STORAGE = "gs"
46
50
 
47
51
  MAX_BATCH_SIZE = 1000
48
52
 
@@ -75,19 +79,22 @@ def google_retry(f):
75
79
 
76
80
  It should wrap any function that makes use of the Google Client API
77
81
  """
82
+
78
83
  @wraps(f)
79
84
  def wrapper(*args, **kwargs):
80
- for attempt in old_retry(delays=truncExpBackoff(),
81
- timeout=300,
82
- predicate=google_retry_predicate):
85
+ for attempt in old_retry(
86
+ delays=truncExpBackoff(), timeout=300, predicate=google_retry_predicate
87
+ ):
83
88
  with attempt:
84
89
  return f(*args, **kwargs)
90
+
85
91
  return wrapper
86
92
 
87
93
 
88
94
  class GoogleJobStore(AbstractJobStore):
89
95
 
90
- nodeServiceAccountJson = '/root/service_account.json'
96
+ nodeServiceAccountJson = "/root/service_account.json"
97
+
91
98
  def __init__(self, locator: str) -> None:
92
99
  super().__init__(locator)
93
100
 
@@ -99,20 +106,19 @@ class GoogleJobStore(AbstractJobStore):
99
106
  projectID = None
100
107
 
101
108
  self.projectID = projectID
102
- self.bucketName = namePrefix+"--toil"
109
+ self.bucketName = namePrefix + "--toil"
103
110
  log.debug("Instantiating google jobStore with name: %s", self.bucketName)
104
111
 
105
112
  # this is a :class:`~google.cloud.storage.bucket.Bucket`
106
113
  self.bucket = None
107
114
 
108
- self.statsBaseID = 'f16eef0c-b597-4b8b-9b0c-4d605b4f506c'
109
- self.statsReadPrefix = '_'
110
- self.readStatsBaseID = self.statsReadPrefix+self.statsBaseID
115
+ self.statsBaseID = "f16eef0c-b597-4b8b-9b0c-4d605b4f506c"
116
+ self.statsReadPrefix = "_"
117
+ self.readStatsBaseID = self.statsReadPrefix + self.statsBaseID
111
118
 
112
119
  self.sseKey = None
113
120
  self.storageClient = self.create_client()
114
121
 
115
-
116
122
  @classmethod
117
123
  def create_client(cls) -> storage.Client:
118
124
  """
@@ -127,28 +133,36 @@ class GoogleJobStore(AbstractJobStore):
127
133
  # Determine if we have an override environment variable for our credentials.
128
134
  # We get the path to check existence, but Google Storage works out what
129
135
  # to use later by looking at the environment again.
130
- credentials_path: Optional[str] = os.getenv('GOOGLE_APPLICATION_CREDENTIALS', None)
136
+ credentials_path: Optional[str] = os.getenv(
137
+ "GOOGLE_APPLICATION_CREDENTIALS", None
138
+ )
131
139
  if credentials_path is not None and not os.path.exists(credentials_path):
132
140
  # If the file is missing, complain.
133
141
  # This variable holds a file name and not any sensitive data itself.
134
- log.warning("File '%s' from GOOGLE_APPLICATION_CREDENTIALS is unavailable! "
135
- "We may not be able to authenticate!",
136
- credentials_path)
142
+ log.warning(
143
+ "File '%s' from GOOGLE_APPLICATION_CREDENTIALS is unavailable! "
144
+ "We may not be able to authenticate!",
145
+ credentials_path,
146
+ )
137
147
 
138
148
  if credentials_path is None and os.path.exists(cls.nodeServiceAccountJson):
139
149
  try:
140
150
  # load credentials from a particular file on GCE nodes if an override path is not set
141
- return storage.Client.from_service_account_json(cls.nodeServiceAccountJson)
151
+ return storage.Client.from_service_account_json(
152
+ cls.nodeServiceAccountJson
153
+ )
142
154
  except OSError:
143
155
  # Probably we don't have permission to use the file.
144
- log.warning("File '%s' exists but didn't work to authenticate!",
145
- cls.nodeServiceAccountJson)
156
+ log.warning(
157
+ "File '%s' exists but didn't work to authenticate!",
158
+ cls.nodeServiceAccountJson,
159
+ )
146
160
 
147
161
  # Either a filename is specified, or our fallback file isn't there.
148
162
  try:
149
163
  # See if Google can work out how to authenticate.
150
164
  return storage.Client()
151
- except (DefaultCredentialsError, EnvironmentError):
165
+ except (DefaultCredentialsError, OSError):
152
166
  # Depending on which Google codepath or module version (???)
153
167
  # realizes we have no credentials, we can get an EnvironemntError,
154
168
  # or the new DefaultCredentialsError we are supposedly specced to
@@ -158,18 +172,17 @@ class GoogleJobStore(AbstractJobStore):
158
172
  # This is likely to happen all the time so don't warn.
159
173
  return storage.Client.create_anonymous_client()
160
174
 
161
-
162
175
  @google_retry
163
176
  def initialize(self, config=None):
164
177
  try:
165
178
  self.bucket = self.storageClient.create_bucket(self.bucketName)
166
179
  except exceptions.Conflict:
167
- raise JobStoreExistsException(self.locator)
180
+ raise JobStoreExistsException(self.locator, "google")
168
181
  super().initialize(config)
169
182
 
170
183
  # set up sever side encryption after we set up config in super
171
184
  if self.config.sseKey is not None:
172
- with open(self.config.sseKey, 'rb') as f:
185
+ with open(self.config.sseKey, "rb") as f:
173
186
  self.sseKey = compat_bytes(f.read())
174
187
  assert len(self.sseKey) == 32
175
188
 
@@ -178,7 +191,7 @@ class GoogleJobStore(AbstractJobStore):
178
191
  try:
179
192
  self.bucket = self.storageClient.get_bucket(self.bucketName)
180
193
  except exceptions.NotFound:
181
- raise NoSuchJobStoreException(self.locator)
194
+ raise NoSuchJobStoreException(self.locator, "google")
182
195
  super().resume()
183
196
 
184
197
  @google_retry
@@ -199,18 +212,17 @@ class GoogleJobStore(AbstractJobStore):
199
212
  count = 0
200
213
  while count < len(blobs_to_delete):
201
214
  with self.storageClient.batch():
202
- for blob in blobs_to_delete[count:count + MAX_BATCH_SIZE]:
215
+ for blob in blobs_to_delete[count : count + MAX_BATCH_SIZE]:
203
216
  blob.delete()
204
217
  count = count + MAX_BATCH_SIZE
205
218
  self.bucket.delete()
206
219
 
207
220
  def _new_job_id(self):
208
- return f'job-{uuid.uuid4()}'
221
+ return f"job-{uuid.uuid4()}"
209
222
 
210
223
  def assign_job_id(self, job_description):
211
224
  jobStoreID = self._new_job_id()
212
- log.debug("Assigning ID to job %s for '%s'",
213
- jobStoreID, '<no command>' if job_description.command is None else job_description.command)
225
+ log.debug("Assigning ID to job %s", jobStoreID)
214
226
  job_description.jobStoreID = jobStoreID
215
227
 
216
228
  @contextmanager
@@ -220,12 +232,17 @@ class GoogleJobStore(AbstractJobStore):
220
232
 
221
233
  def create_job(self, job_description):
222
234
  job_description.pre_update_hook()
223
- self._write_bytes(job_description.jobStoreID, pickle.dumps(job_description, protocol=pickle.HIGHEST_PROTOCOL))
235
+ self._write_bytes(
236
+ job_description.jobStoreID,
237
+ pickle.dumps(job_description, protocol=pickle.HIGHEST_PROTOCOL),
238
+ )
224
239
  return job_description
225
240
 
226
241
  @google_retry
227
242
  def job_exists(self, job_id):
228
- return self.bucket.blob(compat_bytes(job_id), encryption_key=self.sseKey).exists()
243
+ return self.bucket.blob(
244
+ compat_bytes(job_id), encryption_key=self.sseKey
245
+ ).exists()
229
246
 
230
247
  @google_retry
231
248
  def get_public_url(self, fileName):
@@ -252,7 +269,11 @@ class GoogleJobStore(AbstractJobStore):
252
269
 
253
270
  def update_job(self, job):
254
271
  job.pre_update_hook()
255
- self._write_bytes(job.jobStoreID, pickle.dumps(job, protocol=pickle.HIGHEST_PROTOCOL), update=True)
272
+ self._write_bytes(
273
+ job.jobStoreID,
274
+ pickle.dumps(job, protocol=pickle.HIGHEST_PROTOCOL),
275
+ update=True,
276
+ )
256
277
 
257
278
  @google_retry
258
279
  def delete_job(self, job_id):
@@ -270,32 +291,40 @@ class GoogleJobStore(AbstractJobStore):
270
291
 
271
292
  env = {}
272
293
 
273
- credentials_path: Optional[str] = os.getenv('GOOGLE_APPLICATION_CREDENTIALS', None)
294
+ credentials_path: Optional[str] = os.getenv(
295
+ "GOOGLE_APPLICATION_CREDENTIALS", None
296
+ )
274
297
  if credentials_path is not None:
275
298
  # Send along the environment variable that points to the credentials file.
276
299
  # It must be available in the same place on all nodes.
277
- env['GOOGLE_APPLICATION_CREDENTIALS'] = credentials_path
300
+ env["GOOGLE_APPLICATION_CREDENTIALS"] = credentials_path
278
301
 
279
302
  return env
280
303
 
281
304
  @google_retry
282
305
  def jobs(self):
283
- for blob in self.bucket.list_blobs(prefix=b'job-'):
306
+ for blob in self.bucket.list_blobs(prefix=b"job-"):
284
307
  jobStoreID = blob.name
285
308
  # TODO: do this better
286
- if len(jobStoreID) == 40 and jobStoreID.startswith('job-'): # 'job-' + uuid length
309
+ if len(jobStoreID) == 40 and jobStoreID.startswith(
310
+ "job-"
311
+ ): # 'job-' + uuid length
287
312
  yield self.load_job(jobStoreID)
288
313
 
289
314
  def write_file(self, local_path, job_id=None, cleanup=False):
290
315
  fileID = self._new_id(isFile=True, jobStoreID=job_id if cleanup else None)
291
- with open(local_path, 'rb') as f:
316
+ with open(local_path, "rb") as f:
292
317
  self._write_file(fileID, f)
293
318
  return fileID
294
319
 
295
320
  @contextmanager
296
- def write_file_stream(self, job_id=None, cleanup=False, basename=None, encoding=None, errors=None):
321
+ def write_file_stream(
322
+ self, job_id=None, cleanup=False, basename=None, encoding=None, errors=None
323
+ ):
297
324
  fileID = self._new_id(isFile=True, jobStoreID=job_id if cleanup else None)
298
- with self._upload_stream(fileID, update=False, encoding=encoding, errors=errors) as writable:
325
+ with self._upload_stream(
326
+ fileID, update=False, encoding=encoding, errors=errors
327
+ ) as writable:
299
328
  yield writable, fileID
300
329
 
301
330
  def get_empty_file_store_id(self, jobStoreID=None, cleanup=False, basename=None):
@@ -310,16 +339,19 @@ class GoogleJobStore(AbstractJobStore):
310
339
  if not self.file_exists(file_id):
311
340
  raise NoSuchFileException(file_id)
312
341
  with AtomicFileCreate(local_path) as tmpPath:
313
- with open(tmpPath, 'wb') as writeable:
314
- blob = self.bucket.get_blob(compat_bytes(file_id), encryption_key=self.sseKey)
342
+ with open(tmpPath, "wb") as writeable:
343
+ blob = self.bucket.get_blob(
344
+ compat_bytes(file_id), encryption_key=self.sseKey
345
+ )
315
346
  blob.download_to_file(writeable)
316
- if getattr(file_id, 'executable', False):
347
+ if getattr(file_id, "executable", False):
317
348
  os.chmod(local_path, os.stat(local_path).st_mode | stat.S_IXUSR)
318
349
 
319
350
  @contextmanager
320
351
  def read_file_stream(self, file_id, encoding=None, errors=None):
321
- with self.read_shared_file_stream(file_id, isProtected=True, encoding=encoding,
322
- errors=errors) as readable:
352
+ with self.read_shared_file_stream(
353
+ file_id, isProtected=True, encoding=encoding, errors=errors
354
+ ) as readable:
323
355
  yield readable
324
356
 
325
357
  def delete_file(self, file_id):
@@ -327,32 +359,49 @@ class GoogleJobStore(AbstractJobStore):
327
359
 
328
360
  @google_retry
329
361
  def file_exists(self, file_id):
330
- return self.bucket.blob(compat_bytes(file_id), encryption_key=self.sseKey).exists()
362
+ return self.bucket.blob(
363
+ compat_bytes(file_id), encryption_key=self.sseKey
364
+ ).exists()
331
365
 
332
366
  @google_retry
333
367
  def get_file_size(self, file_id):
334
368
  if not self.file_exists(file_id):
335
369
  return 0
336
- return self.bucket.get_blob(compat_bytes(file_id), encryption_key=self.sseKey).size
370
+ return self.bucket.get_blob(
371
+ compat_bytes(file_id), encryption_key=self.sseKey
372
+ ).size
337
373
 
338
374
  def update_file(self, file_id, local_path):
339
- with open(local_path, 'rb') as f:
375
+ with open(local_path, "rb") as f:
340
376
  self._write_file(file_id, f, update=True)
341
377
 
342
378
  @contextmanager
343
379
  def update_file_stream(self, file_id, encoding=None, errors=None):
344
- with self._upload_stream(file_id, update=True, encoding=encoding, errors=errors) as writable:
380
+ with self._upload_stream(
381
+ file_id, update=True, encoding=encoding, errors=errors
382
+ ) as writable:
345
383
  yield writable
346
384
 
347
385
  @contextmanager
348
- def write_shared_file_stream(self, shared_file_name, encrypted=True, encoding=None, errors=None):
349
- with self._upload_stream(shared_file_name, encrypt=encrypted, update=True, encoding=encoding,
350
- errors=errors) as writable:
386
+ def write_shared_file_stream(
387
+ self, shared_file_name, encrypted=True, encoding=None, errors=None
388
+ ):
389
+ with self._upload_stream(
390
+ shared_file_name,
391
+ encrypt=encrypted,
392
+ update=True,
393
+ encoding=encoding,
394
+ errors=errors,
395
+ ) as writable:
351
396
  yield writable
352
397
 
353
398
  @contextmanager
354
- def read_shared_file_stream(self, shared_file_name, isProtected=True, encoding=None, errors=None):
355
- with self._download_stream(shared_file_name, encrypt=isProtected, encoding=encoding, errors=errors) as readable:
399
+ def read_shared_file_stream(
400
+ self, shared_file_name, isProtected=True, encoding=None, errors=None
401
+ ):
402
+ with self._download_stream(
403
+ shared_file_name, encrypt=isProtected, encoding=encoding, errors=errors
404
+ ) as readable:
356
405
  yield readable
357
406
 
358
407
  @classmethod
@@ -375,7 +424,7 @@ class GoogleJobStore(AbstractJobStore):
375
424
  fileName = url.path
376
425
 
377
426
  # remove leading '/', which can cause problems if fileName is a path
378
- if fileName.startswith('/'):
427
+ if fileName.startswith("/"):
379
428
  fileName = fileName[1:]
380
429
 
381
430
  storageClient = cls.create_client()
@@ -384,7 +433,7 @@ class GoogleJobStore(AbstractJobStore):
384
433
 
385
434
  if exists:
386
435
  if not blob.exists():
387
- raise NoSuchFileException
436
+ raise NoSuchFileException(fileName)
388
437
  # sync with cloud so info like size is available
389
438
  blob.reload()
390
439
  return blob
@@ -414,7 +463,7 @@ class GoogleJobStore(AbstractJobStore):
414
463
 
415
464
  @classmethod
416
465
  def _supports_url(cls, url, export=False):
417
- return url.scheme.lower() == 'gs'
466
+ return url.scheme.lower() == "gs"
418
467
 
419
468
  @classmethod
420
469
  def _write_to_url(cls, readable: bytes, url: str, executable: bool = False) -> None:
@@ -422,12 +471,16 @@ class GoogleJobStore(AbstractJobStore):
422
471
  blob.upload_from_file(readable)
423
472
 
424
473
  @classmethod
425
- def _list_url(cls, url: ParseResult) -> List[str]:
426
- raise NotImplementedError("Listing files in Google buckets is not yet implemented!")
474
+ def _list_url(cls, url: ParseResult) -> list[str]:
475
+ raise NotImplementedError(
476
+ "Listing files in Google buckets is not yet implemented!"
477
+ )
427
478
 
428
479
  @classmethod
429
480
  def _get_is_directory(cls, url: ParseResult) -> bool:
430
- raise NotImplementedError("Checking directory status in Google buckets is not yet implemented!")
481
+ raise NotImplementedError(
482
+ "Checking directory status in Google buckets is not yet implemented!"
483
+ )
431
484
 
432
485
  @google_retry
433
486
  def write_logs(self, msg: bytes) -> None:
@@ -457,7 +510,9 @@ class GoogleJobStore(AbstractJobStore):
457
510
  if not read_all:
458
511
  # rename this file by copying it and deleting the old version to avoid
459
512
  # rereading it
460
- newID = self.readStatsBaseID + blob.name[len(self.statsBaseID):]
513
+ newID = (
514
+ self.readStatsBaseID + blob.name[len(self.statsBaseID) :]
515
+ )
461
516
  # NOTE: just copies then deletes old.
462
517
  self.bucket.rename_blob(blob, compat_bytes(newID))
463
518
  except NoSuchFileException:
@@ -473,7 +528,7 @@ class GoogleJobStore(AbstractJobStore):
473
528
  if lastTry:
474
529
  # this was our second try, we are reasonably sure there aren't any stats
475
530
  # left to gather
476
- break
531
+ break
477
532
  # Try one more time in a couple seconds
478
533
  time.sleep(5)
479
534
  lastTry = True
@@ -487,11 +542,11 @@ class GoogleJobStore(AbstractJobStore):
487
542
  @staticmethod
488
543
  def _new_id(isFile=False, jobStoreID=None):
489
544
  if isFile and jobStoreID: # file associated with job
490
- return jobStoreID+str(uuid.uuid4())
545
+ return jobStoreID + str(uuid.uuid4())
491
546
  elif isFile: # nonassociated file
492
547
  return str(uuid.uuid4())
493
548
  else: # job id
494
- return f'job-{uuid.uuid4()}'
549
+ return f"job-{uuid.uuid4()}"
495
550
 
496
551
  @google_retry
497
552
  def _delete(self, jobStoreFileID):
@@ -515,8 +570,12 @@ class GoogleJobStore(AbstractJobStore):
515
570
  return job.download_as_string()
516
571
 
517
572
  @google_retry
518
- def _write_file(self, jobStoreID: str, fileObj: bytes, update=False, encrypt=True) -> None:
519
- blob = self.bucket.blob(compat_bytes(jobStoreID), encryption_key=self.sseKey if encrypt else None)
573
+ def _write_file(
574
+ self, jobStoreID: str, fileObj: bytes, update=False, encrypt=True
575
+ ) -> None:
576
+ blob = self.bucket.blob(
577
+ compat_bytes(jobStoreID), encryption_key=self.sseKey if encrypt else None
578
+ )
520
579
  if not update:
521
580
  # TODO: should probably raise a special exception and be added to all jobStores
522
581
  assert not blob.exists()
@@ -530,7 +589,9 @@ class GoogleJobStore(AbstractJobStore):
530
589
 
531
590
  @contextmanager
532
591
  @google_retry
533
- def _upload_stream(self, fileName, update=False, encrypt=True, encoding=None, errors=None):
592
+ def _upload_stream(
593
+ self, fileName, update=False, encrypt=True, encoding=None, errors=None
594
+ ):
534
595
  """
535
596
  Yields a context manager that can be used to write to the bucket
536
597
  with a stream. See :class:`~toil.jobStores.utils.WritablePipe` for an example.
@@ -556,7 +617,10 @@ class GoogleJobStore(AbstractJobStore):
556
617
  :return: an instance of WritablePipe.
557
618
  :rtype: :class:`~toil.jobStores.utils.writablePipe`
558
619
  """
559
- blob = self.bucket.blob(compat_bytes(fileName), encryption_key=self.sseKey if encrypt else None)
620
+ blob = self.bucket.blob(
621
+ compat_bytes(fileName), encryption_key=self.sseKey if encrypt else None
622
+ )
623
+
560
624
  class UploadPipe(WritablePipe):
561
625
  def readFrom(self, readable):
562
626
  if not update:
@@ -592,7 +656,9 @@ class GoogleJobStore(AbstractJobStore):
592
656
  :rtype: :class:`~toil.jobStores.utils.ReadablePipe`
593
657
  """
594
658
 
595
- blob = self.bucket.get_blob(compat_bytes(fileName), encryption_key=self.sseKey if encrypt else None)
659
+ blob = self.bucket.get_blob(
660
+ compat_bytes(fileName), encryption_key=self.sseKey if encrypt else None
661
+ )
596
662
  if blob is None:
597
663
  raise NoSuchFileException(fileName)
598
664
 
toil/jobStores/utils.py CHANGED
@@ -10,6 +10,7 @@ from toil.lib.threading import ExceptionalThread
10
10
 
11
11
  log = logging.getLogger(__name__)
12
12
 
13
+
13
14
  class WritablePipe(ABC):
14
15
  """
15
16
  An object-oriented wrapper for os.pipe. Clients should subclass it, implement
@@ -84,7 +85,7 @@ class WritablePipe(ABC):
84
85
  raise NotImplementedError()
85
86
 
86
87
  def _reader(self):
87
- with os.fdopen(self.readable_fh, 'rb') as readable:
88
+ with os.fdopen(self.readable_fh, "rb") as readable:
88
89
  # TODO: If the reader somehow crashes here, both threads might try
89
90
  # to close readable_fh. Fortunately we don't do anything that
90
91
  # should be able to fail here.
@@ -112,7 +113,12 @@ class WritablePipe(ABC):
112
113
 
113
114
  def __enter__(self):
114
115
  self.readable_fh, writable_fh = os.pipe()
115
- self.writable = os.fdopen(writable_fh, 'wb' if self.encoding == None else 'wt', encoding=self.encoding, errors=self.errors)
116
+ self.writable = os.fdopen(
117
+ writable_fh,
118
+ "wb" if self.encoding == None else "wt",
119
+ encoding=self.encoding,
120
+ errors=self.errors,
121
+ )
116
122
  self.thread = ExceptionalThread(target=self._reader)
117
123
  self.thread.start()
118
124
  return self.writable
@@ -132,7 +138,9 @@ class WritablePipe(ABC):
132
138
  # already an exception in the main thread
133
139
  raise
134
140
  else:
135
- log.error('Swallowing additional exception in reader thread: %s', str(e))
141
+ log.error(
142
+ "Swallowing additional exception in reader thread: %s", str(e)
143
+ )
136
144
  finally:
137
145
  # The responsibility for closing the readable end is generally that of the reader
138
146
  # thread. To cover the small window before the reader takes over we also close it here.
@@ -217,7 +225,7 @@ class ReadablePipe(ABC):
217
225
 
218
226
  def _writer(self):
219
227
  try:
220
- with os.fdopen(self.writable_fh, 'wb') as writable:
228
+ with os.fdopen(self.writable_fh, "wb") as writable:
221
229
  self.writeTo(writable)
222
230
  except OSError as e:
223
231
  # The other side of the pipe may have been closed by the
@@ -244,7 +252,12 @@ class ReadablePipe(ABC):
244
252
 
245
253
  def __enter__(self):
246
254
  readable_fh, self.writable_fh = os.pipe()
247
- self.readable = os.fdopen(readable_fh, 'rb' if self.encoding == None else 'rt', encoding=self.encoding, errors=self.errors)
255
+ self.readable = os.fdopen(
256
+ readable_fh,
257
+ "rb" if self.encoding == None else "rt",
258
+ encoding=self.encoding,
259
+ errors=self.errors,
260
+ )
248
261
  self.thread = ExceptionalThread(target=self._writer)
249
262
  self.thread.start()
250
263
  return self.readable
@@ -264,6 +277,7 @@ class ReadablePipe(ABC):
264
277
  # already an exception in the main thread
265
278
  raise
266
279
 
280
+
267
281
  class ReadableTransformingPipe(ReadablePipe):
268
282
  """
269
283
  A pipe which is constructed around a readable stream, and which provides a
@@ -296,7 +310,6 @@ class ReadableTransformingPipe(ReadablePipe):
296
310
 
297
311
  """
298
312
 
299
-
300
313
  def __init__(self, source, encoding=None, errors=None):
301
314
  """
302
315
  :param str encoding: the name of the encoding used to encode the file. Encodings are the same
@@ -323,15 +336,17 @@ class ReadableTransformingPipe(ReadablePipe):
323
336
  def writeTo(self, writable):
324
337
  self.transform(self.source, writable)
325
338
 
339
+
326
340
  class JobStoreUnavailableException(RuntimeError):
327
341
  """
328
342
  Raised when a particular type of job store is requested but can't be used.
329
343
  """
330
344
 
345
+
331
346
  def generate_locator(
332
347
  job_store_type: str,
333
348
  local_suggestion: Optional[str] = None,
334
- decoration: Optional[str] = None
349
+ decoration: Optional[str] = None,
335
350
  ) -> str:
336
351
  """
337
352
  Generate a random locator for a job store of the given type. Raises an
@@ -347,7 +362,7 @@ def generate_locator(
347
362
  """
348
363
 
349
364
  # Prepare decoration for splicing into strings
350
- decoration = ('-' + decoration) if decoration else ''
365
+ decoration = ("-" + decoration) if decoration else ""
351
366
 
352
367
  try:
353
368
  if job_store_type == "google":
@@ -363,6 +378,7 @@ def generate_locator(
363
378
  elif job_store_type == "aws":
364
379
  # Make sure we have AWS
365
380
  from toil.jobStores.aws.jobStore import AWSJobStore # noqa
381
+
366
382
  # Find a region
367
383
  from toil.lib.aws import get_current_aws_region
368
384
 
@@ -370,7 +386,9 @@ def generate_locator(
370
386
 
371
387
  if not region:
372
388
  # We can't generate an AWS job store without a region
373
- raise JobStoreUnavailableException(f"{job_store_type} job store can't be made without a region")
389
+ raise JobStoreUnavailableException(
390
+ f"{job_store_type} job store can't be made without a region"
391
+ )
374
392
 
375
393
  # Roll a random name
376
394
  return f"aws:{region}:toil{decoration}-{str(uuid.uuid4())}"
@@ -380,11 +398,14 @@ def generate_locator(
380
398
  return local_suggestion
381
399
  else:
382
400
  # Pick a temp path
383
- return os.path.join(tempfile.gettempdir(), 'toil-' + str(uuid.uuid4()) + decoration)
401
+ return os.path.join(
402
+ tempfile.gettempdir(), "toil-" + str(uuid.uuid4()) + decoration
403
+ )
384
404
  else:
385
- raise JobStoreUnavailableException(f"{job_store_type} job store isn't known")
405
+ raise JobStoreUnavailableException(
406
+ f"{job_store_type} job store isn't known"
407
+ )
386
408
  except ImportError:
387
- raise JobStoreUnavailableException(f"libraries for {job_store_type} job store are not installed")
388
-
389
-
390
-
409
+ raise JobStoreUnavailableException(
410
+ f"libraries for {job_store_type} job store are not installed"
411
+ )