toil 5.12.0__py3-none-any.whl → 6.1.0a1__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 (157) hide show
  1. toil/__init__.py +18 -13
  2. toil/batchSystems/abstractBatchSystem.py +21 -10
  3. toil/batchSystems/abstractGridEngineBatchSystem.py +2 -2
  4. toil/batchSystems/awsBatch.py +14 -14
  5. toil/batchSystems/contained_executor.py +3 -3
  6. toil/batchSystems/htcondor.py +0 -1
  7. toil/batchSystems/kubernetes.py +34 -31
  8. toil/batchSystems/local_support.py +3 -1
  9. toil/batchSystems/mesos/batchSystem.py +7 -7
  10. toil/batchSystems/options.py +32 -83
  11. toil/batchSystems/registry.py +104 -23
  12. toil/batchSystems/singleMachine.py +16 -13
  13. toil/batchSystems/slurm.py +3 -3
  14. toil/batchSystems/torque.py +0 -1
  15. toil/bus.py +6 -8
  16. toil/common.py +532 -743
  17. toil/cwl/__init__.py +28 -32
  18. toil/cwl/cwltoil.py +523 -520
  19. toil/cwl/utils.py +55 -10
  20. toil/fileStores/__init__.py +2 -2
  21. toil/fileStores/abstractFileStore.py +36 -11
  22. toil/fileStores/cachingFileStore.py +607 -530
  23. toil/fileStores/nonCachingFileStore.py +43 -10
  24. toil/job.py +140 -75
  25. toil/jobStores/abstractJobStore.py +147 -79
  26. toil/jobStores/aws/jobStore.py +23 -9
  27. toil/jobStores/aws/utils.py +1 -2
  28. toil/jobStores/fileJobStore.py +117 -19
  29. toil/jobStores/googleJobStore.py +16 -7
  30. toil/jobStores/utils.py +5 -6
  31. toil/leader.py +71 -43
  32. toil/lib/accelerators.py +10 -5
  33. toil/lib/aws/__init__.py +3 -14
  34. toil/lib/aws/ami.py +22 -9
  35. toil/lib/aws/iam.py +21 -13
  36. toil/lib/aws/session.py +2 -16
  37. toil/lib/aws/utils.py +4 -5
  38. toil/lib/compatibility.py +1 -1
  39. toil/lib/conversions.py +7 -3
  40. toil/lib/docker.py +22 -23
  41. toil/lib/ec2.py +10 -6
  42. toil/lib/ec2nodes.py +106 -100
  43. toil/lib/encryption/_nacl.py +2 -1
  44. toil/lib/generatedEC2Lists.py +325 -18
  45. toil/lib/io.py +21 -0
  46. toil/lib/misc.py +1 -1
  47. toil/lib/resources.py +1 -1
  48. toil/lib/threading.py +74 -26
  49. toil/options/common.py +738 -0
  50. toil/options/cwl.py +336 -0
  51. toil/options/wdl.py +32 -0
  52. toil/provisioners/abstractProvisioner.py +1 -4
  53. toil/provisioners/aws/__init__.py +3 -6
  54. toil/provisioners/aws/awsProvisioner.py +6 -0
  55. toil/provisioners/clusterScaler.py +3 -2
  56. toil/provisioners/gceProvisioner.py +2 -2
  57. toil/realtimeLogger.py +2 -1
  58. toil/resource.py +24 -18
  59. toil/server/app.py +2 -3
  60. toil/server/cli/wes_cwl_runner.py +4 -4
  61. toil/server/utils.py +1 -1
  62. toil/server/wes/abstract_backend.py +3 -2
  63. toil/server/wes/amazon_wes_utils.py +5 -4
  64. toil/server/wes/tasks.py +2 -3
  65. toil/server/wes/toil_backend.py +2 -10
  66. toil/server/wsgi_app.py +2 -0
  67. toil/serviceManager.py +12 -10
  68. toil/statsAndLogging.py +5 -1
  69. toil/test/__init__.py +29 -54
  70. toil/test/batchSystems/batchSystemTest.py +11 -111
  71. toil/test/batchSystems/test_slurm.py +3 -2
  72. toil/test/cwl/cwlTest.py +213 -90
  73. toil/test/cwl/glob_dir.cwl +15 -0
  74. toil/test/cwl/preemptible.cwl +21 -0
  75. toil/test/cwl/preemptible_expression.cwl +28 -0
  76. toil/test/cwl/revsort.cwl +1 -1
  77. toil/test/cwl/revsort2.cwl +1 -1
  78. toil/test/docs/scriptsTest.py +0 -1
  79. toil/test/jobStores/jobStoreTest.py +27 -16
  80. toil/test/lib/aws/test_iam.py +4 -14
  81. toil/test/lib/aws/test_utils.py +0 -3
  82. toil/test/lib/dockerTest.py +4 -4
  83. toil/test/lib/test_ec2.py +11 -16
  84. toil/test/mesos/helloWorld.py +4 -5
  85. toil/test/mesos/stress.py +1 -1
  86. toil/test/provisioners/aws/awsProvisionerTest.py +9 -5
  87. toil/test/provisioners/clusterScalerTest.py +6 -4
  88. toil/test/provisioners/clusterTest.py +14 -3
  89. toil/test/provisioners/gceProvisionerTest.py +0 -6
  90. toil/test/provisioners/restartScript.py +3 -2
  91. toil/test/server/serverTest.py +1 -1
  92. toil/test/sort/restart_sort.py +2 -1
  93. toil/test/sort/sort.py +2 -1
  94. toil/test/sort/sortTest.py +2 -13
  95. toil/test/src/autoDeploymentTest.py +45 -45
  96. toil/test/src/busTest.py +5 -5
  97. toil/test/src/checkpointTest.py +2 -2
  98. toil/test/src/deferredFunctionTest.py +1 -1
  99. toil/test/src/fileStoreTest.py +32 -16
  100. toil/test/src/helloWorldTest.py +1 -1
  101. toil/test/src/importExportFileTest.py +1 -1
  102. toil/test/src/jobDescriptionTest.py +2 -1
  103. toil/test/src/jobServiceTest.py +1 -1
  104. toil/test/src/jobTest.py +18 -18
  105. toil/test/src/miscTests.py +5 -3
  106. toil/test/src/promisedRequirementTest.py +3 -3
  107. toil/test/src/realtimeLoggerTest.py +1 -1
  108. toil/test/src/resourceTest.py +2 -2
  109. toil/test/src/restartDAGTest.py +1 -1
  110. toil/test/src/resumabilityTest.py +36 -2
  111. toil/test/src/retainTempDirTest.py +1 -1
  112. toil/test/src/systemTest.py +2 -2
  113. toil/test/src/toilContextManagerTest.py +2 -2
  114. toil/test/src/userDefinedJobArgTypeTest.py +1 -1
  115. toil/test/utils/toilDebugTest.py +98 -32
  116. toil/test/utils/toilKillTest.py +2 -2
  117. toil/test/utils/utilsTest.py +20 -0
  118. toil/test/wdl/wdltoil_test.py +148 -45
  119. toil/toilState.py +7 -6
  120. toil/utils/toilClean.py +1 -1
  121. toil/utils/toilConfig.py +36 -0
  122. toil/utils/toilDebugFile.py +60 -33
  123. toil/utils/toilDebugJob.py +39 -12
  124. toil/utils/toilDestroyCluster.py +1 -1
  125. toil/utils/toilKill.py +1 -1
  126. toil/utils/toilLaunchCluster.py +13 -2
  127. toil/utils/toilMain.py +3 -2
  128. toil/utils/toilRsyncCluster.py +1 -1
  129. toil/utils/toilSshCluster.py +1 -1
  130. toil/utils/toilStats.py +240 -143
  131. toil/utils/toilStatus.py +1 -4
  132. toil/version.py +11 -11
  133. toil/wdl/utils.py +2 -122
  134. toil/wdl/wdltoil.py +999 -386
  135. toil/worker.py +25 -31
  136. {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/METADATA +60 -53
  137. toil-6.1.0a1.dist-info/RECORD +237 -0
  138. {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/WHEEL +1 -1
  139. {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/entry_points.txt +0 -1
  140. toil/batchSystems/parasol.py +0 -379
  141. toil/batchSystems/tes.py +0 -459
  142. toil/test/batchSystems/parasolTestSupport.py +0 -117
  143. toil/test/wdl/builtinTest.py +0 -506
  144. toil/test/wdl/conftest.py +0 -23
  145. toil/test/wdl/toilwdlTest.py +0 -522
  146. toil/wdl/toilwdl.py +0 -141
  147. toil/wdl/versions/dev.py +0 -107
  148. toil/wdl/versions/draft2.py +0 -980
  149. toil/wdl/versions/v1.py +0 -794
  150. toil/wdl/wdl_analysis.py +0 -116
  151. toil/wdl/wdl_functions.py +0 -997
  152. toil/wdl/wdl_synthesis.py +0 -1011
  153. toil/wdl/wdl_types.py +0 -243
  154. toil-5.12.0.dist-info/RECORD +0 -244
  155. /toil/{wdl/versions → options}/__init__.py +0 -0
  156. {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/LICENSE +0 -0
  157. {toil-5.12.0.dist-info → toil-6.1.0a1.dist-info}/top_level.txt +0 -0
toil/lib/accelerators.py CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  import os
18
18
  import subprocess
19
- from typing import Dict, List, Optional, Set, Union
19
+ from typing import Dict, List, Set, Union, cast
20
20
  from xml.dom import minidom
21
21
 
22
22
  from toil.job import AcceleratorRequirement
@@ -92,10 +92,15 @@ def count_nvidia_gpus() -> int:
92
92
  # <https://github.com/common-workflow-language/cwltool/blob/6f29c59fb1b5426ef6f2891605e8fa2d08f1a8da/cwltool/cuda.py>
93
93
  # Some example output is here: <https://gist.github.com/loretoparisi/2620b777562c2dfd50d6b618b5f20867>
94
94
  try:
95
- return int(minidom.parseString(
96
- subprocess.check_output(["nvidia-smi", "-q", "-x"])
97
- ).getElementsByTagName("attached_gpus")[0].firstChild.data)
98
- except (FileNotFoundError, subprocess.CalledProcessError, IndexError, ValueError, PermissionError):
95
+ return int(
96
+ cast(
97
+ minidom.Text,
98
+ minidom.parseString(subprocess.check_output(["nvidia-smi", "-q", "-x"]))
99
+ .getElementsByTagName("attached_gpus")[0]
100
+ .firstChild,
101
+ ).data
102
+ )
103
+ except:
99
104
  return 0
100
105
 
101
106
  # TODO: Parse each gpu > product_name > text content and convert to some
toil/lib/aws/__init__.py CHANGED
@@ -11,27 +11,15 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- import collections
15
- import inspect
16
14
  import json
17
15
  import logging
18
16
  import os
19
17
  import re
20
18
  import socket
21
- import threading
22
- from functools import lru_cache
23
- from typing import (Any,
24
- Callable,
25
- Dict,
26
- Iterable,
27
- List,
28
- MutableMapping,
29
- Optional,
30
- TypeVar,
31
- Union)
19
+ from http.client import HTTPException
20
+ from typing import Dict, MutableMapping, Optional
32
21
  from urllib.error import URLError
33
22
  from urllib.request import urlopen
34
- from http.client import HTTPException
35
23
 
36
24
  logger = logging.getLogger(__name__)
37
25
 
@@ -80,6 +68,7 @@ def get_aws_zone_from_metadata() -> Optional[str]:
80
68
  try:
81
69
  # Use the EC2 metadata service
82
70
  import boto
71
+ str(boto) # to prevent removal of the import
83
72
  from boto.utils import get_instance_metadata
84
73
  logger.debug("Fetch AZ from EC2 metadata")
85
74
  return get_instance_metadata()['placement']['availability-zone']
toil/lib/aws/ami.py CHANGED
@@ -1,12 +1,12 @@
1
1
  import json
2
2
  import logging
3
3
  import os
4
- import time
5
4
  import urllib.request
6
- from urllib.error import HTTPError
7
- from typing import Dict, Optional, Iterator, cast
5
+ from typing import Dict, Iterator, Optional, cast
6
+ from urllib.error import HTTPError, URLError
8
7
 
9
8
  from botocore.client import BaseClient
9
+ from botocore.exceptions import ClientError
10
10
 
11
11
  from toil.lib.retry import retry
12
12
 
@@ -110,6 +110,12 @@ def flatcar_release_feed_amis(region: str, architecture: str = 'amd64', source:
110
110
  # Try again
111
111
  try_number += 1
112
112
  continue
113
+ except URLError:
114
+ # Could be a connection timeout
115
+ logger.exception(f'Failed to retrieve {source} Flatcar release feed JSON')
116
+ # Try again
117
+ try_number += 1
118
+ continue
113
119
  if try_number == MAX_TRIES:
114
120
  # We could not get the JSON
115
121
  logger.error(f'Could not get a readable {source} Flatcar release feed JSON')
@@ -150,11 +156,18 @@ def feed_flatcar_ami_release(ec2_client: BaseClient, architecture: str = 'amd64'
150
156
 
151
157
  for ami in flatcar_release_feed_amis(region, architecture, source):
152
158
  # verify it exists on AWS
153
- response = ec2_client.describe_images(Filters=[{'Name': 'image-id', 'Values': [ami]}]) # type: ignore
154
- if len(response['Images']) == 1 and response['Images'][0]['State'] == 'available':
155
- return ami
156
- else:
157
- logger.warning(f'Flatcar release feed suggests image {ami} which does not exist on AWS in {region}')
159
+ try:
160
+ response = ec2_client.describe_images(Filters=[{'Name': 'image-id', 'Values': [ami]}]) # type: ignore
161
+ if len(response['Images']) == 1 and response['Images'][0]['State'] == 'available':
162
+ return ami
163
+ else:
164
+ logger.warning(f'Flatcar release feed suggests image {ami} which does not exist on AWS in {region}')
165
+ except ClientError:
166
+ # Sometimes we get back nonsense like:
167
+ # botocore.exceptions.ClientError: An error occurred (AuthFailure) when calling the DescribeImages operation: AWS was not able to validate the provided access credentials
168
+ # Don't hold that against the AMI.
169
+ logger.exception(f'Unable to check if AMI {ami} exists on AWS in {region}; assuming it does')
170
+ return ami
158
171
  # We didn't find it
159
172
  logger.warning(f'Flatcar release feed does not have an image for region {region} that exists on AWS')
160
173
  return None
@@ -162,7 +175,7 @@ def feed_flatcar_ami_release(ec2_client: BaseClient, architecture: str = 'amd64'
162
175
 
163
176
  @retry() # TODO: What errors do we get for timeout, JSON parse failure, etc?
164
177
  def aws_marketplace_flatcar_ami_search(ec2_client: BaseClient, architecture: str = 'amd64') -> Optional[str]:
165
- """Query AWS for all AMI names matching 'Flatcar-stable-*' and return the most recent one."""
178
+ """Query AWS for all AMI names matching ``Flatcar-stable-*`` and return the most recent one."""
166
179
 
167
180
  # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.describe_images
168
181
  # Possible arch choices on AWS: 'i386'|'x86_64'|'arm64'|'x86_64_mac'
toil/lib/aws/iam.py CHANGED
@@ -3,16 +3,15 @@ import json
3
3
  import logging
4
4
  from collections import defaultdict
5
5
  from functools import lru_cache
6
- from typing import Any, Dict, List, Optional, Set, cast, Union, Sequence
6
+ from typing import Dict, List, Optional, Union, cast
7
7
 
8
8
  import boto3
9
9
  from mypy_boto3_iam import IAMClient
10
- from mypy_boto3_iam.type_defs import AttachedPolicyTypeDef
10
+ from mypy_boto3_iam.type_defs import (AttachedPolicyTypeDef,
11
+ PolicyDocumentDictTypeDef)
11
12
  from mypy_boto3_sts import STSClient
12
13
 
13
- from toil.lib.aws import zone_to_region
14
14
  from toil.lib.aws.session import client as get_client
15
- from toil.provisioners.aws import get_best_aws_zone
16
15
 
17
16
  logger = logging.getLogger(__name__)
18
17
 
@@ -121,7 +120,7 @@ def permission_matches_any(perm: str, list_perms: List[str]) -> bool:
121
120
  return True
122
121
  return False
123
122
 
124
- def get_actions_from_policy_document(policy_doc: Dict[str, Any]) -> AllowedActionCollection:
123
+ def get_actions_from_policy_document(policy_doc: PolicyDocumentDictTypeDef) -> AllowedActionCollection:
125
124
  '''
126
125
  Given a policy document, go through each statement and create an AllowedActionCollection representing the
127
126
  permissions granted in the policy document.
@@ -138,11 +137,16 @@ def get_actions_from_policy_document(policy_doc: Dict[str, Any]) -> AllowedActio
138
137
  for resource in statement["Resource"]:
139
138
  for key in ["Action", "NotAction"]:
140
139
  if key in statement.keys():
141
- if isinstance(statement[key], list):
142
- allowed_actions[resource][key] += statement[key]
140
+ # mypy_boto3_iam declares policy document as a TypedDict
141
+ # This type expects 4 string keys, of which NotAction is not an option
142
+ # Thus mypy complains. NotAction seems to be valid according to Amazon:
143
+ # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_notaction.html
144
+ # so type: ignore for now
145
+ if isinstance(statement[key], list): # type: ignore[literal-required]
146
+ allowed_actions[resource][key] += statement[key] # type: ignore[literal-required]
143
147
  else:
144
148
  #Assumes that if it isn't a list it's probably a string
145
- allowed_actions[resource][key].append(statement[key])
149
+ allowed_actions[resource][key].append(statement[key]) # type: ignore[literal-required]
146
150
 
147
151
  return allowed_actions
148
152
  def allowed_actions_attached(iam: IAMClient, attached_policies: List[AttachedPolicyTypeDef]) -> AllowedActionCollection:
@@ -181,24 +185,28 @@ def allowed_actions_roles(iam: IAMClient, policy_names: List[str], role_name: st
181
185
  PolicyName=policy_name
182
186
  )
183
187
  logger.debug("Checking role policy")
184
- policy_document = json.loads(role_policy["PolicyDocument"])
188
+ # PolicyDocument is now a TypedDict, but an instance of TypedDict is not an instance of dict?
189
+ if isinstance(role_policy["PolicyDocument"], str):
190
+ policy_document = json.loads(role_policy["PolicyDocument"])
191
+ else:
192
+ policy_document = role_policy["PolicyDocument"]
185
193
 
186
194
  allowed_actions = add_to_action_collection(allowed_actions, get_actions_from_policy_document(policy_document))
187
195
 
188
196
  return allowed_actions
189
197
 
190
198
 
191
- def collect_policy_actions(policy_documents: Sequence[Union[str, Dict[str, Any]]]) -> AllowedActionCollection:
199
+ def collect_policy_actions(policy_documents: List[Union[str, PolicyDocumentDictTypeDef]]) -> AllowedActionCollection:
192
200
  """
193
201
  Collect all of the actions allowed by the given policy documents into one AllowedActionCollection.
194
202
  """
195
203
  allowed_actions: AllowedActionCollection = init_action_collection()
196
204
  for policy_str in policy_documents:
197
205
  # sometimes a string is returned from the api, so convert to a dictionary
198
- if isinstance(policy_str, dict):
199
- policy_dict = policy_str
200
- else:
206
+ if isinstance(policy_str, str):
201
207
  policy_dict = json.loads(policy_str)
208
+ else:
209
+ policy_dict = policy_str
202
210
  allowed_actions = add_to_action_collection(allowed_actions, get_actions_from_policy_document(policy_dict))
203
211
  return allowed_actions
204
212
 
toil/lib/aws/session.py CHANGED
@@ -12,24 +12,10 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  import collections
15
- import inspect
16
15
  import logging
17
16
  import os
18
- import re
19
- import socket
20
17
  import threading
21
- from typing import (Any,
22
- Callable,
23
- Dict,
24
- Iterable,
25
- List,
26
- Optional,
27
- Tuple,
28
- TypeVar,
29
- Union,
30
- cast)
31
- from urllib.error import URLError
32
- from urllib.request import urlopen
18
+ from typing import Dict, Optional, Tuple, cast
33
19
 
34
20
  import boto3
35
21
  import boto3.resources.base
@@ -37,8 +23,8 @@ import boto.connection
37
23
  import botocore
38
24
  from boto3 import Session
39
25
  from botocore.client import Config
40
- from botocore.utils import JSONFileCache
41
26
  from botocore.session import get_session
27
+ from botocore.utils import JSONFileCache
42
28
 
43
29
  logger = logging.getLogger(__name__)
44
30
 
toil/lib/aws/utils.py CHANGED
@@ -12,7 +12,6 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  import errno
15
- import json
16
15
  import logging
17
16
  import os
18
17
  import socket
@@ -21,15 +20,13 @@ from typing import (Any,
21
20
  Callable,
22
21
  ContextManager,
23
22
  Dict,
24
- Hashable,
25
23
  Iterable,
26
24
  Iterator,
27
25
  List,
28
26
  Optional,
29
27
  Set,
30
28
  Union,
31
- cast,
32
- MutableMapping)
29
+ cast)
33
30
  from urllib.parse import ParseResult
34
31
 
35
32
  from toil.lib.aws import session
@@ -345,6 +342,8 @@ def get_object_for_url(url: ParseResult, existing: Optional[bool] = None) -> "Ob
345
342
  """
346
343
  Extracts a key (object) from a given parsed s3:// URL.
347
344
 
345
+ If existing is true and the object does not exist, raises FileNotFoundError.
346
+
348
347
  :param bool existing: If True, key is expected to exist. If False, key is expected not to
349
348
  exists and it will be created. If None, the key will be created if it doesn't exist.
350
349
  """
@@ -386,7 +385,7 @@ def get_object_for_url(url: ParseResult, existing: Optional[bool] = None) -> "Ob
386
385
  else:
387
386
  raise
388
387
  if existing is True and not objExists:
389
- raise RuntimeError(f"Key '{key_name}' does not exist in bucket '{bucket_name}'.")
388
+ raise FileNotFoundError(f"Key '{key_name}' does not exist in bucket '{bucket_name}'.")
390
389
  elif existing is False and objExists:
391
390
  raise RuntimeError(f"Key '{key_name}' exists in bucket '{bucket_name}'.")
392
391
 
toil/lib/compatibility.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import functools
2
2
  import warnings
3
- from typing import Any, Dict, Callable, Union, TypeVar, overload
3
+ from typing import Any, Callable, Union
4
4
 
5
5
 
6
6
  def deprecated(new_function_name: str) -> Callable[..., Any]:
toil/lib/conversions.py CHANGED
@@ -46,8 +46,10 @@ def convert_units(num: float,
46
46
  src_unit: str,
47
47
  dst_unit: str = 'B') -> float:
48
48
  """Returns a float representing the converted input in dst_units."""
49
- assert src_unit.lower() in VALID_PREFIXES, f"{src_unit} not a valid unit, valid units are {VALID_PREFIXES}."
50
- assert dst_unit.lower() in VALID_PREFIXES, f"{dst_unit} not a valid unit, valid units are {VALID_PREFIXES}."
49
+ if not src_unit.lower() in VALID_PREFIXES:
50
+ raise RuntimeError(f"{src_unit} not a valid unit, valid units are {VALID_PREFIXES}.")
51
+ if not dst_unit.lower() in VALID_PREFIXES:
52
+ raise RuntimeError(f"{dst_unit} not a valid unit, valid units are {VALID_PREFIXES}.")
51
53
  return (num * bytes_in_unit(src_unit)) / bytes_in_unit(dst_unit)
52
54
 
53
55
 
@@ -60,7 +62,8 @@ def parse_memory_string(string: str) -> Tuple[float, str]:
60
62
  # find the first character of the unit
61
63
  if character not in '0123456789.-_ ':
62
64
  units = string[i:].strip()
63
- assert units.lower() in VALID_PREFIXES, f"{units} not a valid unit, valid units are {VALID_PREFIXES}."
65
+ if not units.lower() in VALID_PREFIXES:
66
+ raise RuntimeError(f"{units} not a valid unit, valid units are {VALID_PREFIXES}.")
64
67
  return float(string[:i]), units
65
68
  return float(string), 'b'
66
69
 
@@ -71,6 +74,7 @@ def human2bytes(string: str) -> int:
71
74
  integer number of bytes.
72
75
  """
73
76
  value, unit = parse_memory_string(string)
77
+
74
78
  return int(convert_units(value, src_unit=unit, dst_unit='b'))
75
79
 
76
80
 
toil/lib/docker.py CHANGED
@@ -17,7 +17,7 @@ import os
17
17
  import re
18
18
  import struct
19
19
  from shlex import quote
20
- from typing import Optional, List
20
+ from typing import List, Optional
21
21
 
22
22
  import requests
23
23
 
@@ -27,7 +27,6 @@ from docker.errors import (ContainerError,
27
27
  NotFound,
28
28
  create_api_error_from_http_exception)
29
29
  from docker.utils.socket import consume_socket_output, demux_adaptor
30
-
31
30
  from toil.lib.accelerators import get_host_accelerator_numbers
32
31
 
33
32
  logger = logging.getLogger(__name__)
@@ -84,16 +83,17 @@ def apiDockerCall(job,
84
83
  jobs, with the intention that failed/orphaned docker jobs be handled
85
84
  appropriately.
86
85
 
87
- Example of using dockerCall in toil to index a FASTA file with SAMtools:
88
- def toil_job(job):
89
- working_dir = job.fileStore.getLocalTempDir()
90
- path = job.fileStore.readGlobalFile(ref_id,
91
- os.path.join(working_dir, 'ref.fasta')
92
- parameters = ['faidx', path]
93
- apiDockerCall(job,
94
- image='quay.io/ucgc_cgl/samtools:latest',
95
- working_dir=working_dir,
96
- parameters=parameters)
86
+ Example of using dockerCall in toil to index a FASTA file with SAMtools::
87
+
88
+ def toil_job(job):
89
+ working_dir = job.fileStore.getLocalTempDir()
90
+ path = job.fileStore.readGlobalFile(ref_id,
91
+ os.path.join(working_dir, 'ref.fasta')
92
+ parameters = ['faidx', path]
93
+ apiDockerCall(job,
94
+ image='quay.io/ucgc_cgl/samtools:latest',
95
+ working_dir=working_dir,
96
+ parameters=parameters)
97
97
 
98
98
  Note that when run with detach=False, or with detach=True and stdout=True
99
99
  or stderr=True, this is a blocking call. When run with detach=True and
@@ -103,13 +103,13 @@ def apiDockerCall(job,
103
103
  :param toil.Job.job job: The Job instance for the calling function.
104
104
  :param str image: Name of the Docker image to be used.
105
105
  (e.g. 'quay.io/ucsc_cgl/samtools:latest')
106
- :param list[str] parameters: A list of string elements. If there are
106
+ :param list[str] parameters: A list of string elements. If there are
107
107
  multiple elements, these will be joined with
108
- spaces. This handling of multiple elements
108
+ spaces. This handling of multiple elements
109
109
  provides backwards compatibility with previous
110
110
  versions which called docker using
111
111
  subprocess.check_call().
112
- **If list of lists: list[list[str]], then treat
112
+ If list of lists: list[list[str]], then treat
113
113
  as successive commands chained with pipe.
114
114
  :param str working_dir: The working directory.
115
115
  :param int deferParam: Action to take on the container upon job completion.
@@ -225,8 +225,8 @@ def apiDockerCall(job,
225
225
  working_dir = os.path.abspath(working_dir)
226
226
 
227
227
  # Ensure the user has passed a valid value for deferParam
228
- assert deferParam in (None, FORGO, STOP, RM), \
229
- 'Please provide a valid value for deferParam.'
228
+ if deferParam not in (None, FORGO, STOP, RM):
229
+ raise RuntimeError('Please provide a valid value for deferParam.')
230
230
 
231
231
  client = docker.from_env(version='auto', timeout=timeout)
232
232
 
@@ -413,12 +413,11 @@ def containerIsRunning(container_name: str, timeout: int = 365 * 24 * 60 * 60):
413
413
 
414
414
  :param container_name: Name of the container being checked.
415
415
  :param int timeout: Use the given timeout in seconds for interactions with
416
- the Docker daemon. Note that the underlying docker module is
417
- not always able to abort ongoing reads and writes in order
418
- to respect the timeout. Defaults to 1 year (i.e. wait
419
- essentially indefinitely).
416
+ the Docker daemon. Note that the underlying docker module is not always
417
+ able to abort ongoing reads and writes in order to respect the timeout.
418
+ Defaults to 1 year (i.e. wait essentially indefinitely).
420
419
  :returns: True if status is 'running', False if status is anything else,
421
- and None if the container does not exist.
420
+ and None if the container does not exist.
422
421
  """
423
422
  client = docker.from_env(version='auto', timeout=timeout)
424
423
  try:
@@ -439,7 +438,7 @@ def containerIsRunning(container_name: str, timeout: int = 365 * 24 * 60 * 60):
439
438
  def getContainerName(job):
440
439
  """
441
440
  Create a random string including the job name, and return it. Name will
442
- match [a-zA-Z0-9][a-zA-Z0-9_.-]
441
+ match ``[a-zA-Z0-9][a-zA-Z0-9_.-]``.
443
442
  """
444
443
  parts = ['toil', str(job.description), base64.b64encode(os.urandom(9), b'-_').decode('utf-8')]
445
444
  name = re.sub('[^a-zA-Z0-9_.-]', '', '--'.join(parts))
toil/lib/ec2.py CHANGED
@@ -103,11 +103,13 @@ def wait_instances_running(ec2, instances: Iterable[Boto2Instance]) -> Iterable[
103
103
  if i.state == 'pending':
104
104
  pending_ids.add(i.id)
105
105
  elif i.state == 'running':
106
- assert i.id not in running_ids
106
+ if i.id in running_ids:
107
+ raise RuntimeError("An instance was already added to the list of running instance IDs. Maybe there is a duplicate.")
107
108
  running_ids.add(i.id)
108
109
  yield i
109
110
  else:
110
- assert i.id not in other_ids
111
+ if i.id in other_ids:
112
+ raise RuntimeError("An instance was already added to the list of other instances. Maybe there is a duplicate.")
111
113
  other_ids.add(i.id)
112
114
  yield i
113
115
  logger.info('%i instance(s) pending, %i running, %i other.',
@@ -130,10 +132,10 @@ def wait_spot_requests_active(ec2, requests: Iterable[SpotInstanceRequest], time
130
132
  :param requests: The requests to wait on.
131
133
 
132
134
  :param timeout: Maximum time in seconds to spend waiting or None to wait forever. If a
133
- timeout occurs, the remaining open requests will be cancelled.
135
+ timeout occurs, the remaining open requests will be cancelled.
134
136
 
135
137
  :param tentative: if True, give up on a spot request at the earliest indication of it
136
- not being fulfilled immediately
138
+ not being fulfilled immediately
137
139
 
138
140
  """
139
141
 
@@ -166,11 +168,13 @@ def wait_spot_requests_active(ec2, requests: Iterable[SpotInstanceRequest], time
166
168
  'Request %s entered status %s indicating that it will not be '
167
169
  'fulfilled anytime soon.', r.id, r.status.code)
168
170
  elif r.state == 'active':
169
- assert r.id not in active_ids
171
+ if r.id in active_ids:
172
+ raise RuntimeError("A request was already added to the list of active requests. Maybe there are duplicate requests.")
170
173
  active_ids.add(r.id)
171
174
  batch.append(r)
172
175
  else:
173
- assert r.id not in other_ids
176
+ if r.id in other_ids:
177
+ raise RuntimeError("A request was already added to the list of other IDs. Maybe there are duplicate requests.")
174
178
  other_ids.add(r.id)
175
179
  batch.append(r)
176
180
  if batch: