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.
Files changed (164) hide show
  1. toil/__init__.py +18 -13
  2. toil/batchSystems/abstractBatchSystem.py +39 -13
  3. toil/batchSystems/abstractGridEngineBatchSystem.py +24 -24
  4. toil/batchSystems/awsBatch.py +14 -14
  5. toil/batchSystems/cleanup_support.py +7 -3
  6. toil/batchSystems/contained_executor.py +3 -3
  7. toil/batchSystems/htcondor.py +0 -1
  8. toil/batchSystems/kubernetes.py +34 -31
  9. toil/batchSystems/local_support.py +3 -1
  10. toil/batchSystems/lsf.py +7 -7
  11. toil/batchSystems/mesos/batchSystem.py +7 -7
  12. toil/batchSystems/options.py +32 -83
  13. toil/batchSystems/registry.py +104 -23
  14. toil/batchSystems/singleMachine.py +16 -13
  15. toil/batchSystems/slurm.py +87 -16
  16. toil/batchSystems/torque.py +0 -1
  17. toil/bus.py +44 -8
  18. toil/common.py +544 -753
  19. toil/cwl/__init__.py +28 -32
  20. toil/cwl/cwltoil.py +595 -574
  21. toil/cwl/utils.py +55 -10
  22. toil/exceptions.py +1 -1
  23. toil/fileStores/__init__.py +2 -2
  24. toil/fileStores/abstractFileStore.py +88 -14
  25. toil/fileStores/cachingFileStore.py +610 -549
  26. toil/fileStores/nonCachingFileStore.py +46 -22
  27. toil/job.py +182 -101
  28. toil/jobStores/abstractJobStore.py +161 -95
  29. toil/jobStores/aws/jobStore.py +23 -9
  30. toil/jobStores/aws/utils.py +6 -6
  31. toil/jobStores/fileJobStore.py +116 -18
  32. toil/jobStores/googleJobStore.py +16 -7
  33. toil/jobStores/utils.py +5 -6
  34. toil/leader.py +87 -56
  35. toil/lib/accelerators.py +10 -5
  36. toil/lib/aws/__init__.py +3 -14
  37. toil/lib/aws/ami.py +22 -9
  38. toil/lib/aws/iam.py +21 -13
  39. toil/lib/aws/session.py +2 -16
  40. toil/lib/aws/utils.py +4 -5
  41. toil/lib/compatibility.py +1 -1
  42. toil/lib/conversions.py +26 -3
  43. toil/lib/docker.py +22 -23
  44. toil/lib/ec2.py +10 -6
  45. toil/lib/ec2nodes.py +106 -100
  46. toil/lib/encryption/_nacl.py +2 -1
  47. toil/lib/generatedEC2Lists.py +325 -18
  48. toil/lib/io.py +49 -2
  49. toil/lib/misc.py +1 -1
  50. toil/lib/resources.py +9 -2
  51. toil/lib/threading.py +101 -38
  52. toil/options/common.py +736 -0
  53. toil/options/cwl.py +336 -0
  54. toil/options/wdl.py +37 -0
  55. toil/provisioners/abstractProvisioner.py +9 -4
  56. toil/provisioners/aws/__init__.py +3 -6
  57. toil/provisioners/aws/awsProvisioner.py +6 -0
  58. toil/provisioners/clusterScaler.py +3 -2
  59. toil/provisioners/gceProvisioner.py +2 -2
  60. toil/realtimeLogger.py +2 -1
  61. toil/resource.py +24 -18
  62. toil/server/app.py +2 -3
  63. toil/server/cli/wes_cwl_runner.py +4 -4
  64. toil/server/utils.py +1 -1
  65. toil/server/wes/abstract_backend.py +3 -2
  66. toil/server/wes/amazon_wes_utils.py +5 -4
  67. toil/server/wes/tasks.py +2 -3
  68. toil/server/wes/toil_backend.py +2 -10
  69. toil/server/wsgi_app.py +2 -0
  70. toil/serviceManager.py +12 -10
  71. toil/statsAndLogging.py +41 -9
  72. toil/test/__init__.py +29 -54
  73. toil/test/batchSystems/batchSystemTest.py +11 -111
  74. toil/test/batchSystems/test_slurm.py +24 -8
  75. toil/test/cactus/__init__.py +0 -0
  76. toil/test/cactus/test_cactus_integration.py +58 -0
  77. toil/test/cwl/cwlTest.py +438 -223
  78. toil/test/cwl/glob_dir.cwl +15 -0
  79. toil/test/cwl/preemptible.cwl +21 -0
  80. toil/test/cwl/preemptible_expression.cwl +28 -0
  81. toil/test/cwl/revsort.cwl +1 -1
  82. toil/test/cwl/revsort2.cwl +1 -1
  83. toil/test/docs/scriptsTest.py +2 -3
  84. toil/test/jobStores/jobStoreTest.py +34 -21
  85. toil/test/lib/aws/test_iam.py +4 -14
  86. toil/test/lib/aws/test_utils.py +0 -3
  87. toil/test/lib/dockerTest.py +4 -4
  88. toil/test/lib/test_ec2.py +12 -17
  89. toil/test/mesos/helloWorld.py +4 -5
  90. toil/test/mesos/stress.py +1 -1
  91. toil/test/{wdl/conftest.py → options/__init__.py} +0 -10
  92. toil/test/options/options.py +37 -0
  93. toil/test/provisioners/aws/awsProvisionerTest.py +9 -5
  94. toil/test/provisioners/clusterScalerTest.py +6 -4
  95. toil/test/provisioners/clusterTest.py +23 -11
  96. toil/test/provisioners/gceProvisionerTest.py +0 -6
  97. toil/test/provisioners/restartScript.py +3 -2
  98. toil/test/server/serverTest.py +1 -1
  99. toil/test/sort/restart_sort.py +2 -1
  100. toil/test/sort/sort.py +2 -1
  101. toil/test/sort/sortTest.py +2 -13
  102. toil/test/src/autoDeploymentTest.py +45 -45
  103. toil/test/src/busTest.py +5 -5
  104. toil/test/src/checkpointTest.py +2 -2
  105. toil/test/src/deferredFunctionTest.py +1 -1
  106. toil/test/src/fileStoreTest.py +32 -16
  107. toil/test/src/helloWorldTest.py +1 -1
  108. toil/test/src/importExportFileTest.py +1 -1
  109. toil/test/src/jobDescriptionTest.py +2 -1
  110. toil/test/src/jobServiceTest.py +1 -1
  111. toil/test/src/jobTest.py +18 -18
  112. toil/test/src/miscTests.py +5 -3
  113. toil/test/src/promisedRequirementTest.py +3 -3
  114. toil/test/src/realtimeLoggerTest.py +1 -1
  115. toil/test/src/resourceTest.py +2 -2
  116. toil/test/src/restartDAGTest.py +1 -1
  117. toil/test/src/resumabilityTest.py +36 -2
  118. toil/test/src/retainTempDirTest.py +1 -1
  119. toil/test/src/systemTest.py +2 -2
  120. toil/test/src/toilContextManagerTest.py +2 -2
  121. toil/test/src/userDefinedJobArgTypeTest.py +1 -1
  122. toil/test/utils/toilDebugTest.py +98 -32
  123. toil/test/utils/toilKillTest.py +2 -2
  124. toil/test/utils/utilsTest.py +23 -3
  125. toil/test/wdl/wdltoil_test.py +223 -45
  126. toil/toilState.py +7 -6
  127. toil/utils/toilClean.py +1 -1
  128. toil/utils/toilConfig.py +36 -0
  129. toil/utils/toilDebugFile.py +60 -33
  130. toil/utils/toilDebugJob.py +39 -12
  131. toil/utils/toilDestroyCluster.py +1 -1
  132. toil/utils/toilKill.py +1 -1
  133. toil/utils/toilLaunchCluster.py +13 -2
  134. toil/utils/toilMain.py +3 -2
  135. toil/utils/toilRsyncCluster.py +1 -1
  136. toil/utils/toilSshCluster.py +1 -1
  137. toil/utils/toilStats.py +445 -305
  138. toil/utils/toilStatus.py +2 -5
  139. toil/version.py +10 -10
  140. toil/wdl/utils.py +2 -122
  141. toil/wdl/wdltoil.py +1257 -492
  142. toil/worker.py +55 -46
  143. toil-6.1.0.dist-info/METADATA +124 -0
  144. toil-6.1.0.dist-info/RECORD +241 -0
  145. {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/WHEEL +1 -1
  146. {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/entry_points.txt +0 -1
  147. toil/batchSystems/parasol.py +0 -379
  148. toil/batchSystems/tes.py +0 -459
  149. toil/test/batchSystems/parasolTestSupport.py +0 -117
  150. toil/test/wdl/builtinTest.py +0 -506
  151. toil/test/wdl/toilwdlTest.py +0 -522
  152. toil/wdl/toilwdl.py +0 -141
  153. toil/wdl/versions/dev.py +0 -107
  154. toil/wdl/versions/draft2.py +0 -980
  155. toil/wdl/versions/v1.py +0 -794
  156. toil/wdl/wdl_analysis.py +0 -116
  157. toil/wdl/wdl_functions.py +0 -997
  158. toil/wdl/wdl_synthesis.py +0 -1011
  159. toil/wdl/wdl_types.py +0 -243
  160. toil-5.12.0.dist-info/METADATA +0 -118
  161. toil-5.12.0.dist-info/RECORD +0 -244
  162. /toil/{wdl/versions → options}/__init__.py +0 -0
  163. {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/LICENSE +0 -0
  164. {toil-5.12.0.dist-info → toil-6.1.0.dist-info}/top_level.txt +0 -0
@@ -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.linkImports
125
- self.moveExports = config.moveExports
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 = tempfile.mkdtemp(prefix=self.JOB_DIR_PREFIX,
151
- dir=self._get_arbitrary_jobs_dir_for_name(usefulFilename))
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 systems "move"
262
+ # Atomicity guarantees use the fact the underlying file system's "move"
261
263
  # function is atomic.
262
- with open(self._get_job_file_name(job.jobStoreID) + ".new", 'xb') as f:
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(self._get_job_file_name(job.jobStoreID) + ".new", self._get_job_file_name(job.jobStoreID))
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(uri.path):
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(uri.path).st_mode & stat.S_IXUSR != 0
325
- absPath = self._get_unique_file_path(uri.path) # use this to get a valid path to write to in job store
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 get_size(cls, url):
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 open(cls._extract_path_from_url(url), 'rb') as readable:
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
@@ -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 get_size(cls, url):
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
- explicitly invoke the close() method of the object, that will be done automatically.
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
- explicitly invoke the close() method of the object, that will be done automatically.
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
- explicitly invoke the close() method of the object, that will be done automatically.
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
- use as a file job store.
342
+ use as a file job store.
344
343
  :param decoration: Extra string to add to the job store locator, if
345
- convenient.
344
+ convenient.
346
345
 
347
346
  :return str: Job store locator for a usable job store.
348
347
  """