toil 9.1.1__py3-none-any.whl → 9.2.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 +5 -9
- toil/batchSystems/abstractBatchSystem.py +23 -22
- toil/batchSystems/abstractGridEngineBatchSystem.py +17 -12
- toil/batchSystems/awsBatch.py +8 -8
- toil/batchSystems/cleanup_support.py +4 -4
- toil/batchSystems/contained_executor.py +3 -3
- toil/batchSystems/gridengine.py +3 -4
- toil/batchSystems/htcondor.py +5 -5
- toil/batchSystems/kubernetes.py +65 -63
- toil/batchSystems/local_support.py +2 -3
- toil/batchSystems/lsf.py +6 -7
- toil/batchSystems/mesos/batchSystem.py +11 -7
- toil/batchSystems/mesos/test/__init__.py +1 -2
- toil/batchSystems/options.py +9 -10
- toil/batchSystems/registry.py +3 -7
- toil/batchSystems/singleMachine.py +8 -11
- toil/batchSystems/slurm.py +49 -38
- toil/batchSystems/torque.py +3 -4
- toil/bus.py +36 -34
- toil/common.py +129 -89
- toil/cwl/cwltoil.py +857 -729
- toil/cwl/utils.py +44 -35
- toil/fileStores/__init__.py +3 -1
- toil/fileStores/abstractFileStore.py +28 -30
- toil/fileStores/cachingFileStore.py +8 -8
- toil/fileStores/nonCachingFileStore.py +10 -21
- toil/job.py +159 -158
- toil/jobStores/abstractJobStore.py +68 -69
- toil/jobStores/aws/jobStore.py +249 -213
- toil/jobStores/aws/utils.py +13 -24
- toil/jobStores/fileJobStore.py +28 -22
- toil/jobStores/googleJobStore.py +21 -17
- toil/jobStores/utils.py +3 -7
- toil/leader.py +17 -22
- toil/lib/accelerators.py +6 -4
- toil/lib/aws/__init__.py +9 -10
- toil/lib/aws/ami.py +33 -19
- toil/lib/aws/iam.py +6 -6
- toil/lib/aws/s3.py +259 -157
- toil/lib/aws/session.py +76 -76
- toil/lib/aws/utils.py +51 -43
- toil/lib/checksum.py +19 -15
- toil/lib/compatibility.py +3 -2
- toil/lib/conversions.py +45 -18
- toil/lib/directory.py +29 -26
- toil/lib/docker.py +93 -99
- toil/lib/dockstore.py +77 -50
- toil/lib/ec2.py +39 -38
- toil/lib/ec2nodes.py +11 -4
- toil/lib/exceptions.py +8 -5
- toil/lib/ftp_utils.py +9 -14
- toil/lib/generatedEC2Lists.py +161 -20
- toil/lib/history.py +141 -97
- toil/lib/history_submission.py +163 -72
- toil/lib/io.py +27 -17
- toil/lib/memoize.py +2 -1
- toil/lib/misc.py +15 -11
- toil/lib/pipes.py +40 -25
- toil/lib/plugins.py +12 -8
- toil/lib/resources.py +1 -0
- toil/lib/retry.py +32 -38
- toil/lib/threading.py +12 -12
- toil/lib/throttle.py +1 -2
- toil/lib/trs.py +113 -51
- toil/lib/url.py +14 -23
- toil/lib/web.py +7 -2
- toil/options/common.py +18 -15
- toil/options/cwl.py +2 -2
- toil/options/runner.py +9 -5
- toil/options/wdl.py +1 -3
- toil/provisioners/__init__.py +9 -9
- toil/provisioners/abstractProvisioner.py +22 -20
- toil/provisioners/aws/__init__.py +20 -14
- toil/provisioners/aws/awsProvisioner.py +10 -8
- toil/provisioners/clusterScaler.py +19 -18
- toil/provisioners/gceProvisioner.py +2 -3
- toil/provisioners/node.py +11 -13
- toil/realtimeLogger.py +4 -4
- toil/resource.py +5 -5
- toil/server/app.py +2 -2
- toil/server/cli/wes_cwl_runner.py +11 -11
- toil/server/utils.py +18 -21
- toil/server/wes/abstract_backend.py +9 -8
- toil/server/wes/amazon_wes_utils.py +3 -3
- toil/server/wes/tasks.py +3 -5
- toil/server/wes/toil_backend.py +17 -21
- toil/server/wsgi_app.py +3 -3
- toil/serviceManager.py +3 -4
- toil/statsAndLogging.py +12 -13
- toil/test/__init__.py +33 -24
- toil/test/batchSystems/batchSystemTest.py +12 -11
- toil/test/batchSystems/batch_system_plugin_test.py +3 -5
- toil/test/batchSystems/test_slurm.py +38 -24
- toil/test/cwl/conftest.py +5 -6
- toil/test/cwl/cwlTest.py +194 -78
- toil/test/cwl/download_file_uri.json +6 -0
- toil/test/cwl/download_file_uri_no_hostname.json +6 -0
- toil/test/docs/scripts/tutorial_staging.py +1 -0
- toil/test/jobStores/jobStoreTest.py +9 -7
- toil/test/lib/aws/test_iam.py +1 -3
- toil/test/lib/aws/test_s3.py +1 -1
- toil/test/lib/dockerTest.py +9 -9
- toil/test/lib/test_ec2.py +12 -11
- toil/test/lib/test_history.py +4 -4
- toil/test/lib/test_trs.py +16 -14
- toil/test/lib/test_url.py +7 -6
- toil/test/lib/url_plugin_test.py +12 -18
- toil/test/provisioners/aws/awsProvisionerTest.py +10 -8
- toil/test/provisioners/clusterScalerTest.py +2 -5
- toil/test/provisioners/clusterTest.py +1 -3
- toil/test/server/serverTest.py +13 -4
- toil/test/sort/restart_sort.py +2 -6
- toil/test/sort/sort.py +3 -8
- toil/test/src/deferredFunctionTest.py +7 -7
- toil/test/src/environmentTest.py +1 -2
- toil/test/src/fileStoreTest.py +5 -5
- toil/test/src/importExportFileTest.py +5 -6
- toil/test/src/jobServiceTest.py +22 -14
- toil/test/src/jobTest.py +121 -25
- toil/test/src/miscTests.py +5 -7
- toil/test/src/promisedRequirementTest.py +8 -7
- toil/test/src/regularLogTest.py +2 -3
- toil/test/src/resourceTest.py +5 -8
- toil/test/src/restartDAGTest.py +5 -6
- toil/test/src/resumabilityTest.py +2 -2
- toil/test/src/retainTempDirTest.py +3 -3
- toil/test/src/systemTest.py +3 -3
- toil/test/src/threadingTest.py +1 -1
- toil/test/src/workerTest.py +1 -2
- toil/test/utils/toilDebugTest.py +6 -4
- toil/test/utils/toilKillTest.py +1 -1
- toil/test/utils/utilsTest.py +15 -14
- toil/test/wdl/wdltoil_test.py +247 -124
- toil/test/wdl/wdltoil_test_kubernetes.py +2 -2
- toil/toilState.py +2 -3
- toil/utils/toilDebugFile.py +3 -8
- toil/utils/toilDebugJob.py +1 -2
- toil/utils/toilLaunchCluster.py +1 -2
- toil/utils/toilSshCluster.py +2 -0
- toil/utils/toilStats.py +19 -24
- toil/utils/toilStatus.py +11 -14
- toil/version.py +10 -10
- toil/wdl/wdltoil.py +313 -209
- toil/worker.py +18 -12
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/METADATA +11 -14
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/RECORD +150 -153
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/WHEEL +1 -1
- toil/test/cwl/staging_cat.cwl +0 -27
- toil/test/cwl/staging_make_file.cwl +0 -25
- toil/test/cwl/staging_workflow.cwl +0 -43
- toil/test/cwl/zero_default.cwl +0 -61
- toil/test/utils/ABCWorkflowDebug/ABC.txt +0 -1
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/entry_points.txt +0 -0
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/licenses/LICENSE +0 -0
- {toil-9.1.1.dist-info → toil-9.2.0.dist-info}/top_level.txt +0 -0
toil/job.py
CHANGED
|
@@ -27,36 +27,29 @@ import time
|
|
|
27
27
|
import uuid
|
|
28
28
|
from abc import ABCMeta, abstractmethod
|
|
29
29
|
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser, Namespace
|
|
30
|
+
from collections.abc import Callable, Iterator, Mapping, Sequence
|
|
30
31
|
from contextlib import contextmanager
|
|
31
32
|
from io import BytesIO
|
|
32
33
|
from typing import (
|
|
33
34
|
TYPE_CHECKING,
|
|
34
35
|
Any,
|
|
35
|
-
|
|
36
|
-
Dict,
|
|
37
|
-
Iterator,
|
|
38
|
-
List,
|
|
39
|
-
Mapping,
|
|
36
|
+
Literal,
|
|
40
37
|
NamedTuple,
|
|
41
|
-
|
|
42
|
-
Sequence,
|
|
43
|
-
Tuple,
|
|
38
|
+
TypedDict,
|
|
44
39
|
TypeVar,
|
|
45
40
|
Union,
|
|
46
41
|
cast,
|
|
47
42
|
overload,
|
|
48
|
-
TypedDict,
|
|
49
|
-
Literal,
|
|
50
43
|
)
|
|
51
44
|
from urllib.error import HTTPError
|
|
52
|
-
from urllib.parse import
|
|
45
|
+
from urllib.parse import unquote, urljoin, urlsplit
|
|
53
46
|
|
|
54
47
|
import dill
|
|
55
48
|
from configargparse import ArgParser
|
|
56
49
|
|
|
50
|
+
from toil.lib.io import is_remote_url
|
|
57
51
|
from toil.lib.memoize import memoize
|
|
58
52
|
from toil.lib.misc import StrPath
|
|
59
|
-
from toil.lib.io import is_remote_url
|
|
60
53
|
|
|
61
54
|
if sys.version_info < (3, 11):
|
|
62
55
|
from typing_extensions import NotRequired
|
|
@@ -69,19 +62,16 @@ from toil.deferred import DeferredFunction
|
|
|
69
62
|
from toil.fileStores import FileID
|
|
70
63
|
from toil.lib.compatibility import deprecated
|
|
71
64
|
from toil.lib.conversions import bytes2human, human2bytes
|
|
65
|
+
from toil.lib.exceptions import UnimplementedURLException
|
|
72
66
|
from toil.lib.expando import Expando
|
|
73
67
|
from toil.lib.resources import ResourceMonitor
|
|
74
68
|
from toil.resource import ModuleDescriptor
|
|
75
69
|
from toil.statsAndLogging import set_logging_from_options
|
|
76
70
|
|
|
77
|
-
from toil.lib.exceptions import UnimplementedURLException
|
|
78
|
-
|
|
79
71
|
if TYPE_CHECKING:
|
|
80
72
|
from optparse import OptionParser
|
|
81
73
|
|
|
82
|
-
from toil.batchSystems.abstractBatchSystem import
|
|
83
|
-
BatchJobExitReason
|
|
84
|
-
)
|
|
74
|
+
from toil.batchSystems.abstractBatchSystem import BatchJobExitReason
|
|
85
75
|
from toil.fileStores.abstractFileStore import AbstractFileStore
|
|
86
76
|
from toil.jobStores.abstractJobStore import AbstractJobStore
|
|
87
77
|
|
|
@@ -95,9 +85,7 @@ class JobPromiseConstraintError(RuntimeError):
|
|
|
95
85
|
(Due to the return value not yet been hit in the topological order of the job graph.)
|
|
96
86
|
"""
|
|
97
87
|
|
|
98
|
-
def __init__(
|
|
99
|
-
self, promisingJob: "Job", recipientJob: Optional["Job"] = None
|
|
100
|
-
) -> None:
|
|
88
|
+
def __init__(self, promisingJob: Job, recipientJob: Job | None = None) -> None:
|
|
101
89
|
"""
|
|
102
90
|
Initialize this error.
|
|
103
91
|
|
|
@@ -122,7 +110,7 @@ class JobPromiseConstraintError(RuntimeError):
|
|
|
122
110
|
|
|
123
111
|
|
|
124
112
|
class ConflictingPredecessorError(Exception):
|
|
125
|
-
def __init__(self, predecessor:
|
|
113
|
+
def __init__(self, predecessor: Job, successor: Job) -> None:
|
|
126
114
|
super().__init__(
|
|
127
115
|
f'The given job: "{predecessor.description}" is already a predecessor of job: "{successor.description}".'
|
|
128
116
|
)
|
|
@@ -140,7 +128,7 @@ class FilesDownloadedStoppingPointReached(DebugStoppingPointReached):
|
|
|
140
128
|
"""
|
|
141
129
|
|
|
142
130
|
def __init__(
|
|
143
|
-
self, message: str, host_and_job_paths:
|
|
131
|
+
self, message: str, host_and_job_paths: list[tuple[str, str]] | None = None
|
|
144
132
|
) -> None:
|
|
145
133
|
super().__init__(message)
|
|
146
134
|
|
|
@@ -222,7 +210,7 @@ class AcceleratorRequirement(TypedDict):
|
|
|
222
210
|
|
|
223
211
|
|
|
224
212
|
def parse_accelerator(
|
|
225
|
-
spec:
|
|
213
|
+
spec: int | str | dict[str, str | int],
|
|
226
214
|
) -> AcceleratorRequirement:
|
|
227
215
|
"""
|
|
228
216
|
Parse an AcceleratorRequirement specified by user code.
|
|
@@ -375,7 +363,7 @@ def accelerator_satisfies(
|
|
|
375
363
|
|
|
376
364
|
|
|
377
365
|
def accelerators_fully_satisfy(
|
|
378
|
-
candidates:
|
|
366
|
+
candidates: list[AcceleratorRequirement] | None,
|
|
379
367
|
requirement: AcceleratorRequirement,
|
|
380
368
|
ignore: list[str] = [],
|
|
381
369
|
) -> bool:
|
|
@@ -412,7 +400,7 @@ class RequirementsDict(TypedDict):
|
|
|
412
400
|
Where requirement values are of different types depending on the requirement.
|
|
413
401
|
"""
|
|
414
402
|
|
|
415
|
-
cores: NotRequired[
|
|
403
|
+
cores: NotRequired[int | float]
|
|
416
404
|
memory: NotRequired[int]
|
|
417
405
|
disk: NotRequired[int]
|
|
418
406
|
accelerators: NotRequired[list[AcceleratorRequirement]]
|
|
@@ -472,7 +460,7 @@ class Requirer:
|
|
|
472
460
|
|
|
473
461
|
# We can have a toil.common.Config assigned to fill in default values
|
|
474
462
|
# for e.g. job requirements not explicitly specified.
|
|
475
|
-
self._config:
|
|
463
|
+
self._config: Config | None = None
|
|
476
464
|
|
|
477
465
|
# Save requirements, parsing and validating anything that needs parsing
|
|
478
466
|
# or validating. Don't save Nones.
|
|
@@ -502,7 +490,7 @@ class Requirer:
|
|
|
502
490
|
state["_config"] = None
|
|
503
491
|
return state
|
|
504
492
|
|
|
505
|
-
def __copy__(self) ->
|
|
493
|
+
def __copy__(self) -> Requirer:
|
|
506
494
|
"""Return a semantically-shallow copy of the object, for :meth:`copy.copy`."""
|
|
507
495
|
# The hide-the-method-and-call-the-copy-module approach from
|
|
508
496
|
# <https://stackoverflow.com/a/40484215> doesn't seem to work for
|
|
@@ -519,7 +507,7 @@ class Requirer:
|
|
|
519
507
|
|
|
520
508
|
return clone
|
|
521
509
|
|
|
522
|
-
def __deepcopy__(self, memo: Any) ->
|
|
510
|
+
def __deepcopy__(self, memo: Any) -> Requirer:
|
|
523
511
|
"""Return a semantically-deep copy of the object, for :meth:`copy.deepcopy`."""
|
|
524
512
|
# We used to use <https://stackoverflow.com/a/40484215> and
|
|
525
513
|
# <https://stackoverflow.com/a/71125311> but that would result in
|
|
@@ -540,7 +528,7 @@ class Requirer:
|
|
|
540
528
|
@overload
|
|
541
529
|
@staticmethod
|
|
542
530
|
def _parseResource(
|
|
543
|
-
name:
|
|
531
|
+
name: Literal["memory"] | Literal["disks"],
|
|
544
532
|
value: ParseableIndivisibleResource,
|
|
545
533
|
) -> int: ...
|
|
546
534
|
|
|
@@ -548,7 +536,7 @@ class Requirer:
|
|
|
548
536
|
@staticmethod
|
|
549
537
|
def _parseResource(
|
|
550
538
|
name: Literal["cores"], value: ParseableDivisibleResource
|
|
551
|
-
) ->
|
|
539
|
+
) -> int | float: ...
|
|
552
540
|
|
|
553
541
|
@overload
|
|
554
542
|
@staticmethod
|
|
@@ -566,8 +554,8 @@ class Requirer:
|
|
|
566
554
|
|
|
567
555
|
@staticmethod
|
|
568
556
|
def _parseResource(
|
|
569
|
-
name: str, value:
|
|
570
|
-
) ->
|
|
557
|
+
name: str, value: ParseableRequirement | None
|
|
558
|
+
) -> ParsedRequirement | None:
|
|
571
559
|
"""
|
|
572
560
|
Parse a Toil resource requirement value and apply resource-specific type checks.
|
|
573
561
|
|
|
@@ -650,7 +638,7 @@ class Requirer:
|
|
|
650
638
|
# Anything else we just pass along without opinons
|
|
651
639
|
return cast(ParsedRequirement, value)
|
|
652
640
|
|
|
653
|
-
def _fetchRequirement(self, requirement: str) ->
|
|
641
|
+
def _fetchRequirement(self, requirement: str) -> ParsedRequirement | None:
|
|
654
642
|
"""
|
|
655
643
|
Get the value of the specified requirement ('blah').
|
|
656
644
|
|
|
@@ -708,7 +696,7 @@ class Requirer:
|
|
|
708
696
|
self._requirementOverrides["memory"] = Requirer._parseResource("memory", val)
|
|
709
697
|
|
|
710
698
|
@property
|
|
711
|
-
def cores(self) ->
|
|
699
|
+
def cores(self) -> int | float:
|
|
712
700
|
"""Get the number of CPU cores required."""
|
|
713
701
|
return cast(Union[int, float], self._fetchRequirement("cores"))
|
|
714
702
|
|
|
@@ -746,7 +734,7 @@ class Requirer:
|
|
|
746
734
|
"accelerators", val
|
|
747
735
|
)
|
|
748
736
|
|
|
749
|
-
def scale(self, requirement: str, factor: float) ->
|
|
737
|
+
def scale(self, requirement: str, factor: float) -> Requirer:
|
|
750
738
|
"""
|
|
751
739
|
Return a copy of this object with the given requirement scaled up or down.
|
|
752
740
|
|
|
@@ -813,14 +801,15 @@ class JobDescription(Requirer):
|
|
|
813
801
|
Subclassed into variants for checkpoint jobs and service jobs that have
|
|
814
802
|
their specific parameters.
|
|
815
803
|
"""
|
|
804
|
+
|
|
816
805
|
def __init__(
|
|
817
806
|
self,
|
|
818
|
-
requirements: Mapping[str,
|
|
807
|
+
requirements: Mapping[str, int | str | float | bool | list],
|
|
819
808
|
jobName: str,
|
|
820
|
-
unitName:
|
|
821
|
-
displayName:
|
|
822
|
-
local:
|
|
823
|
-
files:
|
|
809
|
+
unitName: str | None = "",
|
|
810
|
+
displayName: str | None = "",
|
|
811
|
+
local: bool | None = None,
|
|
812
|
+
files: set[FileID] | None = None,
|
|
824
813
|
) -> None:
|
|
825
814
|
"""
|
|
826
815
|
Create a new JobDescription.
|
|
@@ -852,7 +841,7 @@ class JobDescription(Requirer):
|
|
|
852
841
|
self.local: bool = local or False
|
|
853
842
|
|
|
854
843
|
# Save names, making sure they are strings and not e.g. bytes or None.
|
|
855
|
-
def makeString(x:
|
|
844
|
+
def makeString(x: str | bytes | None) -> str:
|
|
856
845
|
if isinstance(x, bytes):
|
|
857
846
|
return x.decode("utf-8", errors="replace")
|
|
858
847
|
if x is None:
|
|
@@ -866,12 +855,12 @@ class JobDescription(Requirer):
|
|
|
866
855
|
# Set properties that are not fully filled in on creation.
|
|
867
856
|
|
|
868
857
|
# ID of this job description in the JobStore.
|
|
869
|
-
self.jobStoreID:
|
|
858
|
+
self.jobStoreID: str | TemporaryID = TemporaryID()
|
|
870
859
|
|
|
871
860
|
# Information that encodes how to find the Job body data that this
|
|
872
861
|
# JobDescription describes, and the module(s) needed to unpickle it.
|
|
873
862
|
# None if no body needs to run.
|
|
874
|
-
self._body:
|
|
863
|
+
self._body: JobBodyReference | None = None
|
|
875
864
|
|
|
876
865
|
# Set scheduling properties that the leader read to think about scheduling.
|
|
877
866
|
|
|
@@ -1091,7 +1080,7 @@ class JobDescription(Requirer):
|
|
|
1091
1080
|
self._body.module_string
|
|
1092
1081
|
)
|
|
1093
1082
|
|
|
1094
|
-
def nextSuccessors(self) ->
|
|
1083
|
+
def nextSuccessors(self) -> set[str] | None:
|
|
1095
1084
|
"""
|
|
1096
1085
|
Return the collection of job IDs for the successors of this job that are ready to run.
|
|
1097
1086
|
|
|
@@ -1150,7 +1139,7 @@ class JobDescription(Requirer):
|
|
|
1150
1139
|
if k not in toRemove
|
|
1151
1140
|
}
|
|
1152
1141
|
|
|
1153
|
-
def clear_nonexistent_dependents(self, job_store:
|
|
1142
|
+
def clear_nonexistent_dependents(self, job_store: AbstractJobStore) -> None:
|
|
1154
1143
|
"""
|
|
1155
1144
|
Remove all references to child, follow-on, and associated service jobs that do not exist.
|
|
1156
1145
|
|
|
@@ -1178,7 +1167,7 @@ class JobDescription(Requirer):
|
|
|
1178
1167
|
not self.has_body() and next(self.successorsAndServiceHosts(), None) is None
|
|
1179
1168
|
)
|
|
1180
1169
|
|
|
1181
|
-
def replace(self, other:
|
|
1170
|
+
def replace(self, other: JobDescription) -> None:
|
|
1182
1171
|
"""
|
|
1183
1172
|
Take on the ID of another JobDescription, retaining our own state and type.
|
|
1184
1173
|
|
|
@@ -1238,7 +1227,7 @@ class JobDescription(Requirer):
|
|
|
1238
1227
|
self._job_version = other._job_version
|
|
1239
1228
|
self._job_version_writer = os.getpid()
|
|
1240
1229
|
|
|
1241
|
-
def assert_is_not_newer_than(self, other:
|
|
1230
|
+
def assert_is_not_newer_than(self, other: JobDescription) -> None:
|
|
1242
1231
|
"""
|
|
1243
1232
|
Make sure this JobDescription is not newer than a prospective new version of the JobDescription.
|
|
1244
1233
|
"""
|
|
@@ -1247,7 +1236,7 @@ class JobDescription(Requirer):
|
|
|
1247
1236
|
f"Cannot replace {self} from PID {self._job_version_writer} with older version {other} from PID {other._job_version_writer}"
|
|
1248
1237
|
)
|
|
1249
1238
|
|
|
1250
|
-
def is_updated_by(self, other:
|
|
1239
|
+
def is_updated_by(self, other: JobDescription) -> bool:
|
|
1251
1240
|
"""
|
|
1252
1241
|
Return True if the passed JobDescription is a distinct, newer version of this one.
|
|
1253
1242
|
"""
|
|
@@ -1341,7 +1330,7 @@ class JobDescription(Requirer):
|
|
|
1341
1330
|
"""Notify the JobDescription that a predecessor has been added to its Job."""
|
|
1342
1331
|
self.predecessorNumber += 1
|
|
1343
1332
|
|
|
1344
|
-
def onRegistration(self, jobStore:
|
|
1333
|
+
def onRegistration(self, jobStore: AbstractJobStore) -> None:
|
|
1345
1334
|
"""
|
|
1346
1335
|
Perform setup work that requires the JobStore.
|
|
1347
1336
|
|
|
@@ -1355,8 +1344,8 @@ class JobDescription(Requirer):
|
|
|
1355
1344
|
|
|
1356
1345
|
def setupJobAfterFailure(
|
|
1357
1346
|
self,
|
|
1358
|
-
exit_status:
|
|
1359
|
-
exit_reason:
|
|
1347
|
+
exit_status: int | None = None,
|
|
1348
|
+
exit_reason: BatchJobExitReason | None = None,
|
|
1360
1349
|
) -> None:
|
|
1361
1350
|
"""
|
|
1362
1351
|
Configure job after a failure.
|
|
@@ -1519,15 +1508,15 @@ class ServiceJobDescription(JobDescription):
|
|
|
1519
1508
|
|
|
1520
1509
|
# An empty file in the jobStore which when deleted is used to signal that the service
|
|
1521
1510
|
# should cease.
|
|
1522
|
-
self.terminateJobStoreID:
|
|
1511
|
+
self.terminateJobStoreID: str | None = None
|
|
1523
1512
|
|
|
1524
1513
|
# Similarly a empty file which when deleted is used to signal that the service is
|
|
1525
1514
|
# established
|
|
1526
|
-
self.startJobStoreID:
|
|
1515
|
+
self.startJobStoreID: str | None = None
|
|
1527
1516
|
|
|
1528
1517
|
# An empty file in the jobStore which when deleted is used to signal that the service
|
|
1529
1518
|
# should terminate signaling an error.
|
|
1530
|
-
self.errorJobStoreID:
|
|
1519
|
+
self.errorJobStoreID: str | None = None
|
|
1531
1520
|
|
|
1532
1521
|
def onRegistration(self, jobStore):
|
|
1533
1522
|
"""
|
|
@@ -1555,7 +1544,7 @@ class CheckpointJobDescription(JobDescription):
|
|
|
1555
1544
|
# Set checkpoint-specific properties
|
|
1556
1545
|
|
|
1557
1546
|
# None, or a copy of the original self._body used to reestablish the job after failure.
|
|
1558
|
-
self.checkpoint:
|
|
1547
|
+
self.checkpoint: JobBodyReference | None = None
|
|
1559
1548
|
|
|
1560
1549
|
# Files that can not be deleted until the job and its successors have completed
|
|
1561
1550
|
self.checkpointFilesToDelete = []
|
|
@@ -1577,7 +1566,7 @@ class CheckpointJobDescription(JobDescription):
|
|
|
1577
1566
|
raise RuntimeError(f"Cannot restore an empty checkpoint for a job {self}")
|
|
1578
1567
|
self._body = self.checkpoint
|
|
1579
1568
|
|
|
1580
|
-
def restartCheckpoint(self, jobStore:
|
|
1569
|
+
def restartCheckpoint(self, jobStore: AbstractJobStore) -> list[str]:
|
|
1581
1570
|
"""
|
|
1582
1571
|
Restart a checkpoint after the total failure of jobs in its subtree.
|
|
1583
1572
|
|
|
@@ -1648,18 +1637,18 @@ class Job:
|
|
|
1648
1637
|
|
|
1649
1638
|
def __init__(
|
|
1650
1639
|
self,
|
|
1651
|
-
memory:
|
|
1652
|
-
cores:
|
|
1653
|
-
disk:
|
|
1654
|
-
accelerators:
|
|
1655
|
-
preemptible:
|
|
1656
|
-
preemptable:
|
|
1657
|
-
unitName:
|
|
1658
|
-
checkpoint:
|
|
1659
|
-
displayName:
|
|
1660
|
-
descriptionClass:
|
|
1661
|
-
local:
|
|
1662
|
-
files:
|
|
1640
|
+
memory: ParseableIndivisibleResource | None = None,
|
|
1641
|
+
cores: ParseableDivisibleResource | None = None,
|
|
1642
|
+
disk: ParseableIndivisibleResource | None = None,
|
|
1643
|
+
accelerators: ParseableAcceleratorRequirement | None = None,
|
|
1644
|
+
preemptible: ParseableFlag | None = None,
|
|
1645
|
+
preemptable: ParseableFlag | None = None,
|
|
1646
|
+
unitName: str | None = "",
|
|
1647
|
+
checkpoint: bool | None = False,
|
|
1648
|
+
displayName: str | None = "",
|
|
1649
|
+
descriptionClass: type | None = None,
|
|
1650
|
+
local: bool | None = None,
|
|
1651
|
+
files: set[FileID] | None = None,
|
|
1663
1652
|
) -> None:
|
|
1664
1653
|
"""
|
|
1665
1654
|
Job initializer.
|
|
@@ -1792,7 +1781,7 @@ class Job:
|
|
|
1792
1781
|
)
|
|
1793
1782
|
|
|
1794
1783
|
@property
|
|
1795
|
-
def jobStoreID(self) ->
|
|
1784
|
+
def jobStoreID(self) -> str | TemporaryID:
|
|
1796
1785
|
"""Get the ID of this Job."""
|
|
1797
1786
|
# This is managed by the JobDescription.
|
|
1798
1787
|
return self._description.jobStoreID
|
|
@@ -1824,7 +1813,7 @@ class Job:
|
|
|
1824
1813
|
self.description.memory = val
|
|
1825
1814
|
|
|
1826
1815
|
@property
|
|
1827
|
-
def cores(self) ->
|
|
1816
|
+
def cores(self) -> int | float:
|
|
1828
1817
|
"""The number of CPU cores required."""
|
|
1829
1818
|
return self.description.cores
|
|
1830
1819
|
|
|
@@ -1883,7 +1872,7 @@ class Job:
|
|
|
1883
1872
|
"""
|
|
1884
1873
|
self.description.assignConfig(config)
|
|
1885
1874
|
|
|
1886
|
-
def run(self, fileStore:
|
|
1875
|
+
def run(self, fileStore: AbstractFileStore) -> Any:
|
|
1887
1876
|
"""
|
|
1888
1877
|
Override this function to perform work and dynamically create successor jobs.
|
|
1889
1878
|
|
|
@@ -1894,7 +1883,7 @@ class Job:
|
|
|
1894
1883
|
:func:`toil.job.Job.rv`.
|
|
1895
1884
|
"""
|
|
1896
1885
|
|
|
1897
|
-
def _jobGraphsJoined(self, other:
|
|
1886
|
+
def _jobGraphsJoined(self, other: Job) -> None:
|
|
1898
1887
|
"""
|
|
1899
1888
|
Called whenever the job graphs of this job and the other job may have been merged into one connected component.
|
|
1900
1889
|
|
|
@@ -1929,7 +1918,7 @@ class Job:
|
|
|
1929
1918
|
# Point all their jobs at the new combined registry
|
|
1930
1919
|
job._registry = self._registry
|
|
1931
1920
|
|
|
1932
|
-
def addChild(self, childJob:
|
|
1921
|
+
def addChild(self, childJob: Job) -> Job:
|
|
1933
1922
|
"""
|
|
1934
1923
|
Add a childJob to be run as child of this job.
|
|
1935
1924
|
|
|
@@ -1954,7 +1943,7 @@ class Job:
|
|
|
1954
1943
|
|
|
1955
1944
|
return childJob
|
|
1956
1945
|
|
|
1957
|
-
def hasChild(self, childJob:
|
|
1946
|
+
def hasChild(self, childJob: Job) -> bool:
|
|
1958
1947
|
"""
|
|
1959
1948
|
Check if childJob is already a child of this job.
|
|
1960
1949
|
|
|
@@ -1962,7 +1951,7 @@ class Job:
|
|
|
1962
1951
|
"""
|
|
1963
1952
|
return self._description.hasChild(childJob.jobStoreID)
|
|
1964
1953
|
|
|
1965
|
-
def addFollowOn(self, followOnJob:
|
|
1954
|
+
def addFollowOn(self, followOnJob: Job) -> Job:
|
|
1966
1955
|
"""
|
|
1967
1956
|
Add a follow-on job.
|
|
1968
1957
|
|
|
@@ -1986,11 +1975,11 @@ class Job:
|
|
|
1986
1975
|
|
|
1987
1976
|
return followOnJob
|
|
1988
1977
|
|
|
1989
|
-
def hasPredecessor(self, job:
|
|
1978
|
+
def hasPredecessor(self, job: Job) -> bool:
|
|
1990
1979
|
"""Check if a given job is already a predecessor of this job."""
|
|
1991
1980
|
return job in self._directPredecessors
|
|
1992
1981
|
|
|
1993
|
-
def hasFollowOn(self, followOnJob:
|
|
1982
|
+
def hasFollowOn(self, followOnJob: Job) -> bool:
|
|
1994
1983
|
"""
|
|
1995
1984
|
Check if given job is already a follow-on of this job.
|
|
1996
1985
|
|
|
@@ -1999,8 +1988,8 @@ class Job:
|
|
|
1999
1988
|
return self._description.hasChild(followOnJob.jobStoreID)
|
|
2000
1989
|
|
|
2001
1990
|
def addService(
|
|
2002
|
-
self, service:
|
|
2003
|
-
) ->
|
|
1991
|
+
self, service: Job.Service, parentService: Job.Service | None = None
|
|
1992
|
+
) -> Promise:
|
|
2004
1993
|
"""
|
|
2005
1994
|
Add a service.
|
|
2006
1995
|
|
|
@@ -2046,7 +2035,7 @@ class Job:
|
|
|
2046
2035
|
# Return the promise for the service's startup result
|
|
2047
2036
|
return hostingJob.rv()
|
|
2048
2037
|
|
|
2049
|
-
def hasService(self, service:
|
|
2038
|
+
def hasService(self, service: Job.Service) -> bool:
|
|
2050
2039
|
"""Return True if the given Service is a service of this job, and False otherwise."""
|
|
2051
2040
|
return service.hostID is None or self._description.hasServiceHostJob(
|
|
2052
2041
|
service.hostID
|
|
@@ -2054,7 +2043,7 @@ class Job:
|
|
|
2054
2043
|
|
|
2055
2044
|
# Convenience functions for creating jobs
|
|
2056
2045
|
|
|
2057
|
-
def addChildFn(self, fn: Callable, *args, **kwargs) ->
|
|
2046
|
+
def addChildFn(self, fn: Callable, *args, **kwargs) -> FunctionWrappingJob:
|
|
2058
2047
|
"""
|
|
2059
2048
|
Add a function as a child job.
|
|
2060
2049
|
|
|
@@ -2070,7 +2059,7 @@ class Job:
|
|
|
2070
2059
|
else:
|
|
2071
2060
|
return self.addChild(FunctionWrappingJob(fn, *args, **kwargs))
|
|
2072
2061
|
|
|
2073
|
-
def addFollowOnFn(self, fn: Callable, *args, **kwargs) ->
|
|
2062
|
+
def addFollowOnFn(self, fn: Callable, *args, **kwargs) -> FunctionWrappingJob:
|
|
2074
2063
|
"""
|
|
2075
2064
|
Add a function as a follow-on job.
|
|
2076
2065
|
|
|
@@ -2086,7 +2075,7 @@ class Job:
|
|
|
2086
2075
|
else:
|
|
2087
2076
|
return self.addFollowOn(FunctionWrappingJob(fn, *args, **kwargs))
|
|
2088
2077
|
|
|
2089
|
-
def addChildJobFn(self, fn: Callable, *args, **kwargs) ->
|
|
2078
|
+
def addChildJobFn(self, fn: Callable, *args, **kwargs) -> FunctionWrappingJob:
|
|
2090
2079
|
"""
|
|
2091
2080
|
Add a job function as a child job.
|
|
2092
2081
|
|
|
@@ -2104,7 +2093,7 @@ class Job:
|
|
|
2104
2093
|
else:
|
|
2105
2094
|
return self.addChild(JobFunctionWrappingJob(fn, *args, **kwargs))
|
|
2106
2095
|
|
|
2107
|
-
def addFollowOnJobFn(self, fn: Callable, *args, **kwargs) ->
|
|
2096
|
+
def addFollowOnJobFn(self, fn: Callable, *args, **kwargs) -> FunctionWrappingJob:
|
|
2108
2097
|
"""
|
|
2109
2098
|
Add a follow-on job function.
|
|
2110
2099
|
|
|
@@ -2139,7 +2128,7 @@ class Job:
|
|
|
2139
2128
|
self._fileStore.log_to_leader(text, level)
|
|
2140
2129
|
|
|
2141
2130
|
@staticmethod
|
|
2142
|
-
def wrapFn(fn, *args, **kwargs) ->
|
|
2131
|
+
def wrapFn(fn, *args, **kwargs) -> FunctionWrappingJob:
|
|
2143
2132
|
"""
|
|
2144
2133
|
Makes a Job out of a function.
|
|
2145
2134
|
|
|
@@ -2156,7 +2145,7 @@ class Job:
|
|
|
2156
2145
|
return FunctionWrappingJob(fn, *args, **kwargs)
|
|
2157
2146
|
|
|
2158
2147
|
@staticmethod
|
|
2159
|
-
def wrapJobFn(fn, *args, **kwargs) ->
|
|
2148
|
+
def wrapJobFn(fn, *args, **kwargs) -> JobFunctionWrappingJob:
|
|
2160
2149
|
"""
|
|
2161
2150
|
Makes a Job out of a job function.
|
|
2162
2151
|
|
|
@@ -2172,7 +2161,7 @@ class Job:
|
|
|
2172
2161
|
else:
|
|
2173
2162
|
return JobFunctionWrappingJob(fn, *args, **kwargs)
|
|
2174
2163
|
|
|
2175
|
-
def encapsulate(self, name:
|
|
2164
|
+
def encapsulate(self, name: str | None = None) -> EncapsulatedJob:
|
|
2176
2165
|
"""
|
|
2177
2166
|
Encapsulates the job, see :class:`toil.job.EncapsulatedJob`.
|
|
2178
2167
|
Convenience function for constructor of :class:`toil.job.EncapsulatedJob`.
|
|
@@ -2188,7 +2177,7 @@ class Job:
|
|
|
2188
2177
|
# job run functions
|
|
2189
2178
|
####################################################
|
|
2190
2179
|
|
|
2191
|
-
def rv(self, *path) ->
|
|
2180
|
+
def rv(self, *path) -> Promise:
|
|
2192
2181
|
"""
|
|
2193
2182
|
Create a *promise* (:class:`toil.job.Promise`).
|
|
2194
2183
|
|
|
@@ -2230,7 +2219,7 @@ class Job:
|
|
|
2230
2219
|
self._rvs[path].append(jobStoreFileID)
|
|
2231
2220
|
return self._promiseJobStore.config.jobStore, jobStoreFileID
|
|
2232
2221
|
|
|
2233
|
-
def prepareForPromiseRegistration(self, jobStore:
|
|
2222
|
+
def prepareForPromiseRegistration(self, jobStore: AbstractJobStore) -> None:
|
|
2234
2223
|
"""
|
|
2235
2224
|
Set up to allow this job's promises to register themselves.
|
|
2236
2225
|
|
|
@@ -2275,7 +2264,7 @@ class Job:
|
|
|
2275
2264
|
self.checkJobGraphAcylic()
|
|
2276
2265
|
self.checkNewCheckpointsAreLeafVertices()
|
|
2277
2266
|
|
|
2278
|
-
def getRootJobs(self) -> set[
|
|
2267
|
+
def getRootJobs(self) -> set[Job]:
|
|
2279
2268
|
"""
|
|
2280
2269
|
Return the set of root job objects that contain this job.
|
|
2281
2270
|
|
|
@@ -2363,7 +2352,7 @@ class Job:
|
|
|
2363
2352
|
)
|
|
2364
2353
|
|
|
2365
2354
|
@staticmethod
|
|
2366
|
-
def _getImpliedEdges(roots) -> dict[
|
|
2355
|
+
def _getImpliedEdges(roots) -> dict[Job, list[Job]]:
|
|
2367
2356
|
"""
|
|
2368
2357
|
Gets the set of implied edges (between children and follow-ons of a common job).
|
|
2369
2358
|
|
|
@@ -2501,7 +2490,7 @@ class Job:
|
|
|
2501
2490
|
|
|
2502
2491
|
@staticmethod
|
|
2503
2492
|
def getDefaultOptions(
|
|
2504
|
-
jobStore:
|
|
2493
|
+
jobStore: StrPath | None = None, jobstore_as_flag: bool = False
|
|
2505
2494
|
) -> Namespace:
|
|
2506
2495
|
"""
|
|
2507
2496
|
Get default options for a toil workflow.
|
|
@@ -2529,7 +2518,7 @@ class Job:
|
|
|
2529
2518
|
|
|
2530
2519
|
@staticmethod
|
|
2531
2520
|
def addToilOptions(
|
|
2532
|
-
parser:
|
|
2521
|
+
parser: OptionParser | ArgumentParser,
|
|
2533
2522
|
jobstore_as_flag: bool = False,
|
|
2534
2523
|
) -> None:
|
|
2535
2524
|
"""
|
|
@@ -2549,7 +2538,7 @@ class Job:
|
|
|
2549
2538
|
addOptions(parser, jobstore_as_flag=jobstore_as_flag)
|
|
2550
2539
|
|
|
2551
2540
|
@staticmethod
|
|
2552
|
-
def startToil(job:
|
|
2541
|
+
def startToil(job: Job, options) -> Any:
|
|
2553
2542
|
"""
|
|
2554
2543
|
Run the toil workflow using the given options.
|
|
2555
2544
|
|
|
@@ -2580,12 +2569,12 @@ class Job:
|
|
|
2580
2569
|
|
|
2581
2570
|
def __init__(
|
|
2582
2571
|
self,
|
|
2583
|
-
memory:
|
|
2584
|
-
cores:
|
|
2585
|
-
disk:
|
|
2586
|
-
accelerators:
|
|
2587
|
-
preemptible:
|
|
2588
|
-
unitName:
|
|
2572
|
+
memory: ParseableIndivisibleResource | None = None,
|
|
2573
|
+
cores: ParseableDivisibleResource | None = None,
|
|
2574
|
+
disk: ParseableIndivisibleResource | None = None,
|
|
2575
|
+
accelerators: ParseableAcceleratorRequirement | None = None,
|
|
2576
|
+
preemptible: ParseableFlag | None = None,
|
|
2577
|
+
unitName: str | None = "",
|
|
2589
2578
|
) -> None:
|
|
2590
2579
|
"""
|
|
2591
2580
|
Memory, core and disk requirements are specified identically to as in \
|
|
@@ -2612,7 +2601,7 @@ class Job:
|
|
|
2612
2601
|
self.hostID = None
|
|
2613
2602
|
|
|
2614
2603
|
@abstractmethod
|
|
2615
|
-
def start(self, job:
|
|
2604
|
+
def start(self, job: ServiceHostJob) -> Any:
|
|
2616
2605
|
"""
|
|
2617
2606
|
Start the service.
|
|
2618
2607
|
|
|
@@ -2625,7 +2614,7 @@ class Job:
|
|
|
2625
2614
|
"""
|
|
2626
2615
|
|
|
2627
2616
|
@abstractmethod
|
|
2628
|
-
def stop(self, job:
|
|
2617
|
+
def stop(self, job: ServiceHostJob) -> None:
|
|
2629
2618
|
"""
|
|
2630
2619
|
Stops the service. Function can block until complete.
|
|
2631
2620
|
|
|
@@ -2781,7 +2770,7 @@ class Job:
|
|
|
2781
2770
|
# We added this successor locally
|
|
2782
2771
|
todo.append(self._registry[successorID])
|
|
2783
2772
|
|
|
2784
|
-
def getTopologicalOrderingOfJobs(self) -> list[
|
|
2773
|
+
def getTopologicalOrderingOfJobs(self) -> list[Job]:
|
|
2785
2774
|
"""
|
|
2786
2775
|
:returns: a list of jobs such that for all pairs of indices i, j for which i < j, \
|
|
2787
2776
|
the job at index i can be run before the job at index j.
|
|
@@ -2872,7 +2861,7 @@ class Job:
|
|
|
2872
2861
|
# Do renames in the description
|
|
2873
2862
|
self._description.renameReferences(renames)
|
|
2874
2863
|
|
|
2875
|
-
def saveBody(self, jobStore:
|
|
2864
|
+
def saveBody(self, jobStore: AbstractJobStore) -> None:
|
|
2876
2865
|
"""
|
|
2877
2866
|
Save the execution data for just this job to the JobStore, and fill in
|
|
2878
2867
|
the JobDescription with the information needed to retrieve it.
|
|
@@ -2943,7 +2932,7 @@ class Job:
|
|
|
2943
2932
|
|
|
2944
2933
|
def _saveJobGraph(
|
|
2945
2934
|
self,
|
|
2946
|
-
jobStore:
|
|
2935
|
+
jobStore: AbstractJobStore,
|
|
2947
2936
|
saveSelf: bool = False,
|
|
2948
2937
|
returnValues: bool = None,
|
|
2949
2938
|
):
|
|
@@ -3050,7 +3039,7 @@ class Job:
|
|
|
3050
3039
|
if job != self or saveSelf:
|
|
3051
3040
|
jobStore.create_job(job.description)
|
|
3052
3041
|
|
|
3053
|
-
def saveAsRootJob(self, jobStore:
|
|
3042
|
+
def saveAsRootJob(self, jobStore: AbstractJobStore) -> JobDescription:
|
|
3054
3043
|
"""
|
|
3055
3044
|
Save this job to the given jobStore as the root job of the workflow.
|
|
3056
3045
|
|
|
@@ -3079,8 +3068,8 @@ class Job:
|
|
|
3079
3068
|
|
|
3080
3069
|
@classmethod
|
|
3081
3070
|
def loadJob(
|
|
3082
|
-
cls, job_store:
|
|
3083
|
-
) ->
|
|
3071
|
+
cls, job_store: AbstractJobStore, job_description: JobDescription
|
|
3072
|
+
) -> Job:
|
|
3084
3073
|
"""
|
|
3085
3074
|
Retrieves a :class:`toil.job.Job` instance from a JobStore
|
|
3086
3075
|
|
|
@@ -3155,7 +3144,9 @@ class Job:
|
|
|
3155
3144
|
|
|
3156
3145
|
if "download_only" in self._debug_flags:
|
|
3157
3146
|
# We should stop right away
|
|
3158
|
-
logger.debug(
|
|
3147
|
+
logger.debug(
|
|
3148
|
+
"Job did not stop itself after downloading files; stopping."
|
|
3149
|
+
)
|
|
3159
3150
|
raise DebugStoppingPointReached()
|
|
3160
3151
|
|
|
3161
3152
|
# If the job is not a checkpoint job, add the promise files to delete
|
|
@@ -3178,7 +3169,7 @@ class Job:
|
|
|
3178
3169
|
# Change dir back to cwd dir, if changed by job (this is a safety issue)
|
|
3179
3170
|
if os.getcwd() != baseDir:
|
|
3180
3171
|
os.chdir(baseDir)
|
|
3181
|
-
|
|
3172
|
+
|
|
3182
3173
|
totalCpuTime, total_memory_kib = (
|
|
3183
3174
|
ResourceMonitor.get_total_cpu_time_and_memory_usage()
|
|
3184
3175
|
)
|
|
@@ -3196,7 +3187,7 @@ class Job:
|
|
|
3196
3187
|
f"CPU than the requested {self.cores} cores. Consider "
|
|
3197
3188
|
f"increasing the job's required CPU cores or limiting the "
|
|
3198
3189
|
f"number of processes/threads launched.",
|
|
3199
|
-
level=logging.WARNING
|
|
3190
|
+
level=logging.WARNING,
|
|
3200
3191
|
)
|
|
3201
3192
|
|
|
3202
3193
|
# Finish up the stats
|
|
@@ -3210,7 +3201,9 @@ class Job:
|
|
|
3210
3201
|
clock=str(job_cpu_time),
|
|
3211
3202
|
class_name=self._jobName(),
|
|
3212
3203
|
memory=str(total_memory_kib),
|
|
3213
|
-
requested_cores=str(
|
|
3204
|
+
requested_cores=str(
|
|
3205
|
+
self.cores
|
|
3206
|
+
), # TODO: Isn't this really consumed cores?
|
|
3214
3207
|
disk=str(fileStore.get_disk_usage()),
|
|
3215
3208
|
succeeded=str(succeeded),
|
|
3216
3209
|
)
|
|
@@ -3218,8 +3211,8 @@ class Job:
|
|
|
3218
3211
|
|
|
3219
3212
|
def _runner(
|
|
3220
3213
|
self,
|
|
3221
|
-
jobStore:
|
|
3222
|
-
fileStore:
|
|
3214
|
+
jobStore: AbstractJobStore,
|
|
3215
|
+
fileStore: AbstractFileStore,
|
|
3223
3216
|
defer: Callable[[Any], None],
|
|
3224
3217
|
**kwargs,
|
|
3225
3218
|
) -> None:
|
|
@@ -3286,7 +3279,7 @@ class Job:
|
|
|
3286
3279
|
return flag in self._debug_flags
|
|
3287
3280
|
|
|
3288
3281
|
def files_downloaded_hook(
|
|
3289
|
-
self, host_and_job_paths:
|
|
3282
|
+
self, host_and_job_paths: list[tuple[str, str]] | None = None
|
|
3290
3283
|
) -> None:
|
|
3291
3284
|
"""
|
|
3292
3285
|
Function that subclasses can call when they have downloaded their input files.
|
|
@@ -3354,9 +3347,7 @@ class FunctionWrappingJob(Job):
|
|
|
3354
3347
|
list(zip(argSpec.args[-len(argSpec.defaults) :], argSpec.defaults))
|
|
3355
3348
|
)
|
|
3356
3349
|
|
|
3357
|
-
def resolve(
|
|
3358
|
-
key, default: Optional[Any] = None, dehumanize: bool = False
|
|
3359
|
-
) -> Any:
|
|
3350
|
+
def resolve(key, default: Any | None = None, dehumanize: bool = False) -> Any:
|
|
3360
3351
|
try:
|
|
3361
3352
|
# First, try constructor arguments, ...
|
|
3362
3353
|
value = kwargs.pop(key)
|
|
@@ -3399,7 +3390,7 @@ class FunctionWrappingJob(Job):
|
|
|
3399
3390
|
userFunctionModule = self._loadUserModule(self.userFunctionModule)
|
|
3400
3391
|
return getattr(userFunctionModule, self.userFunctionName)
|
|
3401
3392
|
|
|
3402
|
-
def run(self, fileStore:
|
|
3393
|
+
def run(self, fileStore: AbstractFileStore) -> Any:
|
|
3403
3394
|
userFunction = self._getUserFunction()
|
|
3404
3395
|
return userFunction(*self._args, **self._kwargs)
|
|
3405
3396
|
|
|
@@ -3444,10 +3435,10 @@ class JobFunctionWrappingJob(FunctionWrappingJob):
|
|
|
3444
3435
|
"""
|
|
3445
3436
|
|
|
3446
3437
|
@property
|
|
3447
|
-
def fileStore(self) ->
|
|
3438
|
+
def fileStore(self) -> AbstractFileStore:
|
|
3448
3439
|
return self._fileStore
|
|
3449
3440
|
|
|
3450
|
-
def run(self, fileStore:
|
|
3441
|
+
def run(self, fileStore: AbstractFileStore) -> Any:
|
|
3451
3442
|
userFunction = self._getUserFunction()
|
|
3452
3443
|
rValue = userFunction(*((self,) + tuple(self._args)), **self._kwargs)
|
|
3453
3444
|
return rValue
|
|
@@ -3543,7 +3534,7 @@ class EncapsulatedJob(Job):
|
|
|
3543
3534
|
the same value after A or A.encapsulate() has been run.
|
|
3544
3535
|
"""
|
|
3545
3536
|
|
|
3546
|
-
def __init__(self, job:
|
|
3537
|
+
def __init__(self, job: Job | None, unitName: str | None = None) -> None:
|
|
3547
3538
|
"""
|
|
3548
3539
|
:param toil.job.Job job: the job to encapsulate.
|
|
3549
3540
|
:param str unitName: human-readable name to identify this job instance.
|
|
@@ -3600,7 +3591,7 @@ class EncapsulatedJob(Job):
|
|
|
3600
3591
|
)
|
|
3601
3592
|
return Job.addFollowOn(self.encapsulatedFollowOn, followOnJob)
|
|
3602
3593
|
|
|
3603
|
-
def rv(self, *path) ->
|
|
3594
|
+
def rv(self, *path) -> Promise:
|
|
3604
3595
|
if self.encapsulatedJob is None:
|
|
3605
3596
|
raise RuntimeError("The encapsulated job was not set.")
|
|
3606
3597
|
return self.encapsulatedJob.rv(*path)
|
|
@@ -3824,14 +3815,14 @@ class FileMetadata(NamedTuple):
|
|
|
3824
3815
|
|
|
3825
3816
|
source: str
|
|
3826
3817
|
parent_dir: str
|
|
3827
|
-
size:
|
|
3818
|
+
size: int | None
|
|
3828
3819
|
|
|
3829
3820
|
|
|
3830
3821
|
def potential_absolute_uris(
|
|
3831
3822
|
uri: str,
|
|
3832
3823
|
path: list[str],
|
|
3833
|
-
importer:
|
|
3834
|
-
execution_dir:
|
|
3824
|
+
importer: str | None = None,
|
|
3825
|
+
execution_dir: str | None = None,
|
|
3835
3826
|
) -> Iterator[str]:
|
|
3836
3827
|
"""
|
|
3837
3828
|
Get potential absolute URIs to check for an imported file.
|
|
@@ -3903,12 +3894,12 @@ def potential_absolute_uris(
|
|
|
3903
3894
|
|
|
3904
3895
|
|
|
3905
3896
|
def get_file_sizes(
|
|
3906
|
-
filenames:
|
|
3897
|
+
filenames: list[str],
|
|
3907
3898
|
file_source: AbstractJobStore,
|
|
3908
|
-
search_paths:
|
|
3899
|
+
search_paths: list[str] | None = None,
|
|
3909
3900
|
include_remote_files: bool = True,
|
|
3910
|
-
execution_dir:
|
|
3911
|
-
) ->
|
|
3901
|
+
execution_dir: str | None = None,
|
|
3902
|
+
) -> dict[str, FileMetadata]:
|
|
3912
3903
|
"""
|
|
3913
3904
|
Resolve relative-URI files in the given environment and turn them into absolute normalized URIs. Returns a dictionary of the *string values* from the WDL file values
|
|
3914
3905
|
to a tuple of the normalized URI, parent directory ID, and size of the file. The size of the file may be None, which means unknown size.
|
|
@@ -3975,9 +3966,7 @@ def get_file_sizes(
|
|
|
3975
3966
|
if file_basename == "":
|
|
3976
3967
|
# We can't have files with no basename because we need to
|
|
3977
3968
|
# download them at that basename later in WDL.
|
|
3978
|
-
raise RuntimeError(
|
|
3979
|
-
f"File {candidate_uri} has no basename"
|
|
3980
|
-
)
|
|
3969
|
+
raise RuntimeError(f"File {candidate_uri} has no basename")
|
|
3981
3970
|
|
|
3982
3971
|
# Was actually found
|
|
3983
3972
|
if is_remote_url(candidate_uri):
|
|
@@ -3985,7 +3974,7 @@ def get_file_sizes(
|
|
|
3985
3974
|
# We need to make sure file URIs and local paths that point to
|
|
3986
3975
|
# the same place are treated the same.
|
|
3987
3976
|
parsed = urlsplit(candidate_uri)
|
|
3988
|
-
if parsed.scheme == "file
|
|
3977
|
+
if parsed.scheme == "file":
|
|
3989
3978
|
# This is a local file URI. Convert to a path for source directory tracking.
|
|
3990
3979
|
parent_dir = os.path.dirname(unquote(parsed.path))
|
|
3991
3980
|
else:
|
|
@@ -3995,7 +3984,7 @@ def get_file_sizes(
|
|
|
3995
3984
|
# Must be a local path
|
|
3996
3985
|
parent_dir = os.path.dirname(candidate_uri)
|
|
3997
3986
|
|
|
3998
|
-
return
|
|
3987
|
+
return FileMetadata(candidate_uri, parent_dir, filesize)
|
|
3999
3988
|
# Not found
|
|
4000
3989
|
raise RuntimeError(
|
|
4001
3990
|
f"Could not find {filename} at any of: {list(potential_absolute_uris(filename, search_paths if search_paths is not None else []))}"
|
|
@@ -4006,17 +3995,17 @@ def get_file_sizes(
|
|
|
4006
3995
|
|
|
4007
3996
|
class CombineImportsJob(Job):
|
|
4008
3997
|
"""
|
|
4009
|
-
Combine the outputs of multiple
|
|
3998
|
+
Combine the outputs of multiple WorkerImportJobs into one promise
|
|
4010
3999
|
"""
|
|
4011
4000
|
|
|
4012
|
-
def __init__(self, d: Sequence[Promised[
|
|
4001
|
+
def __init__(self, d: Sequence[Promised[dict[str, FileID]]], **kwargs):
|
|
4013
4002
|
"""
|
|
4014
4003
|
:param d: Sequence of dictionaries to merge
|
|
4015
4004
|
"""
|
|
4016
4005
|
self._d = d
|
|
4017
4006
|
super().__init__(**kwargs)
|
|
4018
4007
|
|
|
4019
|
-
def run(self, file_store:
|
|
4008
|
+
def run(self, file_store: AbstractFileStore) -> Promised[dict[str, FileID]]:
|
|
4020
4009
|
"""
|
|
4021
4010
|
Merge the dicts
|
|
4022
4011
|
"""
|
|
@@ -4031,12 +4020,7 @@ class WorkerImportJob(Job):
|
|
|
4031
4020
|
For the CWL/WDL runners, this class is only used when runImportsOnWorkers is enabled.
|
|
4032
4021
|
"""
|
|
4033
4022
|
|
|
4034
|
-
def __init__(
|
|
4035
|
-
self,
|
|
4036
|
-
filenames: List[str],
|
|
4037
|
-
local: bool = False,
|
|
4038
|
-
**kwargs: Any
|
|
4039
|
-
):
|
|
4023
|
+
def __init__(self, filenames: list[str], local: bool = False, **kwargs: Any):
|
|
4040
4024
|
"""
|
|
4041
4025
|
Setup importing files on a worker.
|
|
4042
4026
|
:param filenames: List of file URIs to import
|
|
@@ -4047,23 +4031,29 @@ class WorkerImportJob(Job):
|
|
|
4047
4031
|
|
|
4048
4032
|
@staticmethod
|
|
4049
4033
|
def import_files(
|
|
4050
|
-
files:
|
|
4051
|
-
) ->
|
|
4034
|
+
files: list[str], file_source: AbstractJobStore, symlink: bool = True
|
|
4035
|
+
) -> dict[str, FileID]:
|
|
4052
4036
|
"""
|
|
4053
4037
|
Import a list of files into the jobstore. Returns a mapping of the filename to the associated FileIDs
|
|
4054
4038
|
|
|
4055
4039
|
When stream is true but the import is not streamable, the worker will run out of
|
|
4056
4040
|
disk space and run a new import job with enough disk space instead.
|
|
4041
|
+
|
|
4057
4042
|
:param files: list of files to import
|
|
4043
|
+
|
|
4058
4044
|
:param file_source: AbstractJobStore
|
|
4059
|
-
|
|
4045
|
+
|
|
4046
|
+
:param symlink: whether to allow symlinking the imported files
|
|
4047
|
+
|
|
4048
|
+
:return: Dictionary mapping filenames from files to associated jobstore
|
|
4049
|
+
FileID.
|
|
4060
4050
|
"""
|
|
4061
4051
|
# todo: make the import ensure streaming is done instead of relying on running out of disk space
|
|
4062
4052
|
path_to_fileid = {}
|
|
4063
4053
|
|
|
4064
4054
|
@memoize
|
|
4065
|
-
def import_filename(filename: str) ->
|
|
4066
|
-
return file_source.import_file(filename, symlink=
|
|
4055
|
+
def import_filename(filename: str) -> FileID | None:
|
|
4056
|
+
return file_source.import_file(filename, symlink=symlink)
|
|
4067
4057
|
|
|
4068
4058
|
for file in files:
|
|
4069
4059
|
imported = import_filename(file)
|
|
@@ -4071,7 +4061,7 @@ class WorkerImportJob(Job):
|
|
|
4071
4061
|
path_to_fileid[file] = imported
|
|
4072
4062
|
return path_to_fileid
|
|
4073
4063
|
|
|
4074
|
-
def run(self, file_store:
|
|
4064
|
+
def run(self, file_store: AbstractFileStore) -> Promised[dict[str, FileID]]:
|
|
4075
4065
|
"""
|
|
4076
4066
|
Import the workflow inputs and then create and run the workflow.
|
|
4077
4067
|
:return: Promise of workflow outputs
|
|
@@ -4088,7 +4078,7 @@ class ImportsJob(Job):
|
|
|
4088
4078
|
|
|
4089
4079
|
def __init__(
|
|
4090
4080
|
self,
|
|
4091
|
-
file_to_data:
|
|
4081
|
+
file_to_data: dict[str, FileMetadata],
|
|
4092
4082
|
max_batch_size: ParseableIndivisibleResource,
|
|
4093
4083
|
import_worker_disk: ParseableIndivisibleResource,
|
|
4094
4084
|
**kwargs: Any,
|
|
@@ -4107,11 +4097,20 @@ class ImportsJob(Job):
|
|
|
4107
4097
|
self._import_worker_disk = import_worker_disk
|
|
4108
4098
|
|
|
4109
4099
|
def run(
|
|
4110
|
-
self, file_store:
|
|
4111
|
-
) ->
|
|
4100
|
+
self, file_store: AbstractFileStore
|
|
4101
|
+
) -> tuple[Promised[dict[str, FileID]], dict[str, FileMetadata]]:
|
|
4112
4102
|
"""
|
|
4113
4103
|
Import the workflow inputs and then create and run the workflow.
|
|
4114
|
-
|
|
4104
|
+
|
|
4105
|
+
The two parts of the return value must be used together and cannot be
|
|
4106
|
+
safely split apart. To look up a FileID from an original filename, you
|
|
4107
|
+
must first look up the FileMetadata in the second dict, extract its
|
|
4108
|
+
.source (the normalized candidate URI), and then look that up in the
|
|
4109
|
+
first dict.
|
|
4110
|
+
|
|
4111
|
+
:return: Tuple of (candidate URI to FileID mapping, original filename
|
|
4112
|
+
to FileMetadata mapping). The candidate URI is stored in
|
|
4113
|
+
FileMetadata.source.
|
|
4115
4114
|
"""
|
|
4116
4115
|
max_batch_size = self._max_batch_size
|
|
4117
4116
|
file_to_data = self._file_to_data
|
|
@@ -4150,7 +4149,9 @@ class ImportsJob(Job):
|
|
|
4150
4149
|
# Create batch import jobs for each group of files
|
|
4151
4150
|
for batch in file_batches:
|
|
4152
4151
|
candidate_uris = [file_to_data[filename][0] for filename in batch]
|
|
4153
|
-
import_jobs.append(
|
|
4152
|
+
import_jobs.append(
|
|
4153
|
+
WorkerImportJob(candidate_uris, disk=self._import_worker_disk)
|
|
4154
|
+
)
|
|
4154
4155
|
|
|
4155
4156
|
for job in import_jobs:
|
|
4156
4157
|
self.addChild(job)
|
|
@@ -4178,7 +4179,7 @@ class Promise:
|
|
|
4178
4179
|
run function has been executed.
|
|
4179
4180
|
"""
|
|
4180
4181
|
|
|
4181
|
-
_jobstore:
|
|
4182
|
+
_jobstore: AbstractJobStore | None = None
|
|
4182
4183
|
"""
|
|
4183
4184
|
Caches the job store instance used during unpickling to prevent it from being instantiated
|
|
4184
4185
|
for each promise
|
|
@@ -4189,7 +4190,7 @@ class Promise:
|
|
|
4189
4190
|
A set of IDs of files containing promised values when we know we won't need them anymore
|
|
4190
4191
|
"""
|
|
4191
4192
|
|
|
4192
|
-
def __init__(self, job:
|
|
4193
|
+
def __init__(self, job: Job, path: Any):
|
|
4193
4194
|
"""
|
|
4194
4195
|
Initialize this promise.
|
|
4195
4196
|
|
|
@@ -4220,7 +4221,7 @@ class Promise:
|
|
|
4220
4221
|
return self.__class__, (jobStoreLocator, jobStoreFileID)
|
|
4221
4222
|
|
|
4222
4223
|
@staticmethod
|
|
4223
|
-
def __new__(cls, *args) ->
|
|
4224
|
+
def __new__(cls, *args) -> Promise:
|
|
4224
4225
|
"""Instantiate this Promise."""
|
|
4225
4226
|
if len(args) != 2:
|
|
4226
4227
|
raise RuntimeError(
|