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