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.
- toil/__init__.py +122 -315
- toil/batchSystems/__init__.py +1 -0
- toil/batchSystems/abstractBatchSystem.py +173 -89
- toil/batchSystems/abstractGridEngineBatchSystem.py +272 -148
- toil/batchSystems/awsBatch.py +244 -135
- toil/batchSystems/cleanup_support.py +26 -16
- toil/batchSystems/contained_executor.py +31 -28
- toil/batchSystems/gridengine.py +86 -50
- toil/batchSystems/htcondor.py +166 -89
- toil/batchSystems/kubernetes.py +632 -382
- toil/batchSystems/local_support.py +20 -15
- toil/batchSystems/lsf.py +134 -81
- toil/batchSystems/lsfHelper.py +13 -11
- toil/batchSystems/mesos/__init__.py +41 -29
- toil/batchSystems/mesos/batchSystem.py +290 -151
- toil/batchSystems/mesos/executor.py +79 -50
- toil/batchSystems/mesos/test/__init__.py +31 -23
- toil/batchSystems/options.py +46 -28
- toil/batchSystems/registry.py +53 -19
- toil/batchSystems/singleMachine.py +296 -125
- toil/batchSystems/slurm.py +603 -138
- toil/batchSystems/torque.py +47 -33
- toil/bus.py +186 -76
- toil/common.py +664 -368
- toil/cwl/__init__.py +1 -1
- toil/cwl/cwltoil.py +1136 -483
- toil/cwl/utils.py +17 -22
- toil/deferred.py +63 -42
- toil/exceptions.py +5 -3
- toil/fileStores/__init__.py +5 -5
- toil/fileStores/abstractFileStore.py +140 -60
- toil/fileStores/cachingFileStore.py +717 -269
- toil/fileStores/nonCachingFileStore.py +116 -87
- toil/job.py +1225 -368
- toil/jobStores/abstractJobStore.py +416 -266
- toil/jobStores/aws/jobStore.py +863 -477
- toil/jobStores/aws/utils.py +201 -120
- toil/jobStores/conftest.py +3 -2
- toil/jobStores/fileJobStore.py +292 -154
- toil/jobStores/googleJobStore.py +140 -74
- toil/jobStores/utils.py +36 -15
- toil/leader.py +668 -272
- toil/lib/accelerators.py +115 -18
- toil/lib/aws/__init__.py +74 -31
- toil/lib/aws/ami.py +122 -87
- toil/lib/aws/iam.py +284 -108
- toil/lib/aws/s3.py +31 -0
- toil/lib/aws/session.py +214 -39
- toil/lib/aws/utils.py +287 -231
- toil/lib/bioio.py +13 -5
- toil/lib/compatibility.py +11 -6
- toil/lib/conversions.py +104 -47
- toil/lib/docker.py +131 -103
- toil/lib/ec2.py +361 -199
- toil/lib/ec2nodes.py +174 -106
- toil/lib/encryption/_dummy.py +5 -3
- toil/lib/encryption/_nacl.py +10 -6
- toil/lib/encryption/conftest.py +1 -0
- toil/lib/exceptions.py +26 -7
- toil/lib/expando.py +5 -3
- toil/lib/ftp_utils.py +217 -0
- toil/lib/generatedEC2Lists.py +127 -19
- toil/lib/humanize.py +6 -2
- toil/lib/integration.py +341 -0
- toil/lib/io.py +141 -15
- toil/lib/iterables.py +4 -2
- toil/lib/memoize.py +12 -8
- toil/lib/misc.py +66 -21
- toil/lib/objects.py +2 -2
- toil/lib/resources.py +68 -15
- toil/lib/retry.py +126 -81
- toil/lib/threading.py +299 -82
- toil/lib/throttle.py +16 -15
- toil/options/common.py +843 -409
- toil/options/cwl.py +175 -90
- toil/options/runner.py +50 -0
- toil/options/wdl.py +73 -17
- toil/provisioners/__init__.py +117 -46
- toil/provisioners/abstractProvisioner.py +332 -157
- toil/provisioners/aws/__init__.py +70 -33
- toil/provisioners/aws/awsProvisioner.py +1145 -715
- toil/provisioners/clusterScaler.py +541 -279
- toil/provisioners/gceProvisioner.py +282 -179
- toil/provisioners/node.py +155 -79
- toil/realtimeLogger.py +34 -22
- toil/resource.py +137 -75
- toil/server/app.py +128 -62
- toil/server/celery_app.py +3 -1
- toil/server/cli/wes_cwl_runner.py +82 -53
- toil/server/utils.py +54 -28
- toil/server/wes/abstract_backend.py +64 -26
- toil/server/wes/amazon_wes_utils.py +21 -15
- toil/server/wes/tasks.py +121 -63
- toil/server/wes/toil_backend.py +142 -107
- toil/server/wsgi_app.py +4 -3
- toil/serviceManager.py +58 -22
- toil/statsAndLogging.py +224 -70
- toil/test/__init__.py +282 -183
- toil/test/batchSystems/batchSystemTest.py +460 -210
- toil/test/batchSystems/batch_system_plugin_test.py +90 -0
- toil/test/batchSystems/test_gridengine.py +173 -0
- toil/test/batchSystems/test_lsf_helper.py +67 -58
- toil/test/batchSystems/test_slurm.py +110 -49
- toil/test/cactus/__init__.py +0 -0
- toil/test/cactus/test_cactus_integration.py +56 -0
- toil/test/cwl/cwlTest.py +496 -287
- toil/test/cwl/measure_default_memory.cwl +12 -0
- toil/test/cwl/not_run_required_input.cwl +29 -0
- toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
- toil/test/cwl/seqtk_seq.cwl +1 -1
- toil/test/docs/scriptsTest.py +69 -46
- toil/test/jobStores/jobStoreTest.py +427 -264
- toil/test/lib/aws/test_iam.py +118 -50
- toil/test/lib/aws/test_s3.py +16 -9
- toil/test/lib/aws/test_utils.py +5 -6
- toil/test/lib/dockerTest.py +118 -141
- toil/test/lib/test_conversions.py +113 -115
- toil/test/lib/test_ec2.py +58 -50
- toil/test/lib/test_integration.py +104 -0
- toil/test/lib/test_misc.py +12 -5
- toil/test/mesos/MesosDataStructuresTest.py +23 -10
- toil/test/mesos/helloWorld.py +7 -6
- toil/test/mesos/stress.py +25 -20
- toil/test/options/__init__.py +13 -0
- toil/test/options/options.py +42 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +320 -150
- toil/test/provisioners/clusterScalerTest.py +440 -250
- toil/test/provisioners/clusterTest.py +166 -44
- toil/test/provisioners/gceProvisionerTest.py +174 -100
- toil/test/provisioners/provisionerTest.py +25 -13
- toil/test/provisioners/restartScript.py +5 -4
- toil/test/server/serverTest.py +188 -141
- toil/test/sort/restart_sort.py +137 -68
- toil/test/sort/sort.py +134 -66
- toil/test/sort/sortTest.py +91 -49
- toil/test/src/autoDeploymentTest.py +141 -101
- toil/test/src/busTest.py +20 -18
- toil/test/src/checkpointTest.py +8 -2
- toil/test/src/deferredFunctionTest.py +49 -35
- toil/test/src/dockerCheckTest.py +32 -24
- toil/test/src/environmentTest.py +135 -0
- toil/test/src/fileStoreTest.py +539 -272
- toil/test/src/helloWorldTest.py +7 -4
- toil/test/src/importExportFileTest.py +61 -31
- toil/test/src/jobDescriptionTest.py +46 -21
- toil/test/src/jobEncapsulationTest.py +2 -0
- toil/test/src/jobFileStoreTest.py +74 -50
- toil/test/src/jobServiceTest.py +187 -73
- toil/test/src/jobTest.py +121 -71
- toil/test/src/miscTests.py +19 -18
- toil/test/src/promisedRequirementTest.py +82 -36
- toil/test/src/promisesTest.py +7 -6
- toil/test/src/realtimeLoggerTest.py +10 -6
- toil/test/src/regularLogTest.py +71 -37
- toil/test/src/resourceTest.py +80 -49
- toil/test/src/restartDAGTest.py +36 -22
- toil/test/src/resumabilityTest.py +9 -2
- toil/test/src/retainTempDirTest.py +45 -14
- toil/test/src/systemTest.py +12 -8
- toil/test/src/threadingTest.py +44 -25
- toil/test/src/toilContextManagerTest.py +10 -7
- toil/test/src/userDefinedJobArgTypeTest.py +8 -5
- toil/test/src/workerTest.py +73 -23
- toil/test/utils/toilDebugTest.py +103 -33
- toil/test/utils/toilKillTest.py +4 -5
- toil/test/utils/utilsTest.py +245 -106
- toil/test/wdl/wdltoil_test.py +818 -149
- toil/test/wdl/wdltoil_test_kubernetes.py +91 -0
- toil/toilState.py +120 -35
- toil/utils/toilConfig.py +13 -4
- toil/utils/toilDebugFile.py +44 -27
- toil/utils/toilDebugJob.py +214 -27
- toil/utils/toilDestroyCluster.py +11 -6
- toil/utils/toilKill.py +8 -3
- toil/utils/toilLaunchCluster.py +256 -140
- toil/utils/toilMain.py +37 -16
- toil/utils/toilRsyncCluster.py +32 -14
- toil/utils/toilSshCluster.py +49 -22
- toil/utils/toilStats.py +356 -273
- toil/utils/toilStatus.py +292 -139
- toil/utils/toilUpdateEC2Instances.py +3 -1
- toil/version.py +12 -12
- toil/wdl/utils.py +5 -5
- toil/wdl/wdltoil.py +3913 -1033
- toil/worker.py +367 -184
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/LICENSE +25 -0
- toil-8.0.0.dist-info/METADATA +173 -0
- toil-8.0.0.dist-info/RECORD +253 -0
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
- toil-6.1.0a1.dist-info/METADATA +0 -125
- toil-6.1.0a1.dist-info/RECORD +0 -237
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
- {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
|
@@ -14,34 +14,31 @@
|
|
|
14
14
|
import logging
|
|
15
15
|
import os
|
|
16
16
|
from abc import ABC, abstractmethod
|
|
17
|
+
from collections.abc import Generator, Iterator
|
|
17
18
|
from contextlib import contextmanager
|
|
18
19
|
from tempfile import mkstemp
|
|
19
20
|
from threading import Event, Semaphore
|
|
20
|
-
from typing import (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Tuple,
|
|
33
|
-
Type,
|
|
34
|
-
Union,
|
|
35
|
-
cast,
|
|
36
|
-
overload)
|
|
21
|
+
from typing import (
|
|
22
|
+
IO,
|
|
23
|
+
TYPE_CHECKING,
|
|
24
|
+
Any,
|
|
25
|
+
Callable,
|
|
26
|
+
ContextManager,
|
|
27
|
+
Literal,
|
|
28
|
+
Optional,
|
|
29
|
+
Union,
|
|
30
|
+
cast,
|
|
31
|
+
overload,
|
|
32
|
+
)
|
|
37
33
|
|
|
38
34
|
import dill
|
|
39
35
|
|
|
40
|
-
from toil.common import Toil, cacheDirName
|
|
36
|
+
from toil.common import Toil, cacheDirName, getDirSizeRecursively
|
|
41
37
|
from toil.fileStores import FileID
|
|
42
|
-
from toil.job import Job, JobDescription
|
|
38
|
+
from toil.job import DebugStoppingPointReached, Job, JobDescription
|
|
43
39
|
from toil.jobStores.abstractJobStore import AbstractJobStore
|
|
44
40
|
from toil.lib.compatibility import deprecated
|
|
41
|
+
from toil.lib.conversions import bytes2human
|
|
45
42
|
from toil.lib.io import WriteWatchingStream, mkdtemp
|
|
46
43
|
|
|
47
44
|
logger = logging.getLogger(__name__)
|
|
@@ -75,9 +72,10 @@ class AbstractFileStore(ABC):
|
|
|
75
72
|
Also responsible for committing completed jobs back to the job store with
|
|
76
73
|
an update operation, and allowing that commit operation to be waited for.
|
|
77
74
|
"""
|
|
75
|
+
|
|
78
76
|
# Variables used for syncing reads/writes
|
|
79
77
|
_pendingFileWritesLock = Semaphore()
|
|
80
|
-
_pendingFileWrites:
|
|
78
|
+
_pendingFileWrites: set[str] = set()
|
|
81
79
|
_terminateEvent = Event() # Used to signify crashes in threads
|
|
82
80
|
|
|
83
81
|
def __init__(
|
|
@@ -110,21 +108,28 @@ class AbstractFileStore(ABC):
|
|
|
110
108
|
# This gets replaced with a subdirectory of itself on open()
|
|
111
109
|
self.localTempDir: str = os.path.abspath(file_store_dir)
|
|
112
110
|
assert self.jobStore.config.workflowID is not None
|
|
113
|
-
self.workflow_dir: str = Toil.getLocalWorkflowDir(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
111
|
+
self.workflow_dir: str = Toil.getLocalWorkflowDir(
|
|
112
|
+
self.jobStore.config.workflowID, self.jobStore.config.workDir
|
|
113
|
+
)
|
|
114
|
+
self.coordination_dir: str = Toil.get_local_workflow_coordination_dir(
|
|
115
|
+
self.jobStore.config.workflowID,
|
|
116
|
+
self.jobStore.config.workDir,
|
|
117
|
+
self.jobStore.config.coordination_dir,
|
|
117
118
|
)
|
|
119
|
+
self.jobName: str = str(self.jobDesc)
|
|
118
120
|
self.waitForPreviousCommit = waitForPreviousCommit
|
|
119
|
-
self.
|
|
121
|
+
self.logging_messages: list[dict[str, Union[int, str]]] = []
|
|
122
|
+
self.logging_user_streams: list[dict[str, str]] = []
|
|
120
123
|
# Records file IDs of files deleted during the current job. Doesn't get
|
|
121
124
|
# committed back until the job is completely successful, because if the
|
|
122
125
|
# job is re-run it will need to be able to re-delete these files.
|
|
123
126
|
# This is a set of str objects, not FileIDs.
|
|
124
|
-
self.filesToDelete:
|
|
127
|
+
self.filesToDelete: set[str] = set()
|
|
125
128
|
# Holds records of file ID, or file ID and local path, for reporting
|
|
126
129
|
# the accessed files of failed jobs.
|
|
127
|
-
self._accessLog:
|
|
130
|
+
self._accessLog: list[tuple[str, ...]] = []
|
|
131
|
+
# Holds total bytes of observed disk usage for the last job run under open()
|
|
132
|
+
self._job_disk_used: Optional[int] = None
|
|
128
133
|
|
|
129
134
|
@staticmethod
|
|
130
135
|
def createFileStore(
|
|
@@ -139,13 +144,17 @@ class AbstractFileStore(ABC):
|
|
|
139
144
|
from toil.fileStores.cachingFileStore import CachingFileStore
|
|
140
145
|
from toil.fileStores.nonCachingFileStore import NonCachingFileStore
|
|
141
146
|
|
|
142
|
-
fileStoreCls: Union[
|
|
147
|
+
fileStoreCls: Union[type["CachingFileStore"], type["NonCachingFileStore"]] = (
|
|
143
148
|
CachingFileStore if caching else NonCachingFileStore
|
|
144
149
|
)
|
|
145
150
|
return fileStoreCls(jobStore, jobDesc, file_store_dir, waitForPreviousCommit)
|
|
146
151
|
|
|
147
152
|
@staticmethod
|
|
148
|
-
def shutdownFileStore(
|
|
153
|
+
def shutdownFileStore(
|
|
154
|
+
workflowID: str,
|
|
155
|
+
config_work_dir: Optional[str],
|
|
156
|
+
config_coordination_dir: Optional[str],
|
|
157
|
+
) -> None:
|
|
149
158
|
"""
|
|
150
159
|
Carry out any necessary filestore-specific cleanup.
|
|
151
160
|
|
|
@@ -165,7 +174,9 @@ class AbstractFileStore(ABC):
|
|
|
165
174
|
from toil.fileStores.nonCachingFileStore import NonCachingFileStore
|
|
166
175
|
|
|
167
176
|
workflowDir = Toil.getLocalWorkflowDir(workflowID, config_work_dir)
|
|
168
|
-
coordination_dir = Toil.get_local_workflow_coordination_dir(
|
|
177
|
+
coordination_dir = Toil.get_local_workflow_coordination_dir(
|
|
178
|
+
workflowID, config_work_dir, config_coordination_dir
|
|
179
|
+
)
|
|
169
180
|
cacheDir = os.path.join(workflowDir, cacheDirName(workflowID))
|
|
170
181
|
if os.path.exists(cacheDir):
|
|
171
182
|
# The presence of the cacheDir suggests this was a cached run. We don't need
|
|
@@ -187,15 +198,46 @@ class AbstractFileStore(ABC):
|
|
|
187
198
|
|
|
188
199
|
:param job: The job instance of the toil job to run.
|
|
189
200
|
"""
|
|
190
|
-
|
|
201
|
+
job_requested_disk = job.disk
|
|
191
202
|
try:
|
|
192
203
|
yield
|
|
193
204
|
failed = False
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
205
|
+
except BaseException as e:
|
|
206
|
+
if isinstance(e, DebugStoppingPointReached):
|
|
207
|
+
self._dumpAccessLogs(job_type="Debugged", log_level=logging.INFO)
|
|
208
|
+
else:
|
|
198
209
|
self._dumpAccessLogs()
|
|
210
|
+
raise
|
|
211
|
+
finally:
|
|
212
|
+
# See how much disk space is used at the end of the job.
|
|
213
|
+
# Not a real peak disk usage, but close enough to be useful for warning the user.
|
|
214
|
+
self._job_disk_used = getDirSizeRecursively(self.localTempDir)
|
|
215
|
+
|
|
216
|
+
# Report disk usage
|
|
217
|
+
percent: float = 0.0
|
|
218
|
+
if job_requested_disk and job_requested_disk > 0:
|
|
219
|
+
percent = float(self._job_disk_used) / job_requested_disk * 100
|
|
220
|
+
disk_usage: str = (
|
|
221
|
+
f"Job {self.jobName} used {percent:.2f}% disk ({bytes2human(self._job_disk_used)}B [{self._job_disk_used}B] used, "
|
|
222
|
+
f"{bytes2human(job_requested_disk)}B [{job_requested_disk}B] requested)."
|
|
223
|
+
)
|
|
224
|
+
if self._job_disk_used > job_requested_disk:
|
|
225
|
+
self.log_to_leader(
|
|
226
|
+
"Job used more disk than requested. For CWL, consider increasing the outdirMin "
|
|
227
|
+
f"requirement, otherwise, consider increasing the disk requirement. {disk_usage}",
|
|
228
|
+
level=logging.WARNING,
|
|
229
|
+
)
|
|
230
|
+
else:
|
|
231
|
+
self.log_to_leader(disk_usage, level=logging.DEBUG)
|
|
232
|
+
|
|
233
|
+
def get_disk_usage(self) -> Optional[int]:
|
|
234
|
+
"""
|
|
235
|
+
Get the number of bytes of disk used by the last job run under open().
|
|
236
|
+
|
|
237
|
+
Disk usage is measured at the end of the job.
|
|
238
|
+
TODO: Sample periodically and record peak usage.
|
|
239
|
+
"""
|
|
240
|
+
return self._job_disk_used
|
|
199
241
|
|
|
200
242
|
# Functions related to temp files and directories
|
|
201
243
|
def getLocalTempDir(self) -> str:
|
|
@@ -211,7 +253,9 @@ class AbstractFileStore(ABC):
|
|
|
211
253
|
"""
|
|
212
254
|
return os.path.abspath(mkdtemp(dir=self.localTempDir))
|
|
213
255
|
|
|
214
|
-
def getLocalTempFile(
|
|
256
|
+
def getLocalTempFile(
|
|
257
|
+
self, suffix: Optional[str] = None, prefix: Optional[str] = None
|
|
258
|
+
) -> str:
|
|
215
259
|
"""
|
|
216
260
|
Get a new local temporary file that will persist for the duration of the job.
|
|
217
261
|
|
|
@@ -228,12 +272,14 @@ class AbstractFileStore(ABC):
|
|
|
228
272
|
handle, tmpFile = mkstemp(
|
|
229
273
|
suffix=".tmp" if suffix is None else suffix,
|
|
230
274
|
prefix="tmp" if prefix is None else prefix,
|
|
231
|
-
dir=self.localTempDir
|
|
275
|
+
dir=self.localTempDir,
|
|
232
276
|
)
|
|
233
277
|
os.close(handle)
|
|
234
278
|
return os.path.abspath(tmpFile)
|
|
235
279
|
|
|
236
|
-
def getLocalTempFileName(
|
|
280
|
+
def getLocalTempFileName(
|
|
281
|
+
self, suffix: Optional[str] = None, prefix: Optional[str] = None
|
|
282
|
+
) -> str:
|
|
237
283
|
"""
|
|
238
284
|
Get a valid name for a new local file. Don't actually create a file at the path.
|
|
239
285
|
|
|
@@ -287,7 +333,7 @@ class AbstractFileStore(ABC):
|
|
|
287
333
|
basename: Optional[str] = None,
|
|
288
334
|
encoding: Optional[str] = None,
|
|
289
335
|
errors: Optional[str] = None,
|
|
290
|
-
) -> Iterator[
|
|
336
|
+
) -> Iterator[tuple[WriteWatchingStream, FileID]]:
|
|
291
337
|
"""
|
|
292
338
|
Similar to writeGlobalFile, but allows the writing of a stream to the job store.
|
|
293
339
|
The yielded file handle does not need to and should not be closed explicitly.
|
|
@@ -327,18 +373,23 @@ class AbstractFileStore(ABC):
|
|
|
327
373
|
def handle(numBytes: int) -> None:
|
|
328
374
|
# No scope problem here, because we don't assign to a fileID local
|
|
329
375
|
fileID.size += numBytes
|
|
376
|
+
|
|
330
377
|
wrappedStream.onWrite(handle)
|
|
331
378
|
|
|
332
379
|
yield wrappedStream, fileID
|
|
333
380
|
|
|
334
|
-
def _dumpAccessLogs(
|
|
381
|
+
def _dumpAccessLogs(
|
|
382
|
+
self, job_type: str = "Failed", log_level: int = logging.WARNING
|
|
383
|
+
) -> None:
|
|
335
384
|
"""
|
|
336
|
-
|
|
385
|
+
Log a report of the files accessed.
|
|
337
386
|
|
|
338
387
|
Includes the files that were accessed while the file store was open.
|
|
388
|
+
|
|
389
|
+
:param job_type: Adjective to describe the job in the report.
|
|
339
390
|
"""
|
|
340
391
|
if len(self._accessLog) > 0:
|
|
341
|
-
logger.
|
|
392
|
+
logger.log(log_level, "%s job accessed files:", job_type)
|
|
342
393
|
|
|
343
394
|
for item in self._accessLog:
|
|
344
395
|
# For each access record
|
|
@@ -347,14 +398,29 @@ class AbstractFileStore(ABC):
|
|
|
347
398
|
file_id, dest_path = item
|
|
348
399
|
if os.path.exists(dest_path):
|
|
349
400
|
if os.path.islink(dest_path):
|
|
350
|
-
logger.
|
|
401
|
+
logger.log(
|
|
402
|
+
log_level,
|
|
403
|
+
"Symlinked file '%s' to path '%s'",
|
|
404
|
+
file_id,
|
|
405
|
+
dest_path,
|
|
406
|
+
)
|
|
351
407
|
else:
|
|
352
|
-
logger.
|
|
408
|
+
logger.log(
|
|
409
|
+
log_level,
|
|
410
|
+
"Downloaded file '%s' to path '%s'",
|
|
411
|
+
file_id,
|
|
412
|
+
dest_path,
|
|
413
|
+
)
|
|
353
414
|
else:
|
|
354
|
-
logger.
|
|
415
|
+
logger.log(
|
|
416
|
+
log_level,
|
|
417
|
+
"Downloaded file '%s' to path '%s' (gone!)",
|
|
418
|
+
file_id,
|
|
419
|
+
dest_path,
|
|
420
|
+
)
|
|
355
421
|
else:
|
|
356
422
|
# Otherwise dump without the name
|
|
357
|
-
logger.
|
|
423
|
+
logger.log(log_level, "Streamed file '%s'", *item)
|
|
358
424
|
|
|
359
425
|
def logAccess(
|
|
360
426
|
self, fileStoreID: Union[FileID, str], destination: Union[str, None] = None
|
|
@@ -421,14 +487,12 @@ class AbstractFileStore(ABC):
|
|
|
421
487
|
fileStoreID: str,
|
|
422
488
|
encoding: Literal[None] = None,
|
|
423
489
|
errors: Optional[str] = None,
|
|
424
|
-
) -> ContextManager[IO[bytes]]:
|
|
425
|
-
...
|
|
490
|
+
) -> ContextManager[IO[bytes]]: ...
|
|
426
491
|
|
|
427
492
|
@overload
|
|
428
493
|
def readGlobalFileStream(
|
|
429
494
|
self, fileStoreID: str, encoding: str, errors: Optional[str] = None
|
|
430
|
-
) -> ContextManager[IO[str]]:
|
|
431
|
-
...
|
|
495
|
+
) -> ContextManager[IO[str]]: ...
|
|
432
496
|
|
|
433
497
|
@abstractmethod
|
|
434
498
|
def readGlobalFileStream(
|
|
@@ -472,7 +536,7 @@ class AbstractFileStore(ABC):
|
|
|
472
536
|
:return: File's size in bytes, as stored in the job store
|
|
473
537
|
"""
|
|
474
538
|
# First try and see if the size is still attached
|
|
475
|
-
size = getattr(fileStoreID,
|
|
539
|
+
size = getattr(fileStoreID, "size", None)
|
|
476
540
|
|
|
477
541
|
if size is None:
|
|
478
542
|
# It fell off
|
|
@@ -525,7 +589,7 @@ class AbstractFileStore(ABC):
|
|
|
525
589
|
) -> Optional[FileID]:
|
|
526
590
|
return self.jobStore.import_file(src_uri, shared_file_name=shared_file_name)
|
|
527
591
|
|
|
528
|
-
@deprecated(new_function_name=
|
|
592
|
+
@deprecated(new_function_name="export_file")
|
|
529
593
|
def exportFile(self, jobStoreFileID: FileID, dstUrl: str) -> None:
|
|
530
594
|
return self.export_file(jobStoreFileID, dstUrl)
|
|
531
595
|
|
|
@@ -554,7 +618,7 @@ class AbstractFileStore(ABC):
|
|
|
554
618
|
class _StateFile:
|
|
555
619
|
"""Read and write dill-ed state dictionaries from/to a file into a namespace."""
|
|
556
620
|
|
|
557
|
-
def __init__(self, stateDict:
|
|
621
|
+
def __init__(self, stateDict: dict[str, Any]):
|
|
558
622
|
assert isinstance(stateDict, dict)
|
|
559
623
|
self.__dict__.update(stateDict)
|
|
560
624
|
|
|
@@ -582,7 +646,7 @@ class AbstractFileStore(ABC):
|
|
|
582
646
|
"""
|
|
583
647
|
# Read the value from the cache state file then initialize and instance of
|
|
584
648
|
# _CacheState with it.
|
|
585
|
-
with open(fileName,
|
|
649
|
+
with open(fileName, "rb") as fH:
|
|
586
650
|
infoDict = dill.load(fH)
|
|
587
651
|
return cls(infoDict)
|
|
588
652
|
|
|
@@ -592,14 +656,14 @@ class AbstractFileStore(ABC):
|
|
|
592
656
|
|
|
593
657
|
:param fileName: Path to the state file.
|
|
594
658
|
"""
|
|
595
|
-
with open(fileName +
|
|
659
|
+
with open(fileName + ".tmp", "wb") as fH:
|
|
596
660
|
# Based on answer by user "Mark" at:
|
|
597
661
|
# http://stackoverflow.com/questions/2709800/how-to-pickle-yourself
|
|
598
662
|
# We can't pickle nested classes. So we have to pickle the variables
|
|
599
663
|
# of the class.
|
|
600
664
|
# If we ever change this, we need to ensure it doesn't break FileID
|
|
601
665
|
dill.dump(self.__dict__, fH)
|
|
602
|
-
os.rename(fileName +
|
|
666
|
+
os.rename(fileName + ".tmp", fileName)
|
|
603
667
|
|
|
604
668
|
# Functions related to logging
|
|
605
669
|
def log_to_leader(self, text: str, level: int = logging.INFO) -> None:
|
|
@@ -611,13 +675,29 @@ class AbstractFileStore(ABC):
|
|
|
611
675
|
:param level: The logging level.
|
|
612
676
|
"""
|
|
613
677
|
logger.log(level=level, msg=("LOG-TO-MASTER: " + text))
|
|
614
|
-
self.
|
|
615
|
-
|
|
678
|
+
self.logging_messages.append(dict(text=text, level=level))
|
|
616
679
|
|
|
617
|
-
@deprecated(new_function_name=
|
|
680
|
+
@deprecated(new_function_name="export_file")
|
|
618
681
|
def logToMaster(self, text: str, level: int = logging.INFO) -> None:
|
|
619
682
|
self.log_to_leader(text, level)
|
|
620
|
-
|
|
683
|
+
|
|
684
|
+
def log_user_stream(self, name: str, stream: IO[bytes]) -> None:
|
|
685
|
+
"""
|
|
686
|
+
Send a stream of UTF-8 text to the leader as a named log stream.
|
|
687
|
+
|
|
688
|
+
Useful for things like the error logs of Docker containers. The leader
|
|
689
|
+
will show it to the user or organize it appropriately for user-level
|
|
690
|
+
log information.
|
|
691
|
+
|
|
692
|
+
:param name: A hierarchical, .-delimited string.
|
|
693
|
+
:param stream: A stream of encoded text. Encoding errors will be
|
|
694
|
+
tolerated.
|
|
695
|
+
"""
|
|
696
|
+
|
|
697
|
+
# Read the whole stream into memory
|
|
698
|
+
steam_data = stream.read().decode("utf-8", errors="replace")
|
|
699
|
+
# And remember it for the worker to fish out
|
|
700
|
+
self.logging_user_streams.append(dict(name=name, text=steam_data))
|
|
621
701
|
|
|
622
702
|
# Functions run after the completion of the job.
|
|
623
703
|
@abstractmethod
|