toil 6.1.0a1__py3-none-any.whl → 8.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. toil/__init__.py +122 -315
  2. toil/batchSystems/__init__.py +1 -0
  3. toil/batchSystems/abstractBatchSystem.py +173 -89
  4. toil/batchSystems/abstractGridEngineBatchSystem.py +272 -148
  5. toil/batchSystems/awsBatch.py +244 -135
  6. toil/batchSystems/cleanup_support.py +26 -16
  7. toil/batchSystems/contained_executor.py +31 -28
  8. toil/batchSystems/gridengine.py +86 -50
  9. toil/batchSystems/htcondor.py +166 -89
  10. toil/batchSystems/kubernetes.py +632 -382
  11. toil/batchSystems/local_support.py +20 -15
  12. toil/batchSystems/lsf.py +134 -81
  13. toil/batchSystems/lsfHelper.py +13 -11
  14. toil/batchSystems/mesos/__init__.py +41 -29
  15. toil/batchSystems/mesos/batchSystem.py +290 -151
  16. toil/batchSystems/mesos/executor.py +79 -50
  17. toil/batchSystems/mesos/test/__init__.py +31 -23
  18. toil/batchSystems/options.py +46 -28
  19. toil/batchSystems/registry.py +53 -19
  20. toil/batchSystems/singleMachine.py +296 -125
  21. toil/batchSystems/slurm.py +603 -138
  22. toil/batchSystems/torque.py +47 -33
  23. toil/bus.py +186 -76
  24. toil/common.py +664 -368
  25. toil/cwl/__init__.py +1 -1
  26. toil/cwl/cwltoil.py +1136 -483
  27. toil/cwl/utils.py +17 -22
  28. toil/deferred.py +63 -42
  29. toil/exceptions.py +5 -3
  30. toil/fileStores/__init__.py +5 -5
  31. toil/fileStores/abstractFileStore.py +140 -60
  32. toil/fileStores/cachingFileStore.py +717 -269
  33. toil/fileStores/nonCachingFileStore.py +116 -87
  34. toil/job.py +1225 -368
  35. toil/jobStores/abstractJobStore.py +416 -266
  36. toil/jobStores/aws/jobStore.py +863 -477
  37. toil/jobStores/aws/utils.py +201 -120
  38. toil/jobStores/conftest.py +3 -2
  39. toil/jobStores/fileJobStore.py +292 -154
  40. toil/jobStores/googleJobStore.py +140 -74
  41. toil/jobStores/utils.py +36 -15
  42. toil/leader.py +668 -272
  43. toil/lib/accelerators.py +115 -18
  44. toil/lib/aws/__init__.py +74 -31
  45. toil/lib/aws/ami.py +122 -87
  46. toil/lib/aws/iam.py +284 -108
  47. toil/lib/aws/s3.py +31 -0
  48. toil/lib/aws/session.py +214 -39
  49. toil/lib/aws/utils.py +287 -231
  50. toil/lib/bioio.py +13 -5
  51. toil/lib/compatibility.py +11 -6
  52. toil/lib/conversions.py +104 -47
  53. toil/lib/docker.py +131 -103
  54. toil/lib/ec2.py +361 -199
  55. toil/lib/ec2nodes.py +174 -106
  56. toil/lib/encryption/_dummy.py +5 -3
  57. toil/lib/encryption/_nacl.py +10 -6
  58. toil/lib/encryption/conftest.py +1 -0
  59. toil/lib/exceptions.py +26 -7
  60. toil/lib/expando.py +5 -3
  61. toil/lib/ftp_utils.py +217 -0
  62. toil/lib/generatedEC2Lists.py +127 -19
  63. toil/lib/humanize.py +6 -2
  64. toil/lib/integration.py +341 -0
  65. toil/lib/io.py +141 -15
  66. toil/lib/iterables.py +4 -2
  67. toil/lib/memoize.py +12 -8
  68. toil/lib/misc.py +66 -21
  69. toil/lib/objects.py +2 -2
  70. toil/lib/resources.py +68 -15
  71. toil/lib/retry.py +126 -81
  72. toil/lib/threading.py +299 -82
  73. toil/lib/throttle.py +16 -15
  74. toil/options/common.py +843 -409
  75. toil/options/cwl.py +175 -90
  76. toil/options/runner.py +50 -0
  77. toil/options/wdl.py +73 -17
  78. toil/provisioners/__init__.py +117 -46
  79. toil/provisioners/abstractProvisioner.py +332 -157
  80. toil/provisioners/aws/__init__.py +70 -33
  81. toil/provisioners/aws/awsProvisioner.py +1145 -715
  82. toil/provisioners/clusterScaler.py +541 -279
  83. toil/provisioners/gceProvisioner.py +282 -179
  84. toil/provisioners/node.py +155 -79
  85. toil/realtimeLogger.py +34 -22
  86. toil/resource.py +137 -75
  87. toil/server/app.py +128 -62
  88. toil/server/celery_app.py +3 -1
  89. toil/server/cli/wes_cwl_runner.py +82 -53
  90. toil/server/utils.py +54 -28
  91. toil/server/wes/abstract_backend.py +64 -26
  92. toil/server/wes/amazon_wes_utils.py +21 -15
  93. toil/server/wes/tasks.py +121 -63
  94. toil/server/wes/toil_backend.py +142 -107
  95. toil/server/wsgi_app.py +4 -3
  96. toil/serviceManager.py +58 -22
  97. toil/statsAndLogging.py +224 -70
  98. toil/test/__init__.py +282 -183
  99. toil/test/batchSystems/batchSystemTest.py +460 -210
  100. toil/test/batchSystems/batch_system_plugin_test.py +90 -0
  101. toil/test/batchSystems/test_gridengine.py +173 -0
  102. toil/test/batchSystems/test_lsf_helper.py +67 -58
  103. toil/test/batchSystems/test_slurm.py +110 -49
  104. toil/test/cactus/__init__.py +0 -0
  105. toil/test/cactus/test_cactus_integration.py +56 -0
  106. toil/test/cwl/cwlTest.py +496 -287
  107. toil/test/cwl/measure_default_memory.cwl +12 -0
  108. toil/test/cwl/not_run_required_input.cwl +29 -0
  109. toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
  110. toil/test/cwl/seqtk_seq.cwl +1 -1
  111. toil/test/docs/scriptsTest.py +69 -46
  112. toil/test/jobStores/jobStoreTest.py +427 -264
  113. toil/test/lib/aws/test_iam.py +118 -50
  114. toil/test/lib/aws/test_s3.py +16 -9
  115. toil/test/lib/aws/test_utils.py +5 -6
  116. toil/test/lib/dockerTest.py +118 -141
  117. toil/test/lib/test_conversions.py +113 -115
  118. toil/test/lib/test_ec2.py +58 -50
  119. toil/test/lib/test_integration.py +104 -0
  120. toil/test/lib/test_misc.py +12 -5
  121. toil/test/mesos/MesosDataStructuresTest.py +23 -10
  122. toil/test/mesos/helloWorld.py +7 -6
  123. toil/test/mesos/stress.py +25 -20
  124. toil/test/options/__init__.py +13 -0
  125. toil/test/options/options.py +42 -0
  126. toil/test/provisioners/aws/awsProvisionerTest.py +320 -150
  127. toil/test/provisioners/clusterScalerTest.py +440 -250
  128. toil/test/provisioners/clusterTest.py +166 -44
  129. toil/test/provisioners/gceProvisionerTest.py +174 -100
  130. toil/test/provisioners/provisionerTest.py +25 -13
  131. toil/test/provisioners/restartScript.py +5 -4
  132. toil/test/server/serverTest.py +188 -141
  133. toil/test/sort/restart_sort.py +137 -68
  134. toil/test/sort/sort.py +134 -66
  135. toil/test/sort/sortTest.py +91 -49
  136. toil/test/src/autoDeploymentTest.py +141 -101
  137. toil/test/src/busTest.py +20 -18
  138. toil/test/src/checkpointTest.py +8 -2
  139. toil/test/src/deferredFunctionTest.py +49 -35
  140. toil/test/src/dockerCheckTest.py +32 -24
  141. toil/test/src/environmentTest.py +135 -0
  142. toil/test/src/fileStoreTest.py +539 -272
  143. toil/test/src/helloWorldTest.py +7 -4
  144. toil/test/src/importExportFileTest.py +61 -31
  145. toil/test/src/jobDescriptionTest.py +46 -21
  146. toil/test/src/jobEncapsulationTest.py +2 -0
  147. toil/test/src/jobFileStoreTest.py +74 -50
  148. toil/test/src/jobServiceTest.py +187 -73
  149. toil/test/src/jobTest.py +121 -71
  150. toil/test/src/miscTests.py +19 -18
  151. toil/test/src/promisedRequirementTest.py +82 -36
  152. toil/test/src/promisesTest.py +7 -6
  153. toil/test/src/realtimeLoggerTest.py +10 -6
  154. toil/test/src/regularLogTest.py +71 -37
  155. toil/test/src/resourceTest.py +80 -49
  156. toil/test/src/restartDAGTest.py +36 -22
  157. toil/test/src/resumabilityTest.py +9 -2
  158. toil/test/src/retainTempDirTest.py +45 -14
  159. toil/test/src/systemTest.py +12 -8
  160. toil/test/src/threadingTest.py +44 -25
  161. toil/test/src/toilContextManagerTest.py +10 -7
  162. toil/test/src/userDefinedJobArgTypeTest.py +8 -5
  163. toil/test/src/workerTest.py +73 -23
  164. toil/test/utils/toilDebugTest.py +103 -33
  165. toil/test/utils/toilKillTest.py +4 -5
  166. toil/test/utils/utilsTest.py +245 -106
  167. toil/test/wdl/wdltoil_test.py +818 -149
  168. toil/test/wdl/wdltoil_test_kubernetes.py +91 -0
  169. toil/toilState.py +120 -35
  170. toil/utils/toilConfig.py +13 -4
  171. toil/utils/toilDebugFile.py +44 -27
  172. toil/utils/toilDebugJob.py +214 -27
  173. toil/utils/toilDestroyCluster.py +11 -6
  174. toil/utils/toilKill.py +8 -3
  175. toil/utils/toilLaunchCluster.py +256 -140
  176. toil/utils/toilMain.py +37 -16
  177. toil/utils/toilRsyncCluster.py +32 -14
  178. toil/utils/toilSshCluster.py +49 -22
  179. toil/utils/toilStats.py +356 -273
  180. toil/utils/toilStatus.py +292 -139
  181. toil/utils/toilUpdateEC2Instances.py +3 -1
  182. toil/version.py +12 -12
  183. toil/wdl/utils.py +5 -5
  184. toil/wdl/wdltoil.py +3913 -1033
  185. toil/worker.py +367 -184
  186. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/LICENSE +25 -0
  187. toil-8.0.0.dist-info/METADATA +173 -0
  188. toil-8.0.0.dist-info/RECORD +253 -0
  189. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
  190. toil-6.1.0a1.dist-info/METADATA +0 -125
  191. toil-6.1.0a1.dist-info/RECORD +0 -237
  192. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
  193. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
toil/cwl/utils.py CHANGED
@@ -16,21 +16,11 @@
16
16
 
17
17
  import logging
18
18
  import os
19
- from pathlib import PurePosixPath
20
19
  import posixpath
21
20
  import stat
22
- from typing import (
23
- Any,
24
- Callable,
25
- Dict,
26
- Iterable,
27
- List,
28
- MutableMapping,
29
- MutableSequence,
30
- Type,
31
- TypeVar,
32
- Union,
33
- )
21
+ from collections.abc import Iterable, MutableMapping, MutableSequence
22
+ from pathlib import PurePosixPath
23
+ from typing import Any, Callable, TypeVar, Union
34
24
 
35
25
  from toil.fileStores import FileID
36
26
  from toil.fileStores.abstractFileStore import AbstractFileStore
@@ -55,7 +45,7 @@ try:
55
45
  import cwltool.errors
56
46
 
57
47
  CWL_UNSUPPORTED_REQUIREMENT_EXCEPTION: Union[
58
- Type[cwltool.errors.UnsupportedRequirement], Type[CWLUnsupportedException]
48
+ type[cwltool.errors.UnsupportedRequirement], type[CWLUnsupportedException]
59
49
  ] = cwltool.errors.UnsupportedRequirement
60
50
  except ImportError:
61
51
  CWL_UNSUPPORTED_REQUIREMENT_EXCEPTION = CWLUnsupportedException
@@ -92,8 +82,8 @@ def visit_cwl_class_and_reduce(
92
82
  rec: Any,
93
83
  classes: Iterable[str],
94
84
  op_down: Callable[[Any], DownReturnType],
95
- op_up: Callable[[Any, DownReturnType, List[UpReturnType]], UpReturnType],
96
- ) -> List[UpReturnType]:
85
+ op_up: Callable[[Any, DownReturnType, list[UpReturnType]], UpReturnType],
86
+ ) -> list[UpReturnType]:
97
87
  """
98
88
  Apply the given operations to all CWL objects with the given named CWL class.
99
89
 
@@ -130,9 +120,12 @@ def visit_cwl_class_and_reduce(
130
120
  return results
131
121
 
132
122
 
133
- DirectoryStructure = Dict[str, Union[str, "DirectoryStructure"]]
123
+ DirectoryStructure = dict[str, Union[str, "DirectoryStructure"]]
134
124
 
135
- def get_from_structure(dir_dict: DirectoryStructure, path: str) -> Union[str, DirectoryStructure, None]:
125
+
126
+ def get_from_structure(
127
+ dir_dict: DirectoryStructure, path: str
128
+ ) -> Union[str, DirectoryStructure, None]:
136
129
  """
137
130
  Given a relative path, follow it in the given directory structure.
138
131
 
@@ -144,7 +137,7 @@ def get_from_structure(dir_dict: DirectoryStructure, path: str) -> Union[str, Di
144
137
  parts = PurePosixPath(posixpath.normpath(path)).parts
145
138
  if len(parts) == 0:
146
139
  return dir_dict
147
- if parts[0] in ('..', '/'):
140
+ if parts[0] in ("..", "/"):
148
141
  raise RuntimeError(f"Path {path} not resolvable in virtual directory")
149
142
  found: Union[str, DirectoryStructure] = dir_dict
150
143
  for part in parts:
@@ -161,8 +154,8 @@ def get_from_structure(dir_dict: DirectoryStructure, path: str) -> Union[str, Di
161
154
 
162
155
  def download_structure(
163
156
  file_store: AbstractFileStore,
164
- index: Dict[str, str],
165
- existing: Dict[str, str],
157
+ index: dict[str, str],
158
+ existing: dict[str, str],
166
159
  dir_dict: DirectoryStructure,
167
160
  into_dir: str,
168
161
  ) -> None:
@@ -215,7 +208,9 @@ def download_structure(
215
208
  )
216
209
  else:
217
210
  # We need to download from some other kind of URL.
218
- size, executable = AbstractJobStore.read_from_url(value, open(dest_path, 'wb'))
211
+ size, executable = AbstractJobStore.read_from_url(
212
+ value, open(dest_path, "wb")
213
+ )
219
214
  if executable:
220
215
  # Make the written file executable
221
216
  os.chmod(dest_path, os.stat(dest_path).st_mode | stat.S_IXUSR)
toil/deferred.py CHANGED
@@ -11,7 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- import fcntl
14
+ import errno
15
15
  import logging
16
16
  import os
17
17
  import tempfile
@@ -21,13 +21,16 @@ from contextlib import contextmanager
21
21
  import dill
22
22
 
23
23
  from toil.lib.io import robust_rmtree
24
+ from toil.lib.threading import safe_lock, safe_unlock_and_close
24
25
  from toil.realtimeLogger import RealtimeLogger
25
26
  from toil.resource import ModuleDescriptor
26
27
 
27
28
  logger = logging.getLogger(__name__)
28
29
 
29
30
 
30
- class DeferredFunction(namedtuple('DeferredFunction', 'function args kwargs name module')):
31
+ class DeferredFunction(
32
+ namedtuple("DeferredFunction", "function args kwargs name module")
33
+ ):
31
34
  """
32
35
  >>> from collections import defaultdict
33
36
  >>> df = DeferredFunction.create(defaultdict, None, {'x':1}, y=2)
@@ -36,6 +39,7 @@ class DeferredFunction(namedtuple('DeferredFunction', 'function args kwargs name
36
39
  >>> df.invoke() == defaultdict(None, x=1, y=2)
37
40
  True
38
41
  """
42
+
39
43
  @classmethod
40
44
  def create(cls, function, *args, **kwargs):
41
45
  """
@@ -50,21 +54,25 @@ class DeferredFunction(namedtuple('DeferredFunction', 'function args kwargs name
50
54
  # concurrently running jobs when the cache state is loaded from disk. By implication we
51
55
  # should serialize as early as possible. We need to serialize the function as well as its
52
56
  # arguments.
53
- return cls(*list(map(dill.dumps, (function, args, kwargs))),
54
- name=function.__name__,
55
- module=ModuleDescriptor.forModule(function.__module__).globalize())
57
+ return cls(
58
+ *list(map(dill.dumps, (function, args, kwargs))),
59
+ name=function.__name__,
60
+ module=ModuleDescriptor.forModule(function.__module__).globalize(),
61
+ )
56
62
 
57
63
  def invoke(self):
58
64
  """
59
65
  Invoke the captured function with the captured arguments.
60
66
  """
61
- logger.debug('Running deferred function %s.', self)
67
+ logger.debug("Running deferred function %s.", self)
62
68
  self.module.makeLoadable()
63
- function, args, kwargs = list(map(dill.loads, (self.function, self.args, self.kwargs)))
69
+ function, args, kwargs = list(
70
+ map(dill.loads, (self.function, self.args, self.kwargs))
71
+ )
64
72
  return function(*args, **kwargs)
65
73
 
66
74
  def __str__(self):
67
- return f'{self.__class__.__name__}({self.name}, ...)'
75
+ return f"{self.__class__.__name__}({self.name}, ...)"
68
76
 
69
77
  __repr__ = __str__
70
78
 
@@ -93,13 +101,13 @@ class DeferredFunctionManager:
93
101
  """
94
102
 
95
103
  # Define what directory the state directory should actaully be, under the base
96
- STATE_DIR_STEM = 'deferred'
104
+ STATE_DIR_STEM = "deferred"
97
105
  # Have a prefix to distinguish our deferred functions from e.g. NFS
98
106
  # "silly rename" files, or other garbage that people put in our
99
107
  # directory
100
- PREFIX = 'func'
108
+ PREFIX = "func"
101
109
  # And a suffix to distinguish in-progress from completed files
102
- WIP_SUFFIX = '.tmp'
110
+ WIP_SUFFIX = ".tmp"
103
111
 
104
112
  def __init__(self, stateDirBase: str) -> None:
105
113
  """
@@ -122,25 +130,31 @@ class DeferredFunctionManager:
122
130
 
123
131
  # We need to get a state file, locked by us and not somebody scanning for abandoned state files.
124
132
  # So we suffix not-yet-ready ones with our suffix
125
- self.stateFD, self.stateFileName = tempfile.mkstemp(dir=self.stateDir,
126
- prefix=self.PREFIX,
127
- suffix=self.WIP_SUFFIX)
133
+ self.stateFD, self.stateFileName = tempfile.mkstemp(
134
+ dir=self.stateDir, prefix=self.PREFIX, suffix=self.WIP_SUFFIX
135
+ )
128
136
 
129
137
  # Lock the state file. The lock will automatically go away if our process does.
130
138
  try:
131
- fcntl.lockf(self.stateFD, fcntl.LOCK_EX | fcntl.LOCK_NB)
139
+ safe_lock(self.stateFD, block=False)
132
140
  except OSError as e:
133
- # Someone else might have locked it even though they should not have.
134
- raise RuntimeError(f"Could not lock deferred function state file {self.stateFileName}: {str(e)}")
141
+ if e.errno in (errno.EACCES, errno.EAGAIN):
142
+ # Someone else locked it even though they should not have.
143
+ raise RuntimeError(
144
+ f"Could not lock deferred function state file {self.stateFileName}"
145
+ ) from e
146
+ else:
147
+ # Something else went wrong
148
+ raise
135
149
 
136
150
  # Rename it to remove the suffix
137
- os.rename(self.stateFileName, self.stateFileName[:-len(self.WIP_SUFFIX)])
138
- self.stateFileName = self.stateFileName[:-len(self.WIP_SUFFIX)]
151
+ os.rename(self.stateFileName, self.stateFileName[: -len(self.WIP_SUFFIX)])
152
+ self.stateFileName = self.stateFileName[: -len(self.WIP_SUFFIX)]
139
153
 
140
- # Wrap the FD in a Python file object, which we will use to actually use it.
154
+ # Get a Python file object for the file, which we will use to actually use it.
141
155
  # Problem: we can't be readable and writable at the same time. So we need two file objects.
142
- self.stateFileOut = os.fdopen(self.stateFD, 'wb')
143
- self.stateFileIn = open(self.stateFileName, 'rb')
156
+ self.stateFileOut = open(self.stateFileName, "wb")
157
+ self.stateFileIn = open(self.stateFileName, "rb")
144
158
 
145
159
  logger.debug("Opened with own state file %s" % self.stateFileName)
146
160
 
@@ -157,10 +171,7 @@ class DeferredFunctionManager:
157
171
  os.unlink(self.stateFileName)
158
172
 
159
173
  # Unlock it
160
- fcntl.lockf(self.stateFD, fcntl.LOCK_UN)
161
-
162
- # Don't bother with close, destroying will close and it seems to maybe
163
- # have been GC'd already anyway.
174
+ safe_unlock_and_close(self.stateFD)
164
175
 
165
176
  @contextmanager
166
177
  def open(self):
@@ -177,8 +188,9 @@ class DeferredFunctionManager:
177
188
  self._runOrphanedDeferredFunctions()
178
189
 
179
190
  try:
191
+
180
192
  def defer(deferredFunction):
181
- # Just serialize defered functions one after the other.
193
+ # Just serialize deferred functions one after the other.
182
194
  # If serializing later ones fails, eariler ones will still be intact.
183
195
  # We trust dill to protect sufficiently against partial reads later.
184
196
  logger.debug("Deferring function %s" % repr(deferredFunction))
@@ -216,7 +228,6 @@ class DeferredFunctionManager:
216
228
  logger.exception(err)
217
229
  # we tried, lets move on
218
230
 
219
-
220
231
  def _runDeferredFunction(self, deferredFunction):
221
232
  """
222
233
  Run a deferred function (either our own or someone else's).
@@ -228,9 +239,15 @@ class DeferredFunctionManager:
228
239
  deferredFunction.invoke()
229
240
  except Exception as err:
230
241
  # Report this in real time, if enabled. Otherwise the only place it ends up is the worker log.
231
- RealtimeLogger.error("Failed to run deferred function %s: %s", repr(deferredFunction), str(err))
242
+ RealtimeLogger.error(
243
+ "Failed to run deferred function %s: %s",
244
+ repr(deferredFunction),
245
+ str(err),
246
+ )
232
247
  except:
233
- RealtimeLogger.error("Failed to run deferred function %s", repr(deferredFunction))
248
+ RealtimeLogger.error(
249
+ "Failed to run deferred function %s", repr(deferredFunction)
250
+ )
234
251
 
235
252
  def _runAllDeferredFunctions(self, fileObj):
236
253
  """
@@ -318,11 +335,16 @@ class DeferredFunctionManager:
318
335
  continue
319
336
 
320
337
  try:
321
- fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
322
- except OSError:
323
- # File is still locked by someone else.
324
- # Look at the next file instead
325
- continue
338
+ safe_lock(fd, block=False)
339
+ except OSError as e:
340
+ os.close(fd)
341
+ if e.errno in (errno.EACCES, errno.EAGAIN):
342
+ # File is still locked by someone else.
343
+ # Look at the next file instead
344
+ continue
345
+ else:
346
+ # Something else went wrong
347
+ raise
326
348
 
327
349
  logger.debug("Locked file %s" % fullFilename)
328
350
 
@@ -330,7 +352,7 @@ class DeferredFunctionManager:
330
352
  foundFiles = True
331
353
 
332
354
  # Actually run all the stored deferred functions
333
- fileObj = os.fdopen(fd, 'rb')
355
+ fileObj = open(fullFilename, "rb")
334
356
  self._runAllDeferredFunctions(fileObj)
335
357
  states_handled += 1
336
358
 
@@ -342,10 +364,9 @@ class DeferredFunctionManager:
342
364
  pass
343
365
 
344
366
  # Unlock it
345
- fcntl.lockf(fd, fcntl.LOCK_UN)
346
-
347
- # Now close it. This closes the backing file descriptor. See
348
- # <https://stackoverflow.com/a/24984929>
349
- fileObj.close()
367
+ safe_unlock_and_close(fd)
350
368
 
351
- logger.debug("Ran orphaned deferred functions from %d abandoned state files", states_handled)
369
+ logger.debug(
370
+ "Ran orphaned deferred functions from %d abandoned state files",
371
+ states_handled,
372
+ )
toil/exceptions.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Neutral place for exceptions, to break import cycles."""
2
2
 
3
3
  import logging
4
- from typing import TYPE_CHECKING, List
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  from toil.statsAndLogging import StatsAndLogging
7
7
 
@@ -16,7 +16,7 @@ class FailedJobsException(Exception):
16
16
  def __init__(
17
17
  self,
18
18
  job_store: "AbstractJobStore",
19
- failed_jobs: List["JobDescription"],
19
+ failed_jobs: list["JobDescription"],
20
20
  exit_code: int = 1,
21
21
  ):
22
22
  """
@@ -36,7 +36,9 @@ class FailedJobsException(Exception):
36
36
  for job_desc in failed_jobs:
37
37
  if job_desc.logJobStoreFileID:
38
38
  with job_desc.getLogFileHandle(job_store) as f:
39
- self.msg += "\n" + StatsAndLogging.formatLogStream(f, job_desc)
39
+ self.msg += "\n" + StatsAndLogging.formatLogStream(
40
+ f, f'Log from job "{job_desc}"'
41
+ )
40
42
  # catch failures to prepare more complex details and only return the basics
41
43
  except Exception:
42
44
  logger.exception("Exception when compiling information about failed jobs")
@@ -28,7 +28,7 @@ class FileID(str):
28
28
  the job store if unavailable in the ID.
29
29
  """
30
30
 
31
- def __new__(cls, fileStoreID: str, *args: Any) -> 'FileID':
31
+ def __new__(cls, fileStoreID: str, *args: Any) -> "FileID":
32
32
  return super().__new__(cls, fileStoreID)
33
33
 
34
34
  def __init__(self, fileStoreID: str, size: int, executable: bool = False) -> None:
@@ -43,18 +43,18 @@ class FileID(str):
43
43
  return f'{self.size}:{"1" if self.executable else "0"}:{self}'
44
44
 
45
45
  @classmethod
46
- def forPath(cls, fileStoreID: str, filePath: str) -> 'FileID':
46
+ def forPath(cls, fileStoreID: str, filePath: str) -> "FileID":
47
47
  executable = os.stat(filePath).st_mode & stat.S_IXUSR != 0
48
48
  return cls(fileStoreID, os.stat(filePath).st_size, executable)
49
49
 
50
50
  @classmethod
51
- def unpack(cls, packedFileStoreID: str) -> 'FileID':
51
+ def unpack(cls, packedFileStoreID: str) -> "FileID":
52
52
  """Unpack the result of pack() into a FileID object."""
53
53
  # Only separate twice in case the FileID itself has colons in it
54
- vals = packedFileStoreID.split(':', 2)
54
+ vals = packedFileStoreID.split(":", 2)
55
55
  # Break up the packed value
56
56
  size = int(vals[0])
57
- executable = (vals[1] == "1")
57
+ executable = vals[1] == "1"
58
58
  value = vals[2]
59
59
  # Create the FileID
60
60
  return cls(value, size, executable)