arcaflow-plugin-sdk 0.13.0__tar.gz → 0.14.0.dev1__tar.gz
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.
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/PKG-INFO +2 -2
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/pyproject.toml +19 -4
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/atp.py +100 -61
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/jsonschema.py +6 -4
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/plugin.py +66 -47
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/predefined_schemas.py +4 -1
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/schema.py +1203 -732
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/serialization.py +12 -7
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/test_atp.py +120 -73
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/test_jsonschema.py +13 -4
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/test_plugin.py +6 -2
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/test_schema.py +886 -294
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/LICENSE +0 -0
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/README.md +0 -0
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/__init__.py +0 -0
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/annotations.py +0 -0
- {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: arcaflow-plugin-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.0.dev1
|
|
4
4
|
Summary: Plugin SDK for Python for the Arcaflow workflow engine
|
|
5
5
|
Home-page: https://github.com/arcalot/arcaflow-plugin-sdk-python
|
|
6
6
|
License: Apache-2.0
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Requires-Dist: PyYAML (>=6.0.1,<6.1.0)
|
|
17
|
-
Requires-Dist: cbor2 (>=5.
|
|
17
|
+
Requires-Dist: cbor2 (>=5.6.0,<5.7.0)
|
|
18
18
|
Project-URL: Bug Tracker, https://github.com/arcalot/arcaflow-plugin-sdk-python/issues
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "arcaflow-plugin-sdk"
|
|
3
3
|
# Do not change, the version is automatically updated in CI.
|
|
4
|
-
version = "
|
|
4
|
+
version = "v0.14.0-dev1"
|
|
5
5
|
description = "Plugin SDK for Python for the Arcaflow workflow engine"
|
|
6
6
|
authors = ["Arcalot Contributors"]
|
|
7
7
|
license = "Apache-2.0"
|
|
@@ -19,15 +19,30 @@ homepage = "https://github.com/arcalot/arcaflow-plugin-sdk-python"
|
|
|
19
19
|
|
|
20
20
|
[tool.poetry.dependencies]
|
|
21
21
|
python = "^3.9"
|
|
22
|
-
cbor2 = "~5.
|
|
22
|
+
cbor2 = "~5.6.0"
|
|
23
23
|
PyYAML = "~6.0.1"
|
|
24
24
|
|
|
25
25
|
[tool.poetry.group.dev.dependencies]
|
|
26
26
|
coverage = "^7.0.0"
|
|
27
|
-
html2text = "^
|
|
27
|
+
html2text = "^2024.0.0"
|
|
28
28
|
pre-commit = "^3.0.0"
|
|
29
29
|
sphinx = "^7.0.0"
|
|
30
|
-
sphinx-rtd-theme = "^
|
|
30
|
+
sphinx-rtd-theme = "^2.0.0"
|
|
31
|
+
isort = "^5.12.0"
|
|
32
|
+
black = "^24.0.0"
|
|
33
|
+
autoflake = "^2.2.1"
|
|
34
|
+
docformatter = "^1.7.5"
|
|
35
|
+
flake8 = "^7.0.0"
|
|
36
|
+
autopep8 = "^2.0.4"
|
|
37
|
+
pytest-repeat = "^0.9.3"
|
|
38
|
+
|
|
39
|
+
[tool.isort]
|
|
40
|
+
profile = 'black'
|
|
41
|
+
|
|
42
|
+
[tool.docformatter]
|
|
43
|
+
recursive = true
|
|
44
|
+
wrap-summaries = 79
|
|
45
|
+
wrap-descriptions = 79
|
|
31
46
|
|
|
32
47
|
|
|
33
48
|
[build-system]
|
{arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/atp.py
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
it is not possible to use multiple clients (engines) with a plugin.
|
|
1
|
+
"""The Arcaflow Transport Protocol is a protocol to communicate between the
|
|
2
|
+
Engine and a plugin. This library provides the services to do so. The main use-
|
|
3
|
+
case is running over STDIO, so the communication protocol is designed to run
|
|
4
|
+
1:1, it is not possible to use multiple clients (engines) with a plugin.
|
|
5
5
|
|
|
6
6
|
The message flow is as follows:
|
|
7
7
|
|
|
@@ -31,10 +31,11 @@ ATP_SERVER_VERSION = 3
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class MessageType(IntEnum):
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
"""An integer ID that indicates the type of runtime message that is stored
|
|
35
|
+
in the data field.
|
|
36
|
+
|
|
37
|
+
The corresponding class can then be used to deserialize the inner data.
|
|
38
|
+
Look at the go SDK for the reference data structure.
|
|
38
39
|
"""
|
|
39
40
|
|
|
40
41
|
WORK_START = 1
|
|
@@ -46,9 +47,8 @@ class MessageType(IntEnum):
|
|
|
46
47
|
|
|
47
48
|
@dataclasses.dataclass
|
|
48
49
|
class HelloMessage:
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
"""
|
|
50
|
+
"""This message is the initial greeting message a plugin sends to the
|
|
51
|
+
output."""
|
|
52
52
|
|
|
53
53
|
version: typing.Annotated[
|
|
54
54
|
int,
|
|
@@ -60,8 +60,8 @@ class HelloMessage:
|
|
|
60
60
|
schema.Schema,
|
|
61
61
|
schema.name("Schema"),
|
|
62
62
|
schema.description(
|
|
63
|
-
"Schema information describing the required inputs and outputs, as
|
|
64
|
-
"plugin."
|
|
63
|
+
"Schema information describing the required inputs and outputs, as"
|
|
64
|
+
" well as the steps offered by this plugin."
|
|
65
65
|
),
|
|
66
66
|
]
|
|
67
67
|
|
|
@@ -98,9 +98,7 @@ class ATPServer:
|
|
|
98
98
|
self,
|
|
99
99
|
plugin_schema: schema.SchemaType,
|
|
100
100
|
) -> int:
|
|
101
|
-
"""
|
|
102
|
-
This function wraps running a plugin.
|
|
103
|
-
"""
|
|
101
|
+
"""This function wraps running a plugin."""
|
|
104
102
|
signal.signal(
|
|
105
103
|
signal.SIGINT, signal.SIG_IGN
|
|
106
104
|
) # Ignore sigint. Only care about arcaflow signals.
|
|
@@ -112,7 +110,8 @@ class ATPServer:
|
|
|
112
110
|
self.plugin_schema = plugin_schema
|
|
113
111
|
self.handle_handshake()
|
|
114
112
|
|
|
115
|
-
# First replace stdout so that prints are handled by us, instead of
|
|
113
|
+
# First replace stdout so that prints are handled by us, instead of
|
|
114
|
+
# potentially interfering with the atp pipes.
|
|
116
115
|
original_stdout = sys.stdout
|
|
117
116
|
original_stderr = sys.stderr
|
|
118
117
|
self.user_out_buffer = io.StringIO()
|
|
@@ -121,11 +120,13 @@ class ATPServer:
|
|
|
121
120
|
|
|
122
121
|
# Run the read loop. This blocks to wait for the loop to finish.
|
|
123
122
|
self.run_server_read_loop()
|
|
124
|
-
# Wait for the step/signal threads to finish. If it gets stuck here
|
|
123
|
+
# Wait for the step/signal threads to finish. If it gets stuck here
|
|
124
|
+
# then there is another thread blocked.
|
|
125
125
|
for thread in self.running_threads:
|
|
126
126
|
thread.join()
|
|
127
127
|
|
|
128
|
-
# Don't reset stdout/stderr until after the read and step/signal
|
|
128
|
+
# Don't reset stdout/stderr until after the read and step/signal
|
|
129
|
+
# threads are done.
|
|
129
130
|
sys.stdout = original_stdout
|
|
130
131
|
sys.stderr = original_stderr
|
|
131
132
|
|
|
@@ -136,8 +137,12 @@ class ATPServer:
|
|
|
136
137
|
self.decoder.decode()
|
|
137
138
|
|
|
138
139
|
# Serialize then send HelloMessage
|
|
139
|
-
start_hello_message = HelloMessage(
|
|
140
|
-
|
|
140
|
+
start_hello_message = HelloMessage(
|
|
141
|
+
ATP_SERVER_VERSION, self.plugin_schema
|
|
142
|
+
)
|
|
143
|
+
serialized_message = _HELLO_MESSAGE_SCHEMA.serialize(
|
|
144
|
+
start_hello_message
|
|
145
|
+
)
|
|
141
146
|
self.send_message(serialized_message)
|
|
142
147
|
|
|
143
148
|
def run_server_read_loop(self) -> None:
|
|
@@ -166,8 +171,10 @@ class ATPServer:
|
|
|
166
171
|
run_id,
|
|
167
172
|
step_fatal=True,
|
|
168
173
|
server_fatal=False,
|
|
169
|
-
error_msg=
|
|
170
|
-
|
|
174
|
+
error_msg=(
|
|
175
|
+
"Exception while handling work start: "
|
|
176
|
+
f"{e} {traceback.format_exc()}"
|
|
177
|
+
),
|
|
171
178
|
)
|
|
172
179
|
elif msg_id == MessageType.SIGNAL:
|
|
173
180
|
signal_msg = runtime_msg["data"]
|
|
@@ -178,7 +185,10 @@ class ATPServer:
|
|
|
178
185
|
run_id,
|
|
179
186
|
step_fatal=False,
|
|
180
187
|
server_fatal=False,
|
|
181
|
-
error_msg=
|
|
188
|
+
error_msg=(
|
|
189
|
+
"Exception while handling signal:"
|
|
190
|
+
f" {e} {traceback.format_exc()}"
|
|
191
|
+
),
|
|
182
192
|
)
|
|
183
193
|
elif msg_id == MessageType.CLIENT_DONE:
|
|
184
194
|
return
|
|
@@ -189,41 +199,63 @@ class ATPServer:
|
|
|
189
199
|
server_fatal=False,
|
|
190
200
|
error_msg=f"Unknown runtime message ID: {msg_id}",
|
|
191
201
|
)
|
|
192
|
-
self.stderr.write(
|
|
202
|
+
self.stderr.write(bytes(
|
|
203
|
+
f"Unknown kind of runtime message: {msg_id}",
|
|
204
|
+
encoding="utf")
|
|
205
|
+
)
|
|
193
206
|
|
|
194
207
|
except cbor2.CBORDecodeError as err:
|
|
195
|
-
self.stderr.write(
|
|
208
|
+
self.stderr.write(bytes(
|
|
209
|
+
f"Error while decoding CBOR: {err}", encoding="utf"))
|
|
196
210
|
self.send_error_message(
|
|
197
211
|
"",
|
|
198
212
|
step_fatal=False,
|
|
199
213
|
server_fatal=True,
|
|
200
|
-
error_msg=
|
|
214
|
+
error_msg=(
|
|
215
|
+
"Error occurred while decoding CBOR:"
|
|
216
|
+
f" {err} {traceback.format_exc()}"
|
|
217
|
+
),
|
|
201
218
|
)
|
|
202
219
|
except Exception as e:
|
|
203
220
|
self.send_error_message(
|
|
204
221
|
"",
|
|
205
222
|
step_fatal=False,
|
|
206
223
|
server_fatal=True,
|
|
207
|
-
error_msg=
|
|
224
|
+
error_msg=(
|
|
225
|
+
"Exception occurred in ATP server read loop:"
|
|
226
|
+
f" {e} {traceback.format_exc()}"
|
|
227
|
+
),
|
|
208
228
|
)
|
|
209
229
|
|
|
210
230
|
def handle_signal(self, run_id, signal_msg):
|
|
211
231
|
saved_step_id = self.step_ids[run_id]
|
|
212
232
|
received_signal_id = signal_msg["signal_id"]
|
|
213
233
|
|
|
214
|
-
unserialized_data =
|
|
215
|
-
|
|
234
|
+
unserialized_data = (
|
|
235
|
+
self.plugin_schema.unserialize_signal_handler_input(
|
|
236
|
+
saved_step_id, received_signal_id, signal_msg["data"]
|
|
237
|
+
)
|
|
216
238
|
)
|
|
217
|
-
# The data is verified and unserialized. Now call the signal in its own
|
|
239
|
+
# The data is verified and unserialized. Now call the signal in its own
|
|
240
|
+
# thread.
|
|
218
241
|
run_thread = threading.Thread(
|
|
219
242
|
target=self.run_signal,
|
|
220
|
-
args=(
|
|
243
|
+
args=(
|
|
244
|
+
run_id,
|
|
245
|
+
saved_step_id,
|
|
246
|
+
received_signal_id,
|
|
247
|
+
unserialized_data,
|
|
248
|
+
),
|
|
221
249
|
)
|
|
222
250
|
self.running_threads.append(run_thread)
|
|
223
251
|
run_thread.start()
|
|
224
252
|
|
|
225
253
|
def run_signal(
|
|
226
|
-
self,
|
|
254
|
+
self,
|
|
255
|
+
run_id: str,
|
|
256
|
+
step_id: str,
|
|
257
|
+
signal_id: str,
|
|
258
|
+
unserialized_input_param: any,
|
|
227
259
|
):
|
|
228
260
|
try:
|
|
229
261
|
self.plugin_schema.call_step_signal(
|
|
@@ -234,11 +266,15 @@ class ATPServer:
|
|
|
234
266
|
run_id,
|
|
235
267
|
step_fatal=False,
|
|
236
268
|
server_fatal=False,
|
|
237
|
-
error_msg=
|
|
238
|
-
|
|
269
|
+
error_msg=(
|
|
270
|
+
"Error while calling signal for step with run ID"
|
|
271
|
+
f" {run_id}: {e} {traceback.format_exc()}"
|
|
272
|
+
),
|
|
239
273
|
)
|
|
240
274
|
|
|
241
|
-
def handle_work_start(
|
|
275
|
+
def handle_work_start(
|
|
276
|
+
self, run_id: str, work_start_msg: typing.Dict[str, any]
|
|
277
|
+
):
|
|
242
278
|
if work_start_msg is None:
|
|
243
279
|
self.send_error_message(
|
|
244
280
|
run_id,
|
|
@@ -305,8 +341,10 @@ class ATPServer:
|
|
|
305
341
|
run_id,
|
|
306
342
|
step_fatal=True,
|
|
307
343
|
server_fatal=False,
|
|
308
|
-
error_msg=
|
|
309
|
-
|
|
344
|
+
error_msg=(
|
|
345
|
+
f"Error while calling step {run_id}/{step_id}:"
|
|
346
|
+
f"{e} {traceback.format_exc()}"
|
|
347
|
+
),
|
|
310
348
|
)
|
|
311
349
|
return
|
|
312
350
|
|
|
@@ -315,7 +353,9 @@ class ATPServer:
|
|
|
315
353
|
self.encoder.encode(data)
|
|
316
354
|
self.output_pipe.flush() # Sends it to the ATP client immediately.
|
|
317
355
|
|
|
318
|
-
def send_runtime_message(
|
|
356
|
+
def send_runtime_message(
|
|
357
|
+
self, message_type: MessageType, run_id: str, data: any
|
|
358
|
+
):
|
|
319
359
|
self.send_message(
|
|
320
360
|
{
|
|
321
361
|
"id": message_type,
|
|
@@ -339,9 +379,8 @@ class ATPServer:
|
|
|
339
379
|
|
|
340
380
|
|
|
341
381
|
class PluginClientStateException(Exception):
|
|
342
|
-
"""
|
|
343
|
-
|
|
344
|
-
"""
|
|
382
|
+
"""This exception is for client ATP client errors, like problems
|
|
383
|
+
decoding."""
|
|
345
384
|
|
|
346
385
|
msg: str
|
|
347
386
|
|
|
@@ -361,9 +400,10 @@ class StepResult:
|
|
|
361
400
|
|
|
362
401
|
|
|
363
402
|
class PluginClient:
|
|
364
|
-
"""
|
|
365
|
-
|
|
366
|
-
|
|
403
|
+
"""This is a rudimentary client that reads information from a plugin and
|
|
404
|
+
starts work on the plugin.
|
|
405
|
+
|
|
406
|
+
The functions must be executed in order.
|
|
367
407
|
"""
|
|
368
408
|
|
|
369
409
|
to_server_pipe: io.FileIO # Usually the stdin of the sub-process
|
|
@@ -384,16 +424,13 @@ class PluginClient:
|
|
|
384
424
|
self.encoder.encode(None)
|
|
385
425
|
|
|
386
426
|
def read_hello(self) -> HelloMessage:
|
|
387
|
-
"""
|
|
388
|
-
This function reads the initial "Hello" message from the plugin.
|
|
389
|
-
"""
|
|
427
|
+
"""This function reads the initial "Hello" message from the plugin."""
|
|
390
428
|
message = self.decoder.decode()
|
|
391
429
|
return _HELLO_MESSAGE_SCHEMA.unserialize(message)
|
|
392
430
|
|
|
393
431
|
def start_work(self, run_id: str, step_id: str, config: any):
|
|
394
|
-
"""
|
|
395
|
-
|
|
396
|
-
"""
|
|
432
|
+
"""After the Hello message has been read, this function starts work in
|
|
433
|
+
a plugin with the specified data."""
|
|
397
434
|
self.send_runtime_message(
|
|
398
435
|
MessageType.WORK_START,
|
|
399
436
|
run_id,
|
|
@@ -404,9 +441,7 @@ class PluginClient:
|
|
|
404
441
|
)
|
|
405
442
|
|
|
406
443
|
def send_signal(self, run_id: str, signal_id: str, input_data: any):
|
|
407
|
-
"""
|
|
408
|
-
This function sends any signals to the plugin.
|
|
409
|
-
"""
|
|
444
|
+
"""This function sends any signals to the plugin."""
|
|
410
445
|
self.send_runtime_message(
|
|
411
446
|
MessageType.SIGNAL,
|
|
412
447
|
run_id,
|
|
@@ -419,7 +454,9 @@ class PluginClient:
|
|
|
419
454
|
def send_client_done(self):
|
|
420
455
|
self.send_runtime_message(MessageType.CLIENT_DONE, "", {})
|
|
421
456
|
|
|
422
|
-
def send_runtime_message(
|
|
457
|
+
def send_runtime_message(
|
|
458
|
+
self, message_type: MessageType, run_id: str, data: any
|
|
459
|
+
):
|
|
423
460
|
self.encoder.encode(
|
|
424
461
|
{
|
|
425
462
|
"id": message_type,
|
|
@@ -430,9 +467,8 @@ class PluginClient:
|
|
|
430
467
|
self.to_server_pipe.flush()
|
|
431
468
|
|
|
432
469
|
def read_single_result(self) -> StepResult:
|
|
433
|
-
"""
|
|
434
|
-
|
|
435
|
-
"""
|
|
470
|
+
"""This function reads until it gets the next result of an execution
|
|
471
|
+
from the plugin, or an error."""
|
|
436
472
|
while True:
|
|
437
473
|
runtime_msg = self.decoder.decode()
|
|
438
474
|
msg_id = runtime_msg["id"]
|
|
@@ -440,15 +476,18 @@ class PluginClient:
|
|
|
440
476
|
signal_msg = runtime_msg["data"]
|
|
441
477
|
if signal_msg["output_id"] is None:
|
|
442
478
|
raise PluginClientStateException(
|
|
443
|
-
"Missing 'output_id' in CBOR message. Possibly wrong
|
|
479
|
+
"Missing 'output_id' in CBOR message. Possibly wrong"
|
|
480
|
+
" order of calls?"
|
|
444
481
|
)
|
|
445
482
|
if signal_msg["output_data"] is None:
|
|
446
483
|
raise PluginClientStateException(
|
|
447
|
-
"Missing 'output_data' in CBOR message. Possibly wrong
|
|
484
|
+
"Missing 'output_data' in CBOR message. Possibly wrong"
|
|
485
|
+
" order of calls?"
|
|
448
486
|
)
|
|
449
487
|
if signal_msg["debug_logs"] is None:
|
|
450
488
|
raise PluginClientStateException(
|
|
451
|
-
"Missing 'output_data' in CBOR message. Possibly wrong
|
|
489
|
+
"Missing 'output_data' in CBOR message. Possibly wrong"
|
|
490
|
+
" order of calls?"
|
|
452
491
|
)
|
|
453
492
|
return StepResult(
|
|
454
493
|
run_id=runtime_msg["run_id"],
|
{arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/jsonschema.py
RENAMED
|
@@ -2,8 +2,9 @@ from arcaflow_plugin_sdk import schema
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def step_input(t: schema.StepSchema) -> dict:
|
|
5
|
-
"""
|
|
6
|
-
|
|
5
|
+
"""This function takes a schema step and creates a JSON schema object from
|
|
6
|
+
the input parameter.
|
|
7
|
+
|
|
7
8
|
:return: the JSON schema represented as a dict.
|
|
8
9
|
"""
|
|
9
10
|
result = t.input.to_jsonschema()
|
|
@@ -15,8 +16,9 @@ def step_input(t: schema.StepSchema) -> dict:
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
def step_outputs(t: schema.StepSchema):
|
|
18
|
-
"""
|
|
19
|
-
|
|
19
|
+
"""This function takes a schema step and creates a JSON schema object from
|
|
20
|
+
the output parameters.
|
|
21
|
+
|
|
20
22
|
:return: the JSON schema represented as a dict.
|
|
21
23
|
"""
|
|
22
24
|
result = {
|