toil 7.0.0__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 (190) hide show
  1. toil/__init__.py +121 -83
  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 +38 -29
  19. toil/batchSystems/registry.py +53 -19
  20. toil/batchSystems/singleMachine.py +293 -123
  21. toil/batchSystems/slurm.py +489 -137
  22. toil/batchSystems/torque.py +46 -32
  23. toil/bus.py +141 -73
  24. toil/common.py +630 -359
  25. toil/cwl/__init__.py +1 -1
  26. toil/cwl/cwltoil.py +1114 -532
  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 +988 -315
  35. toil/jobStores/abstractJobStore.py +387 -243
  36. toil/jobStores/aws/jobStore.py +727 -403
  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 +193 -58
  49. toil/lib/aws/utils.py +238 -218
  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/ec2.py +322 -209
  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 +4 -2
  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 +99 -11
  66. toil/lib/iterables.py +4 -2
  67. toil/lib/memoize.py +12 -8
  68. toil/lib/misc.py +65 -18
  69. toil/lib/objects.py +2 -2
  70. toil/lib/resources.py +19 -7
  71. toil/lib/retry.py +115 -77
  72. toil/lib/threading.py +282 -80
  73. toil/lib/throttle.py +15 -14
  74. toil/options/common.py +834 -401
  75. toil/options/cwl.py +175 -90
  76. toil/options/runner.py +50 -0
  77. toil/options/wdl.py +70 -19
  78. toil/provisioners/__init__.py +111 -46
  79. toil/provisioners/abstractProvisioner.py +322 -157
  80. toil/provisioners/aws/__init__.py +62 -30
  81. toil/provisioners/aws/awsProvisioner.py +980 -627
  82. toil/provisioners/clusterScaler.py +541 -279
  83. toil/provisioners/gceProvisioner.py +282 -179
  84. toil/provisioners/node.py +147 -79
  85. toil/realtimeLogger.py +34 -22
  86. toil/resource.py +137 -75
  87. toil/server/app.py +127 -61
  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 +148 -64
  98. toil/test/__init__.py +263 -179
  99. toil/test/batchSystems/batchSystemTest.py +438 -195
  100. toil/test/batchSystems/batch_system_plugin_test.py +18 -7
  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 +93 -47
  104. toil/test/cactus/test_cactus_integration.py +20 -22
  105. toil/test/cwl/cwlTest.py +271 -71
  106. toil/test/cwl/measure_default_memory.cwl +12 -0
  107. toil/test/cwl/not_run_required_input.cwl +29 -0
  108. toil/test/cwl/scatter_duplicate_outputs.cwl +40 -0
  109. toil/test/docs/scriptsTest.py +60 -34
  110. toil/test/jobStores/jobStoreTest.py +412 -235
  111. toil/test/lib/aws/test_iam.py +116 -48
  112. toil/test/lib/aws/test_s3.py +16 -9
  113. toil/test/lib/aws/test_utils.py +5 -6
  114. toil/test/lib/dockerTest.py +118 -141
  115. toil/test/lib/test_conversions.py +113 -115
  116. toil/test/lib/test_ec2.py +57 -49
  117. toil/test/lib/test_integration.py +104 -0
  118. toil/test/lib/test_misc.py +12 -5
  119. toil/test/mesos/MesosDataStructuresTest.py +23 -10
  120. toil/test/mesos/helloWorld.py +7 -6
  121. toil/test/mesos/stress.py +25 -20
  122. toil/test/options/options.py +7 -2
  123. toil/test/provisioners/aws/awsProvisionerTest.py +293 -140
  124. toil/test/provisioners/clusterScalerTest.py +440 -250
  125. toil/test/provisioners/clusterTest.py +81 -42
  126. toil/test/provisioners/gceProvisionerTest.py +174 -100
  127. toil/test/provisioners/provisionerTest.py +25 -13
  128. toil/test/provisioners/restartScript.py +5 -4
  129. toil/test/server/serverTest.py +188 -141
  130. toil/test/sort/restart_sort.py +137 -68
  131. toil/test/sort/sort.py +134 -66
  132. toil/test/sort/sortTest.py +91 -49
  133. toil/test/src/autoDeploymentTest.py +140 -100
  134. toil/test/src/busTest.py +20 -18
  135. toil/test/src/checkpointTest.py +8 -2
  136. toil/test/src/deferredFunctionTest.py +49 -35
  137. toil/test/src/dockerCheckTest.py +33 -26
  138. toil/test/src/environmentTest.py +20 -10
  139. toil/test/src/fileStoreTest.py +538 -271
  140. toil/test/src/helloWorldTest.py +7 -4
  141. toil/test/src/importExportFileTest.py +61 -31
  142. toil/test/src/jobDescriptionTest.py +32 -17
  143. toil/test/src/jobEncapsulationTest.py +2 -0
  144. toil/test/src/jobFileStoreTest.py +74 -50
  145. toil/test/src/jobServiceTest.py +187 -73
  146. toil/test/src/jobTest.py +120 -70
  147. toil/test/src/miscTests.py +19 -18
  148. toil/test/src/promisedRequirementTest.py +82 -36
  149. toil/test/src/promisesTest.py +7 -6
  150. toil/test/src/realtimeLoggerTest.py +6 -6
  151. toil/test/src/regularLogTest.py +71 -37
  152. toil/test/src/resourceTest.py +80 -49
  153. toil/test/src/restartDAGTest.py +36 -22
  154. toil/test/src/resumabilityTest.py +9 -2
  155. toil/test/src/retainTempDirTest.py +45 -14
  156. toil/test/src/systemTest.py +12 -8
  157. toil/test/src/threadingTest.py +44 -25
  158. toil/test/src/toilContextManagerTest.py +10 -7
  159. toil/test/src/userDefinedJobArgTypeTest.py +8 -5
  160. toil/test/src/workerTest.py +33 -16
  161. toil/test/utils/toilDebugTest.py +70 -58
  162. toil/test/utils/toilKillTest.py +4 -5
  163. toil/test/utils/utilsTest.py +239 -102
  164. toil/test/wdl/wdltoil_test.py +789 -148
  165. toil/test/wdl/wdltoil_test_kubernetes.py +37 -23
  166. toil/toilState.py +52 -26
  167. toil/utils/toilConfig.py +13 -4
  168. toil/utils/toilDebugFile.py +44 -27
  169. toil/utils/toilDebugJob.py +85 -25
  170. toil/utils/toilDestroyCluster.py +11 -6
  171. toil/utils/toilKill.py +8 -3
  172. toil/utils/toilLaunchCluster.py +251 -145
  173. toil/utils/toilMain.py +37 -16
  174. toil/utils/toilRsyncCluster.py +27 -14
  175. toil/utils/toilSshCluster.py +45 -22
  176. toil/utils/toilStats.py +75 -36
  177. toil/utils/toilStatus.py +226 -119
  178. toil/utils/toilUpdateEC2Instances.py +3 -1
  179. toil/version.py +11 -11
  180. toil/wdl/utils.py +5 -5
  181. toil/wdl/wdltoil.py +3513 -1052
  182. toil/worker.py +269 -128
  183. toil-8.0.0.dist-info/METADATA +173 -0
  184. toil-8.0.0.dist-info/RECORD +253 -0
  185. {toil-7.0.0.dist-info → toil-8.0.0.dist-info}/WHEEL +1 -1
  186. toil-7.0.0.dist-info/METADATA +0 -158
  187. toil-7.0.0.dist-info/RECORD +0 -244
  188. {toil-7.0.0.dist-info → toil-8.0.0.dist-info}/LICENSE +0 -0
  189. {toil-7.0.0.dist-info → toil-8.0.0.dist-info}/entry_points.txt +0 -0
  190. {toil-7.0.0.dist-info → toil-8.0.0.dist-info}/top_level.txt +0 -0
toil/server/app.py CHANGED
@@ -29,49 +29,103 @@ logger = logging.getLogger(__name__)
29
29
  def parser_with_server_options() -> argparse.ArgumentParser:
30
30
  parser = ArgumentParser(description="Toil server mode.")
31
31
 
32
- parser.add_argument("--debug", action="store_true", default=False,
33
- help="Enable debug mode.")
34
- parser.add_argument("--bypass_celery", action="store_true", default=False,
35
- help="Skip sending workflows to Celery and just run them under the "
36
- "server. For testing.")
37
- parser.add_argument("--host", type=str, default="127.0.0.1",
38
- help="The host interface that the Toil server binds on. (default: '127.0.0.1').")
39
- parser.add_argument("--port", type=int, default=8080,
40
- help="The port that the Toil server listens on. (default: 8080).")
41
- parser.add_argument("--swagger_ui", action="store_true", default=False,
42
- help="If True, the swagger UI will be enabled and hosted on the "
43
- "`{api_base_path}/ui` endpoint. (default: False)")
32
+ parser.add_argument(
33
+ "--debug", action="store_true", default=False, help="Enable debug mode."
34
+ )
35
+ parser.add_argument(
36
+ "--bypass_celery",
37
+ action="store_true",
38
+ default=False,
39
+ help="Skip sending workflows to Celery and just run them under the "
40
+ "server. For testing.",
41
+ )
42
+ parser.add_argument(
43
+ "--host",
44
+ type=str,
45
+ default="127.0.0.1",
46
+ help="The host interface that the Toil server binds on. (default: '127.0.0.1').",
47
+ )
48
+ parser.add_argument(
49
+ "--port",
50
+ type=int,
51
+ default=8080,
52
+ help="The port that the Toil server listens on. (default: 8080).",
53
+ )
54
+ parser.add_argument(
55
+ "--swagger_ui",
56
+ action="store_true",
57
+ default=False,
58
+ help="If True, the swagger UI will be enabled and hosted on the "
59
+ "`{api_base_path}/ui` endpoint. (default: False)",
60
+ )
44
61
  # CORS
45
- parser.add_argument("--cors", action="store_true", default=False,
46
- help="Enable Cross Origin Resource Sharing (CORS). This should only be turned on "
47
- "if the server is intended to be used by a website or domain.")
48
- parser.add_argument("--cors_origins", type=str, default="*",
49
- help="Ignored if --cors is False. This sets the allowed origins for CORS. "
50
- "For details about CORS and its security risks, see: "
51
- "https://w3id.org/ga4gh/product-approval-support/cors. (default: '*')")
62
+ parser.add_argument(
63
+ "--cors",
64
+ action="store_true",
65
+ default=False,
66
+ help="Enable Cross Origin Resource Sharing (CORS). This should only be turned on "
67
+ "if the server is intended to be used by a website or domain.",
68
+ )
69
+ parser.add_argument(
70
+ "--cors_origins",
71
+ type=str,
72
+ default="*",
73
+ help="Ignored if --cors is False. This sets the allowed origins for CORS. "
74
+ "For details about CORS and its security risks, see: "
75
+ "https://w3id.org/ga4gh/product-approval-support/cors. (default: '*')",
76
+ )
52
77
  # production only
53
- parser.add_argument("-w", "--workers", dest='workers', type=int, default=2,
54
- help="Ignored if --debug is True. The number of worker processes launched by the "
55
- "WSGI server. (default: 2).")
56
-
57
- parser.add_argument("--work_dir", type=str, default=os.path.join(os.getcwd(), "workflows"),
58
- help="The directory where workflows should be stored. This directory should be "
59
- "empty or only contain previous workflows. (default: './workflows').")
60
- parser.add_argument("--state_store", type=str, default=None,
61
- help="The local path or S3 URL where workflow state metadata should be stored. "
62
- "(default: in --work_dir)")
63
- parser.add_argument("--opt", "-o", type=str, action="append", default=[],
64
- help="Specify the default parameters to be sent to the workflow engine for each "
65
- "run. Options taking arguments must use = syntax. Accepts multiple values.\n"
66
- "Example: '--opt=--logLevel=CRITICAL --opt=--workDir=/tmp'.")
67
- parser.add_argument("--dest_bucket_base", type=str, default=None,
68
- help="Direct CWL workflows to save output files to dynamically generated "
69
- "unique paths under the given URL. Supports AWS S3.")
70
- parser.add_argument("--wes_dialect", type=str, default="standard", choices=["standard", "agc"],
71
- help="Restrict WES responses to a dialect compatible with clients that do not fully "
72
- "implement the WES standard. (default: 'standard')")
73
-
74
- parser.add_argument("--version", action='version', version=version)
78
+ parser.add_argument(
79
+ "-w",
80
+ "--workers",
81
+ dest="workers",
82
+ type=int,
83
+ default=2,
84
+ help="Ignored if --debug is True. The number of worker processes launched by the "
85
+ "WSGI server. (default: 2).",
86
+ )
87
+
88
+ parser.add_argument(
89
+ "--work_dir",
90
+ type=str,
91
+ default=os.path.join(os.getcwd(), "workflows"),
92
+ help="The directory where workflows should be stored. This directory should be "
93
+ "empty or only contain previous workflows. (default: './workflows').",
94
+ )
95
+ parser.add_argument(
96
+ "--state_store",
97
+ type=str,
98
+ default=None,
99
+ help="The local path or S3 URL where workflow state metadata should be stored. "
100
+ "(default: in --work_dir)",
101
+ )
102
+ parser.add_argument(
103
+ "--opt",
104
+ "-o",
105
+ type=str,
106
+ action="append",
107
+ default=[],
108
+ help="Specify the default parameters to be sent to the workflow engine for each "
109
+ "run. Options taking arguments must use = syntax. Accepts multiple values.\n"
110
+ "Example: '--opt=--logLevel=CRITICAL --opt=--workDir=/tmp'.",
111
+ )
112
+ parser.add_argument(
113
+ "--dest_bucket_base",
114
+ type=str,
115
+ default=None,
116
+ help="Direct CWL workflows to save output files to dynamically generated "
117
+ "unique paths under the given URL. Supports AWS S3.",
118
+ )
119
+ parser.add_argument(
120
+ "--wes_dialect",
121
+ type=str,
122
+ default="standard",
123
+ choices=["standard", "agc"],
124
+ help="Restrict WES responses to a dialect compatible with clients that do not fully "
125
+ "implement the WES standard. (default: 'standard')",
126
+ )
127
+
128
+ parser.add_argument("--version", action="version", version=version)
75
129
  return parser
76
130
 
77
131
 
@@ -79,34 +133,43 @@ def create_app(args: argparse.Namespace) -> "connexion.FlaskApp":
79
133
  """
80
134
  Create a "connexion.FlaskApp" instance with Toil server configurations.
81
135
  """
82
- flask_app = connexion.FlaskApp(__name__,
83
- specification_dir='api_spec/',
84
- options={"swagger_ui": args.swagger_ui})
136
+ flask_app = connexion.FlaskApp(
137
+ __name__, specification_dir="api_spec/", options={"swagger_ui": args.swagger_ui}
138
+ )
85
139
 
86
- flask_app.app.config['JSON_SORT_KEYS'] = False
140
+ flask_app.app.config["JSON_SORT_KEYS"] = False
87
141
 
88
142
  if args.cors:
89
143
  # enable cross origin resource sharing
90
144
  from flask_cors import CORS
145
+
91
146
  CORS(flask_app.app, resources={r"/ga4gh/*": {"origins": args.cors_origins}})
92
147
 
93
148
  # add workflow execution service (WES) API endpoints
94
- backend = ToilBackend(work_dir=args.work_dir,
95
- state_store=args.state_store,
96
- options=args.opt,
97
- dest_bucket_base=args.dest_bucket_base,
98
- bypass_celery=args.bypass_celery,
99
- wes_dialect=args.wes_dialect)
100
-
101
- flask_app.add_api('workflow_execution_service.swagger.yaml',
102
- resolver=connexion.Resolver(backend.resolve_operation_id)) # noqa
149
+ backend = ToilBackend(
150
+ work_dir=args.work_dir,
151
+ state_store=args.state_store,
152
+ options=args.opt,
153
+ dest_bucket_base=args.dest_bucket_base,
154
+ bypass_celery=args.bypass_celery,
155
+ wes_dialect=args.wes_dialect,
156
+ )
157
+
158
+ flask_app.add_api(
159
+ "workflow_execution_service.swagger.yaml",
160
+ resolver=connexion.Resolver(backend.resolve_operation_id),
161
+ ) # noqa
103
162
 
104
163
  # add custom endpoints
105
164
  if isinstance(backend, ToilBackend):
106
165
  # We extend the WES API to allow presenting log data
107
166
  base_url = "/toil/wes/v1"
108
- flask_app.app.add_url_rule(f"{base_url}/logs/<run_id>/stdout", view_func=backend.get_stdout)
109
- flask_app.app.add_url_rule(f"{base_url}/logs/<run_id>/stderr", view_func=backend.get_stderr)
167
+ flask_app.app.add_url_rule(
168
+ f"{base_url}/logs/<run_id>/stdout", view_func=backend.get_stdout
169
+ )
170
+ flask_app.app.add_url_rule(
171
+ f"{base_url}/logs/<run_id>/stderr", view_func=backend.get_stderr
172
+ )
110
173
  # To be a well-behaved AGC engine we can implement the default status check endpoint
111
174
  flask_app.app.add_url_rule("/engine/v1/status", view_func=backend.get_health)
112
175
  # And we can provide lost humans some information on what they are looking at
@@ -116,7 +179,7 @@ def create_app(args: argparse.Namespace) -> "connexion.FlaskApp":
116
179
 
117
180
 
118
181
  def start_server(args: argparse.Namespace) -> None:
119
- """ Start a Toil server."""
182
+ """Start a Toil server."""
120
183
 
121
184
  # Explain a bit about who and where we are
122
185
  logger.info("Toil WES server version %s starting...", version)
@@ -137,7 +200,10 @@ def start_server(args: argparse.Namespace) -> None:
137
200
  flask_app.run(host=host, port=port)
138
201
  else:
139
202
  # start a production WSGI server
140
- run_app(flask_app.app, options={
141
- "bind": f"{host}:{port}",
142
- "workers": args.workers,
143
- })
203
+ run_app(
204
+ flask_app.app,
205
+ options={
206
+ "bind": f"{host}:{port}",
207
+ "workers": args.workers,
208
+ },
209
+ )
toil/server/celery_app.py CHANGED
@@ -11,7 +11,9 @@ Also the entrypoint for starting celery workers.
11
11
 
12
12
  def create_celery_app() -> Celery:
13
13
  """ """
14
- broker = os.environ.get("TOIL_WES_BROKER_URL", "amqp://guest:guest@localhost:5672//")
14
+ broker = os.environ.get(
15
+ "TOIL_WES_BROKER_URL", "amqp://guest:guest@localhost:5672//"
16
+ )
15
17
  app = Celery("toil_wes", broker=broker)
16
18
 
17
19
  # Celery configurations
@@ -5,8 +5,9 @@ import subprocess
5
5
  import sys
6
6
  import time
7
7
  from base64 import b64encode
8
+ from collections.abc import Iterable
8
9
  from io import BytesIO
9
- from typing import Any, Dict, Iterable, List, Optional, Tuple, cast
10
+ from typing import Any, Optional, cast
10
11
  from urllib.parse import urldefrag, urljoin, urlparse
11
12
 
12
13
  import requests
@@ -56,7 +57,7 @@ cwltest --verbose \
56
57
  logger = logging.getLogger(__name__)
57
58
 
58
59
 
59
- def generate_attachment_path_names(paths: List[str]) -> Tuple[str, List[str]]:
60
+ def generate_attachment_path_names(paths: list[str]) -> tuple[str, list[str]]:
60
61
  """
61
62
  Take in a list of path names and return a list of names with the common path
62
63
  name stripped out, while preserving the input order. This guarantees that
@@ -105,7 +106,8 @@ class WESClientWithWorkflowEngineParameters(WESClient): # type: ignore
105
106
 
106
107
  TODO: Propose a PR in wes-service to include workflow_engine_params.
107
108
  """
108
- def __init__(self, endpoint: str, auth: Optional[Tuple[str, str]] = None) -> None:
109
+
110
+ def __init__(self, endpoint: str, auth: Optional[tuple[str, str]] = None) -> None:
109
111
  """
110
112
  :param endpoint: The http(s) URL of the WES server. Must include the
111
113
  protocol.
@@ -113,12 +115,21 @@ class WESClientWithWorkflowEngineParameters(WESClient): # type: ignore
113
115
  request to the WES server.
114
116
  """
115
117
  proto, host = endpoint.split("://")
116
- super().__init__({
117
- # TODO: use the auth argument in requests.post so we don't need to encode it ourselves
118
- "auth": {"Authorization": "Basic " + b64encode(f"{auth[0]}:{auth[1]}".encode()).decode("utf-8")} if auth else {},
119
- "proto": proto,
120
- "host": host
121
- })
118
+ super().__init__(
119
+ {
120
+ # TODO: use the auth argument in requests.post so we don't need to encode it ourselves
121
+ "auth": (
122
+ {
123
+ "Authorization": "Basic "
124
+ + b64encode(f"{auth[0]}:{auth[1]}".encode()).decode("utf-8")
125
+ }
126
+ if auth
127
+ else {}
128
+ ),
129
+ "proto": proto,
130
+ "host": host,
131
+ }
132
+ )
122
133
 
123
134
  def get_version(self, extension: str, workflow_file: str) -> str:
124
135
  """Determines the version of a .py, .wdl, or .cwl file."""
@@ -140,7 +151,7 @@ class WESClientWithWorkflowEngineParameters(WESClient): # type: ignore
140
151
  else:
141
152
  raise RuntimeError(f"Invalid workflow extension: {extension}.")
142
153
 
143
- def parse_params(self, workflow_params_file: str) -> Dict[str, Any]:
154
+ def parse_params(self, workflow_params_file: str) -> dict[str, Any]:
144
155
  """
145
156
  Parse the CWL input file into a dictionary to be attached to the body of
146
157
  the WES run request.
@@ -155,9 +166,11 @@ class WESClientWithWorkflowEngineParameters(WESClient): # type: ignore
155
166
  workflow_params: Any
156
167
  workflow_params, _ = loader.resolve_ref(workflow_params_file, checklinks=False)
157
168
 
158
- return cast(Dict[str, Any], workflow_params)
169
+ return cast(dict[str, Any], workflow_params)
159
170
 
160
- def modify_param_paths(self, base_dir: str, workflow_params: Dict[str, Any]) -> None:
171
+ def modify_param_paths(
172
+ self, base_dir: str, workflow_params: dict[str, Any]
173
+ ) -> None:
161
174
  """
162
175
  Modify the file paths in the input workflow parameters to be relative
163
176
  to base_dir.
@@ -168,7 +181,7 @@ class WESClientWithWorkflowEngineParameters(WESClient): # type: ignore
168
181
  :param workflow_params: A dict containing the workflow parameters.
169
182
  """
170
183
 
171
- def replace(field: str, file_obj: Dict[str, str]) -> None:
184
+ def replace(field: str, file_obj: dict[str, str]) -> None:
172
185
  """
173
186
  Given a file object with the "location" or "path" field, replace it
174
187
  to be relative to base_dir.
@@ -192,15 +205,16 @@ class WESClientWithWorkflowEngineParameters(WESClient): # type: ignore
192
205
  replace_paths(file.values())
193
206
  elif isinstance(file, list):
194
207
  replace_paths(file)
208
+
195
209
  replace_paths(workflow_params.values())
196
210
 
197
211
  def build_wes_request(
198
- self,
199
- workflow_file: str,
200
- workflow_params_file: Optional[str],
201
- attachments: Optional[List[str]],
202
- workflow_engine_parameters: Optional[List[str]] = None
203
- ) -> Tuple[Dict[str, str], Iterable[Tuple[str, Tuple[str, BytesIO]]]]:
212
+ self,
213
+ workflow_file: str,
214
+ workflow_params_file: Optional[str],
215
+ attachments: Optional[list[str]],
216
+ workflow_engine_parameters: Optional[list[str]] = None,
217
+ ) -> tuple[dict[str, str], Iterable[tuple[str, tuple[str, BytesIO]]]]:
204
218
  """
205
219
  Build the workflow run request to submit to WES.
206
220
 
@@ -233,21 +247,21 @@ class WESClientWithWorkflowEngineParameters(WESClient): # type: ignore
233
247
 
234
248
  workflow_type = wf_url.lower().split(".")[-1] # Grab the file extension
235
249
  workflow_type_version = self.get_version(workflow_type, wf_url)
236
- data: Dict[str, str] = {
250
+ data: dict[str, str] = {
237
251
  "workflow_url": workflow_file,
238
252
  "workflow_params": "", # to be set after attachments are processed
239
253
  "workflow_type": workflow_type,
240
- "workflow_type_version": workflow_type_version
254
+ "workflow_type_version": workflow_type_version,
241
255
  }
242
256
 
243
257
  # Convert engine arguments into a JSON object
244
258
  if workflow_engine_parameters:
245
259
  params = {}
246
260
  for param in workflow_engine_parameters:
247
- if '=' not in param: # flags like "--logDebug"
261
+ if "=" not in param: # flags like "--logDebug"
248
262
  k, v = param, None
249
263
  else:
250
- k, v = param.split('=', 1)
264
+ k, v = param.split("=", 1)
251
265
  params[k] = v
252
266
  data["workflow_engine_parameters"] = json.dumps(params)
253
267
 
@@ -278,12 +292,12 @@ class WESClientWithWorkflowEngineParameters(WESClient): # type: ignore
278
292
  return data, [("workflow_attachment", val) for val in workflow_attachments]
279
293
 
280
294
  def run_with_engine_options(
281
- self,
282
- workflow_file: str,
283
- workflow_params_file: Optional[str],
284
- attachments: Optional[List[str]],
285
- workflow_engine_parameters: Optional[List[str]]
286
- ) -> Dict[str, Any]:
295
+ self,
296
+ workflow_file: str,
297
+ workflow_params_file: Optional[str],
298
+ attachments: Optional[list[str]],
299
+ workflow_engine_parameters: Optional[list[str]],
300
+ ) -> dict[str, Any]:
287
301
  """
288
302
  Composes and sends a post request that signals the WES server to run a
289
303
  workflow.
@@ -297,10 +311,9 @@ class WESClientWithWorkflowEngineParameters(WESClient): # type: ignore
297
311
 
298
312
  :return: The body of the post result as a dictionary.
299
313
  """
300
- data, files = self.build_wes_request(workflow_file,
301
- workflow_params_file,
302
- attachments,
303
- workflow_engine_parameters)
314
+ data, files = self.build_wes_request(
315
+ workflow_file, workflow_params_file, attachments, workflow_engine_parameters
316
+ )
304
317
  post_result = requests.post(
305
318
  urljoin(f"{self.proto}://{self.host}", "/ga4gh/wes/v1/runs"),
306
319
  data=data,
@@ -308,10 +321,10 @@ class WESClientWithWorkflowEngineParameters(WESClient): # type: ignore
308
321
  headers=self.auth,
309
322
  )
310
323
 
311
- return cast(Dict[str, Any], wes_response(post_result))
324
+ return cast(dict[str, Any], wes_response(post_result))
312
325
 
313
326
 
314
- def get_deps_from_cwltool(cwl_file: str, input_file: Optional[str] = None) -> List[str]:
327
+ def get_deps_from_cwltool(cwl_file: str, input_file: Optional[str] = None) -> list[str]:
315
328
  """
316
329
  Return a list of dependencies of the given workflow from cwltool.
317
330
 
@@ -320,19 +333,21 @@ def get_deps_from_cwltool(cwl_file: str, input_file: Optional[str] = None) -> Li
320
333
  this returns the dependencies from the input file.
321
334
  """
322
335
 
323
- option = '--print-input-deps' if input_file else '--print-deps'
336
+ option = "--print-input-deps" if input_file else "--print-deps"
324
337
 
325
- args = ['cwltool', option, '--relative-deps', 'cwd', cwl_file]
338
+ args = ["cwltool", option, "--relative-deps", "cwd", cwl_file]
326
339
  if input_file:
327
340
  args.append(input_file)
328
341
 
329
- p = subprocess.run(args=args, check=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
342
+ p = subprocess.run(
343
+ args=args, check=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
344
+ )
330
345
 
331
346
  result = p.stdout.decode()
332
347
  if not result:
333
348
  return []
334
349
 
335
- json_result: Dict[str, Any] = json.loads(result)
350
+ json_result: dict[str, Any] = json.loads(result)
336
351
  deps = []
337
352
 
338
353
  def get_deps(obj: Any) -> None:
@@ -368,10 +383,12 @@ def get_deps_from_cwltool(cwl_file: str, input_file: Optional[str] = None) -> Li
368
383
  return deps
369
384
 
370
385
 
371
- def submit_run(client: WESClientWithWorkflowEngineParameters,
372
- cwl_file: str,
373
- input_file: Optional[str] = None,
374
- engine_options: Optional[List[str]] = None) -> str:
386
+ def submit_run(
387
+ client: WESClientWithWorkflowEngineParameters,
388
+ cwl_file: str,
389
+ input_file: Optional[str] = None,
390
+ engine_options: Optional[list[str]] = None,
391
+ ) -> str:
375
392
  """
376
393
  Given a CWL file, its input files, and an optional list of engine options,
377
394
  submit the CWL workflow to the WES server via the WES client.
@@ -391,23 +408,32 @@ def submit_run(client: WESClientWithWorkflowEngineParameters,
391
408
  if input_file:
392
409
  attachments.extend(get_deps_from_cwltool(cwl_file, input_file))
393
410
 
394
- run_result: Dict[str, Any] = client.run_with_engine_options(
411
+ run_result: dict[str, Any] = client.run_with_engine_options(
395
412
  cwl_file,
396
413
  input_file,
397
414
  attachments=attachments,
398
- workflow_engine_parameters=engine_options)
415
+ workflow_engine_parameters=engine_options,
416
+ )
399
417
  return str(run_result["run_id"])
400
418
 
401
419
 
402
420
  def poll_run(client: WESClientWithWorkflowEngineParameters, run_id: str) -> bool:
403
- """ Return True if the given workflow run is in a finished state."""
421
+ """Return True if the given workflow run is in a finished state."""
404
422
  status_result = client.get_run_status(run_id)
405
423
  state = status_result.get("state")
406
424
 
407
- return state in ("COMPLETE", "CANCELING", "CANCELED", "EXECUTOR_ERROR", "SYSTEM_ERROR")
425
+ return state in (
426
+ "COMPLETE",
427
+ "CANCELING",
428
+ "CANCELED",
429
+ "EXECUTOR_ERROR",
430
+ "SYSTEM_ERROR",
431
+ )
408
432
 
409
433
 
410
- def print_logs_and_exit(client: WESClientWithWorkflowEngineParameters, run_id: str) -> None:
434
+ def print_logs_and_exit(
435
+ client: WESClientWithWorkflowEngineParameters, run_id: str
436
+ ) -> None:
411
437
  """
412
438
  Fetch the workflow logs from the WES server, print the results, then exit
413
439
  the program with the same exit code as the workflow run.
@@ -431,9 +457,11 @@ def main() -> None:
431
457
  parser.add_argument("cwl_file", type=str)
432
458
  parser.add_argument("input_file", type=str, nargs="?", default=None)
433
459
  # arguments used by the WES runner
434
- parser.add_argument("--wes_endpoint",
435
- default=os.environ.get("TOIL_WES_ENDPOINT", "http://localhost:8080"),
436
- help="The http(s) URL of the WES server. (default: %(default)s)")
460
+ parser.add_argument(
461
+ "--wes_endpoint",
462
+ default=os.environ.get("TOIL_WES_ENDPOINT", "http://localhost:8080"),
463
+ help="The http(s) URL of the WES server. (default: %(default)s)",
464
+ )
437
465
  # the rest of the arguments are passed as engine options to the WES server
438
466
  options, rest = parser.parse_known_args()
439
467
 
@@ -449,7 +477,8 @@ def main() -> None:
449
477
 
450
478
  client = WESClientWithWorkflowEngineParameters(
451
479
  endpoint=endpoint,
452
- auth=(wes_user, wes_password) if wes_user and wes_password else None)
480
+ auth=(wes_user, wes_password) if wes_user and wes_password else None,
481
+ )
453
482
 
454
483
  run_id = submit_run(client, cwl_file, input_file, engine_options=rest)
455
484
  assert run_id
@@ -462,5 +491,5 @@ def main() -> None:
462
491
  print_logs_and_exit(client, run_id)
463
492
 
464
493
 
465
- if __name__ == '__main__':
494
+ if __name__ == "__main__":
466
495
  main()