toil 5.12.0__py3-none-any.whl → 6.1.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 +18 -13
- toil/batchSystems/abstractBatchSystem.py +39 -13
- toil/batchSystems/abstractGridEngineBatchSystem.py +24 -24
- toil/batchSystems/awsBatch.py +14 -14
- toil/batchSystems/cleanup_support.py +7 -3
- toil/batchSystems/contained_executor.py +3 -3
- toil/batchSystems/htcondor.py +0 -1
- toil/batchSystems/kubernetes.py +34 -31
- toil/batchSystems/local_support.py +3 -1
- toil/batchSystems/lsf.py +7 -7
- toil/batchSystems/mesos/batchSystem.py +7 -7
- toil/batchSystems/options.py +32 -83
- toil/batchSystems/registry.py +104 -23
- toil/batchSystems/singleMachine.py +16 -13
- toil/batchSystems/slurm.py +87 -16
- toil/batchSystems/torque.py +0 -1
- toil/bus.py +44 -8
- toil/common.py +544 -753
- toil/cwl/__init__.py +28 -32
- toil/cwl/cwltoil.py +595 -574
- toil/cwl/utils.py +55 -10
- toil/exceptions.py +1 -1
- toil/fileStores/__init__.py +2 -2
- toil/fileStores/abstractFileStore.py +88 -14
- toil/fileStores/cachingFileStore.py +610 -549
- toil/fileStores/nonCachingFileStore.py +46 -22
- toil/job.py +182 -101
- toil/jobStores/abstractJobStore.py +161 -95
- toil/jobStores/aws/jobStore.py +23 -9
- toil/jobStores/aws/utils.py +6 -6
- toil/jobStores/fileJobStore.py +116 -18
- toil/jobStores/googleJobStore.py +16 -7
- toil/jobStores/utils.py +5 -6
- toil/leader.py +87 -56
- toil/lib/accelerators.py +10 -5
- toil/lib/aws/__init__.py +3 -14
- toil/lib/aws/ami.py +22 -9
- toil/lib/aws/iam.py +21 -13
- toil/lib/aws/session.py +2 -16
- toil/lib/aws/utils.py +4 -5
- toil/lib/compatibility.py +1 -1
- toil/lib/conversions.py +26 -3
- toil/lib/docker.py +22 -23
- toil/lib/ec2.py +10 -6
- toil/lib/ec2nodes.py +106 -100
- toil/lib/encryption/_nacl.py +2 -1
- toil/lib/generatedEC2Lists.py +325 -18
- toil/lib/io.py +49 -2
- toil/lib/misc.py +1 -1
- toil/lib/resources.py +9 -2
- toil/lib/threading.py +101 -38
- toil/options/common.py +736 -0
- toil/options/cwl.py +336 -0
- toil/options/wdl.py +37 -0
- toil/provisioners/abstractProvisioner.py +9 -4
- toil/provisioners/aws/__init__.py +3 -6
- toil/provisioners/aws/awsProvisioner.py +6 -0
- toil/provisioners/clusterScaler.py +3 -2
- toil/provisioners/gceProvisioner.py +2 -2
- toil/realtimeLogger.py +2 -1
- toil/resource.py +24 -18
- toil/server/app.py +2 -3
- toil/server/cli/wes_cwl_runner.py +4 -4
- toil/server/utils.py +1 -1
- toil/server/wes/abstract_backend.py +3 -2
- toil/server/wes/amazon_wes_utils.py +5 -4
- toil/server/wes/tasks.py +2 -3
- toil/server/wes/toil_backend.py +2 -10
- toil/server/wsgi_app.py +2 -0
- toil/serviceManager.py +12 -10
- toil/statsAndLogging.py +41 -9
- toil/test/__init__.py +29 -54
- toil/test/batchSystems/batchSystemTest.py +11 -111
- toil/test/batchSystems/test_slurm.py +24 -8
- toil/test/cactus/__init__.py +0 -0
- toil/test/cactus/test_cactus_integration.py +58 -0
- toil/test/cwl/cwlTest.py +438 -223
- toil/test/cwl/glob_dir.cwl +15 -0
- toil/test/cwl/preemptible.cwl +21 -0
- toil/test/cwl/preemptible_expression.cwl +28 -0
- toil/test/cwl/revsort.cwl +1 -1
- toil/test/cwl/revsort2.cwl +1 -1
- toil/test/docs/scriptsTest.py +2 -3
- toil/test/jobStores/jobStoreTest.py +34 -21
- toil/test/lib/aws/test_iam.py +4 -14
- toil/test/lib/aws/test_utils.py +0 -3
- toil/test/lib/dockerTest.py +4 -4
- toil/test/lib/test_ec2.py +12 -17
- toil/test/mesos/helloWorld.py +4 -5
- toil/test/mesos/stress.py +1 -1
- toil/test/{wdl/conftest.py → options/__init__.py} +0 -10
- toil/test/options/options.py +37 -0
- toil/test/provisioners/aws/awsProvisionerTest.py +9 -5
- toil/test/provisioners/clusterScalerTest.py +6 -4
- toil/test/provisioners/clusterTest.py +23 -11
- toil/test/provisioners/gceProvisionerTest.py +0 -6
- toil/test/provisioners/restartScript.py +3 -2
- toil/test/server/serverTest.py +1 -1
- toil/test/sort/restart_sort.py +2 -1
- toil/test/sort/sort.py +2 -1
- toil/test/sort/sortTest.py +2 -13
- toil/test/src/autoDeploymentTest.py +45 -45
- toil/test/src/busTest.py +5 -5
- toil/test/src/checkpointTest.py +2 -2
- toil/test/src/deferredFunctionTest.py +1 -1
- toil/test/src/fileStoreTest.py +32 -16
- toil/test/src/helloWorldTest.py +1 -1
- toil/test/src/importExportFileTest.py +1 -1
- toil/test/src/jobDescriptionTest.py +2 -1
- toil/test/src/jobServiceTest.py +1 -1
- toil/test/src/jobTest.py +18 -18
- toil/test/src/miscTests.py +5 -3
- toil/test/src/promisedRequirementTest.py +3 -3
- toil/test/src/realtimeLoggerTest.py +1 -1
- toil/test/src/resourceTest.py +2 -2
- toil/test/src/restartDAGTest.py +1 -1
- toil/test/src/resumabilityTest.py +36 -2
- toil/test/src/retainTempDirTest.py +1 -1
- toil/test/src/systemTest.py +2 -2
- toil/test/src/toilContextManagerTest.py +2 -2
- toil/test/src/userDefinedJobArgTypeTest.py +1 -1
- toil/test/utils/toilDebugTest.py +98 -32
- toil/test/utils/toilKillTest.py +2 -2
- toil/test/utils/utilsTest.py +23 -3
- toil/test/wdl/wdltoil_test.py +223 -45
- toil/toilState.py +7 -6
- toil/utils/toilClean.py +1 -1
- toil/utils/toilConfig.py +36 -0
- toil/utils/toilDebugFile.py +60 -33
- toil/utils/toilDebugJob.py +39 -12
- toil/utils/toilDestroyCluster.py +1 -1
- toil/utils/toilKill.py +1 -1
- toil/utils/toilLaunchCluster.py +13 -2
- toil/utils/toilMain.py +3 -2
- toil/utils/toilRsyncCluster.py +1 -1
- toil/utils/toilSshCluster.py +1 -1
- toil/utils/toilStats.py +445 -305
- toil/utils/toilStatus.py +2 -5
- toil/version.py +10 -10
- toil/wdl/utils.py +2 -122
- toil/wdl/wdltoil.py +1257 -492
- toil/worker.py +55 -46
- toil-6.1.0.dist-info/METADATA +124 -0
- toil-6.1.0.dist-info/RECORD +241 -0
- {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/WHEEL +1 -1
- {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/entry_points.txt +0 -1
- toil/batchSystems/parasol.py +0 -379
- toil/batchSystems/tes.py +0 -459
- toil/test/batchSystems/parasolTestSupport.py +0 -117
- toil/test/wdl/builtinTest.py +0 -506
- toil/test/wdl/toilwdlTest.py +0 -522
- toil/wdl/toilwdl.py +0 -141
- toil/wdl/versions/dev.py +0 -107
- toil/wdl/versions/draft2.py +0 -980
- toil/wdl/versions/v1.py +0 -794
- toil/wdl/wdl_analysis.py +0 -116
- toil/wdl/wdl_functions.py +0 -997
- toil/wdl/wdl_synthesis.py +0 -1011
- toil/wdl/wdl_types.py +0 -243
- toil-5.12.0.dist-info/METADATA +0 -118
- toil-5.12.0.dist-info/RECORD +0 -244
- /toil/{wdl/versions → options}/__init__.py +0 -0
- {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/LICENSE +0 -0
- {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/top_level.txt +0 -0
toil/jobStores/fileJobStore.py
CHANGED
|
@@ -20,11 +20,10 @@ import re
|
|
|
20
20
|
import shutil
|
|
21
21
|
import stat
|
|
22
22
|
import sys
|
|
23
|
-
import tempfile
|
|
24
23
|
import time
|
|
25
24
|
import uuid
|
|
26
25
|
from contextlib import contextmanager
|
|
27
|
-
from typing import IO, Iterator, List, Optional, Union, overload
|
|
26
|
+
from typing import IO, Iterable, Iterator, List, Optional, Union, overload
|
|
28
27
|
from urllib.parse import ParseResult, quote, unquote
|
|
29
28
|
|
|
30
29
|
if sys.version_info >= (3, 8):
|
|
@@ -42,6 +41,7 @@ from toil.jobStores.abstractJobStore import (AbstractJobStore,
|
|
|
42
41
|
from toil.lib.io import (AtomicFileCreate,
|
|
43
42
|
atomic_copy,
|
|
44
43
|
atomic_copyobj,
|
|
44
|
+
mkdtemp,
|
|
45
45
|
robust_rmtree)
|
|
46
46
|
|
|
47
47
|
logger = logging.getLogger(__name__)
|
|
@@ -121,8 +121,8 @@ class FileJobStore(AbstractJobStore):
|
|
|
121
121
|
os.makedirs(self.filesDir, exist_ok=True)
|
|
122
122
|
os.makedirs(self.jobFilesDir, exist_ok=True)
|
|
123
123
|
os.makedirs(self.sharedFilesDir, exist_ok=True)
|
|
124
|
-
self.linkImports = config.
|
|
125
|
-
self.moveExports = config.
|
|
124
|
+
self.linkImports = config.symlinkImports
|
|
125
|
+
self.moveExports = config.moveOutputs
|
|
126
126
|
super().initialize(config)
|
|
127
127
|
|
|
128
128
|
def resume(self):
|
|
@@ -147,8 +147,8 @@ class FileJobStore(AbstractJobStore):
|
|
|
147
147
|
|
|
148
148
|
# Make a unique temp directory under a directory for this job name,
|
|
149
149
|
# possibly sprayed across multiple levels of subdirectories.
|
|
150
|
-
absJobDir =
|
|
151
|
-
|
|
150
|
+
absJobDir = mkdtemp(prefix=self.JOB_DIR_PREFIX,
|
|
151
|
+
dir=self._get_arbitrary_jobs_dir_for_name(usefulFilename))
|
|
152
152
|
|
|
153
153
|
job_description.jobStoreID = self._get_job_id_from_dir(absJobDir)
|
|
154
154
|
|
|
@@ -252,18 +252,19 @@ class FileJobStore(AbstractJobStore):
|
|
|
252
252
|
|
|
253
253
|
job.pre_update_hook()
|
|
254
254
|
|
|
255
|
+
dest_filename = self._get_job_file_name(job.jobStoreID)
|
|
256
|
+
|
|
255
257
|
# The job is serialised to a file suffixed by ".new"
|
|
256
258
|
# We insist on creating the file; an existing .new file indicates
|
|
257
259
|
# multiple simultaneous attempts to update the job, which will lose
|
|
258
260
|
# updates.
|
|
259
261
|
# The file is then moved to its correct path.
|
|
260
|
-
# Atomicity guarantees use the fact the underlying file
|
|
262
|
+
# Atomicity guarantees use the fact the underlying file system's "move"
|
|
261
263
|
# function is atomic.
|
|
262
|
-
with open(
|
|
264
|
+
with open(dest_filename + ".new", 'xb') as f:
|
|
263
265
|
pickle.dump(job, f)
|
|
264
266
|
# This should be atomic for the file system
|
|
265
|
-
os.rename(
|
|
266
|
-
|
|
267
|
+
os.rename(dest_filename + ".new", dest_filename)
|
|
267
268
|
def delete_job(self, job_id):
|
|
268
269
|
# The jobStoreID is the relative path to the directory containing the job,
|
|
269
270
|
# removing this directory deletes the job.
|
|
@@ -316,13 +317,14 @@ class FileJobStore(AbstractJobStore):
|
|
|
316
317
|
# symlink argument says whether the caller can take symlinks or not
|
|
317
318
|
# ex: if false, it implies the workflow cannot work with symlinks and thus will hardlink imports
|
|
318
319
|
# default is true since symlinking everything is ideal
|
|
320
|
+
uri_path = unquote(uri.path)
|
|
319
321
|
if issubclass(otherCls, FileJobStore):
|
|
320
|
-
if os.path.isdir(
|
|
322
|
+
if os.path.isdir(uri_path):
|
|
321
323
|
# Don't allow directories (unless someone is racing us)
|
|
322
324
|
raise IsADirectoryError(f"URI {uri} points to a directory but a file was expected")
|
|
323
325
|
if shared_file_name is None:
|
|
324
|
-
executable = os.stat(
|
|
325
|
-
absPath = self._get_unique_file_path(
|
|
326
|
+
executable = os.stat(uri_path).st_mode & stat.S_IXUSR != 0
|
|
327
|
+
absPath = self._get_unique_file_path(uri_path) # use this to get a valid path to write to in job store
|
|
326
328
|
with self.optional_hard_copy(hardlink):
|
|
327
329
|
self._copy_or_link(uri, absPath, symlink=symlink)
|
|
328
330
|
# TODO: os.stat(absPath).st_size consistently gives values lower than
|
|
@@ -341,6 +343,9 @@ class FileJobStore(AbstractJobStore):
|
|
|
341
343
|
if issubclass(otherCls, FileJobStore):
|
|
342
344
|
srcPath = self._get_file_path_from_id(file_id)
|
|
343
345
|
destPath = self._extract_path_from_url(uri)
|
|
346
|
+
# Make sure we don't need to worry about directories when exporting
|
|
347
|
+
# to local files, just like for cloud storage.
|
|
348
|
+
os.makedirs(os.path.dirname(destPath), exist_ok=True)
|
|
344
349
|
executable = getattr(file_id, 'executable', False)
|
|
345
350
|
if self.moveExports:
|
|
346
351
|
self._move_and_linkback(srcPath, destPath, executable=executable)
|
|
@@ -357,7 +362,11 @@ class FileJobStore(AbstractJobStore):
|
|
|
357
362
|
os.chmod(destPath, os.stat(destPath).st_mode | stat.S_IXUSR)
|
|
358
363
|
|
|
359
364
|
@classmethod
|
|
360
|
-
def
|
|
365
|
+
def _url_exists(cls, url: ParseResult) -> bool:
|
|
366
|
+
return os.path.exists(cls._extract_path_from_url(url))
|
|
367
|
+
|
|
368
|
+
@classmethod
|
|
369
|
+
def _get_size(cls, url):
|
|
361
370
|
return os.stat(cls._extract_path_from_url(url)).st_size
|
|
362
371
|
|
|
363
372
|
@classmethod
|
|
@@ -371,12 +380,18 @@ class FileJobStore(AbstractJobStore):
|
|
|
371
380
|
"""
|
|
372
381
|
|
|
373
382
|
# we use a ~10Mb buffer to improve speed
|
|
374
|
-
with
|
|
383
|
+
with cls._open_url(url) as readable:
|
|
375
384
|
shutil.copyfileobj(readable, writable, length=cls.BUFFER_SIZE)
|
|
376
385
|
# Return the number of bytes we read when we reached EOF.
|
|
377
386
|
executable = os.stat(readable.name).st_mode & stat.S_IXUSR
|
|
378
387
|
return readable.tell(), executable
|
|
379
388
|
|
|
389
|
+
@classmethod
|
|
390
|
+
def _open_url(cls, url: ParseResult) -> IO[bytes]:
|
|
391
|
+
"""
|
|
392
|
+
Open a file URL as a binary stream.
|
|
393
|
+
"""
|
|
394
|
+
return open(cls._extract_path_from_url(url), 'rb')
|
|
380
395
|
|
|
381
396
|
@classmethod
|
|
382
397
|
def _write_to_url(cls, readable, url, executable=False):
|
|
@@ -484,7 +499,7 @@ class FileJobStore(AbstractJobStore):
|
|
|
484
499
|
|
|
485
500
|
atomic_copy(local_path, jobStoreFilePath)
|
|
486
501
|
|
|
487
|
-
def read_file(self, file_id, local_path, symlink=False):
|
|
502
|
+
def read_file(self, file_id: str, local_path: str, symlink: bool = False) -> None:
|
|
488
503
|
self._check_job_store_file_id(file_id)
|
|
489
504
|
jobStoreFilePath = self._get_file_path_from_id(file_id)
|
|
490
505
|
localDirPath = os.path.dirname(local_path)
|
|
@@ -701,6 +716,69 @@ class FileJobStore(AbstractJobStore):
|
|
|
701
716
|
else:
|
|
702
717
|
raise
|
|
703
718
|
|
|
719
|
+
def list_all_file_names(self, for_job: Optional[str] = None) -> Iterable[str]:
|
|
720
|
+
"""
|
|
721
|
+
Get all the file names (not file IDs) of files stored in the job store.
|
|
722
|
+
|
|
723
|
+
Used for debugging.
|
|
724
|
+
|
|
725
|
+
:param for_job: If set, restrict the list to files for a particular job.
|
|
726
|
+
"""
|
|
727
|
+
|
|
728
|
+
# TODO: Promote to AbstractJobStore.
|
|
729
|
+
# TODO: Include stats-and-logging files?
|
|
730
|
+
|
|
731
|
+
if for_job is not None:
|
|
732
|
+
# Run on one job
|
|
733
|
+
jobs = [for_job]
|
|
734
|
+
else:
|
|
735
|
+
# Run on all the jobs
|
|
736
|
+
jobs = []
|
|
737
|
+
# But not all the jobs that exist, we want all the jobs that have
|
|
738
|
+
# files. So look at the file directories which mirror the job
|
|
739
|
+
# directories' structure.
|
|
740
|
+
for job_kind_dir in self._list_dynamic_spray_dir(self.jobFilesDir):
|
|
741
|
+
# First we sprayed all the job kinds over a tree
|
|
742
|
+
for job_instance_dir in self._list_dynamic_spray_dir(job_kind_dir):
|
|
743
|
+
# Then we sprayed the job instances over a tree
|
|
744
|
+
# And based on those we get the job name
|
|
745
|
+
job_id = self._get_job_id_from_files_dir(job_instance_dir)
|
|
746
|
+
jobs.append(job_id)
|
|
747
|
+
|
|
748
|
+
for name in os.listdir(self.sharedFilesDir):
|
|
749
|
+
# Announce all the shared files
|
|
750
|
+
yield name
|
|
751
|
+
|
|
752
|
+
for file_dir_path in self._list_dynamic_spray_dir(self.filesDir):
|
|
753
|
+
# Run on all the no-job files
|
|
754
|
+
for dir_file in os.listdir(file_dir_path):
|
|
755
|
+
# There ought to be just one file in here.
|
|
756
|
+
yield dir_file
|
|
757
|
+
|
|
758
|
+
for job_store_id in jobs:
|
|
759
|
+
# Files from _get_job_files_dir
|
|
760
|
+
job_files_dir = os.path.join(self.jobFilesDir, job_store_id)
|
|
761
|
+
if os.path.exists(job_files_dir):
|
|
762
|
+
for file_dir in os.listdir(job_files_dir):
|
|
763
|
+
# Each file is in its own directory
|
|
764
|
+
if file_dir == "cleanup":
|
|
765
|
+
# Except the cleanup directory which we do later.
|
|
766
|
+
continue
|
|
767
|
+
file_dir_path = os.path.join(job_files_dir, file_dir)
|
|
768
|
+
for dir_file in os.listdir(file_dir_path):
|
|
769
|
+
# There ought to be just one file in here.
|
|
770
|
+
yield dir_file
|
|
771
|
+
|
|
772
|
+
# Files from _get_job_files_cleanup_dir
|
|
773
|
+
job_cleanup_files_dir = os.path.join(job_files_dir, "cleanup")
|
|
774
|
+
if os.path.exists(job_cleanup_files_dir):
|
|
775
|
+
for file_dir in os.listdir(job_cleanup_files_dir):
|
|
776
|
+
# Each file is in its own directory
|
|
777
|
+
file_dir_path = os.path.join(job_cleanup_files_dir, file_dir)
|
|
778
|
+
for dir_file in os.listdir(file_dir_path):
|
|
779
|
+
# There ought to be just one file in here.
|
|
780
|
+
yield dir_file
|
|
781
|
+
|
|
704
782
|
def write_logs(self, msg):
|
|
705
783
|
# Temporary files are placed in the stats directory tree
|
|
706
784
|
tempStatsFileName = "stats" + str(uuid.uuid4().hex) + ".new"
|
|
@@ -748,6 +826,13 @@ class FileJobStore(AbstractJobStore):
|
|
|
748
826
|
"""
|
|
749
827
|
return absPath[len(self.jobsDir)+1:]
|
|
750
828
|
|
|
829
|
+
def _get_job_id_from_files_dir(self, absPath: str) -> str:
|
|
830
|
+
"""
|
|
831
|
+
:param str absPath: The absolute path to a job directory under self.jobFilesDir which holds a job's files.
|
|
832
|
+
:rtype : string, string is the job ID
|
|
833
|
+
"""
|
|
834
|
+
return absPath[len(self.jobFilesDir)+1:]
|
|
835
|
+
|
|
751
836
|
def _get_job_file_name(self, jobStoreID):
|
|
752
837
|
"""
|
|
753
838
|
Return the path to the file containing the serialised JobDescription instance for the given
|
|
@@ -815,7 +900,7 @@ class FileJobStore(AbstractJobStore):
|
|
|
815
900
|
"""
|
|
816
901
|
|
|
817
902
|
# We just make the file IDs paths under the job store overall.
|
|
818
|
-
absPath = os.path.join(self.jobStoreDir, jobStoreFileID)
|
|
903
|
+
absPath = os.path.join(self.jobStoreDir, unquote(jobStoreFileID))
|
|
819
904
|
|
|
820
905
|
# Don't validate here, we are called by the validation logic
|
|
821
906
|
|
|
@@ -828,7 +913,7 @@ class FileJobStore(AbstractJobStore):
|
|
|
828
913
|
:rtype : string, string is the file ID.
|
|
829
914
|
"""
|
|
830
915
|
|
|
831
|
-
return absPath[len(self.jobStoreDir)+1:]
|
|
916
|
+
return quote(absPath[len(self.jobStoreDir)+1:])
|
|
832
917
|
|
|
833
918
|
def _check_job_store_file_id(self, jobStoreFileID):
|
|
834
919
|
"""
|
|
@@ -966,6 +1051,19 @@ class FileJobStore(AbstractJobStore):
|
|
|
966
1051
|
# Recurse
|
|
967
1052
|
yield from self._walk_dynamic_spray_dir(childPath)
|
|
968
1053
|
|
|
1054
|
+
def _list_dynamic_spray_dir(self, root):
|
|
1055
|
+
"""
|
|
1056
|
+
For a directory tree filled in by _getDynamicSprayDir, yields each
|
|
1057
|
+
highest-level file or or directory *not* created by _getDynamicSprayDir
|
|
1058
|
+
(i.e. the actual contents).
|
|
1059
|
+
"""
|
|
1060
|
+
|
|
1061
|
+
for spray_dir in self._walk_dynamic_spray_dir(root):
|
|
1062
|
+
for child in os.listdir(spray_dir):
|
|
1063
|
+
if child not in self.validDirsSet:
|
|
1064
|
+
# This is a real content item we are storing
|
|
1065
|
+
yield os.path.join(spray_dir, child)
|
|
1066
|
+
|
|
969
1067
|
def _job_directories(self):
|
|
970
1068
|
"""
|
|
971
1069
|
:rtype : an iterator to the temporary directories containing job
|
toil/jobStores/googleJobStore.py
CHANGED
|
@@ -20,23 +20,20 @@ import uuid
|
|
|
20
20
|
from contextlib import contextmanager
|
|
21
21
|
from functools import wraps
|
|
22
22
|
from io import BytesIO
|
|
23
|
-
from typing import List, Optional
|
|
23
|
+
from typing import IO, List, Optional
|
|
24
24
|
from urllib.parse import ParseResult
|
|
25
25
|
|
|
26
26
|
from google.api_core.exceptions import (GoogleAPICallError,
|
|
27
27
|
InternalServerError,
|
|
28
28
|
ServiceUnavailable)
|
|
29
|
-
from google.cloud import exceptions, storage
|
|
30
29
|
from google.auth.exceptions import DefaultCredentialsError
|
|
30
|
+
from google.cloud import exceptions, storage
|
|
31
31
|
|
|
32
32
|
from toil.jobStores.abstractJobStore import (AbstractJobStore,
|
|
33
33
|
JobStoreExistsException,
|
|
34
34
|
NoSuchFileException,
|
|
35
35
|
NoSuchJobException,
|
|
36
36
|
NoSuchJobStoreException)
|
|
37
|
-
|
|
38
|
-
from toil.fileStores import FileID
|
|
39
|
-
|
|
40
37
|
from toil.jobStores.utils import ReadablePipe, WritablePipe
|
|
41
38
|
from toil.lib.compatibility import compat_bytes
|
|
42
39
|
from toil.lib.io import AtomicFileCreate
|
|
@@ -146,7 +143,6 @@ class GoogleJobStore(AbstractJobStore):
|
|
|
146
143
|
# Probably we don't have permission to use the file.
|
|
147
144
|
log.warning("File '%s' exists but didn't work to authenticate!",
|
|
148
145
|
cls.nodeServiceAccountJson)
|
|
149
|
-
pass
|
|
150
146
|
|
|
151
147
|
# Either a filename is specified, or our fallback file isn't there.
|
|
152
148
|
try:
|
|
@@ -394,7 +390,15 @@ class GoogleJobStore(AbstractJobStore):
|
|
|
394
390
|
return blob
|
|
395
391
|
|
|
396
392
|
@classmethod
|
|
397
|
-
def
|
|
393
|
+
def _url_exists(cls, url: ParseResult) -> bool:
|
|
394
|
+
try:
|
|
395
|
+
cls._get_blob_from_url(url, exists=True)
|
|
396
|
+
return True
|
|
397
|
+
except NoSuchFileException:
|
|
398
|
+
return False
|
|
399
|
+
|
|
400
|
+
@classmethod
|
|
401
|
+
def _get_size(cls, url):
|
|
398
402
|
return cls._get_blob_from_url(url, exists=True).size
|
|
399
403
|
|
|
400
404
|
@classmethod
|
|
@@ -403,6 +407,11 @@ class GoogleJobStore(AbstractJobStore):
|
|
|
403
407
|
blob.download_to_file(writable)
|
|
404
408
|
return blob.size, False
|
|
405
409
|
|
|
410
|
+
@classmethod
|
|
411
|
+
def _open_url(cls, url: ParseResult) -> IO[bytes]:
|
|
412
|
+
blob = cls._get_blob_from_url(url, exists=True)
|
|
413
|
+
return blob.open("rb")
|
|
414
|
+
|
|
406
415
|
@classmethod
|
|
407
416
|
def _supports_url(cls, url, export=False):
|
|
408
417
|
return url.scheme.lower() == 'gs'
|
toil/jobStores/utils.py
CHANGED
|
@@ -79,7 +79,7 @@ class WritablePipe(ABC):
|
|
|
79
79
|
binary and text mode output.
|
|
80
80
|
|
|
81
81
|
:param file readable: the file object representing the readable end of the pipe. Do not
|
|
82
|
-
|
|
82
|
+
explicitly invoke the close() method of the object, that will be done automatically.
|
|
83
83
|
"""
|
|
84
84
|
raise NotImplementedError()
|
|
85
85
|
|
|
@@ -211,7 +211,7 @@ class ReadablePipe(ABC):
|
|
|
211
211
|
binary and text mode input.
|
|
212
212
|
|
|
213
213
|
:param file writable: the file object representing the writable end of the pipe. Do not
|
|
214
|
-
|
|
214
|
+
explicitly invoke the close() method of the object, that will be done automatically.
|
|
215
215
|
"""
|
|
216
216
|
raise NotImplementedError()
|
|
217
217
|
|
|
@@ -316,7 +316,7 @@ class ReadableTransformingPipe(ReadablePipe):
|
|
|
316
316
|
:param file readable: the input stream file object to transform.
|
|
317
317
|
|
|
318
318
|
:param file writable: the file object representing the writable end of the pipe. Do not
|
|
319
|
-
|
|
319
|
+
explicitly invoke the close() method of the object, that will be done automatically.
|
|
320
320
|
"""
|
|
321
321
|
raise NotImplementedError()
|
|
322
322
|
|
|
@@ -327,7 +327,6 @@ class JobStoreUnavailableException(RuntimeError):
|
|
|
327
327
|
"""
|
|
328
328
|
Raised when a particular type of job store is requested but can't be used.
|
|
329
329
|
"""
|
|
330
|
-
pass
|
|
331
330
|
|
|
332
331
|
def generate_locator(
|
|
333
332
|
job_store_type: str,
|
|
@@ -340,9 +339,9 @@ def generate_locator(
|
|
|
340
339
|
|
|
341
340
|
:param job_store_type: Registry name of the job store to use.
|
|
342
341
|
:param local_suggestion: Path to a nonexistent local directory suitable for
|
|
343
|
-
|
|
342
|
+
use as a file job store.
|
|
344
343
|
:param decoration: Extra string to add to the job store locator, if
|
|
345
|
-
|
|
344
|
+
convenient.
|
|
346
345
|
|
|
347
346
|
:return str: Job store locator for a usable job store.
|
|
348
347
|
"""
|