toil 6.1.0a1__py3-none-any.whl → 8.0.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 (193) hide show
  1. toil/__init__.py +122 -315
  2. toil/batchSystems/__init__.py +1 -0
  3. toil/batchSystems/abstractBatchSystem.py +173 -89
  4. toil/batchSystems/abstractGridEngineBatchSystem.py +272 -148
  5. toil/batchSystems/awsBatch.py +244 -135
  6. toil/batchSystems/cleanup_support.py +26 -16
  7. toil/batchSystems/contained_executor.py +31 -28
  8. toil/batchSystems/gridengine.py +86 -50
  9. toil/batchSystems/htcondor.py +166 -89
  10. toil/batchSystems/kubernetes.py +632 -382
  11. toil/batchSystems/local_support.py +20 -15
  12. toil/batchSystems/lsf.py +134 -81
  13. toil/batchSystems/lsfHelper.py +13 -11
  14. toil/batchSystems/mesos/__init__.py +41 -29
  15. toil/batchSystems/mesos/batchSystem.py +290 -151
  16. toil/batchSystems/mesos/executor.py +79 -50
  17. toil/batchSystems/mesos/test/__init__.py +31 -23
  18. toil/batchSystems/options.py +46 -28
  19. toil/batchSystems/registry.py +53 -19
  20. toil/batchSystems/singleMachine.py +296 -125
  21. toil/batchSystems/slurm.py +603 -138
  22. toil/batchSystems/torque.py +47 -33
  23. toil/bus.py +186 -76
  24. toil/common.py +664 -368
  25. toil/cwl/__init__.py +1 -1
  26. toil/cwl/cwltoil.py +1136 -483
  27. toil/cwl/utils.py +17 -22
  28. toil/deferred.py +63 -42
  29. toil/exceptions.py +5 -3
  30. toil/fileStores/__init__.py +5 -5
  31. toil/fileStores/abstractFileStore.py +140 -60
  32. toil/fileStores/cachingFileStore.py +717 -269
  33. toil/fileStores/nonCachingFileStore.py +116 -87
  34. toil/job.py +1225 -368
  35. toil/jobStores/abstractJobStore.py +416 -266
  36. toil/jobStores/aws/jobStore.py +863 -477
  37. toil/jobStores/aws/utils.py +201 -120
  38. toil/jobStores/conftest.py +3 -2
  39. toil/jobStores/fileJobStore.py +292 -154
  40. toil/jobStores/googleJobStore.py +140 -74
  41. toil/jobStores/utils.py +36 -15
  42. toil/leader.py +668 -272
  43. toil/lib/accelerators.py +115 -18
  44. toil/lib/aws/__init__.py +74 -31
  45. toil/lib/aws/ami.py +122 -87
  46. toil/lib/aws/iam.py +284 -108
  47. toil/lib/aws/s3.py +31 -0
  48. toil/lib/aws/session.py +214 -39
  49. toil/lib/aws/utils.py +287 -231
  50. toil/lib/bioio.py +13 -5
  51. toil/lib/compatibility.py +11 -6
  52. toil/lib/conversions.py +104 -47
  53. toil/lib/docker.py +131 -103
  54. toil/lib/ec2.py +361 -199
  55. toil/lib/ec2nodes.py +174 -106
  56. toil/lib/encryption/_dummy.py +5 -3
  57. toil/lib/encryption/_nacl.py +10 -6
  58. toil/lib/encryption/conftest.py +1 -0
  59. toil/lib/exceptions.py +26 -7
  60. toil/lib/expando.py +5 -3
  61. toil/lib/ftp_utils.py +217 -0
  62. toil/lib/generatedEC2Lists.py +127 -19
  63. toil/lib/humanize.py +6 -2
  64. toil/lib/integration.py +341 -0
  65. toil/lib/io.py +141 -15
  66. toil/lib/iterables.py +4 -2
  67. toil/lib/memoize.py +12 -8
  68. toil/lib/misc.py +66 -21
  69. toil/lib/objects.py +2 -2
  70. toil/lib/resources.py +68 -15
  71. toil/lib/retry.py +126 -81
  72. toil/lib/threading.py +299 -82
  73. toil/lib/throttle.py +16 -15
  74. toil/options/common.py +843 -409
  75. toil/options/cwl.py +175 -90
  76. toil/options/runner.py +50 -0
  77. toil/options/wdl.py +73 -17
  78. toil/provisioners/__init__.py +117 -46
  79. toil/provisioners/abstractProvisioner.py +332 -157
  80. toil/provisioners/aws/__init__.py +70 -33
  81. toil/provisioners/aws/awsProvisioner.py +1145 -715
  82. toil/provisioners/clusterScaler.py +541 -279
  83. toil/provisioners/gceProvisioner.py +282 -179
  84. toil/provisioners/node.py +155 -79
  85. toil/realtimeLogger.py +34 -22
  86. toil/resource.py +137 -75
  87. toil/server/app.py +128 -62
  88. toil/server/celery_app.py +3 -1
  89. toil/server/cli/wes_cwl_runner.py +82 -53
  90. toil/server/utils.py +54 -28
  91. toil/server/wes/abstract_backend.py +64 -26
  92. toil/server/wes/amazon_wes_utils.py +21 -15
  93. toil/server/wes/tasks.py +121 -63
  94. toil/server/wes/toil_backend.py +142 -107
  95. toil/server/wsgi_app.py +4 -3
  96. toil/serviceManager.py +58 -22
  97. toil/statsAndLogging.py +224 -70
  98. toil/test/__init__.py +282 -183
  99. toil/test/batchSystems/batchSystemTest.py +460 -210
  100. toil/test/batchSystems/batch_system_plugin_test.py +90 -0
  101. toil/test/batchSystems/test_gridengine.py +173 -0
  102. toil/test/batchSystems/test_lsf_helper.py +67 -58
  103. toil/test/batchSystems/test_slurm.py +110 -49
  104. toil/test/cactus/__init__.py +0 -0
  105. toil/test/cactus/test_cactus_integration.py +56 -0
  106. toil/test/cwl/cwlTest.py +496 -287
  107. toil/test/cwl/measure_default_memory.cwl +12 -0
  108. toil/test/cwl/not_run_required_input.cwl +29 -0
  109. toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
  110. toil/test/cwl/seqtk_seq.cwl +1 -1
  111. toil/test/docs/scriptsTest.py +69 -46
  112. toil/test/jobStores/jobStoreTest.py +427 -264
  113. toil/test/lib/aws/test_iam.py +118 -50
  114. toil/test/lib/aws/test_s3.py +16 -9
  115. toil/test/lib/aws/test_utils.py +5 -6
  116. toil/test/lib/dockerTest.py +118 -141
  117. toil/test/lib/test_conversions.py +113 -115
  118. toil/test/lib/test_ec2.py +58 -50
  119. toil/test/lib/test_integration.py +104 -0
  120. toil/test/lib/test_misc.py +12 -5
  121. toil/test/mesos/MesosDataStructuresTest.py +23 -10
  122. toil/test/mesos/helloWorld.py +7 -6
  123. toil/test/mesos/stress.py +25 -20
  124. toil/test/options/__init__.py +13 -0
  125. toil/test/options/options.py +42 -0
  126. toil/test/provisioners/aws/awsProvisionerTest.py +320 -150
  127. toil/test/provisioners/clusterScalerTest.py +440 -250
  128. toil/test/provisioners/clusterTest.py +166 -44
  129. toil/test/provisioners/gceProvisionerTest.py +174 -100
  130. toil/test/provisioners/provisionerTest.py +25 -13
  131. toil/test/provisioners/restartScript.py +5 -4
  132. toil/test/server/serverTest.py +188 -141
  133. toil/test/sort/restart_sort.py +137 -68
  134. toil/test/sort/sort.py +134 -66
  135. toil/test/sort/sortTest.py +91 -49
  136. toil/test/src/autoDeploymentTest.py +141 -101
  137. toil/test/src/busTest.py +20 -18
  138. toil/test/src/checkpointTest.py +8 -2
  139. toil/test/src/deferredFunctionTest.py +49 -35
  140. toil/test/src/dockerCheckTest.py +32 -24
  141. toil/test/src/environmentTest.py +135 -0
  142. toil/test/src/fileStoreTest.py +539 -272
  143. toil/test/src/helloWorldTest.py +7 -4
  144. toil/test/src/importExportFileTest.py +61 -31
  145. toil/test/src/jobDescriptionTest.py +46 -21
  146. toil/test/src/jobEncapsulationTest.py +2 -0
  147. toil/test/src/jobFileStoreTest.py +74 -50
  148. toil/test/src/jobServiceTest.py +187 -73
  149. toil/test/src/jobTest.py +121 -71
  150. toil/test/src/miscTests.py +19 -18
  151. toil/test/src/promisedRequirementTest.py +82 -36
  152. toil/test/src/promisesTest.py +7 -6
  153. toil/test/src/realtimeLoggerTest.py +10 -6
  154. toil/test/src/regularLogTest.py +71 -37
  155. toil/test/src/resourceTest.py +80 -49
  156. toil/test/src/restartDAGTest.py +36 -22
  157. toil/test/src/resumabilityTest.py +9 -2
  158. toil/test/src/retainTempDirTest.py +45 -14
  159. toil/test/src/systemTest.py +12 -8
  160. toil/test/src/threadingTest.py +44 -25
  161. toil/test/src/toilContextManagerTest.py +10 -7
  162. toil/test/src/userDefinedJobArgTypeTest.py +8 -5
  163. toil/test/src/workerTest.py +73 -23
  164. toil/test/utils/toilDebugTest.py +103 -33
  165. toil/test/utils/toilKillTest.py +4 -5
  166. toil/test/utils/utilsTest.py +245 -106
  167. toil/test/wdl/wdltoil_test.py +818 -149
  168. toil/test/wdl/wdltoil_test_kubernetes.py +91 -0
  169. toil/toilState.py +120 -35
  170. toil/utils/toilConfig.py +13 -4
  171. toil/utils/toilDebugFile.py +44 -27
  172. toil/utils/toilDebugJob.py +214 -27
  173. toil/utils/toilDestroyCluster.py +11 -6
  174. toil/utils/toilKill.py +8 -3
  175. toil/utils/toilLaunchCluster.py +256 -140
  176. toil/utils/toilMain.py +37 -16
  177. toil/utils/toilRsyncCluster.py +32 -14
  178. toil/utils/toilSshCluster.py +49 -22
  179. toil/utils/toilStats.py +356 -273
  180. toil/utils/toilStatus.py +292 -139
  181. toil/utils/toilUpdateEC2Instances.py +3 -1
  182. toil/version.py +12 -12
  183. toil/wdl/utils.py +5 -5
  184. toil/wdl/wdltoil.py +3913 -1033
  185. toil/worker.py +367 -184
  186. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/LICENSE +25 -0
  187. toil-8.0.0.dist-info/METADATA +173 -0
  188. toil-8.0.0.dist-info/RECORD +253 -0
  189. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
  190. toil-6.1.0a1.dist-info/METADATA +0 -125
  191. toil-6.1.0a1.dist-info/RECORD +0 -237
  192. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
  193. {toil-6.1.0a1.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
toil/lib/ec2nodes.py CHANGED
@@ -16,74 +16,90 @@ import json
16
16
  import logging
17
17
  import os
18
18
  import re
19
- import textwrap
20
- import requests
21
19
  import shutil
22
- import enlighten # type: ignore
23
-
24
- from typing import Dict, List, Tuple, Union, Any
20
+ import textwrap
21
+ from typing import Any, Union
25
22
 
23
+ import enlighten # type: ignore
24
+ import requests
26
25
 
27
26
  logger = logging.getLogger(__name__)
28
27
  manager = enlighten.get_manager()
29
28
  dirname = os.path.dirname(__file__)
30
- region_json_dirname = os.path.join(dirname, 'region_jsons')
31
-
32
-
33
- EC2Regions = {'us-west-1': 'US West (N. California)',
34
- 'us-west-2': 'US West (Oregon)',
35
- 'us-east-1': 'US East (N. Virginia)',
36
- 'us-east-2': 'US East (Ohio)',
37
- 'us-gov-west-1': 'AWS GovCloud (US)',
38
- 'ca-central-1': 'Canada (Central)',
39
- 'ap-northeast-1': 'Asia Pacific (Tokyo)',
40
- 'ap-northeast-2': 'Asia Pacific (Seoul)',
41
- 'ap-northeast-3': 'Asia Pacific (Osaka-Local)',
42
- 'ap-southeast-1': 'Asia Pacific (Singapore)',
43
- 'ap-southeast-2': 'Asia Pacific (Sydney)',
44
- 'ap-south-1': 'Asia Pacific (Mumbai)',
45
- 'eu-west-1': 'EU (Ireland)',
46
- 'eu-west-2': 'EU (London)',
47
- 'eu-west-3': 'EU (Paris)',
48
- 'eu-central-1': 'EU (Frankfurt)',
49
- 'sa-east-1': 'South America (Sao Paulo)'}
29
+ region_json_dirname = os.path.join(dirname, "region_jsons")
30
+
31
+
32
+ EC2Regions = {
33
+ "us-west-1": "US West (N. California)",
34
+ "us-west-2": "US West (Oregon)",
35
+ "us-east-1": "US East (N. Virginia)",
36
+ "us-east-2": "US East (Ohio)",
37
+ "us-gov-west-1": "AWS GovCloud (US)",
38
+ "ca-central-1": "Canada (Central)",
39
+ "ap-northeast-1": "Asia Pacific (Tokyo)",
40
+ "ap-northeast-2": "Asia Pacific (Seoul)",
41
+ "ap-northeast-3": "Asia Pacific (Osaka-Local)",
42
+ "ap-southeast-1": "Asia Pacific (Singapore)",
43
+ "ap-southeast-2": "Asia Pacific (Sydney)",
44
+ "ap-south-1": "Asia Pacific (Mumbai)",
45
+ "eu-west-1": "EU (Ireland)",
46
+ "eu-west-2": "EU (London)",
47
+ "eu-west-3": "EU (Paris)",
48
+ "eu-central-1": "EU (Frankfurt)",
49
+ "sa-east-1": "South America (Sao Paulo)",
50
+ }
50
51
 
51
52
 
52
53
  class InstanceType:
53
- __slots__ = ('name', 'cores', 'memory', 'disks', 'disk_capacity', 'architecture')
54
-
55
- def __init__(self, name: str, cores: int, memory: float, disks: float, disk_capacity: float, architecture: str):
54
+ __slots__ = ("name", "cores", "memory", "disks", "disk_capacity", "architecture")
55
+
56
+ def __init__(
57
+ self,
58
+ name: str,
59
+ cores: int,
60
+ memory: float,
61
+ disks: float,
62
+ disk_capacity: float,
63
+ architecture: str,
64
+ ):
56
65
  self.name = name # the API name of the instance type
57
66
  self.cores = cores # the number of cores
58
67
  self.memory = memory # RAM in GiB
59
68
  self.disks = disks # the number of ephemeral (aka 'instance store') volumes
60
- self.disk_capacity = disk_capacity # the capacity of each ephemeral volume in GiB
61
- self.architecture = architecture # the architecture of the instance type. Can be either amd64 or arm64
69
+ self.disk_capacity = (
70
+ disk_capacity # the capacity of each ephemeral volume in GiB
71
+ )
72
+ self.architecture = architecture # the architecture of the instance type. Can be either amd64 or arm64
62
73
 
63
74
  def __str__(self) -> str:
64
- return ("Type: {}\n"
65
- "Cores: {}\n"
66
- "Disks: {}\n"
67
- "Memory: {}\n"
68
- "Disk Capacity: {}\n"
69
- "Architecture: {}\n"
70
- "".format(
75
+ return (
76
+ "Type: {}\n"
77
+ "Cores: {}\n"
78
+ "Disks: {}\n"
79
+ "Memory: {}\n"
80
+ "Disk Capacity: {}\n"
81
+ "Architecture: {}\n"
82
+ "".format(
71
83
  self.name,
72
84
  self.cores,
73
85
  self.disks,
74
86
  self.memory,
75
87
  self.disk_capacity,
76
- self.architecture))
88
+ self.architecture,
89
+ )
90
+ )
77
91
 
78
92
  def __eq__(self, other: object) -> bool:
79
93
  if not isinstance(other, InstanceType):
80
94
  return NotImplemented
81
- if (self.name == other.name and
82
- self.cores == other.cores and
83
- self.memory == other.memory and
84
- self.disks == other.disks and
85
- self.disk_capacity == other.disk_capacity and
86
- self.architecture == other.architecture):
95
+ if (
96
+ self.name == other.name
97
+ and self.cores == other.cores
98
+ and self.memory == other.memory
99
+ and self.disks == other.disks
100
+ and self.disk_capacity == other.disk_capacity
101
+ and self.architecture == other.architecture
102
+ ):
87
103
  return True
88
104
  return False
89
105
 
@@ -95,7 +111,7 @@ def is_number(s: str) -> bool:
95
111
  :param s: Any unicode string.
96
112
  :return: True if s represents a number, False otherwise.
97
113
  """
98
- s = s.replace(',', '')
114
+ s = s.replace(",", "")
99
115
  try:
100
116
  float(s)
101
117
  return True
@@ -103,6 +119,7 @@ def is_number(s: str) -> bool:
103
119
  pass
104
120
  try:
105
121
  import unicodedata
122
+
106
123
  unicodedata.numeric(s)
107
124
  return True
108
125
  except (TypeError, ValueError) as e:
@@ -110,7 +127,9 @@ def is_number(s: str) -> bool:
110
127
  return False
111
128
 
112
129
 
113
- def parse_storage(storage_info: str) -> Union[List[int], Tuple[Union[int, float], float]]:
130
+ def parse_storage(
131
+ storage_info: str,
132
+ ) -> Union[list[int], tuple[Union[int, float], float]]:
114
133
  """
115
134
  Parses EC2 JSON storage param string into a number.
116
135
 
@@ -129,12 +148,21 @@ def parse_storage(storage_info: str) -> Union[List[int], Tuple[Union[int, float]
129
148
  return [0, 0]
130
149
  else:
131
150
  specs = storage_info.strip().split()
132
- if is_number(specs[0]) and specs[1] == 'x' and is_number(specs[2]):
133
- return float(specs[0].replace(',', '')), float(specs[2].replace(',', ''))
134
- elif is_number(specs[0]) and specs[1] == 'GB' and specs[2] == 'NVMe' and specs[3] == 'SSD':
135
- return 1, float(specs[0].replace(',', ''))
151
+ if is_number(specs[0]) and specs[1].lower() == "x" and is_number(specs[2]):
152
+ return float(specs[0].replace(",", "")), float(specs[2].replace(",", ""))
153
+ elif (
154
+ is_number(specs[0])
155
+ and specs[1] == "GB"
156
+ and specs[2] == "NVMe"
157
+ and specs[3] == "SSD"
158
+ ):
159
+ return 1, float(specs[0].replace(",", ""))
160
+ elif is_number(specs[0]) and specs[1].lower() == "x" and is_number(specs[2][:-2]) and specs[2][-2:] == "GB":
161
+ return float(specs[0].replace(",", "")), float(specs[2][:-2].replace(",", ""))
136
162
  else:
137
- raise RuntimeError('EC2 JSON format has likely changed. Error parsing disk specs.')
163
+ raise RuntimeError(
164
+ f"EC2 JSON format has likely changed. Error parsing disk specs : {storage_info.strip()}"
165
+ )
138
166
 
139
167
 
140
168
  def parse_memory(mem_info: str) -> float:
@@ -148,14 +176,14 @@ def parse_memory(mem_info: str) -> float:
148
176
  :param mem_info: EC2 JSON memory param string.
149
177
  :return: A float representing memory in GiB.
150
178
  """
151
- mem = mem_info.replace(',', '').split()
152
- if mem[1] == 'GiB':
179
+ mem = mem_info.replace(",", "").split()
180
+ if mem[1] == "GiB":
153
181
  return float(mem[0])
154
182
  else:
155
- raise RuntimeError('EC2 JSON format has likely changed. Error parsing memory.')
183
+ raise RuntimeError("EC2 JSON format has likely changed. Error parsing memory.")
156
184
 
157
185
 
158
- def download_region_json(filename: str, region: str = 'us-east-1') -> None:
186
+ def download_region_json(filename: str, region: str = "us-east-1") -> None:
159
187
  """
160
188
  Downloads and writes the AWS Billing JSON to a file using the AWS pricing API.
161
189
 
@@ -165,18 +193,25 @@ def download_region_json(filename: str, region: str = 'us-east-1') -> None:
165
193
  aws instance name (example: 't2.micro'), and the value is an
166
194
  InstanceType object representing that aws instance name.
167
195
  """
168
- response = requests.get(f'https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/{region}/index.json', stream=True)
196
+ response = requests.get(
197
+ f"https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonEC2/current/{region}/index.json",
198
+ stream=True,
199
+ )
169
200
  file_size = int(response.headers.get("content-length", 0))
170
- print(f'Downloading ~{file_size / 1000000000}Gb {region} AWS billing file to: {filename}')
201
+ print(
202
+ f"Downloading ~{file_size / 1000000000}Gb {region} AWS billing file to: {filename}"
203
+ )
171
204
 
172
- with manager.counter(total=file_size, desc=os.path.basename(filename), unit='bytes', leave=False) as progress_bar:
205
+ with manager.counter(
206
+ total=file_size, desc=os.path.basename(filename), unit="bytes", leave=False
207
+ ) as progress_bar:
173
208
  with open(filename, "wb") as file:
174
209
  for data in response.iter_content(1048576):
175
210
  progress_bar.update(len(data))
176
211
  file.write(data)
177
212
 
178
213
 
179
- def reduce_region_json_size(filename:str) -> List[Dict[str, Any]]:
214
+ def reduce_region_json_size(filename: str) -> list[dict[str, Any]]:
180
215
  """
181
216
  Deletes information in the json file that we don't need, and rewrites it. This makes the file smaller.
182
217
 
@@ -185,23 +220,31 @@ def reduce_region_json_size(filename:str) -> List[Dict[str, Any]]:
185
220
  (with AWS's new Query API), but even those may eventually one day grow ridiculously large, so we do what we can to
186
221
  keep the file sizes down (and thus also the amount loaded into memory) to keep this script working for longer.
187
222
  """
188
- with open(filename, 'r') as f:
189
- aws_products = json.loads(f.read())['products']
223
+ with open(filename) as f:
224
+ aws_products = json.loads(f.read())["products"]
190
225
  aws_product_list = list()
191
226
  for k in list(aws_products.keys()):
192
- ec2_attributes = aws_products[k]['attributes']
193
- if (ec2_attributes.get('tenancy') == 'Shared' and
194
- ec2_attributes.get('operatingSystem') == 'Linux' and
195
- ec2_attributes.get('operation') == 'RunInstances' and
196
- ec2_attributes.get('usagetype').endswith('BoxUsage:' + ec2_attributes['instanceType'])):
197
- aws_product_list.append(dict(disk=ec2_attributes["storage"],
198
- loc=ec2_attributes["location"],
199
- name=ec2_attributes["instanceType"],
200
- mem=ec2_attributes["memory"],
201
- cpu=ec2_attributes["vcpu"]))
227
+ ec2_attributes = aws_products[k]["attributes"]
228
+ if (
229
+ ec2_attributes.get("tenancy") == "Shared"
230
+ and ec2_attributes.get("operatingSystem") == "Linux"
231
+ and ec2_attributes.get("operation") == "RunInstances"
232
+ and ec2_attributes.get("usagetype").endswith(
233
+ "BoxUsage:" + ec2_attributes["instanceType"]
234
+ )
235
+ ):
236
+ aws_product_list.append(
237
+ dict(
238
+ disk=ec2_attributes["storage"],
239
+ loc=ec2_attributes["location"],
240
+ name=ec2_attributes["instanceType"],
241
+ mem=ec2_attributes["memory"],
242
+ cpu=ec2_attributes["vcpu"],
243
+ )
244
+ )
202
245
  del aws_products[k]
203
246
  del aws_products
204
- with open(filename, 'w') as f:
247
+ with open(filename, "w") as f:
205
248
  f.write(json.dumps(dict(aws=aws_product_list), indent=2))
206
249
  return aws_product_list
207
250
 
@@ -214,14 +257,20 @@ def updateStaticEC2Instances() -> None:
214
257
 
215
258
  :return: Nothing. Writes a new 'generatedEC2Lists.py' file.
216
259
  """
217
- print("Updating Toil's EC2 lists to the most current version from AWS's bulk API.\n"
218
- "This may take a while, depending on your internet connection.\n")
219
-
220
- original_aws_instance_list = os.path.join(dirname, 'generatedEC2Lists.py') # original
260
+ print(
261
+ "Updating Toil's EC2 lists to the most current version from AWS's bulk API.\n"
262
+ "This may take a while, depending on your internet connection.\n"
263
+ )
264
+
265
+ original_aws_instance_list = os.path.join(
266
+ dirname, "generatedEC2Lists.py"
267
+ ) # original
221
268
  if not os.path.exists(original_aws_instance_list):
222
269
  raise RuntimeError(f"Path {original_aws_instance_list} does not exist.")
223
270
  # use a temporary file until all info is fetched
224
- updated_aws_instance_list = os.path.join(dirname, 'generatedEC2Lists_tmp.py') # temp
271
+ updated_aws_instance_list = os.path.join(
272
+ dirname, "generatedEC2Lists_tmp.py"
273
+ ) # temp
225
274
  if os.path.exists(updated_aws_instance_list):
226
275
  os.remove(updated_aws_instance_list)
227
276
 
@@ -229,15 +278,15 @@ def updateStaticEC2Instances() -> None:
229
278
  os.mkdir(region_json_dirname)
230
279
 
231
280
  currentEC2List = []
232
- instancesByRegion: Dict[str, List[str]] = {}
281
+ instancesByRegion: dict[str, list[str]] = {}
233
282
  for region in EC2Regions.keys():
234
- region_json = os.path.join(region_json_dirname, f'{region}.json')
283
+ region_json = os.path.join(region_json_dirname, f"{region}.json")
235
284
 
236
285
  if os.path.exists(region_json):
237
286
  try:
238
- with open(region_json, 'r') as f:
239
- aws_products = json.loads(f.read())['aws']
240
- print(f'Reusing previously downloaded json @: {region_json}')
287
+ with open(region_json) as f:
288
+ aws_products = json.loads(f.read())["aws"]
289
+ print(f"Reusing previously downloaded json @: {region_json}")
241
290
  except:
242
291
  os.remove(region_json)
243
292
  download_region_json(filename=region_json, region=region)
@@ -251,14 +300,24 @@ def updateStaticEC2Instances() -> None:
251
300
  disks, disk_capacity = parse_storage(i["disk"])
252
301
  # Determines whether the instance type is from an ARM or AMD family
253
302
  # ARM instance names include a digit followed by a 'g' before the instance size
254
- architecture = 'arm64' if re.search(r".*\dg.*\..*", i["name"]) else 'amd64'
255
- ec2InstanceList.append(InstanceType(name=i["name"],
256
- cores=i["cpu"],
257
- memory=parse_memory(i["mem"]),
258
- disks=disks,
259
- disk_capacity=disk_capacity,
260
- architecture=architecture))
261
- print('Finished for ' + str(region) + '. ' + str(len(ec2InstanceList)) + ' added.\n')
303
+ architecture = "arm64" if re.search(r".*\dg.*\..*", i["name"]) else "amd64"
304
+ ec2InstanceList.append(
305
+ InstanceType(
306
+ name=i["name"],
307
+ cores=i["cpu"],
308
+ memory=parse_memory(i["mem"]),
309
+ disks=disks,
310
+ disk_capacity=disk_capacity,
311
+ architecture=architecture,
312
+ )
313
+ )
314
+ print(
315
+ "Finished for "
316
+ + str(region)
317
+ + ". "
318
+ + str(len(ec2InstanceList))
319
+ + " added.\n"
320
+ )
262
321
  currentEC2Dict = {_.name: _ for _ in ec2InstanceList}
263
322
  for instanceName, instanceTypeObj in currentEC2Dict.items():
264
323
  if instanceTypeObj not in currentEC2List:
@@ -266,8 +325,10 @@ def updateStaticEC2Instances() -> None:
266
325
  instancesByRegion.setdefault(region, []).append(instanceName)
267
326
 
268
327
  # write provenance note, copyright and imports
269
- with open(updated_aws_instance_list, 'w') as f:
270
- f.write(textwrap.dedent('''
328
+ with open(updated_aws_instance_list, "w") as f:
329
+ f.write(
330
+ textwrap.dedent(
331
+ """
271
332
  # !!! AUTOGENERATED FILE !!!
272
333
  # Update with: src/toil/utils/toilUpdateEC2Instances.py
273
334
  #
@@ -284,36 +345,41 @@ def updateStaticEC2Instances() -> None:
284
345
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
285
346
  # See the License for the specific language governing permissions and
286
347
  # limitations under the License.
287
- from toil.lib.ec2nodes import InstanceType\n\n\n''').format(year=datetime.date.today().strftime("%Y"))[1:])
348
+ from toil.lib.ec2nodes import InstanceType\n\n\n"""
349
+ ).format(year=datetime.date.today().strftime("%Y"))[1:]
350
+ )
288
351
 
289
352
  # write header of total EC2 instance type list
290
- genString = f'# {len(currentEC2List)} Instance Types. Generated {datetime.datetime.now()}.\n'
353
+ genString = f"# {len(currentEC2List)} Instance Types. Generated {datetime.datetime.now()}.\n"
291
354
  genString = genString + "E2Instances = {\n"
292
355
  sortedCurrentEC2List = sorted(currentEC2List, key=lambda x: x.name)
293
356
 
294
357
  # write the list of all instances types
295
358
  for i in sortedCurrentEC2List:
296
- genString = genString + f" '{i.name}': InstanceType(name='{i.name}', cores={i.cores}, memory={i.memory}, disks={i.disks}, disk_capacity={i.disk_capacity}, architecture='{i.architecture}'),\n"
297
- genString = genString + '}\n\n'
359
+ genString = (
360
+ genString
361
+ + f" '{i.name}': InstanceType(name='{i.name}', cores={i.cores}, memory={i.memory}, disks={i.disks}, disk_capacity={i.disk_capacity}, architecture='{i.architecture}'),\n"
362
+ )
363
+ genString = genString + "}\n\n"
298
364
 
299
- genString = genString + 'regionDict = {\n'
365
+ genString = genString + "regionDict = {\n"
300
366
  for regionName, instanceList in instancesByRegion.items():
301
367
  genString = genString + f" '{regionName}': ["
302
368
  for instance in sorted(instanceList):
303
369
  genString = genString + f"'{instance}', "
304
- if genString.endswith(', '):
370
+ if genString.endswith(", "):
305
371
  genString = genString[:-2]
306
- genString = genString + '],\n'
307
- if genString.endswith(',\n'):
308
- genString = genString[:-len(',\n')]
309
- genString = genString + '}\n'
310
- with open(updated_aws_instance_list, 'a+') as f:
372
+ genString = genString + "],\n"
373
+ if genString.endswith(",\n"):
374
+ genString = genString[: -len(",\n")]
375
+ genString = genString + "}\n"
376
+ with open(updated_aws_instance_list, "a+") as f:
311
377
  f.write(genString)
312
378
 
313
379
  # append key for fetching at the end
314
- regionKey = '\nec2InstancesByRegion = {region: [E2Instances[i] for i in instances] for region, instances in regionDict.items()}\n'
380
+ regionKey = "\nec2InstancesByRegion = {region: [E2Instances[i] for i in instances] for region, instances in regionDict.items()}\n"
315
381
 
316
- with open(updated_aws_instance_list, 'a+') as f:
382
+ with open(updated_aws_instance_list, "a+") as f:
317
383
  f.write(regionKey)
318
384
 
319
385
  # replace the instance list with a current list
@@ -321,5 +387,7 @@ def updateStaticEC2Instances() -> None:
321
387
 
322
388
  # delete the aws region json file directory
323
389
  if os.path.exists(region_json_dirname):
324
- print(f'Update Successful! Removing AWS Region JSON Files @: {region_json_dirname}')
390
+ print(
391
+ f"Update Successful! Removing AWS Region JSON Files @: {region_json_dirname}"
392
+ )
325
393
  shutil.rmtree(region_json_dirname)
@@ -27,6 +27,8 @@ def decrypt(ciphertext: bytes, keyPath: str) -> bytes:
27
27
 
28
28
 
29
29
  def _bail():
30
- raise NotImplementedError("Encryption support is not installed. Consider re-installing toil "
31
- "with the 'encryption' extra along with any other extras you might "
32
- "want, e.g. 'pip install toil[encryption,...]'.")
30
+ raise NotImplementedError(
31
+ "Encryption support is not installed. Consider re-installing toil "
32
+ "with the 'encryption' extra along with any other extras you might "
33
+ "want, e.g. 'pip install toil[encryption,...]'."
34
+ )
@@ -40,11 +40,13 @@ def encrypt(message: bytes, keyPath: str) -> bytes:
40
40
  >>> import os
41
41
  >>> os.remove(k)
42
42
  """
43
- with open(keyPath, 'rb') as f:
43
+ with open(keyPath, "rb") as f:
44
44
  key = f.read()
45
45
  if len(key) != SecretBox.KEY_SIZE:
46
- raise ValueError("Key is %d bytes, but must be exactly %d bytes" % (len(key),
47
- SecretBox.KEY_SIZE))
46
+ raise ValueError(
47
+ "Key is %d bytes, but must be exactly %d bytes"
48
+ % (len(key), SecretBox.KEY_SIZE)
49
+ )
48
50
  sb = SecretBox(key)
49
51
  # We generate the nonce using secure random bits. For long enough
50
52
  # nonce size, the chance of a random nonce collision becomes
@@ -87,11 +89,13 @@ def decrypt(ciphertext: bytes, keyPath: str) -> bytes:
87
89
  >>> import os
88
90
  >>> os.remove(k)
89
91
  """
90
- with open(keyPath, 'rb') as f:
92
+ with open(keyPath, "rb") as f:
91
93
  key = f.read()
92
94
  if len(key) != SecretBox.KEY_SIZE:
93
- raise ValueError("Key is %d bytes, but must be exactly %d bytes" % (len(key),
94
- SecretBox.KEY_SIZE))
95
+ raise ValueError(
96
+ "Key is %d bytes, but must be exactly %d bytes"
97
+ % (len(key), SecretBox.KEY_SIZE)
98
+ )
95
99
  sb = SecretBox(key)
96
100
  # The nonce is kept with the message.
97
101
  return sb.decrypt(ciphertext)
@@ -4,6 +4,7 @@ collect_ignore = []
4
4
 
5
5
  try:
6
6
  import nacl
7
+
7
8
  print(nacl.__file__) # to keep this import from being removed
8
9
  except ImportError:
9
10
  collect_ignore.append("_nacl.py")
toil/lib/exceptions.py CHANGED
@@ -15,6 +15,7 @@
15
15
  # 5.14.2018: copied into Toil from https://github.com/BD2KGenomics/bd2k-python-lib
16
16
 
17
17
  import sys
18
+ from urllib.parse import ParseResult
18
19
 
19
20
 
20
21
  # TODO: isn't this built in to Python 3 now?
@@ -38,20 +39,21 @@ class panic:
38
39
  the primary exception will be reraised.
39
40
  """
40
41
 
41
- def __init__( self, log=None ):
42
- super().__init__( )
42
+ def __init__(self, log=None):
43
+ super().__init__()
43
44
  self.log = log
44
45
  self.exc_info = None
45
46
 
46
- def __enter__( self ):
47
- self.exc_info = sys.exc_info( )
47
+ def __enter__(self):
48
+ self.exc_info = sys.exc_info()
48
49
 
49
- def __exit__( self, *exc_info ):
50
- if self.log is not None and exc_info and exc_info[ 0 ]:
51
- self.log.warning( "Exception during panic", exc_info=exc_info )
50
+ def __exit__(self, *exc_info):
51
+ if self.log is not None and exc_info and exc_info[0]:
52
+ self.log.warning("Exception during panic", exc_info=exc_info)
52
53
  exc_type, exc_value, traceback = self.exc_info
53
54
  raise_(exc_type, exc_value, traceback)
54
55
 
56
+
55
57
  def raise_(exc_type, exc_value, traceback) -> None:
56
58
  if exc_value is not None:
57
59
  exc = exc_value
@@ -60,3 +62,20 @@ def raise_(exc_type, exc_value, traceback) -> None:
60
62
  if exc.__traceback__ is not traceback:
61
63
  raise exc.with_traceback(traceback)
62
64
  raise exc
65
+
66
+
67
+ class UnimplementedURLException(RuntimeError):
68
+ def __init__(self, url: ParseResult, operation: str) -> None:
69
+ """
70
+ Make a new exception to report that a URL scheme is not implemented, or
71
+ that the implementation can't be loaded because its dependencies are
72
+ not installed.
73
+
74
+ :param url: The given URL
75
+ :param operation: Whether we are trying to 'import' or 'export'
76
+ """
77
+ super().__init__(
78
+ f"No available job store implementation can {operation} the URL "
79
+ f"'{url.geturl()}'. Ensure Toil has been installed "
80
+ f"with the appropriate extras."
81
+ )
toil/lib/expando.py CHANGED
@@ -14,9 +14,10 @@
14
14
 
15
15
  # 5.14.2018: copied into Toil from https://github.com/BD2KGenomics/bd2k-python-lib
16
16
 
17
+
17
18
  class Expando(dict):
18
19
  """
19
- Pass inital attributes to the constructor:
20
+ Pass initial attributes to the constructor:
20
21
 
21
22
  >>> o = Expando(foo=42)
22
23
  >>> o.foo
@@ -100,14 +101,15 @@ class Expando(dict):
100
101
  True
101
102
  """
102
103
 
103
- def __init__( self, *args, **kwargs ):
104
- super().__init__( *args, **kwargs )
104
+ def __init__(self, *args, **kwargs):
105
+ super().__init__(*args, **kwargs)
105
106
  self.__slots__ = None
106
107
  self.__dict__ = self
107
108
 
108
109
  def copy(self):
109
110
  return type(self)(self)
110
111
 
112
+
111
113
  class MagicExpando(Expando):
112
114
  """
113
115
  Use MagicExpando for chained attribute access.