toil 9.0.0__py3-none-any.whl → 9.1.1__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 (71) hide show
  1. toil/batchSystems/abstractBatchSystem.py +13 -5
  2. toil/batchSystems/abstractGridEngineBatchSystem.py +17 -5
  3. toil/batchSystems/kubernetes.py +13 -2
  4. toil/batchSystems/mesos/batchSystem.py +33 -2
  5. toil/batchSystems/slurm.py +191 -16
  6. toil/cwl/cwltoil.py +17 -82
  7. toil/fileStores/__init__.py +1 -1
  8. toil/fileStores/abstractFileStore.py +5 -2
  9. toil/fileStores/cachingFileStore.py +1 -1
  10. toil/job.py +30 -14
  11. toil/jobStores/abstractJobStore.py +24 -19
  12. toil/jobStores/aws/jobStore.py +862 -1963
  13. toil/jobStores/aws/utils.py +24 -270
  14. toil/jobStores/googleJobStore.py +25 -9
  15. toil/jobStores/utils.py +0 -327
  16. toil/leader.py +27 -22
  17. toil/lib/aws/config.py +22 -0
  18. toil/lib/aws/s3.py +477 -9
  19. toil/lib/aws/utils.py +22 -33
  20. toil/lib/checksum.py +88 -0
  21. toil/lib/conversions.py +33 -31
  22. toil/lib/directory.py +217 -0
  23. toil/lib/ec2.py +97 -29
  24. toil/lib/exceptions.py +2 -1
  25. toil/lib/expando.py +2 -2
  26. toil/lib/generatedEC2Lists.py +73 -16
  27. toil/lib/io.py +33 -2
  28. toil/lib/memoize.py +21 -7
  29. toil/lib/pipes.py +385 -0
  30. toil/lib/retry.py +1 -1
  31. toil/lib/threading.py +1 -1
  32. toil/lib/web.py +4 -5
  33. toil/provisioners/__init__.py +5 -2
  34. toil/provisioners/aws/__init__.py +43 -36
  35. toil/provisioners/aws/awsProvisioner.py +22 -13
  36. toil/provisioners/node.py +60 -12
  37. toil/resource.py +3 -13
  38. toil/test/__init__.py +14 -16
  39. toil/test/batchSystems/test_slurm.py +103 -14
  40. toil/test/cwl/staging_cat.cwl +27 -0
  41. toil/test/cwl/staging_make_file.cwl +25 -0
  42. toil/test/cwl/staging_workflow.cwl +43 -0
  43. toil/test/cwl/zero_default.cwl +61 -0
  44. toil/test/docs/scripts/tutorial_staging.py +17 -8
  45. toil/test/jobStores/jobStoreTest.py +23 -133
  46. toil/test/lib/aws/test_iam.py +7 -7
  47. toil/test/lib/aws/test_s3.py +30 -33
  48. toil/test/lib/aws/test_utils.py +9 -9
  49. toil/test/provisioners/aws/awsProvisionerTest.py +59 -6
  50. toil/test/src/autoDeploymentTest.py +2 -3
  51. toil/test/src/fileStoreTest.py +89 -87
  52. toil/test/utils/ABCWorkflowDebug/ABC.txt +1 -0
  53. toil/test/utils/ABCWorkflowDebug/debugWorkflow.py +4 -4
  54. toil/test/utils/toilKillTest.py +35 -28
  55. toil/test/wdl/md5sum/md5sum.json +1 -1
  56. toil/test/wdl/testfiles/gather.wdl +52 -0
  57. toil/test/wdl/wdltoil_test.py +120 -38
  58. toil/test/wdl/wdltoil_test_kubernetes.py +9 -0
  59. toil/utils/toilDebugFile.py +6 -3
  60. toil/utils/toilStats.py +17 -2
  61. toil/version.py +6 -6
  62. toil/wdl/wdltoil.py +1038 -549
  63. toil/worker.py +5 -2
  64. {toil-9.0.0.dist-info → toil-9.1.1.dist-info}/METADATA +12 -12
  65. {toil-9.0.0.dist-info → toil-9.1.1.dist-info}/RECORD +69 -61
  66. toil/lib/iterables.py +0 -112
  67. toil/test/docs/scripts/stagingExampleFiles/in.txt +0 -1
  68. {toil-9.0.0.dist-info → toil-9.1.1.dist-info}/WHEEL +0 -0
  69. {toil-9.0.0.dist-info → toil-9.1.1.dist-info}/entry_points.txt +0 -0
  70. {toil-9.0.0.dist-info → toil-9.1.1.dist-info}/licenses/LICENSE +0 -0
  71. {toil-9.0.0.dist-info → toil-9.1.1.dist-info}/top_level.txt +0 -0
@@ -671,13 +671,16 @@ class AbstractFileStore(ABC):
671
671
  Send a logging message to the leader. The message will also be \
672
672
  logged by the worker at the same level.
673
673
 
674
+ Does not depend on the commit system, so this is safe to use during an
675
+ ansynchronous commit, or without a commit afterward.
676
+
674
677
  :param text: The string to log.
675
678
  :param level: The logging level.
676
679
  """
677
- logger.log(level=level, msg=("LOG-TO-MASTER: " + text))
680
+ logger.log(level=level, msg=("LOG-TO-LEADER: " + text))
678
681
  self.logging_messages.append(dict(text=text, level=level))
679
682
 
680
- @deprecated(new_function_name="export_file")
683
+ @deprecated(new_function_name="log_to_leader")
681
684
  def logToMaster(self, text: str, level: int = logging.INFO) -> None:
682
685
  self.log_to_leader(text, level)
683
686
 
@@ -1207,7 +1207,7 @@ class CachingFileStore(AbstractFileStore):
1207
1207
  # its temp dir and database entry.
1208
1208
  self._deallocateSpaceForJob()
1209
1209
 
1210
- def writeGlobalFile(self, localFileName, cleanup=False, executable=False):
1210
+ def writeGlobalFile(self, localFileName, cleanup=False):
1211
1211
  """
1212
1212
  Creates a file in the jobstore and returns a FileID reference.
1213
1213
  """
toil/job.py CHANGED
@@ -236,16 +236,16 @@ def parse_accelerator(
236
236
  {'count': 1, 'kind': 'gpu'}
237
237
 
238
238
  >>> parse_accelerator("nvidia-tesla-k80")
239
- {'count': 1, 'kind': 'gpu', 'brand': 'nvidia', 'model': 'nvidia-tesla-k80'}
239
+ {'count': 1, 'kind': 'gpu', 'model': 'nvidia-tesla-k80', 'brand': 'nvidia'}
240
240
 
241
241
  >>> parse_accelerator("nvidia-tesla-k80:2")
242
- {'count': 2, 'kind': 'gpu', 'brand': 'nvidia', 'model': 'nvidia-tesla-k80'}
242
+ {'count': 2, 'kind': 'gpu', 'model': 'nvidia-tesla-k80', 'brand': 'nvidia'}
243
243
 
244
244
  >>> parse_accelerator("gpu")
245
245
  {'count': 1, 'kind': 'gpu'}
246
246
 
247
247
  >>> parse_accelerator("cuda:1")
248
- {'count': 1, 'kind': 'gpu', 'brand': 'nvidia', 'api': 'cuda'}
248
+ {'count': 1, 'kind': 'gpu', 'api': 'cuda', 'brand': 'nvidia'}
249
249
 
250
250
  >>> parse_accelerator({"kind": "gpu"})
251
251
  {'count': 1, 'kind': 'gpu'}
@@ -581,8 +581,8 @@ class Requirer:
581
581
  >>> Requirer._parseResource('cores', 1), Requirer._parseResource('disk', 1), \
582
582
  Requirer._parseResource('memory', 1)
583
583
  (1, 1, 1)
584
- >>> Requirer._parseResource('cores', '1G'), Requirer._parseResource('disk', '1G'), \
585
- Requirer._parseResource('memory', '1G')
584
+ >>> Requirer._parseResource('cores', '1Gi'), Requirer._parseResource('disk', '1Gi'), \
585
+ Requirer._parseResource('memory', '1Gi')
586
586
  (1073741824, 1073741824, 1073741824)
587
587
  >>> Requirer._parseResource('cores', 1.1)
588
588
  1.1
@@ -813,7 +813,6 @@ class JobDescription(Requirer):
813
813
  Subclassed into variants for checkpoint jobs and service jobs that have
814
814
  their specific parameters.
815
815
  """
816
-
817
816
  def __init__(
818
817
  self,
819
818
  requirements: Mapping[str, Union[int, str, float, bool, list]],
@@ -3146,9 +3145,8 @@ class Job:
3146
3145
 
3147
3146
  Will modify the job's description with changes that need to be committed back to the JobStore.
3148
3147
  """
3149
- if stats is not None:
3150
- startTime = time.time()
3151
- startClock = ResourceMonitor.get_total_cpu_time()
3148
+ startTime = time.time()
3149
+ startClock = ResourceMonitor.get_total_cpu_time()
3152
3150
  baseDir = os.getcwd()
3153
3151
 
3154
3152
  succeeded = False
@@ -3180,18 +3178,36 @@ class Job:
3180
3178
  # Change dir back to cwd dir, if changed by job (this is a safety issue)
3181
3179
  if os.getcwd() != baseDir:
3182
3180
  os.chdir(baseDir)
3181
+
3182
+ totalCpuTime, total_memory_kib = (
3183
+ ResourceMonitor.get_total_cpu_time_and_memory_usage()
3184
+ )
3185
+ job_time = time.time() - startTime
3186
+ job_cpu_time = totalCpuTime - startClock
3187
+ allocated_cpu_time = job_time * self.cores
3188
+
3189
+ if job_cpu_time > allocated_cpu_time and allocated_cpu_time > 0:
3190
+ # Too much CPU was used by this job! Maybe we're using a batch
3191
+ # system that doesn't/can't sandbox us and we started too many
3192
+ # threads. Complain to the user!
3193
+ excess_factor = job_cpu_time / allocated_cpu_time
3194
+ fileStore.log_to_leader(
3195
+ f"Job {self.description} used {excess_factor:.2f}x more "
3196
+ f"CPU than the requested {self.cores} cores. Consider "
3197
+ f"increasing the job's required CPU cores or limiting the "
3198
+ f"number of processes/threads launched.",
3199
+ level=logging.WARNING
3200
+ )
3201
+
3183
3202
  # Finish up the stats
3184
3203
  if stats is not None:
3185
- totalCpuTime, total_memory_kib = (
3186
- ResourceMonitor.get_total_cpu_time_and_memory_usage()
3187
- )
3188
3204
  stats.jobs.append(
3189
3205
  # TODO: We represent everything as strings in the stats
3190
3206
  # even though the JSON transport can take bools and floats.
3191
3207
  Expando(
3192
3208
  start=str(startTime),
3193
- time=str(time.time() - startTime),
3194
- clock=str(totalCpuTime - startClock),
3209
+ time=str(job_time),
3210
+ clock=str(job_cpu_time),
3195
3211
  class_name=self._jobName(),
3196
3212
  memory=str(total_memory_kib),
3197
3213
  requested_cores=str(self.cores), # TODO: Isn't this really consumed cores?
@@ -90,7 +90,7 @@ class InvalidImportExportUrlException(Exception):
90
90
  class NoSuchJobException(Exception):
91
91
  """Indicates that the specified job does not exist."""
92
92
 
93
- def __init__(self, jobStoreID: FileID):
93
+ def __init__(self, jobStoreID: Union[FileID, str]):
94
94
  """
95
95
  :param str jobStoreID: the jobStoreID that was mistakenly assumed to exist
96
96
  """
@@ -100,7 +100,7 @@ class NoSuchJobException(Exception):
100
100
  class ConcurrentFileModificationException(Exception):
101
101
  """Indicates that the file was attempted to be modified by multiple processes at once."""
102
102
 
103
- def __init__(self, jobStoreFileID: FileID):
103
+ def __init__(self, jobStoreFileID: Union[FileID, str]):
104
104
  """
105
105
  :param jobStoreFileID: the ID of the file that was modified by multiple workers
106
106
  or processes concurrently
@@ -112,7 +112,7 @@ class NoSuchFileException(Exception):
112
112
  """Indicates that the specified file does not exist."""
113
113
 
114
114
  def __init__(
115
- self, jobStoreFileID: FileID, customName: Optional[str] = None, *extra: Any
115
+ self, jobStoreFileID: Union[FileID, str], customName: Optional[str] = None, *extra: Any
116
116
  ):
117
117
  """
118
118
  :param jobStoreFileID: the ID of the file that was mistakenly assumed to exist
@@ -140,11 +140,12 @@ class NoSuchJobStoreException(LocatorException):
140
140
  def __init__(self, locator: str, prefix: str):
141
141
  """
142
142
  :param str locator: The location of the job store
143
+ :param str prefix: The type of job store
143
144
  """
144
145
  super().__init__(
145
146
  "The job store '%s' does not exist, so there is nothing to restart.",
146
147
  locator,
147
- prefix,
148
+ prefix
148
149
  )
149
150
 
150
151
 
@@ -159,7 +160,7 @@ class JobStoreExistsException(LocatorException):
159
160
  "The job store '%s' already exists. Use --restart to resume the workflow, or remove "
160
161
  "the job store with 'toil clean' to start the workflow from scratch.",
161
162
  locator,
162
- prefix,
163
+ prefix
163
164
  )
164
165
 
165
166
 
@@ -240,7 +241,12 @@ class AbstractJobStore(ABC):
240
241
 
241
242
  @property
242
243
  def config(self) -> Config:
243
- """Return the Toil configuration associated with this job store."""
244
+ """
245
+ Return the Toil configuration associated with this job store.
246
+
247
+ :raises AttributeError: if the config has not yet been assigned (i.e.
248
+ during :meth:`resume`).
249
+ """
244
250
  return self.__config
245
251
 
246
252
  @property
@@ -561,7 +567,6 @@ class AbstractJobStore(ABC):
561
567
  executable = jobStoreFileID.executable
562
568
  otherCls._write_to_url(readable, url, executable)
563
569
 
564
-
565
570
  @abstractmethod
566
571
  def destroy(self) -> None:
567
572
  """
@@ -930,15 +935,6 @@ class AbstractJobStore(ABC):
930
935
  """
931
936
  raise NotImplementedError()
932
937
 
933
- @contextmanager
934
- def batch(self) -> Iterator[None]:
935
- """
936
- If supported by the batch system, calls to create() with this context
937
- manager active will be performed in a batch after the context manager
938
- is released.
939
- """
940
- yield
941
-
942
938
  @deprecated(new_function_name="create_job")
943
939
  def create(self, jobDescription: JobDescription) -> JobDescription:
944
940
  return self.create_job(jobDescription)
@@ -1036,6 +1032,15 @@ class AbstractJobStore(ABC):
1036
1032
  def update(self, jobDescription: JobDescription) -> None:
1037
1033
  return self.update_job(jobDescription)
1038
1034
 
1035
+ @contextmanager
1036
+ def batch(self) -> Iterator[None]:
1037
+ """
1038
+ If supported by the batch system, calls to create() with this context
1039
+ manager active will be performed in a batch after the context manager
1040
+ is released.
1041
+ """
1042
+ yield
1043
+
1039
1044
  @abstractmethod
1040
1045
  def update_job(self, job_description: JobDescription) -> None:
1041
1046
  """
@@ -1206,14 +1211,14 @@ class AbstractJobStore(ABC):
1206
1211
  Creates an empty file in the job store and returns its ID.
1207
1212
  Call to fileExists(getEmptyFileStoreID(jobStoreID)) will return True.
1208
1213
 
1209
- :param str job_id: the id of a job, or None. If specified, the may be associated
1214
+ :param job_id: the id of a job, or None. If specified, the may be associated
1210
1215
  with that job in a job-store-specific way. This may influence the returned ID.
1211
1216
 
1212
- :param bool cleanup: Whether to attempt to delete the file when the job
1217
+ :param cleanup: Whether to attempt to delete the file when the job
1213
1218
  whose jobStoreID was given as jobStoreID is deleted with
1214
1219
  jobStore.delete(job). If jobStoreID was not given, does nothing.
1215
1220
 
1216
- :param str basename: If supported by the implementation, use the given
1221
+ :param basename: If supported by the implementation, use the given
1217
1222
  file basename so that when searching the job store with a query
1218
1223
  matching that basename, the file will be detected.
1219
1224