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.
Files changed (17) hide show
  1. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/PKG-INFO +2 -2
  2. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/pyproject.toml +19 -4
  3. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/atp.py +100 -61
  4. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/jsonschema.py +6 -4
  5. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/plugin.py +66 -47
  6. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/predefined_schemas.py +4 -1
  7. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/schema.py +1203 -732
  8. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/serialization.py +12 -7
  9. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/test_atp.py +120 -73
  10. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/test_jsonschema.py +13 -4
  11. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/test_plugin.py +6 -2
  12. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/test_schema.py +886 -294
  13. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/LICENSE +0 -0
  14. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/README.md +0 -0
  15. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/__init__.py +0 -0
  16. {arcaflow_plugin_sdk-0.13.0 → arcaflow_plugin_sdk-0.14.0.dev1}/src/arcaflow_plugin_sdk/annotations.py +0 -0
  17. {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.13.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.5.0,<5.6.0)
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 = "0.13.0"
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.5.0"
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 = "^2020.1.16"
27
+ html2text = "^2024.0.0"
28
28
  pre-commit = "^3.0.0"
29
29
  sphinx = "^7.0.0"
30
- sphinx-rtd-theme = "^1.1.1"
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]
@@ -1,7 +1,7 @@
1
- """
2
- The Arcaflow Transport Protocol is a protocol to communicate between the Engine and a plugin. This library provides
3
- the services to do so. The main use-case is running over STDIO, so the communication protocol is designed to run 1:1,
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
- An integer ID that indicates the type of runtime message that is stored
36
- in the data field. The corresponding class can then be used to deserialize
37
- the inner data. Look at the go SDK for the reference data structure.
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
- This message is the initial greeting message a plugin sends to the output.
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 well as the steps offered by this "
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 potentially interfering with the atp pipes.
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 then there is another thread blocked.
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 threads are done.
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(ATP_SERVER_VERSION, self.plugin_schema)
140
- serialized_message = _HELLO_MESSAGE_SCHEMA.serialize(start_hello_message)
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="Exception while handling work start: "
170
- f"{e} {traceback.format_exc()}",
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=f"Exception while handling signal: {e} {traceback.format_exc()}",
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(f"Unknown kind of runtime message: {msg_id}")
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(f"Error while decoding CBOR: {err}")
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=f"Error occurred while decoding CBOR: {err} {traceback.format_exc()}",
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=f"Exception occurred in ATP server read loop: {e} {traceback.format_exc()}",
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 = self.plugin_schema.unserialize_signal_handler_input(
215
- saved_step_id, received_signal_id, signal_msg["data"]
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 thread.
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=(run_id, saved_step_id, received_signal_id, unserialized_data),
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, run_id: str, step_id: str, signal_id: str, unserialized_input_param: any
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=f"Error while calling signal for step with run ID {run_id}: {e} "
238
- f"{traceback.format_exc()}",
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(self, run_id: str, work_start_msg: typing.Dict[str, any]):
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=f"Error while calling step {run_id}/{step_id}:"
309
- f"{e} {traceback.format_exc()}",
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(self, message_type: MessageType, run_id: str, data: any):
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
- This exception is for client ATP client errors, like problems decoding
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
- This is a rudimentary client that reads information from a plugin and starts work on the plugin. The functions
366
- must be executed in order.
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
- After the Hello message has been read, this function starts work in a plugin with the specified data.
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(self, message_type: MessageType, run_id: str, data: any):
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
- This function reads until it gets the next result of an execution from the plugin, or an error.
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 order of calls?"
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 order of calls?"
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 order of calls?"
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"],
@@ -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
- This function takes a schema step and creates a JSON schema object from the input parameter.
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
- This function takes a schema step and creates a JSON schema object from the output parameters.
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 = {