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
toil/resource.py
CHANGED
|
@@ -20,17 +20,12 @@ import os
|
|
|
20
20
|
import shutil
|
|
21
21
|
import sys
|
|
22
22
|
from collections import namedtuple
|
|
23
|
+
from collections.abc import Sequence
|
|
23
24
|
from contextlib import closing
|
|
24
25
|
from io import BytesIO
|
|
25
26
|
from pydoc import locate
|
|
26
27
|
from types import ModuleType
|
|
27
|
-
from typing import
|
|
28
|
-
TYPE_CHECKING,
|
|
29
|
-
BinaryIO,
|
|
30
|
-
Callable,
|
|
31
|
-
Optional,
|
|
32
|
-
Sequence,
|
|
33
|
-
Type)
|
|
28
|
+
from typing import IO, TYPE_CHECKING, BinaryIO, Callable, Optional
|
|
34
29
|
from urllib.error import HTTPError
|
|
35
30
|
from urllib.request import urlopen
|
|
36
31
|
from zipfile import ZipFile
|
|
@@ -47,7 +42,8 @@ if TYPE_CHECKING:
|
|
|
47
42
|
|
|
48
43
|
logger = logging.getLogger(__name__)
|
|
49
44
|
|
|
50
|
-
|
|
45
|
+
|
|
46
|
+
class Resource(namedtuple("Resource", ("name", "pathHash", "url", "contentHash"))):
|
|
51
47
|
"""
|
|
52
48
|
Represents a file or directory that will be deployed to each node before any jobs in the user script are invoked.
|
|
53
49
|
|
|
@@ -67,9 +63,9 @@ class Resource(namedtuple('Resource', ('name', 'pathHash', 'url', 'contentHash')
|
|
|
67
63
|
ZIP archive of that directory.
|
|
68
64
|
"""
|
|
69
65
|
|
|
70
|
-
resourceEnvNamePrefix =
|
|
66
|
+
resourceEnvNamePrefix = "JTRES_"
|
|
71
67
|
|
|
72
|
-
rootDirPathEnvName = resourceEnvNamePrefix +
|
|
68
|
+
rootDirPathEnvName = resourceEnvNamePrefix + "ROOT"
|
|
73
69
|
|
|
74
70
|
@classmethod
|
|
75
71
|
def create(cls, jobStore: "AbstractJobStore", leaderPath: str) -> "Resource":
|
|
@@ -86,20 +82,26 @@ class Resource(namedtuple('Resource', ('name', 'pathHash', 'url', 'contentHash')
|
|
|
86
82
|
contentHash = hashlib.md5()
|
|
87
83
|
# noinspection PyProtectedMember
|
|
88
84
|
with cls._load(leaderPath) as src:
|
|
89
|
-
with jobStore.write_shared_file_stream(
|
|
85
|
+
with jobStore.write_shared_file_stream(
|
|
86
|
+
shared_file_name=pathHash, encrypted=False
|
|
87
|
+
) as dst:
|
|
90
88
|
userScript = src.read()
|
|
91
89
|
contentHash.update(userScript)
|
|
92
90
|
dst.write(userScript)
|
|
93
|
-
return cls(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
return cls(
|
|
92
|
+
name=os.path.basename(leaderPath),
|
|
93
|
+
pathHash=pathHash,
|
|
94
|
+
url=jobStore.getSharedPublicUrl(sharedFileName=pathHash),
|
|
95
|
+
contentHash=contentHash.hexdigest(),
|
|
96
|
+
)
|
|
97
97
|
|
|
98
98
|
def refresh(self, jobStore: "AbstractJobStore") -> "Resource":
|
|
99
|
-
return type(self)(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
return type(self)(
|
|
100
|
+
name=self.name,
|
|
101
|
+
pathHash=self.pathHash,
|
|
102
|
+
url=jobStore.get_shared_public_url(shared_file_name=self.pathHash),
|
|
103
|
+
contentHash=self.contentHash,
|
|
104
|
+
)
|
|
103
105
|
|
|
104
106
|
@classmethod
|
|
105
107
|
def prepareSystem(cls) -> None:
|
|
@@ -165,7 +167,9 @@ class Resource(namedtuple('Resource', ('name', 'pathHash', 'url', 'contentHash')
|
|
|
165
167
|
"""
|
|
166
168
|
dirPath = self.localDirPath
|
|
167
169
|
if not os.path.exists(dirPath):
|
|
168
|
-
tempDirPath = mkdtemp(
|
|
170
|
+
tempDirPath = mkdtemp(
|
|
171
|
+
dir=os.path.dirname(dirPath), prefix=self.contentHash + "-"
|
|
172
|
+
)
|
|
169
173
|
self._save(tempDirPath)
|
|
170
174
|
if callback is not None:
|
|
171
175
|
callback(tempDirPath)
|
|
@@ -199,16 +203,22 @@ class Resource(namedtuple('Resource', ('name', 'pathHash', 'url', 'contentHash')
|
|
|
199
203
|
return os.path.join(rootDirPath, self.contentHash)
|
|
200
204
|
|
|
201
205
|
def pickle(self) -> str:
|
|
202
|
-
return
|
|
206
|
+
return (
|
|
207
|
+
self.__class__.__module__
|
|
208
|
+
+ "."
|
|
209
|
+
+ self.__class__.__name__
|
|
210
|
+
+ ":"
|
|
211
|
+
+ json.dumps(self)
|
|
212
|
+
)
|
|
203
213
|
|
|
204
214
|
@classmethod
|
|
205
215
|
def unpickle(cls, s: str) -> "Resource":
|
|
206
|
-
className, _json = s.split(
|
|
207
|
-
return locate(className)(*json.loads(_json))
|
|
216
|
+
className, _json = s.split(":", 1)
|
|
217
|
+
return locate(className)(*json.loads(_json)) # type: ignore
|
|
208
218
|
|
|
209
219
|
@classmethod
|
|
210
220
|
def _pathHash(cls, path: str) -> str:
|
|
211
|
-
return hashlib.md5(path.encode(
|
|
221
|
+
return hashlib.md5(path.encode("utf-8")).hexdigest()
|
|
212
222
|
|
|
213
223
|
@classmethod
|
|
214
224
|
def _load(cls, path: str) -> IO[bytes]:
|
|
@@ -230,11 +240,7 @@ class Resource(namedtuple('Resource', ('name', 'pathHash', 'url', 'contentHash')
|
|
|
230
240
|
"""
|
|
231
241
|
raise NotImplementedError()
|
|
232
242
|
|
|
233
|
-
@retry(errors=[
|
|
234
|
-
ErrorCondition(
|
|
235
|
-
error=HTTPError,
|
|
236
|
-
error_codes=[400])
|
|
237
|
-
])
|
|
243
|
+
@retry(errors=[ErrorCondition(error=HTTPError, error_codes=[400])])
|
|
238
244
|
def _download(self, dstFile: IO[bytes]) -> None:
|
|
239
245
|
"""
|
|
240
246
|
Download this resource from its URL to the given file object.
|
|
@@ -254,10 +260,10 @@ class FileResource(Resource):
|
|
|
254
260
|
|
|
255
261
|
@classmethod
|
|
256
262
|
def _load(cls, path: str) -> BinaryIO:
|
|
257
|
-
return open(path,
|
|
263
|
+
return open(path, "rb")
|
|
258
264
|
|
|
259
265
|
def _save(self, dirPath: str) -> None:
|
|
260
|
-
with open(os.path.join(dirPath, self.name), mode=
|
|
266
|
+
with open(os.path.join(dirPath, self.name), mode="wb") as localFile:
|
|
261
267
|
self._download(localFile)
|
|
262
268
|
|
|
263
269
|
@property
|
|
@@ -277,7 +283,7 @@ class DirectoryResource(Resource):
|
|
|
277
283
|
@classmethod
|
|
278
284
|
def _load(cls, path: str) -> BytesIO:
|
|
279
285
|
bytesIO = BytesIO()
|
|
280
|
-
initfile = os.path.join(path,
|
|
286
|
+
initfile = os.path.join(path, "__init__.py")
|
|
281
287
|
if os.path.isfile(initfile):
|
|
282
288
|
# This is a package directory. To emulate
|
|
283
289
|
# PyZipFile.writepy's behavior, we need to keep everything
|
|
@@ -286,20 +292,37 @@ class DirectoryResource(Resource):
|
|
|
286
292
|
else:
|
|
287
293
|
# This is a simple user script (with possibly a few helper files)
|
|
288
294
|
rootDir = path
|
|
289
|
-
skipdirList = [
|
|
295
|
+
skipdirList = [
|
|
296
|
+
"/tmp",
|
|
297
|
+
"/var",
|
|
298
|
+
"/etc",
|
|
299
|
+
"/bin",
|
|
300
|
+
"/sbin",
|
|
301
|
+
"/home",
|
|
302
|
+
"/dev",
|
|
303
|
+
"/sys",
|
|
304
|
+
"/usr",
|
|
305
|
+
"/run",
|
|
306
|
+
]
|
|
290
307
|
if path not in skipdirList:
|
|
291
|
-
with ZipFile(file=bytesIO, mode=
|
|
308
|
+
with ZipFile(file=bytesIO, mode="w") as zipFile:
|
|
292
309
|
for dirName, _, fileList in os.walk(path):
|
|
293
310
|
for fileName in fileList:
|
|
294
311
|
try:
|
|
295
312
|
fullPath = os.path.join(dirName, fileName)
|
|
296
313
|
zipFile.write(fullPath, os.path.relpath(fullPath, rootDir))
|
|
297
314
|
except OSError:
|
|
298
|
-
logger.critical(
|
|
315
|
+
logger.critical(
|
|
316
|
+
"Cannot access and read the file at path: %s" % fullPath
|
|
317
|
+
)
|
|
299
318
|
sys.exit(1)
|
|
300
319
|
else:
|
|
301
|
-
logger.critical(
|
|
302
|
-
|
|
320
|
+
logger.critical(
|
|
321
|
+
"Couldn't package the directory at {} for hot deployment. Would recommend to create a \
|
|
322
|
+
subdirectory (ie {}/MYDIR_HERE/)".format(
|
|
323
|
+
path, path
|
|
324
|
+
)
|
|
325
|
+
)
|
|
303
326
|
sys.exit(1)
|
|
304
327
|
bytesIO.seek(0)
|
|
305
328
|
return bytesIO
|
|
@@ -308,7 +331,7 @@ class DirectoryResource(Resource):
|
|
|
308
331
|
bytesIO = BytesIO()
|
|
309
332
|
self._download(bytesIO)
|
|
310
333
|
bytesIO.seek(0)
|
|
311
|
-
with ZipFile(file=bytesIO, mode=
|
|
334
|
+
with ZipFile(file=bytesIO, mode="r") as zipFile:
|
|
312
335
|
zipFile.extractall(path=dirPath)
|
|
313
336
|
|
|
314
337
|
@property
|
|
@@ -325,10 +348,10 @@ class VirtualEnvResource(DirectoryResource):
|
|
|
325
348
|
|
|
326
349
|
@classmethod
|
|
327
350
|
def _load(cls, path: str) -> BytesIO:
|
|
328
|
-
if os.path.basename(path) !=
|
|
351
|
+
if os.path.basename(path) != "site-packages":
|
|
329
352
|
raise RuntimeError("An incorrect path was passed through.")
|
|
330
353
|
bytesIO = BytesIO()
|
|
331
|
-
with ZipFile(file=bytesIO, mode=
|
|
354
|
+
with ZipFile(file=bytesIO, mode="w") as zipFile:
|
|
332
355
|
for dirName, _, fileList in os.walk(path):
|
|
333
356
|
zipFile.write(dirName)
|
|
334
357
|
for fileName in fileList:
|
|
@@ -338,7 +361,9 @@ class VirtualEnvResource(DirectoryResource):
|
|
|
338
361
|
return bytesIO
|
|
339
362
|
|
|
340
363
|
|
|
341
|
-
class ModuleDescriptor(
|
|
364
|
+
class ModuleDescriptor(
|
|
365
|
+
namedtuple("ModuleDescriptor", ("dirPath", "name", "fromVirtualEnv"))
|
|
366
|
+
):
|
|
342
367
|
"""
|
|
343
368
|
A path to a Python module decomposed into a namedtuple of three elements
|
|
344
369
|
|
|
@@ -378,6 +403,7 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
378
403
|
Clean up
|
|
379
404
|
>>> rmtree( dirPath )
|
|
380
405
|
"""
|
|
406
|
+
|
|
381
407
|
dirPath: str
|
|
382
408
|
name: str
|
|
383
409
|
|
|
@@ -392,7 +418,7 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
392
418
|
"""
|
|
393
419
|
module = sys.modules[name]
|
|
394
420
|
if module.__file__ is None:
|
|
395
|
-
raise Exception(f
|
|
421
|
+
raise Exception(f"Module {name} does not exist.")
|
|
396
422
|
fileAbsPath = os.path.abspath(module.__file__)
|
|
397
423
|
filePath = fileAbsPath.split(os.path.sep)
|
|
398
424
|
filePath[-1], extension = os.path.splitext(filePath[-1])
|
|
@@ -405,12 +431,12 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
405
431
|
# Invoked as a module via python -m foo.bar
|
|
406
432
|
logger.debug("Script was invoked as a module")
|
|
407
433
|
nameList = [filePath.pop()]
|
|
408
|
-
for package in reversed(module.__package__.split(
|
|
434
|
+
for package in reversed(module.__package__.split(".")):
|
|
409
435
|
dirPathTail = filePath.pop()
|
|
410
436
|
if dirPathTail != package:
|
|
411
437
|
raise RuntimeError("Incorrect path to package.")
|
|
412
438
|
nameList.append(dirPathTail)
|
|
413
|
-
name =
|
|
439
|
+
name = ".".join(reversed(nameList))
|
|
414
440
|
dirPath = os.path.sep.join(filePath)
|
|
415
441
|
else:
|
|
416
442
|
# Invoked as a script via python foo/bar.py
|
|
@@ -419,20 +445,27 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
419
445
|
cls._check_conflict(dirPath, name)
|
|
420
446
|
else:
|
|
421
447
|
# User module was imported. Determine the directory containing the top-level package
|
|
422
|
-
if filePath[-1] ==
|
|
448
|
+
if filePath[-1] == "__init__":
|
|
423
449
|
# module is a subpackage
|
|
424
450
|
filePath.pop()
|
|
425
451
|
|
|
426
|
-
for package in reversed(name.split(
|
|
452
|
+
for package in reversed(name.split(".")):
|
|
427
453
|
dirPathTail = filePath.pop()
|
|
428
454
|
if dirPathTail != package:
|
|
429
455
|
raise RuntimeError("Incorrect path to package.")
|
|
430
456
|
dirPath = os.path.abspath(os.path.sep.join(filePath))
|
|
431
457
|
absPrefix = os.path.abspath(sys.prefix)
|
|
432
458
|
inVenv = inVirtualEnv()
|
|
433
|
-
logger.debug(
|
|
459
|
+
logger.debug(
|
|
460
|
+
"Module dir is %s, our prefix is %s, virtualenv: %s",
|
|
461
|
+
dirPath,
|
|
462
|
+
absPrefix,
|
|
463
|
+
inVenv,
|
|
464
|
+
)
|
|
434
465
|
if not os.path.isdir(dirPath):
|
|
435
|
-
raise Exception(
|
|
466
|
+
raise Exception(
|
|
467
|
+
f"Bad directory path {dirPath} for module {name}. Note that hot-deployment does not support .egg-link files yet, or scripts located in the root directory."
|
|
468
|
+
)
|
|
436
469
|
fromVirtualEnv = inVenv and dirPath.startswith(absPrefix)
|
|
437
470
|
return cls(dirPath=dirPath, name=name, fromVirtualEnv=fromVirtualEnv)
|
|
438
471
|
|
|
@@ -446,7 +479,11 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
446
479
|
"""
|
|
447
480
|
old_sys_path = sys.path
|
|
448
481
|
try:
|
|
449
|
-
sys.path = [
|
|
482
|
+
sys.path = [
|
|
483
|
+
d
|
|
484
|
+
for d in old_sys_path
|
|
485
|
+
if os.path.realpath(d) != os.path.realpath(dirPath)
|
|
486
|
+
]
|
|
450
487
|
try:
|
|
451
488
|
colliding_module = importlib.import_module(name)
|
|
452
489
|
except ImportError:
|
|
@@ -454,7 +491,9 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
454
491
|
else:
|
|
455
492
|
raise ResourceException(
|
|
456
493
|
"The user module '{}' collides with module '{} from '{}'.".format(
|
|
457
|
-
name, colliding_module.__name__, colliding_module.__file__
|
|
494
|
+
name, colliding_module.__name__, colliding_module.__file__
|
|
495
|
+
)
|
|
496
|
+
)
|
|
458
497
|
finally:
|
|
459
498
|
sys.path = old_sys_path
|
|
460
499
|
|
|
@@ -463,7 +502,7 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
463
502
|
"""
|
|
464
503
|
True if this module is part of the Toil distribution
|
|
465
504
|
"""
|
|
466
|
-
return self.name.startswith(
|
|
505
|
+
return self.name.startswith("toil.")
|
|
467
506
|
|
|
468
507
|
def saveAsResourceTo(self, jobStore: "AbstractJobStore") -> Resource:
|
|
469
508
|
"""
|
|
@@ -475,11 +514,11 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
475
514
|
"""
|
|
476
515
|
return self._getResourceClass().create(jobStore, self._resourcePath)
|
|
477
516
|
|
|
478
|
-
def _getResourceClass(self) ->
|
|
517
|
+
def _getResourceClass(self) -> type[Resource]:
|
|
479
518
|
"""
|
|
480
519
|
Return the concrete subclass of Resource that's appropriate for auto-deploying this module.
|
|
481
520
|
"""
|
|
482
|
-
subcls:
|
|
521
|
+
subcls: type[Resource]
|
|
483
522
|
if self.fromVirtualEnv:
|
|
484
523
|
subcls = VirtualEnvResource
|
|
485
524
|
elif os.path.isdir(self._resourcePath):
|
|
@@ -487,7 +526,9 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
487
526
|
elif os.path.isfile(self._resourcePath):
|
|
488
527
|
subcls = FileResource
|
|
489
528
|
elif os.path.exists(self._resourcePath):
|
|
490
|
-
raise AssertionError(
|
|
529
|
+
raise AssertionError(
|
|
530
|
+
"Neither a file or a directory: '%s'" % self._resourcePath
|
|
531
|
+
)
|
|
491
532
|
else:
|
|
492
533
|
raise AssertionError("No such file or directory: '%s'" % self._resourcePath)
|
|
493
534
|
return subcls
|
|
@@ -501,27 +542,30 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
501
542
|
the leader, this method returns this resource, i.e. self.
|
|
502
543
|
"""
|
|
503
544
|
if not self._runningOnWorker():
|
|
504
|
-
logger.warning(
|
|
545
|
+
logger.warning("The localize() method should only be invoked on a worker.")
|
|
505
546
|
resource = Resource.lookup(self._resourcePath)
|
|
506
547
|
if resource is None:
|
|
507
548
|
return self
|
|
508
549
|
else:
|
|
550
|
+
|
|
509
551
|
def stash(tmpDirPath: str) -> None:
|
|
510
552
|
# Save the original dirPath such that we can restore it in globalize()
|
|
511
|
-
with open(os.path.join(tmpDirPath,
|
|
512
|
-
f.write(
|
|
553
|
+
with open(os.path.join(tmpDirPath, ".stash"), "w") as f:
|
|
554
|
+
f.write("1" if self.fromVirtualEnv else "0")
|
|
513
555
|
f.write(self.dirPath)
|
|
514
556
|
|
|
515
557
|
resource.download(callback=stash)
|
|
516
|
-
return self.__class__(
|
|
517
|
-
|
|
518
|
-
|
|
558
|
+
return self.__class__(
|
|
559
|
+
dirPath=resource.localDirPath,
|
|
560
|
+
name=self.name,
|
|
561
|
+
fromVirtualEnv=self.fromVirtualEnv,
|
|
562
|
+
)
|
|
519
563
|
|
|
520
564
|
def _runningOnWorker(self) -> bool:
|
|
521
565
|
try:
|
|
522
|
-
mainModule = sys.modules[
|
|
566
|
+
mainModule = sys.modules["__main__"]
|
|
523
567
|
except KeyError:
|
|
524
|
-
logger.warning(
|
|
568
|
+
logger.warning("Cannot determine main program module.")
|
|
525
569
|
return False
|
|
526
570
|
else:
|
|
527
571
|
# If __file__ is not a valid attribute, it's because
|
|
@@ -535,7 +579,12 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
535
579
|
except AttributeError:
|
|
536
580
|
return False
|
|
537
581
|
|
|
538
|
-
workerModuleFiles = [
|
|
582
|
+
workerModuleFiles = [
|
|
583
|
+
"worker.py",
|
|
584
|
+
"worker.pyc",
|
|
585
|
+
"worker.pyo",
|
|
586
|
+
"_toil_worker",
|
|
587
|
+
] # setuptools entry point
|
|
539
588
|
return mainModuleFile in workerModuleFiles
|
|
540
589
|
|
|
541
590
|
def globalize(self) -> "ModuleDescriptor":
|
|
@@ -543,7 +592,7 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
543
592
|
Reverse the effect of localize().
|
|
544
593
|
"""
|
|
545
594
|
try:
|
|
546
|
-
with open(os.path.join(self.dirPath,
|
|
595
|
+
with open(os.path.join(self.dirPath, ".stash")) as f:
|
|
547
596
|
fromVirtualEnv = [False, True][int(f.read(1))]
|
|
548
597
|
dirPath = f.read()
|
|
549
598
|
except OSError as e:
|
|
@@ -552,9 +601,9 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
552
601
|
else:
|
|
553
602
|
raise
|
|
554
603
|
else:
|
|
555
|
-
return self.__class__(
|
|
556
|
-
|
|
557
|
-
|
|
604
|
+
return self.__class__(
|
|
605
|
+
dirPath=dirPath, name=self.name, fromVirtualEnv=fromVirtualEnv
|
|
606
|
+
)
|
|
558
607
|
|
|
559
608
|
@property
|
|
560
609
|
def _resourcePath(self) -> str:
|
|
@@ -564,7 +613,7 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
564
613
|
"""
|
|
565
614
|
if self.fromVirtualEnv:
|
|
566
615
|
return self.dirPath
|
|
567
|
-
elif
|
|
616
|
+
elif "." in self.name:
|
|
568
617
|
return os.path.join(self.dirPath, self._rootPackage())
|
|
569
618
|
else:
|
|
570
619
|
initName = self._initModuleName(self.dirPath)
|
|
@@ -572,22 +621,31 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
572
621
|
raise ResourceException(
|
|
573
622
|
"Toil does not support loading a user script from a package directory. You "
|
|
574
623
|
"may want to remove %s from %s or invoke the user script as a module via "
|
|
575
|
-
"'PYTHONPATH=\"%s\" %s -m %s.%s'."
|
|
576
|
-
tuple(
|
|
624
|
+
"'PYTHONPATH=\"%s\" %s -m %s.%s'."
|
|
625
|
+
% tuple(
|
|
626
|
+
concat(
|
|
627
|
+
initName,
|
|
628
|
+
self.dirPath,
|
|
629
|
+
exactPython,
|
|
630
|
+
os.path.split(self.dirPath),
|
|
631
|
+
self.name,
|
|
632
|
+
)
|
|
633
|
+
)
|
|
634
|
+
)
|
|
577
635
|
return self.dirPath
|
|
578
636
|
|
|
579
637
|
@classmethod
|
|
580
638
|
def _initModuleName(cls, dirPath: str) -> Optional[str]:
|
|
581
|
-
for name in (
|
|
639
|
+
for name in ("__init__.py", "__init__.pyc", "__init__.pyo"):
|
|
582
640
|
if os.path.exists(os.path.join(dirPath, name)):
|
|
583
641
|
return name
|
|
584
642
|
return None
|
|
585
643
|
|
|
586
644
|
def _rootPackage(self) -> str:
|
|
587
645
|
try:
|
|
588
|
-
head, tail = self.name.split(
|
|
646
|
+
head, tail = self.name.split(".", 1)
|
|
589
647
|
except ValueError:
|
|
590
|
-
raise ValueError(
|
|
648
|
+
raise ValueError("%r is stand-alone module." % self.__repr__())
|
|
591
649
|
else:
|
|
592
650
|
return head
|
|
593
651
|
|
|
@@ -598,7 +656,9 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
598
656
|
def fromCommand(cls, command: Sequence[str]) -> "ModuleDescriptor":
|
|
599
657
|
if len(command) != 3:
|
|
600
658
|
raise RuntimeError("Incorrect number of arguments (Expected 3).")
|
|
601
|
-
return cls(
|
|
659
|
+
return cls(
|
|
660
|
+
dirPath=command[0], name=command[1], fromVirtualEnv=strict_bool(command[2])
|
|
661
|
+
)
|
|
602
662
|
|
|
603
663
|
def makeLoadable(self) -> "ModuleDescriptor":
|
|
604
664
|
module = self if self.belongsToToil else self.localize()
|
|
@@ -611,7 +671,9 @@ class ModuleDescriptor(namedtuple('ModuleDescriptor', ('dirPath', 'name', 'fromV
|
|
|
611
671
|
try:
|
|
612
672
|
return importlib.import_module(module.name)
|
|
613
673
|
except ImportError:
|
|
614
|
-
logger.error(
|
|
674
|
+
logger.error(
|
|
675
|
+
"Failed to import user module %r from sys.path (%r).", module, sys.path
|
|
676
|
+
)
|
|
615
677
|
raise
|
|
616
678
|
|
|
617
679
|
|