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
@@ -28,7 +28,7 @@ from itertools import chain, islice
28
28
  from queue import Queue
29
29
  from tempfile import mkstemp
30
30
  from threading import Thread
31
- from typing import Any, Tuple
31
+ from typing import Any
32
32
  from urllib.request import Request, urlopen
33
33
 
34
34
  import pytest
@@ -37,21 +37,21 @@ from stubserver import FTPStubServer
37
37
  from toil.common import Config, Toil
38
38
  from toil.fileStores import FileID
39
39
  from toil.job import Job, JobDescription, TemporaryID
40
- from toil.jobStores.abstractJobStore import (NoSuchFileException,
41
- NoSuchJobException)
40
+ from toil.jobStores.abstractJobStore import NoSuchFileException, NoSuchJobException
42
41
  from toil.jobStores.fileJobStore import FileJobStore
43
- from toil.lib.aws.utils import create_s3_bucket, get_object_for_url
44
42
  from toil.lib.io import mkdtemp
45
43
  from toil.lib.memoize import memoize
46
44
  from toil.lib.retry import retry
47
45
  from toil.statsAndLogging import StatsAndLogging
48
- from toil.test import (ToilTest,
49
- make_tests,
50
- needs_aws_s3,
51
- needs_encryption,
52
- needs_google_project,
53
- needs_google_storage,
54
- slow)
46
+ from toil.test import (
47
+ ToilTest,
48
+ make_tests,
49
+ needs_aws_s3,
50
+ needs_encryption,
51
+ needs_google_project,
52
+ needs_google_storage,
53
+ slow,
54
+ )
55
55
 
56
56
  # noinspection PyPackageRequirements
57
57
  # (installed by `make prepare`)
@@ -83,11 +83,11 @@ class AbstractJobStoreTest:
83
83
  def setUpClass(cls):
84
84
  super().setUpClass()
85
85
  logging.basicConfig(level=logging.DEBUG)
86
- logging.getLogger('boto').setLevel(logging.CRITICAL)
87
- logging.getLogger('boto').setLevel(logging.WARNING)
88
- logging.getLogger('boto3.resources').setLevel(logging.WARNING)
89
- logging.getLogger('botocore.auth').setLevel(logging.WARNING)
90
- logging.getLogger('botocore.hooks').setLevel(logging.WARNING)
86
+ logging.getLogger("boto").setLevel(logging.CRITICAL)
87
+ logging.getLogger("boto").setLevel(logging.WARNING)
88
+ logging.getLogger("boto3.resources").setLevel(logging.WARNING)
89
+ logging.getLogger("botocore.auth").setLevel(logging.WARNING)
90
+ logging.getLogger("botocore.hooks").setLevel(logging.WARNING)
91
91
 
92
92
  # The use of @memoize ensures that we only have one instance of per class even with the
93
93
  # generative import/export tests attempts to instantiate more. This in turn enables us to
@@ -114,7 +114,7 @@ class AbstractJobStoreTest:
114
114
 
115
115
  def setUp(self):
116
116
  super().setUp()
117
- self.namePrefix = 'jobstore-test-' + str(uuid.uuid4())
117
+ self.namePrefix = "jobstore-test-" + str(uuid.uuid4())
118
118
  self.config = self._createConfig()
119
119
 
120
120
  # Jobstores to be used in testing.
@@ -128,11 +128,16 @@ class AbstractJobStoreTest:
128
128
  self.jobstore_resumed_noconfig.resume()
129
129
 
130
130
  # Requirements for jobs to be created.
131
- self.arbitraryRequirements = {'memory': 1, 'disk': 2, 'cores': 1, 'preemptible': False}
131
+ self.arbitraryRequirements = {
132
+ "memory": 1,
133
+ "disk": 2,
134
+ "cores": 1,
135
+ "preemptible": False,
136
+ }
132
137
  # Function to make an arbitrary new job
133
- self.arbitraryJob = lambda: JobDescription(command='command',
134
- jobName='arbitrary',
135
- requirements=self.arbitraryRequirements)
138
+ self.arbitraryJob = lambda: JobDescription(
139
+ jobName="arbitrary", requirements=self.arbitraryRequirements
140
+ )
136
141
 
137
142
  self.parentJobReqs = dict(memory=12, cores=34, disk=35, preemptible=True)
138
143
  self.childJobReqs1 = dict(memory=23, cores=45, disk=46, preemptible=True)
@@ -144,9 +149,13 @@ class AbstractJobStoreTest:
144
149
  super().tearDown()
145
150
 
146
151
  def testInitialState(self):
147
- """Ensure proper handling of nonexistant files."""
148
- self.assertFalse(self.jobstore_initialized.job_exists('nonexistantFile'))
149
- self.assertRaises(NoSuchJobException, self.jobstore_initialized.load_job, 'nonexistantFile')
152
+ """Ensure proper handling of nonexistent files."""
153
+ self.assertFalse(self.jobstore_initialized.job_exists("nonexistentFile"))
154
+ self.assertRaises(
155
+ NoSuchJobException,
156
+ self.jobstore_initialized.load_job,
157
+ "nonexistentFile",
158
+ )
150
159
 
151
160
  def testJobCreation(self):
152
161
  """
@@ -159,9 +168,9 @@ class AbstractJobStoreTest:
159
168
  jobstore = self.jobstore_initialized
160
169
 
161
170
  # Create a job and verify its existence/properties
162
- job = JobDescription(command='parent1',
163
- requirements=self.parentJobReqs,
164
- jobName='test1', unitName='onParent')
171
+ job = JobDescription(
172
+ requirements=self.parentJobReqs, jobName="test1", unitName="onParent"
173
+ )
165
174
  self.assertTrue(isinstance(job.jobStoreID, TemporaryID))
166
175
  jobstore.assign_job_id(job)
167
176
  self.assertFalse(isinstance(job.jobStoreID, TemporaryID))
@@ -170,13 +179,12 @@ class AbstractJobStoreTest:
170
179
  self.assertEqual(created, job)
171
180
 
172
181
  self.assertTrue(jobstore.job_exists(job.jobStoreID))
173
- self.assertEqual(job.command, 'parent1')
174
- self.assertEqual(job.memory, self.parentJobReqs['memory'])
175
- self.assertEqual(job.cores, self.parentJobReqs['cores'])
176
- self.assertEqual(job.disk, self.parentJobReqs['disk'])
177
- self.assertEqual(job.preemptible, self.parentJobReqs['preemptible'])
178
- self.assertEqual(job.jobName, 'test1')
179
- self.assertEqual(job.unitName, 'onParent')
182
+ self.assertEqual(job.memory, self.parentJobReqs["memory"])
183
+ self.assertEqual(job.cores, self.parentJobReqs["cores"])
184
+ self.assertEqual(job.disk, self.parentJobReqs["disk"])
185
+ self.assertEqual(job.preemptible, self.parentJobReqs["preemptible"])
186
+ self.assertEqual(job.jobName, "test1")
187
+ self.assertEqual(job.unitName, "onParent")
180
188
 
181
189
  def testConfigEquality(self):
182
190
  """
@@ -195,26 +203,26 @@ class AbstractJobStoreTest:
195
203
  """Tests that a job created via one JobStore instance can be loaded from another."""
196
204
 
197
205
  # Create a job on the first jobstore.
198
- jobDesc1 = JobDescription(command='jobstore1',
199
- requirements=self.parentJobReqs,
200
- jobName='test1', unitName='onJS1')
206
+ jobDesc1 = JobDescription(
207
+ requirements=self.parentJobReqs, jobName="test1", unitName="onJS1"
208
+ )
201
209
  self.jobstore_initialized.assign_job_id(jobDesc1)
202
210
  self.jobstore_initialized.create_job(jobDesc1)
203
211
 
204
212
  # Load it from the second jobstore
205
213
  jobDesc2 = self.jobstore_resumed_noconfig.load_job(jobDesc1.jobStoreID)
206
214
 
207
- self.assertEqual(jobDesc1.command, jobDesc2.command)
215
+ self.assertEqual(jobDesc1._body, jobDesc2._body)
208
216
 
209
217
  def testChildLoadingEquality(self):
210
218
  """Test that loading a child job operates as expected."""
211
- job = JobDescription(command='parent1',
212
- requirements=self.parentJobReqs,
213
- jobName='test1', unitName='onParent')
219
+ job = JobDescription(
220
+ requirements=self.parentJobReqs, jobName="test1", unitName="onParent"
221
+ )
214
222
 
215
- childJob = JobDescription(command='child1',
216
- requirements=self.childJobReqs1,
217
- jobName='test2', unitName='onChild1')
223
+ childJob = JobDescription(
224
+ requirements=self.childJobReqs1, jobName="test2", unitName="onChild1"
225
+ )
218
226
  self.jobstore_initialized.assign_job_id(job)
219
227
  self.jobstore_initialized.assign_job_id(childJob)
220
228
  self.jobstore_initialized.create_job(job)
@@ -222,7 +230,10 @@ class AbstractJobStoreTest:
222
230
  job.addChild(childJob.jobStoreID)
223
231
  self.jobstore_initialized.update_job(job)
224
232
 
225
- self.assertEqual(self.jobstore_initialized.load_job(list(job.allSuccessors())[0]).command, childJob.command)
233
+ self.assertEqual(
234
+ self.jobstore_initialized.load_job(list(job.allSuccessors())[0])._body,
235
+ childJob._body,
236
+ )
226
237
 
227
238
  def testPersistantFilesToDelete(self):
228
239
  """
@@ -237,32 +248,35 @@ class AbstractJobStoreTest:
237
248
  """
238
249
 
239
250
  # Create a job.
240
- job = JobDescription(command='job1',
241
- requirements=self.parentJobReqs,
242
- jobName='test1', unitName='onJS1')
251
+ job = JobDescription(
252
+ requirements=self.parentJobReqs, jobName="test1", unitName="onJS1"
253
+ )
243
254
 
244
255
  self.jobstore_initialized.assign_job_id(job)
245
256
  self.jobstore_initialized.create_job(job)
246
- job.filesToDelete = ['1', '2']
257
+ job.filesToDelete = ["1", "2"]
247
258
  self.jobstore_initialized.update_job(job)
248
- self.assertEqual(self.jobstore_initialized.load_job(job.jobStoreID).filesToDelete, ['1', '2'])
259
+ self.assertEqual(
260
+ self.jobstore_initialized.load_job(job.jobStoreID).filesToDelete,
261
+ ["1", "2"],
262
+ )
249
263
 
250
264
  def testUpdateBehavior(self):
251
265
  """Tests the proper behavior during updating jobs."""
252
266
  jobstore1 = self.jobstore_initialized
253
267
  jobstore2 = self.jobstore_resumed_noconfig
254
268
 
255
- job1 = JobDescription(command='parent1',
256
- requirements=self.parentJobReqs,
257
- jobName='test1', unitName='onParent')
269
+ job1 = JobDescription(
270
+ requirements=self.parentJobReqs, jobName="test1", unitName="onParent"
271
+ )
258
272
 
259
- childJob1 = JobDescription(command='child1',
260
- requirements=self.childJobReqs1,
261
- jobName='test2', unitName='onChild1')
273
+ childJob1 = JobDescription(
274
+ requirements=self.childJobReqs1, jobName="test2", unitName="onChild1"
275
+ )
262
276
 
263
- childJob2 = JobDescription(command='child2',
264
- requirements=self.childJobReqs2,
265
- jobName='test3', unitName='onChild2')
277
+ childJob2 = JobDescription(
278
+ requirements=self.childJobReqs2, jobName="test3", unitName="onChild2"
279
+ )
266
280
 
267
281
  jobstore1.assign_job_id(job1)
268
282
  jobstore1.create_job(job1)
@@ -281,7 +295,9 @@ class AbstractJobStoreTest:
281
295
 
282
296
  # Check equivalence between jobstore1 and jobstore2.
283
297
  # While job1 and job2 share a jobStoreID, job1 has not been "refreshed" to show the newly added child jobs.
284
- self.assertNotEqual(sorted(job2.allSuccessors()), sorted(job1.allSuccessors()))
298
+ self.assertNotEqual(
299
+ sorted(job2.allSuccessors()), sorted(job1.allSuccessors())
300
+ )
285
301
 
286
302
  # Reload parent job on jobstore, "refreshing" the job.
287
303
  job1 = jobstore1.load_job(job1.jobStoreID)
@@ -298,21 +314,21 @@ class AbstractJobStoreTest:
298
314
  """Tests the consequences of deleting jobs."""
299
315
  # A local jobstore object for testing.
300
316
  jobstore = self.jobstore_initialized
301
- job = JobDescription(command='job1',
302
- requirements=self.parentJobReqs,
303
- jobName='test1', unitName='onJob')
317
+ job = JobDescription(
318
+ requirements=self.parentJobReqs, jobName="test1", unitName="onJob"
319
+ )
304
320
  # Create job
305
321
  jobstore.assign_job_id(job)
306
322
  jobstore.create_job(job)
307
323
 
308
324
  # Create child Jobs
309
- child1 = JobDescription(command='child1',
310
- requirements=self.childJobReqs1,
311
- jobName='test2', unitName='onChild1')
325
+ child1 = JobDescription(
326
+ requirements=self.childJobReqs1, jobName="test2", unitName="onChild1"
327
+ )
312
328
 
313
- child2 = JobDescription(command='job1',
314
- requirements=self.childJobReqs2,
315
- jobName='test3', unitName='onChild2')
329
+ child2 = JobDescription(
330
+ requirements=self.childJobReqs2, jobName="test3", unitName="onChild2"
331
+ )
316
332
 
317
333
  # Add children to parent.
318
334
  jobstore.assign_job_id(child1)
@@ -323,9 +339,8 @@ class AbstractJobStoreTest:
323
339
  job.addChild(child2.jobStoreID)
324
340
  jobstore.update_job(job)
325
341
 
326
- # Get it ready to run children
327
- job.command = None
328
- jobstore.update_job(job)
342
+ # Parent must have no body to start on children
343
+ assert not job.has_body()
329
344
 
330
345
  # Go get the children
331
346
  childJobs = [jobstore.load_job(childID) for childID in job.nextSuccessors()]
@@ -335,7 +350,10 @@ class AbstractJobStoreTest:
335
350
  # jobs that show up are a subset of all existing jobs. If we had deleted jobs before
336
351
  # this we would have to worry about ghost jobs appearing and this assertion would not
337
352
  # be valid
338
- self.assertTrue({j.jobStoreID for j in (childJobs + [job])} >= {j.jobStoreID for j in jobstore.jobs()})
353
+ self.assertTrue(
354
+ {j.jobStoreID for j in (childJobs + [job])}
355
+ >= {j.jobStoreID for j in jobstore.jobs()}
356
+ )
339
357
 
340
358
  # Test job deletions
341
359
  # First delete parent, this should have no effect on the children
@@ -348,12 +366,14 @@ class AbstractJobStoreTest:
348
366
  self.assertTrue(jobstore.job_exists(childJob.jobStoreID))
349
367
  jobstore.delete_job(childJob.jobStoreID)
350
368
  self.assertFalse(jobstore.job_exists(childJob.jobStoreID))
351
- self.assertRaises(NoSuchJobException, jobstore.load_job, childJob.jobStoreID)
369
+ self.assertRaises(
370
+ NoSuchJobException, jobstore.load_job, childJob.jobStoreID
371
+ )
352
372
 
353
373
  try:
354
- with jobstore.read_shared_file_stream('missing') as _:
374
+ with jobstore.read_shared_file_stream("missing") as _:
355
375
  pass
356
- self.fail('Expecting NoSuchFileException')
376
+ self.fail("Expecting NoSuchFileException")
357
377
  except NoSuchFileException:
358
378
  pass
359
379
 
@@ -362,36 +382,40 @@ class AbstractJobStoreTest:
362
382
  jobstore1 = self.jobstore_initialized
363
383
  jobstore2 = self.jobstore_resumed_noconfig
364
384
 
365
- bar = b'bar'
385
+ bar = b"bar"
366
386
 
367
- with jobstore1.write_shared_file_stream('foo') as f:
387
+ with jobstore1.write_shared_file_stream("foo") as f:
368
388
  f.write(bar)
369
389
  # ... read that file on worker, ...
370
- with jobstore2.read_shared_file_stream('foo') as f:
390
+ with jobstore2.read_shared_file_stream("foo") as f:
371
391
  self.assertEqual(bar, f.read())
372
392
  # ... and read it again on jobstore1.
373
- with jobstore1.read_shared_file_stream('foo') as f:
393
+ with jobstore1.read_shared_file_stream("foo") as f:
374
394
  self.assertEqual(bar, f.read())
375
395
 
376
- with jobstore1.write_shared_file_stream('nonEncrypted', encrypted=False) as f:
396
+ with jobstore1.write_shared_file_stream(
397
+ "nonEncrypted", encrypted=False
398
+ ) as f:
377
399
  f.write(bar)
378
- self.assertUrl(jobstore1.get_shared_public_url('nonEncrypted'))
379
- self.assertRaises(NoSuchFileException, jobstore1.get_shared_public_url, 'missing')
400
+ self.assertUrl(jobstore1.get_shared_public_url("nonEncrypted"))
401
+ self.assertRaises(
402
+ NoSuchFileException, jobstore1.get_shared_public_url, "missing"
403
+ )
380
404
 
381
405
  def testReadWriteSharedFilesTextMode(self):
382
406
  """Checks if text mode is compatible for shared file streams."""
383
407
  jobstore1 = self.jobstore_initialized
384
408
  jobstore2 = self.jobstore_resumed_noconfig
385
409
 
386
- bar = 'bar'
410
+ bar = "bar"
387
411
 
388
- with jobstore1.write_shared_file_stream('foo', encoding='utf-8') as f:
412
+ with jobstore1.write_shared_file_stream("foo", encoding="utf-8") as f:
389
413
  f.write(bar)
390
414
 
391
- with jobstore2.read_shared_file_stream('foo', encoding='utf-8') as f:
415
+ with jobstore2.read_shared_file_stream("foo", encoding="utf-8") as f:
392
416
  self.assertEqual(bar, f.read())
393
417
 
394
- with jobstore1.read_shared_file_stream('foo', encoding='utf-8') as f:
418
+ with jobstore1.read_shared_file_stream("foo", encoding="utf-8") as f:
395
419
  self.assertEqual(bar, f.read())
396
420
 
397
421
  def testReadWriteFileStreamTextMode(self):
@@ -401,19 +425,22 @@ class AbstractJobStoreTest:
401
425
  jobstore.assign_job_id(job)
402
426
  jobstore.create_job(job)
403
427
 
404
- foo = 'foo'
405
- bar = 'bar'
428
+ foo = "foo"
429
+ bar = "bar"
406
430
 
407
- with jobstore.write_file_stream(job.jobStoreID, encoding='utf-8') as (f, fileID):
431
+ with jobstore.write_file_stream(job.jobStoreID, encoding="utf-8") as (
432
+ f,
433
+ fileID,
434
+ ):
408
435
  f.write(foo)
409
436
 
410
- with jobstore.read_file_stream(fileID, encoding='utf-8') as f:
437
+ with jobstore.read_file_stream(fileID, encoding="utf-8") as f:
411
438
  self.assertEqual(foo, f.read())
412
439
 
413
- with jobstore.update_file_stream(fileID, encoding='utf-8') as f:
440
+ with jobstore.update_file_stream(fileID, encoding="utf-8") as f:
414
441
  f.write(bar)
415
442
 
416
- with jobstore.read_file_stream(fileID, encoding='utf-8') as f:
443
+ with jobstore.read_file_stream(fileID, encoding="utf-8") as f:
417
444
  self.assertEqual(bar, f.read())
418
445
 
419
446
  def testPerJobFiles(self):
@@ -422,20 +449,22 @@ class AbstractJobStoreTest:
422
449
  jobstore2 = self.jobstore_resumed_noconfig
423
450
 
424
451
  # Create jobNodeOnJS1
425
- jobOnJobStore1 = JobDescription(command='job1',
426
- requirements=self.parentJobReqs,
427
- jobName='test1', unitName='onJobStore1')
452
+ jobOnJobStore1 = JobDescription(
453
+ requirements=self.parentJobReqs, jobName="test1", unitName="onJobStore1"
454
+ )
428
455
 
429
456
  # First recreate job
430
457
  jobstore1.assign_job_id(jobOnJobStore1)
431
458
  jobstore1.create_job(jobOnJobStore1)
432
- fileOne = jobstore2.get_empty_file_store_id(jobOnJobStore1.jobStoreID, cleanup=True)
459
+ fileOne = jobstore2.get_empty_file_store_id(
460
+ jobOnJobStore1.jobStoreID, cleanup=True
461
+ )
433
462
  # Check file exists
434
463
  self.assertTrue(jobstore2.file_exists(fileOne))
435
464
  self.assertTrue(jobstore1.file_exists(fileOne))
436
- one = b'one'
437
- two = b'two'
438
- three = b'three'
465
+ one = b"one"
466
+ two = b"two"
467
+ three = b"three"
439
468
  # ... write to the file on jobstore2, ...
440
469
  with jobstore2.update_file_stream(fileOne) as f:
441
470
  f.write(one)
@@ -447,20 +476,22 @@ class AbstractJobStoreTest:
447
476
  fh, path = mkstemp()
448
477
  try:
449
478
  os.close(fh)
450
- tmpPath = path + '.read-only'
479
+ tmpPath = path + ".read-only"
451
480
  jobstore1.read_file(fileOne, tmpPath)
452
481
  try:
453
482
  shutil.copyfile(tmpPath, path)
454
483
  finally:
455
484
  os.unlink(tmpPath)
456
- with open(path, 'rb+') as f:
485
+ with open(path, "rb+") as f:
457
486
  self.assertEqual(f.read(), one)
458
487
  # Write a different string to the local file ...
459
488
  f.seek(0)
460
489
  f.truncate(0)
461
490
  f.write(two)
462
491
  # ... and create a second file from the local file.
463
- fileTwo = jobstore1.write_file(path, jobOnJobStore1.jobStoreID, cleanup=True)
492
+ fileTwo = jobstore1.write_file(
493
+ path, jobOnJobStore1.jobStoreID, cleanup=True
494
+ )
464
495
  with jobstore2.read_file_stream(fileTwo) as f:
465
496
  self.assertEqual(f.read(), two)
466
497
  # Now update the first file from the local file ...
@@ -470,7 +501,9 @@ class AbstractJobStoreTest:
470
501
  finally:
471
502
  os.unlink(path)
472
503
  # Create a third file to test the last remaining method.
473
- with jobstore2.write_file_stream(jobOnJobStore1.jobStoreID, cleanup=True) as (f, fileThree):
504
+ with jobstore2.write_file_stream(
505
+ jobOnJobStore1.jobStoreID, cleanup=True
506
+ ) as (f, fileThree):
474
507
  f.write(three)
475
508
  with jobstore1.read_file_stream(fileThree) as f:
476
509
  self.assertEqual(f.read(), three)
@@ -481,30 +514,30 @@ class AbstractJobStoreTest:
481
514
  #
482
515
  for store in jobstore2, jobstore1:
483
516
  self.assertFalse(store.file_exists(fileOne))
484
- self.assertRaises(NoSuchFileException, store.read_file, fileOne, '')
517
+ self.assertRaises(NoSuchFileException, store.read_file, fileOne, "")
485
518
  try:
486
519
  with store.read_file_stream(fileOne) as _:
487
520
  pass
488
- self.fail('Expecting NoSuchFileException')
521
+ self.fail("Expecting NoSuchFileException")
489
522
  except NoSuchFileException:
490
523
  pass
491
524
 
492
525
  def testStatsAndLogging(self):
493
- """Tests behavior of reading and writting stats and logging."""
526
+ """Tests behavior of reading and writing stats and logging."""
494
527
  jobstore1 = self.jobstore_initialized
495
528
  jobstore2 = self.jobstore_resumed_noconfig
496
529
 
497
- jobOnJobStore1 = JobDescription(command='job1',
498
- requirements=self.parentJobReqs,
499
- jobName='test1', unitName='onJobStore1')
530
+ jobOnJobStore1 = JobDescription(
531
+ requirements=self.parentJobReqs, jobName="test1", unitName="onJobStore1"
532
+ )
500
533
 
501
534
  jobstore1.assign_job_id(jobOnJobStore1)
502
535
  jobstore1.create_job(jobOnJobStore1)
503
536
 
504
537
  # Test stats and logging
505
538
  stats = None
506
- one = b'one'
507
- two = b'two'
539
+ one = b"one"
540
+ two = b"two"
508
541
 
509
542
  # Allows stats to be read/written to/from in read/writeStatsAndLogging.
510
543
  def callback(f2):
@@ -521,7 +554,9 @@ class AbstractJobStoreTest:
521
554
  jobstore2.write_logs(one)
522
555
  self.assertEqual(1, jobstore1.read_logs(callback))
523
556
  self.assertEqual({one}, stats)
524
- self.assertEqual(0, jobstore1.read_logs(callback)) # read_logs purges saved stats etc
557
+ self.assertEqual(
558
+ 0, jobstore1.read_logs(callback)
559
+ ) # read_logs purges saved stats etc
525
560
 
526
561
  jobstore2.write_logs(one)
527
562
  jobstore2.write_logs(two)
@@ -545,17 +580,21 @@ class AbstractJobStoreTest:
545
580
 
546
581
  def testWriteLogFiles(self):
547
582
  """Test writing log files."""
548
- jobNames = ['testStatsAndLogging_writeLogFiles']
549
- jobLogList = ['string', b'bytes', '', b'newline\n']
583
+ jobNames = ["testStatsAndLogging_writeLogFiles"]
584
+ jobLogList = ["string", b"bytes", "", b"newline\n"]
550
585
  config = self._createConfig()
551
- setattr(config, 'writeLogs', '.')
552
- setattr(config, 'writeLogsGzip', None)
586
+ setattr(config, "writeLogs", self._createTempDir())
587
+ setattr(config, "writeLogsGzip", None)
553
588
  StatsAndLogging.writeLogFiles(jobNames, jobLogList, config)
554
- jobLogFile = os.path.join(config.writeLogs, jobNames[0] + '000.log')
589
+ jobLogFile = os.path.join(config.writeLogs, jobNames[0] + "_000.log")
590
+ # The log directory should get exactly one file, names after this
591
+ # easy job name with no replacements needed.
592
+ self.assertEqual(
593
+ os.listdir(config.writeLogs), [os.path.basename(jobLogFile)]
594
+ )
555
595
  self.assertTrue(os.path.isfile(jobLogFile))
556
596
  with open(jobLogFile) as f:
557
- self.assertEqual(f.read(), 'string\nbytes\n\nnewline\n')
558
- os.remove(jobLogFile)
597
+ self.assertEqual(f.read(), "string\nbytes\n\nnewline\n")
559
598
 
560
599
  def testBatchCreate(self):
561
600
  """Test creation of many jobs."""
@@ -564,9 +603,11 @@ class AbstractJobStoreTest:
564
603
  jobs = []
565
604
  with jobstore.batch():
566
605
  for i in range(100):
567
- overlargeJob = JobDescription(command='overlarge',
568
- requirements=jobRequirements,
569
- jobName='test-overlarge', unitName='onJobStore')
606
+ overlargeJob = JobDescription(
607
+ requirements=jobRequirements,
608
+ jobName="test-overlarge",
609
+ unitName="onJobStore",
610
+ )
570
611
  jobstore.assign_job_id(overlargeJob)
571
612
  jobstore.create_job(overlargeJob)
572
613
  jobs.append(overlargeJob)
@@ -634,16 +675,16 @@ class AbstractJobStoreTest:
634
675
  try:
635
676
  store = self.externalStoreCache[self]
636
677
  except KeyError:
637
- logger.debug('Creating new external store for %s', self)
678
+ logger.debug("Creating new external store for %s", self)
638
679
  store = self.externalStoreCache[self] = self._createExternalStore()
639
680
  else:
640
- logger.debug('Reusing external store for %s', self)
681
+ logger.debug("Reusing external store for %s", self)
641
682
  return store
642
683
 
643
684
  @classmethod
644
685
  def cleanUpExternalStores(cls):
645
686
  for test, store in cls.externalStoreCache.items():
646
- logger.debug('Cleaning up external store for %s.', test)
687
+ logger.debug("Cleaning up external store for %s.", test)
647
688
  test._cleanUpExternalStore(store)
648
689
 
649
690
  mpTestPartSize = 5 << 20
@@ -653,9 +694,11 @@ class AbstractJobStoreTest:
653
694
 
654
695
  testClasses = [FileJobStoreTest, AWSJobStoreTest, GoogleJobStoreTest]
655
696
 
656
- activeTestClassesByName = {testCls.__name__: testCls
657
- for testCls in testClasses
658
- if not getattr(testCls, '__unittest_skip__', False)}
697
+ activeTestClassesByName = {
698
+ testCls.__name__: testCls
699
+ for testCls in testClasses
700
+ if not getattr(testCls, "__unittest_skip__", False)
701
+ }
659
702
 
660
703
  def testImportExportFile(self, otherCls, size, moveExports):
661
704
  """
@@ -667,7 +710,7 @@ class AbstractJobStoreTest:
667
710
  :param int size: the size of the file to test importing/exporting with
668
711
  """
669
712
  # Prepare test file in other job store
670
- self.jobstore_initialized.partSize = cls.mpTestPartSize
713
+ self.jobstore_initialized.part_size = cls.mpTestPartSize
671
714
  self.jobstore_initialized.moveExports = moveExports
672
715
 
673
716
  # Test assumes imports are not linked
@@ -675,7 +718,7 @@ class AbstractJobStoreTest:
675
718
 
676
719
  # The string in otherCls() is arbitrary as long as it returns a class that has access
677
720
  # to ._externalStore() and ._prepareTestFile()
678
- other = otherCls('testSharedFiles')
721
+ other = otherCls("testSharedFiles")
679
722
  store = other._externalStore()
680
723
 
681
724
  srcUrl, srcMd5 = other._prepareTestFile(store, size)
@@ -690,9 +733,11 @@ class AbstractJobStoreTest:
690
733
  self.jobstore_initialized.export_file(jobStoreFileID, dstUrl)
691
734
  self.assertEqual(fileMD5, other._hashTestFile(dstUrl))
692
735
 
693
- if otherCls.__name__ == 'FileJobStoreTest':
736
+ if otherCls.__name__ == "FileJobStoreTest":
694
737
  if isinstance(self.jobstore_initialized, FileJobStore):
695
- jobStorePath = self.jobstore_initialized._get_file_path_from_id(jobStoreFileID)
738
+ jobStorePath = self.jobstore_initialized._get_file_path_from_id(
739
+ jobStoreFileID
740
+ )
696
741
  jobStoreHasLink = os.path.islink(jobStorePath)
697
742
  if self.jobstore_initialized.moveExports:
698
743
  # Ensure the export performed a move / link
@@ -706,14 +751,20 @@ class AbstractJobStoreTest:
706
751
  os.remove(srcUrl[7:])
707
752
  os.remove(dstUrl[7:])
708
753
 
709
- make_tests(testImportExportFile, cls, otherCls=activeTestClassesByName,
710
- size=dict(zero=0,
711
- one=1,
712
- oneMiB=2 ** 20,
713
- partSizeMinusOne=cls.mpTestPartSize - 1,
714
- partSize=cls.mpTestPartSize,
715
- partSizePlusOne=cls.mpTestPartSize + 1),
716
- moveExports={'deactivated': None, 'activated': True})
754
+ make_tests(
755
+ testImportExportFile,
756
+ cls,
757
+ otherCls=activeTestClassesByName,
758
+ size=dict(
759
+ zero=0,
760
+ one=1,
761
+ oneMiB=2**20,
762
+ partSizeMinusOne=cls.mpTestPartSize - 1,
763
+ partSize=cls.mpTestPartSize,
764
+ partSizePlusOne=cls.mpTestPartSize + 1,
765
+ ),
766
+ moveExports={"deactivated": None, "activated": True},
767
+ )
717
768
 
718
769
  def testImportSharedFile(self, otherCls):
719
770
  """
@@ -723,34 +774,37 @@ class AbstractJobStoreTest:
723
774
  to import from or export to
724
775
  """
725
776
  # Prepare test file in other job store
726
- self.jobstore_initialized.partSize = cls.mpTestPartSize
727
- other = otherCls('testSharedFiles')
777
+ self.jobstore_initialized.part_size = cls.mpTestPartSize
778
+ other = otherCls("testSharedFiles")
728
779
  store = other._externalStore()
729
780
 
730
781
  srcUrl, srcMd5 = other._prepareTestFile(store, 42)
731
782
  # Import into job store under test
732
- self.assertIsNone(self.jobstore_initialized.import_file(srcUrl, shared_file_name='foo'))
733
- with self.jobstore_initialized.read_shared_file_stream('foo') as f:
783
+ self.assertIsNone(
784
+ self.jobstore_initialized.import_file(
785
+ srcUrl, shared_file_name="foo"
786
+ )
787
+ )
788
+ with self.jobstore_initialized.read_shared_file_stream("foo") as f:
734
789
  fileMD5 = hashlib.md5(f.read()).hexdigest()
735
790
  self.assertEqual(fileMD5, srcMd5)
736
- if otherCls.__name__ == 'FileJobStoreTest': # Remove local Files
791
+ if otherCls.__name__ == "FileJobStoreTest": # Remove local Files
737
792
  os.remove(srcUrl[7:])
738
793
 
739
- make_tests(testImportSharedFile,
740
- cls,
741
- otherCls=activeTestClassesByName)
794
+ make_tests(testImportSharedFile, cls, otherCls=activeTestClassesByName)
742
795
 
743
796
  def testImportHttpFile(self):
744
- '''Test importing a file over HTTP.'''
745
- http = socketserver.TCPServer(('', 0), StubHttpRequestHandler)
797
+ """Test importing a file over HTTP."""
798
+ http = socketserver.TCPServer(("", 0), StubHttpRequestHandler)
746
799
  try:
747
800
  httpThread = threading.Thread(target=http.serve_forever)
748
801
  httpThread.start()
749
802
  try:
750
803
  assignedPort = http.server_address[1]
751
- url = 'http://localhost:%d' % assignedPort
804
+ url = "http://localhost:%d" % assignedPort
752
805
  with self.jobstore_initialized.read_file_stream(
753
- self.jobstore_initialized.import_file(url)) as readable:
806
+ self.jobstore_initialized.import_file(url)
807
+ ) as readable:
754
808
  f1 = readable.read()
755
809
  f2 = StubHttpRequestHandler.fileContents
756
810
  if isinstance(f1, bytes) and not isinstance(f2, bytes):
@@ -765,20 +819,25 @@ class AbstractJobStoreTest:
765
819
  http.server_close()
766
820
 
767
821
  def testImportFtpFile(self):
768
- '''Test importing a file over FTP'''
769
- ftpfile = {'name': 'foo', 'content': 'foo bar baz qux'}
822
+ """Test importing a file over FTP"""
823
+ ftpfile = {"name": "foo", "content": "foo bar baz qux"}
770
824
  ftp = FTPStubServer(0)
771
825
  ftp.run()
772
826
  try:
773
827
  ftp.add_file(**ftpfile)
774
828
  assignedPort = ftp.server.server_address[1]
775
- url = 'ftp://user1:passwd@localhost:%d/%s' % (assignedPort, ftpfile['name'])
776
- with self.jobstore_initialized.read_file_stream(self.jobstore_initialized.import_file(url)) as readable:
829
+ url = "ftp://user1:passwd@localhost:%d/%s" % (
830
+ assignedPort,
831
+ ftpfile["name"],
832
+ )
833
+ with self.jobstore_initialized.read_file_stream(
834
+ self.jobstore_initialized.import_file(url)
835
+ ) as readable:
777
836
  imported_content = readable.read()
778
837
  # python 2/3 string/bytestring compat
779
838
  if isinstance(imported_content, bytes):
780
- imported_content = imported_content.decode('utf-8')
781
- self.assertEqual(imported_content, ftpfile['content'])
839
+ imported_content = imported_content.decode("utf-8")
840
+ self.assertEqual(imported_content, ftpfile["content"])
782
841
  finally:
783
842
  ftp.stop()
784
843
 
@@ -794,12 +853,19 @@ class AbstractJobStoreTest:
794
853
  job = self.arbitraryJob()
795
854
  self.jobstore_initialized.assign_job_id(job)
796
855
  self.jobstore_initialized.create_job(job)
797
- fileIDs = [self.jobstore_initialized.get_empty_file_store_id(job.jobStoreID, cleanup=True) for _ in
798
- range(0, numFiles)]
856
+ fileIDs = [
857
+ self.jobstore_initialized.get_empty_file_store_id(
858
+ job.jobStoreID, cleanup=True
859
+ )
860
+ for _ in range(0, numFiles)
861
+ ]
799
862
  self.jobstore_initialized.delete_job(job.jobStoreID)
800
863
  for fileID in fileIDs:
801
864
  # NB: the fooStream() methods return context managers
802
- self.assertRaises(NoSuchFileException, self.jobstore_initialized.read_file_stream(fileID).__enter__)
865
+ self.assertRaises(
866
+ NoSuchFileException,
867
+ self.jobstore_initialized.read_file_stream(fileID).__enter__,
868
+ )
803
869
 
804
870
  @slow
805
871
  def testMultipartUploads(self):
@@ -834,9 +900,10 @@ class AbstractJobStoreTest:
834
900
  checksumThread.start()
835
901
  try:
836
902
  # Should not block. On Linux, /dev/random blocks when it's running low on entropy
837
- with open('/dev/urandom', 'rb') as readable:
838
- with self.jobstore_initialized.write_file_stream(job.jobStoreID, cleanup=True) as (
839
- writable, fileId):
903
+ with open("/dev/urandom", "rb") as readable:
904
+ with self.jobstore_initialized.write_file_stream(
905
+ job.jobStoreID, cleanup=True
906
+ ) as (writable, fileId):
840
907
  for i in range(int(partSize * partsPerFile / bufSize)):
841
908
  buf = readable.read(bufSize)
842
909
  checksumQueue.put(buf)
@@ -861,13 +928,15 @@ class AbstractJobStoreTest:
861
928
  checksum = hashlib.md5()
862
929
  fh, path = mkstemp()
863
930
  try:
864
- with os.fdopen(fh, 'wb+') as writable:
865
- with open('/dev/urandom', 'rb') as readable:
931
+ with os.fdopen(fh, "wb+") as writable:
932
+ with open("/dev/urandom", "rb") as readable:
866
933
  for i in range(int(partSize * partsPerFile / bufSize)):
867
934
  buf = readable.read(bufSize)
868
935
  writable.write(buf)
869
936
  checksum.update(buf)
870
- fileId = self.jobstore_initialized.write_file(path, job.jobStoreID, cleanup=True)
937
+ fileId = self.jobstore_initialized.write_file(
938
+ path, job.jobStoreID, cleanup=True
939
+ )
871
940
  finally:
872
941
  os.unlink(path)
873
942
  before = checksum.hexdigest()
@@ -885,14 +954,18 @@ class AbstractJobStoreTest:
885
954
  self.jobstore_initialized.delete_job(job.jobStoreID)
886
955
 
887
956
  def testZeroLengthFiles(self):
888
- '''Test reading and writing of empty files.'''
957
+ """Test reading and writing of empty files."""
889
958
  job = self.arbitraryJob()
890
959
  self.jobstore_initialized.assign_job_id(job)
891
960
  self.jobstore_initialized.create_job(job)
892
- nullFile = self.jobstore_initialized.write_file('/dev/null', job.jobStoreID, cleanup=True)
961
+ nullFile = self.jobstore_initialized.write_file(
962
+ "/dev/null", job.jobStoreID, cleanup=True
963
+ )
893
964
  with self.jobstore_initialized.read_file_stream(nullFile) as f:
894
965
  assert not f.read()
895
- with self.jobstore_initialized.write_file_stream(job.jobStoreID, cleanup=True) as (f, nullStream):
966
+ with self.jobstore_initialized.write_file_stream(
967
+ job.jobStoreID, cleanup=True
968
+ ) as (f, nullStream):
896
969
  pass
897
970
  with self.jobstore_initialized.read_file_stream(nullStream) as f:
898
971
  assert not f.read()
@@ -900,12 +973,12 @@ class AbstractJobStoreTest:
900
973
 
901
974
  @slow
902
975
  def testLargeFile(self):
903
- '''Test the reading and writing of large files.'''
976
+ """Test the reading and writing of large files."""
904
977
  # Write a large file.
905
978
  dirPath = self._createTempDir()
906
- filePath = os.path.join(dirPath, 'large')
979
+ filePath = os.path.join(dirPath, "large")
907
980
  hashIn = hashlib.md5()
908
- with open(filePath, 'wb') as f:
981
+ with open(filePath, "wb") as f:
909
982
  for i in range(0, 10):
910
983
  buf = os.urandom(self._partSize())
911
984
  f.write(buf)
@@ -915,7 +988,9 @@ class AbstractJobStoreTest:
915
988
  job = self.arbitraryJob()
916
989
  self.jobstore_initialized.assign_job_id(job)
917
990
  self.jobstore_initialized.create_job(job)
918
- jobStoreFileID = self.jobstore_initialized.write_file(filePath, job.jobStoreID, cleanup=True)
991
+ jobStoreFileID = self.jobstore_initialized.write_file(
992
+ filePath, job.jobStoreID, cleanup=True
993
+ )
919
994
 
920
995
  # Remove the local file.
921
996
  os.unlink(filePath)
@@ -925,7 +1000,7 @@ class AbstractJobStoreTest:
925
1000
 
926
1001
  # Reread the file to confirm success.
927
1002
  hashOut = hashlib.md5()
928
- with open(filePath, 'rb') as f:
1003
+ with open(filePath, "rb") as f:
929
1004
  while True:
930
1005
  buf = f.read(self._partSize())
931
1006
  if not buf:
@@ -943,8 +1018,8 @@ class AbstractJobStoreTest:
943
1018
 
944
1019
  def assertUrl(self, url):
945
1020
 
946
- prefix, path = url.split(':', 1)
947
- if prefix == 'file':
1021
+ prefix, path = url.split(":", 1)
1022
+ if prefix == "file":
948
1023
  self.assertTrue(os.path.exists(path))
949
1024
  else:
950
1025
  try:
@@ -982,8 +1057,7 @@ class AbstractJobStoreTest:
982
1057
  self.assertEqual(len(list(jobstore.jobs())), 101)
983
1058
 
984
1059
  # See how long it takes to clean with cache
985
- jobCache = {job.jobStoreID: job
986
- for job in jobstore.jobs()}
1060
+ jobCache = {job.jobStoreID: job for job in jobstore.jobs()}
987
1061
  cacheStart = time.time()
988
1062
  jobstore.clean(jobCache)
989
1063
  cacheEnd = time.time()
@@ -998,13 +1072,15 @@ class AbstractJobStoreTest:
998
1072
  # NB: the 'thread' method seems to be needed here to actually
999
1073
  # ensure the timeout is raised, probably because the only
1000
1074
  # "live" thread doesn't hold the GIL.
1001
- @pytest.mark.timeout(45, method='thread')
1075
+ @pytest.mark.timeout(45, method="thread")
1002
1076
  def testPartialReadFromStream(self):
1003
1077
  """Test whether readFileStream will deadlock on a partial read."""
1004
1078
  job = self.arbitraryJob()
1005
1079
  self.jobstore_initialized.assign_job_id(job)
1006
1080
  self.jobstore_initialized.create_job(job)
1007
- with self.jobstore_initialized.write_file_stream(job.jobStoreID, cleanup=True) as (f, fileID):
1081
+ with self.jobstore_initialized.write_file_stream(
1082
+ job.jobStoreID, cleanup=True
1083
+ ) as (f, fileID):
1008
1084
  # Write enough data to make sure the writer thread
1009
1085
  # will get blocked on the write. Technically anything
1010
1086
  # greater than the pipe buffer size plus the libc
@@ -1012,7 +1088,7 @@ class AbstractJobStoreTest:
1012
1088
  # but this gives us a lot of extra room just to be sure.
1013
1089
 
1014
1090
  # python 3 requires self.fileContents to be a bytestring
1015
- a = b'a'
1091
+ a = b"a"
1016
1092
  f.write(a * 300000)
1017
1093
  with self.jobstore_initialized.read_file_stream(fileID) as f:
1018
1094
  self.assertEqual(f.read(1), a)
@@ -1085,9 +1161,9 @@ class AbstractEncryptedJobStoreTest:
1085
1161
 
1086
1162
  def _createConfig(self):
1087
1163
  config = super()._createConfig()
1088
- sseKeyFile = os.path.join(self.sseKeyDir, 'keyFile')
1089
- with open(sseKeyFile, 'w') as f:
1090
- f.write('01234567890123456789012345678901')
1164
+ sseKeyFile = os.path.join(self.sseKeyDir, "keyFile")
1165
+ with open(sseKeyFile, "w") as f:
1166
+ f.write("01234567890123456789012345678901")
1091
1167
  config.sseKey = sseKeyFile
1092
1168
  # config.attrib['sse_key'] = sseKeyFile
1093
1169
  return config
@@ -1097,9 +1173,11 @@ class AbstractEncryptedJobStoreTest:
1097
1173
  Create an encrypted file. Read it in encrypted mode then try with encryption off
1098
1174
  to ensure that it fails.
1099
1175
  """
1100
- phrase = b'This file is encrypted.'
1101
- fileName = 'foo'
1102
- with self.jobstore_initialized.write_shared_file_stream(fileName, encrypted=True) as f:
1176
+ phrase = b"This file is encrypted."
1177
+ fileName = "foo"
1178
+ with self.jobstore_initialized.write_shared_file_stream(
1179
+ fileName, encrypted=True
1180
+ ) as f:
1103
1181
  f.write(phrase)
1104
1182
  with self.jobstore_initialized.read_shared_file_stream(fileName) as f:
1105
1183
  self.assertEqual(phrase, f.read())
@@ -1110,7 +1188,9 @@ class AbstractEncryptedJobStoreTest:
1110
1188
  with self.jobstore_initialized.read_shared_file_stream(fileName) as f:
1111
1189
  self.assertEqual(phrase, f.read())
1112
1190
  except AssertionError as e:
1113
- self.assertEqual("Content is encrypted but no key was provided.", e.args[0])
1191
+ self.assertEqual(
1192
+ "Content is encrypted but no key was provided.", e.args[0]
1193
+ )
1114
1194
  else:
1115
1195
  self.fail("Read encryption content with encryption off.")
1116
1196
 
@@ -1126,21 +1206,21 @@ class FileJobStoreTest(AbstractJobStoreTest.Test):
1126
1206
  shutil.rmtree(self.jobstore_initialized.jobStoreDir)
1127
1207
 
1128
1208
  def _prepareTestFile(self, dirPath, size=None):
1129
- fileName = 'testfile_%s' % uuid.uuid4()
1209
+ fileName = "testfile_%s" % uuid.uuid4()
1130
1210
  localFilePath = dirPath + fileName
1131
- url = 'file://%s' % localFilePath
1211
+ url = "file://%s" % localFilePath
1132
1212
  if size is None:
1133
1213
  return url
1134
1214
  else:
1135
1215
  content = os.urandom(size)
1136
- with open(localFilePath, 'wb') as writable:
1216
+ with open(localFilePath, "wb") as writable:
1137
1217
  writable.write(content)
1138
1218
 
1139
1219
  return url, hashlib.md5(content).hexdigest()
1140
1220
 
1141
1221
  def _hashTestFile(self, url):
1142
1222
  localFilePath = FileJobStore._extract_path_from_url(urlparse.urlparse(url))
1143
- with open(localFilePath, 'rb') as f:
1223
+ with open(localFilePath, "rb") as f:
1144
1224
  return hashlib.md5(f.read()).hexdigest()
1145
1225
 
1146
1226
  def _createExternalStore(self):
@@ -1157,7 +1237,9 @@ class FileJobStoreTest(AbstractJobStoreTest.Test):
1157
1237
  job = self.arbitraryJob()
1158
1238
  self.jobstore_initialized.assign_job_id(job)
1159
1239
  self.jobstore_initialized.create_job(job)
1160
- fileID = self.jobstore_initialized.write_file(path, job.jobStoreID, cleanup=True)
1240
+ fileID = self.jobstore_initialized.write_file(
1241
+ path, job.jobStoreID, cleanup=True
1242
+ )
1161
1243
  self.assertTrue(fileID.endswith(os.path.basename(path)))
1162
1244
  finally:
1163
1245
  os.unlink(path)
@@ -1168,12 +1250,19 @@ class FileJobStoreTest(AbstractJobStoreTest.Test):
1168
1250
  original_filestore = None
1169
1251
  try:
1170
1252
  original_filestore = self._createExternalStore()
1171
- dir_symlinked_to_original_filestore = f'{original_filestore}-am-i-real'
1253
+ dir_symlinked_to_original_filestore = f"{original_filestore}-am-i-real"
1172
1254
  os.symlink(original_filestore, dir_symlinked_to_original_filestore)
1173
- filejobstore_using_symlink = FileJobStore(dir_symlinked_to_original_filestore, fanOut=2)
1174
- self.assertEqual(dir_symlinked_to_original_filestore, filejobstore_using_symlink.jobStoreDir)
1255
+ filejobstore_using_symlink = FileJobStore(
1256
+ dir_symlinked_to_original_filestore, fanOut=2
1257
+ )
1258
+ self.assertEqual(
1259
+ dir_symlinked_to_original_filestore,
1260
+ filejobstore_using_symlink.jobStoreDir,
1261
+ )
1175
1262
  finally:
1176
- if dir_symlinked_to_original_filestore and os.path.exists(dir_symlinked_to_original_filestore):
1263
+ if dir_symlinked_to_original_filestore and os.path.exists(
1264
+ dir_symlinked_to_original_filestore
1265
+ ):
1177
1266
  os.unlink(dir_symlinked_to_original_filestore)
1178
1267
  if original_filestore and os.path.exists(original_filestore):
1179
1268
  shutil.rmtree(original_filestore)
@@ -1187,21 +1276,23 @@ class FileJobStoreTest(AbstractJobStoreTest.Test):
1187
1276
  try:
1188
1277
  # Grab a temp directory to make files in. Make sure it's on the
1189
1278
  # same device as everything else.
1190
- temp_dir = os.path.abspath(self.namePrefix + '-import')
1279
+ temp_dir = os.path.abspath(self.namePrefix + "-import")
1191
1280
  os.mkdir(temp_dir)
1192
- to_import = os.path.join(temp_dir, 'import-me')
1193
- with open(to_import, 'w') as f:
1194
- f.write('test')
1281
+ to_import = os.path.join(temp_dir, "import-me")
1282
+ with open(to_import, "w") as f:
1283
+ f.write("test")
1195
1284
 
1196
1285
  # And a temp directory next to the job store to download to
1197
- download_dir = os.path.abspath(self.namePrefix + '-dl')
1286
+ download_dir = os.path.abspath(self.namePrefix + "-dl")
1198
1287
  os.mkdir(download_dir)
1199
1288
 
1200
1289
  # Import it as a symlink
1201
- file_id = self.jobstore_initialized.import_file('file://' + to_import, symlink=True)
1290
+ file_id = self.jobstore_initialized.import_file(
1291
+ "file://" + to_import, symlink=True
1292
+ )
1202
1293
 
1203
1294
  # Take it out as a hard link or copy
1204
- download_to = os.path.join(download_dir, 'downloaded')
1295
+ download_to = os.path.join(download_dir, "downloaded")
1205
1296
  self.jobstore_initialized.read_file(file_id, download_to)
1206
1297
 
1207
1298
  # Make sure it isn't a symlink
@@ -1226,7 +1317,9 @@ class FileJobStoreTest(AbstractJobStoreTest.Test):
1226
1317
  for link_imports in [True, False]:
1227
1318
  self.jobstore_initialized.linkImports = link_imports
1228
1319
  # Import into job store under test
1229
- jobStoreFileID = self.jobstore_initialized.import_file(srcUrl, symlink=symlink)
1320
+ jobStoreFileID = self.jobstore_initialized.import_file(
1321
+ srcUrl, symlink=symlink
1322
+ )
1230
1323
  self.assertTrue(isinstance(jobStoreFileID, FileID))
1231
1324
  with self.jobstore_initialized.read_file_stream(jobStoreFileID) as f:
1232
1325
  # gets abs path
@@ -1242,16 +1335,44 @@ class FileJobStoreTest(AbstractJobStoreTest.Test):
1242
1335
  os.remove(filename)
1243
1336
  os.remove(srcUrl[7:])
1244
1337
 
1338
+ def test_symlink_read_control(self):
1339
+ """
1340
+ Test that files are read by symlink when expected
1341
+ """
1342
+
1343
+ for should_link in (False, True):
1344
+ # Configure a jobstore to symlink out reads or not, as appropriate
1345
+ config = self._createConfig()
1346
+ config.symlink_job_store_reads = should_link
1347
+ store = FileJobStore(
1348
+ self.namePrefix + ("-link" if should_link else "-nolink")
1349
+ )
1350
+ store.initialize(config)
1351
+
1352
+ # Put something in the job store
1353
+ src_url, _ = self._prepareTestFile(self._externalStore(), 1)
1354
+ file_id = store.import_file(src_url, symlink=False)
1355
+
1356
+ # Read it out, accepting a symlink
1357
+ dest_dir = self._createTempDir()
1358
+ dest_path = os.path.join(dest_dir, "file.dat")
1359
+ store.read_file(file_id, dest_path, symlink=True)
1360
+
1361
+ # Make sure we get a symlink exactly when configured to
1362
+ assert os.path.exists(dest_path)
1363
+ assert os.path.islink(dest_path) == should_link
1364
+
1245
1365
 
1246
1366
  @needs_google_project
1247
1367
  @needs_google_storage
1248
1368
  @pytest.mark.xfail
1249
1369
  class GoogleJobStoreTest(AbstractJobStoreTest.Test):
1250
- projectID = os.getenv('TOIL_GOOGLE_PROJECTID')
1370
+ projectID = os.getenv("TOIL_GOOGLE_PROJECTID")
1251
1371
  headers = {"x-goog-project-id": projectID}
1252
1372
 
1253
1373
  def _createJobStore(self):
1254
1374
  from toil.jobStores.googleJobStore import GoogleJobStore
1375
+
1255
1376
  return GoogleJobStore(GoogleJobStoreTest.projectID + ":" + self.namePrefix)
1256
1377
 
1257
1378
  def _corruptJobStore(self):
@@ -1261,24 +1382,31 @@ class GoogleJobStoreTest(AbstractJobStoreTest.Test):
1261
1382
 
1262
1383
  def _prepareTestFile(self, bucket, size=None):
1263
1384
  from toil.jobStores.googleJobStore import GoogleJobStore
1264
- fileName = 'testfile_%s' % uuid.uuid4()
1265
- url = f'gs://{bucket.name}/{fileName}'
1385
+
1386
+ fileName = "testfile_%s" % uuid.uuid4()
1387
+ url = f"gs://{bucket.name}/{fileName}"
1266
1388
  if size is None:
1267
1389
  return url
1268
- with open('/dev/urandom', 'rb') as readable:
1390
+ with open("/dev/urandom", "rb") as readable:
1269
1391
  contents = str(readable.read(size))
1270
- GoogleJobStore._write_to_url(BytesIO(bytes(contents, 'utf-8')), urlparse.urlparse(url))
1392
+ GoogleJobStore._write_to_url(
1393
+ BytesIO(bytes(contents, "utf-8")), urlparse.urlparse(url)
1394
+ )
1271
1395
  return url, hashlib.md5(contents.encode()).hexdigest()
1272
1396
 
1273
1397
  def _hashTestFile(self, url):
1274
1398
  from toil.jobStores.googleJobStore import GoogleJobStore
1275
- contents = GoogleJobStore._get_blob_from_url(urlparse.urlparse(url)).download_as_string()
1399
+
1400
+ contents = GoogleJobStore._get_blob_from_url(
1401
+ urlparse.urlparse(url)
1402
+ ).download_as_string()
1276
1403
  return hashlib.md5(contents).hexdigest()
1277
1404
 
1278
1405
  @google_retry
1279
1406
  def _createExternalStore(self):
1280
1407
  from google.cloud import storage
1281
- bucketName = ("import-export-test-" + str(uuid.uuid4()))
1408
+
1409
+ bucketName = "import-export-test-" + str(uuid.uuid4())
1282
1410
  storageClient = storage.Client()
1283
1411
  return storageClient.create_bucket(bucketName)
1284
1412
 
@@ -1297,11 +1425,13 @@ class GoogleJobStoreTest(AbstractJobStoreTest.Test):
1297
1425
  class AWSJobStoreTest(AbstractJobStoreTest.Test):
1298
1426
  def _createJobStore(self):
1299
1427
  from toil.jobStores.aws.jobStore import AWSJobStore
1428
+
1300
1429
  partSize = self._partSize()
1301
- return AWSJobStore(self.awsRegion() + ':' + self.namePrefix, partSize=partSize)
1430
+ return AWSJobStore(self.awsRegion() + ":" + self.namePrefix, partSize=partSize)
1302
1431
 
1303
1432
  def _corruptJobStore(self):
1304
1433
  from toil.jobStores.aws.jobStore import AWSJobStore
1434
+
1305
1435
  assert isinstance(self.jobstore_initialized, AWSJobStore) # type hinting
1306
1436
  self.jobstore_initialized.destroy()
1307
1437
 
@@ -1311,53 +1441,71 @@ class AWSJobStoreTest(AbstractJobStoreTest.Test):
1311
1441
  failed to be created. We simulate a failed jobstore bucket creation by using a bucket in a
1312
1442
  different region with the same name.
1313
1443
  """
1314
- from boto.sdb import connect_to_region
1315
1444
  from botocore.exceptions import ClientError
1316
1445
 
1317
1446
  from toil.jobStores.aws.jobStore import BucketLocationConflictException
1318
1447
  from toil.lib.aws.session import establish_boto3_session
1319
1448
  from toil.lib.aws.utils import retry_s3
1320
1449
 
1321
- externalAWSLocation = 'us-west-1'
1322
- for testRegion in 'us-east-1', 'us-west-2':
1450
+ externalAWSLocation = "us-west-1"
1451
+ for testRegion in "us-east-1", "us-west-2":
1323
1452
  # We run this test twice, once with the default s3 server us-east-1 as the test region
1324
1453
  # and once with another server (us-west-2). The external server is always us-west-1.
1325
1454
  # This incidentally tests that the BucketLocationConflictException is thrown when using
1326
1455
  # both the default, and a non-default server.
1327
1456
  testJobStoreUUID = str(uuid.uuid4())
1328
1457
  # Create the bucket at the external region
1329
- bucketName = 'domain-test-' + testJobStoreUUID + '--files'
1330
- client = establish_boto3_session().client('s3', region_name=externalAWSLocation)
1331
- resource = establish_boto3_session().resource('s3', region_name=externalAWSLocation)
1458
+ bucketName = "domain-test-" + testJobStoreUUID + "--files"
1459
+ client = establish_boto3_session().client(
1460
+ "s3", region_name=externalAWSLocation
1461
+ )
1462
+ resource = establish_boto3_session().resource(
1463
+ "s3", region_name=externalAWSLocation
1464
+ )
1332
1465
 
1333
1466
  for attempt in retry_s3(delays=(2, 5, 10, 30, 60), timeout=600):
1334
1467
  with attempt:
1335
1468
  # Create the bucket at the home region
1336
- client.create_bucket(Bucket=bucketName,
1337
- CreateBucketConfiguration={'LocationConstraint': externalAWSLocation})
1338
-
1339
- owner_tag = os.environ.get('TOIL_OWNER_TAG')
1469
+ client.create_bucket(
1470
+ Bucket=bucketName,
1471
+ CreateBucketConfiguration={
1472
+ "LocationConstraint": externalAWSLocation
1473
+ },
1474
+ )
1475
+
1476
+ owner_tag = os.environ.get("TOIL_OWNER_TAG")
1340
1477
  if owner_tag:
1341
1478
  for attempt in retry_s3(delays=(1, 1, 2, 4, 8, 16), timeout=33):
1342
1479
  with attempt:
1343
1480
  bucket_tagging = resource.BucketTagging(bucketName)
1344
- bucket_tagging.put(Tagging={'TagSet': [{'Key': 'Owner', 'Value': owner_tag}]})
1345
-
1346
- options = Job.Runner.getDefaultOptions('aws:' + testRegion + ':domain-test-' + testJobStoreUUID)
1347
- options.logLevel = 'DEBUG'
1481
+ bucket_tagging.put(
1482
+ Tagging={"TagSet": [{"Key": "Owner", "Value": owner_tag}]}
1483
+ )
1484
+
1485
+ options = Job.Runner.getDefaultOptions(
1486
+ "aws:" + testRegion + ":domain-test-" + testJobStoreUUID
1487
+ )
1488
+ options.logLevel = "DEBUG"
1348
1489
  try:
1349
1490
  with Toil(options) as toil:
1350
1491
  pass
1351
1492
  except BucketLocationConflictException:
1352
1493
  # Catch the expected BucketLocationConflictException and ensure that the bound
1353
1494
  # domains don't exist in SDB.
1354
- sdb = connect_to_region(self.awsRegion())
1495
+ sdb = establish_boto3_session().client(
1496
+ region_name=self.awsRegion(), service_name="sdb"
1497
+ )
1355
1498
  next_token = None
1356
1499
  allDomainNames = []
1357
1500
  while True:
1358
- domains = sdb.get_all_domains(max_domains=100, next_token=next_token)
1359
- allDomainNames.extend([x.name for x in domains])
1360
- next_token = domains.next_token
1501
+ if next_token is None:
1502
+ domains = sdb.list_domains(MaxNumberOfDomains=100)
1503
+ else:
1504
+ domains = sdb.list_domains(
1505
+ MaxNumberOfDomains=100, NextToken=next_token
1506
+ )
1507
+ allDomainNames.extend(domains["DomainNames"])
1508
+ next_token = domains.get("NextToken")
1361
1509
  if next_token is None:
1362
1510
  break
1363
1511
  self.assertFalse([d for d in allDomainNames if testJobStoreUUID in d])
@@ -1370,7 +1518,10 @@ class AWSJobStoreTest(AbstractJobStoreTest.Test):
1370
1518
  client.delete_bucket(Bucket=bucketName)
1371
1519
  except ClientError as e:
1372
1520
  # The actual HTTP code of the error is in status.
1373
- if e.response.get('ResponseMetadata', {}).get('HTTPStatusCode') == 404:
1521
+ if (
1522
+ e.response.get("ResponseMetadata", {}).get("HTTPStatusCode")
1523
+ == 404
1524
+ ):
1374
1525
  # The bucket doesn't exist; maybe a failed delete actually succeeded.
1375
1526
  pass
1376
1527
  else:
@@ -1379,26 +1530,29 @@ class AWSJobStoreTest(AbstractJobStoreTest.Test):
1379
1530
  @slow
1380
1531
  def testInlinedFiles(self):
1381
1532
  from toil.jobStores.aws.jobStore import AWSJobStore
1533
+
1382
1534
  jobstore = self.jobstore_initialized
1383
1535
  for encrypted in (True, False):
1384
1536
  n = AWSJobStore.FileInfo.maxInlinedSize()
1385
1537
  sizes = (1, n // 2, n - 1, n, n + 1, 2 * n)
1386
1538
  for size in chain(sizes, islice(reversed(sizes), 1)):
1387
1539
  s = os.urandom(size)
1388
- with jobstore.write_shared_file_stream('foo') as f:
1540
+ with jobstore.write_shared_file_stream("foo") as f:
1389
1541
  f.write(s)
1390
- with jobstore.read_shared_file_stream('foo') as f:
1542
+ with jobstore.read_shared_file_stream("foo") as f:
1391
1543
  self.assertEqual(s, f.read())
1392
1544
 
1393
1545
  def testOverlargeJob(self):
1394
1546
  jobstore = self.jobstore_initialized
1395
1547
  jobRequirements = dict(memory=12, cores=34, disk=35, preemptible=True)
1396
- overlargeJob = JobDescription(command='overlarge',
1397
- requirements=jobRequirements,
1398
- jobName='test-overlarge', unitName='onJobStore')
1548
+ overlargeJob = JobDescription(
1549
+ requirements=jobRequirements,
1550
+ jobName="test-overlarge",
1551
+ unitName="onJobStore",
1552
+ )
1399
1553
 
1400
1554
  # Make the pickled size of the job larger than 256K
1401
- with open("/dev/urandom", 'rb') as random:
1555
+ with open("/dev/urandom", "rb") as random:
1402
1556
  overlargeJob.jobName = str(random.read(512 * 1024))
1403
1557
  jobstore.assign_job_id(overlargeJob)
1404
1558
  jobstore.create_job(overlargeJob)
@@ -1410,33 +1564,39 @@ class AWSJobStoreTest(AbstractJobStoreTest.Test):
1410
1564
  jobstore.delete_job(overlargeJob.jobStoreID)
1411
1565
 
1412
1566
  def testMultiThreadImportFile(self) -> None:
1413
- """ Tests that importFile is thread-safe."""
1567
+ """Tests that importFile is thread-safe."""
1414
1568
 
1415
1569
  from concurrent.futures.thread import ThreadPoolExecutor
1416
1570
 
1417
1571
  from toil.lib.threading import cpu_count
1418
1572
 
1419
- threads: Tuple[int, ...] = (2, cpu_count()) if cpu_count() > 2 else (2, )
1573
+ threads: tuple[int, ...] = (2, cpu_count()) if cpu_count() > 2 else (2,)
1420
1574
  num_of_files: int = 5
1421
1575
  size: int = 1 << 16 + 1
1422
1576
 
1423
1577
  # The string in otherCls() is arbitrary as long as it returns a class that has access
1424
1578
  # to ._externalStore() and ._prepareTestFile()
1425
- other: AbstractJobStoreTest.Test = AWSJobStoreTest('testSharedFiles')
1579
+ other: AbstractJobStoreTest.Test = AWSJobStoreTest("testSharedFiles")
1426
1580
  store: Any = other._externalStore()
1427
1581
 
1428
1582
  # prepare test files to import
1429
- logger.debug(f'Preparing {num_of_files} test files for testMultiThreadImportFile().')
1583
+ logger.debug(
1584
+ f"Preparing {num_of_files} test files for testMultiThreadImportFile()."
1585
+ )
1430
1586
  test_files = [other._prepareTestFile(store, size) for _ in range(num_of_files)]
1431
1587
 
1432
1588
  for thread_count in threads:
1433
- with self.subTest(f'Testing threaded importFile with "{thread_count}" threads.'):
1589
+ with self.subTest(
1590
+ f'Testing threaded importFile with "{thread_count}" threads.'
1591
+ ):
1434
1592
  results = []
1435
1593
 
1436
1594
  with ThreadPoolExecutor(max_workers=thread_count) as executor:
1437
1595
  for url, expected_md5 in test_files:
1438
1596
  # run jobStore.importFile() asynchronously
1439
- future = executor.submit(self.jobstore_initialized.import_file, url)
1597
+ future = executor.submit(
1598
+ self.jobstore_initialized.import_file, url
1599
+ )
1440
1600
  results.append((future, expected_md5))
1441
1601
 
1442
1602
  self.assertEqual(len(results), num_of_files)
@@ -1446,32 +1606,39 @@ class AWSJobStoreTest(AbstractJobStoreTest.Test):
1446
1606
  self.assertIsInstance(file_id, FileID)
1447
1607
 
1448
1608
  with self.jobstore_initialized.read_file_stream(file_id) as f:
1449
- self.assertEqual(hashlib.md5(f.read()).hexdigest(), expected_md5)
1609
+ self.assertEqual(
1610
+ hashlib.md5(f.read()).hexdigest(), expected_md5
1611
+ )
1450
1612
 
1451
1613
  def _prepareTestFile(self, bucket, size=None):
1452
1614
  from toil.lib.aws.utils import retry_s3
1453
1615
 
1454
- file_name = 'testfile_%s' % uuid.uuid4()
1455
- url = f's3://{bucket.name}/{file_name}'
1616
+ file_name = "testfile_%s" % uuid.uuid4()
1617
+ url = f"s3://{bucket.name}/{file_name}"
1456
1618
  if size is None:
1457
1619
  return url
1458
- with open('/dev/urandom', 'rb') as readable:
1620
+ with open("/dev/urandom", "rb") as readable:
1459
1621
  for attempt in retry_s3():
1460
1622
  with attempt:
1461
1623
  bucket.put_object(Key=file_name, Body=str(readable.read(size)))
1462
- return url, hashlib.md5(bucket.Object(file_name).get().get('Body').read()).hexdigest()
1624
+ return (
1625
+ url,
1626
+ hashlib.md5(bucket.Object(file_name).get().get("Body").read()).hexdigest(),
1627
+ )
1463
1628
 
1464
1629
  def _hashTestFile(self, url: str) -> str:
1465
1630
  from toil.jobStores.aws.jobStore import AWSJobStore
1631
+ from toil.lib.aws.utils import get_object_for_url
1632
+
1466
1633
  str(AWSJobStore) # to prevent removal of that import
1467
1634
  key = get_object_for_url(urlparse.urlparse(url), existing=True)
1468
- contents = key.get().get('Body').read()
1635
+ contents = key.get().get("Body").read()
1469
1636
  return hashlib.md5(contents).hexdigest()
1470
1637
 
1471
1638
  def _createExternalStore(self):
1472
1639
  """A S3.Bucket instance is returned"""
1473
1640
  from toil.jobStores.aws.jobStore import establish_boto3_session
1474
- from toil.lib.aws.utils import retry_s3
1641
+ from toil.lib.aws.utils import create_s3_bucket, retry_s3
1475
1642
 
1476
1643
  resource = establish_boto3_session().resource(
1477
1644
  "s3", region_name=self.awsRegion()
@@ -1502,6 +1669,7 @@ class AWSJobStoreTest(AbstractJobStoreTest.Test):
1502
1669
 
1503
1670
  def _batchDeletionSize(self):
1504
1671
  from toil.jobStores.aws.jobStore import AWSJobStore
1672
+
1505
1673
  return AWSJobStore.itemsPerBatchDelete
1506
1674
 
1507
1675
 
@@ -1509,15 +1677,10 @@ class AWSJobStoreTest(AbstractJobStoreTest.Test):
1509
1677
  class InvalidAWSJobStoreTest(ToilTest):
1510
1678
  def testInvalidJobStoreName(self):
1511
1679
  from toil.jobStores.aws.jobStore import AWSJobStore
1512
- self.assertRaises(ValueError,
1513
- AWSJobStore,
1514
- 'us-west-2:a--b')
1515
- self.assertRaises(ValueError,
1516
- AWSJobStore,
1517
- 'us-west-2:' + ('a' * 100))
1518
- self.assertRaises(ValueError,
1519
- AWSJobStore,
1520
- 'us-west-2:a_b')
1680
+
1681
+ self.assertRaises(ValueError, AWSJobStore, "us-west-2:a--b")
1682
+ self.assertRaises(ValueError, AWSJobStore, "us-west-2:" + ("a" * 100))
1683
+ self.assertRaises(ValueError, AWSJobStore, "us-west-2:a_b")
1521
1684
 
1522
1685
 
1523
1686
  @needs_aws_s3
@@ -1528,14 +1691,14 @@ class EncryptedAWSJobStoreTest(AWSJobStoreTest, AbstractEncryptedJobStoreTest.Te
1528
1691
 
1529
1692
 
1530
1693
  class StubHttpRequestHandler(http.server.SimpleHTTPRequestHandler):
1531
- fileContents = 'A good programmer looks both ways before crossing a one-way street'
1694
+ fileContents = "A good programmer looks both ways before crossing a one-way street"
1532
1695
 
1533
1696
  def do_GET(self):
1534
1697
  self.send_response(200)
1535
1698
  self.send_header("Content-type", "text/plain")
1536
1699
  self.send_header("Content-length", len(self.fileContents))
1537
1700
  self.end_headers()
1538
- self.fileContents = self.fileContents.encode('utf-8')
1701
+ self.fileContents = self.fileContents.encode("utf-8")
1539
1702
  self.wfile.write(self.fileContents)
1540
1703
 
1541
1704