harness-sdk-python 0.13.1__tar.gz → 0.13.2__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.
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/PKG-INFO +1 -1
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/pyproject.toml +1 -1
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/src/harness_sdk/run_manager.py +15 -26
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_batches.py +5 -11
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_stopping_window.py +15 -17
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/.gitignore +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/README.md +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/examples/__init__.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/examples/runs_app.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/src/harness_sdk/__init__.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/src/harness_sdk/fenced_postgres.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/src/harness_sdk/linear_thread.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/run_helpers.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_ack_visibility.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_branch_anchor.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_dispatching.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_edit_dispatched.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_edit_reload.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_enqueue.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_fenced_postgres.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_input_required.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_linear_thread.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_meta.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_outcomes.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_placement.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_prepare_hooks.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_restore.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_rewind_during_run.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_settle.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_steer.py +0 -0
- {harness_sdk_python-0.13.1 → harness_sdk_python-0.13.2}/tests/test_stop_continue.py +0 -0
|
@@ -10,10 +10,10 @@ dispatchable send appends straight into ``dispatching`` — it never transits a
|
|
|
10
10
|
queue lane — and everything staged there by the end of the drain dispatches
|
|
11
11
|
as one run; in error/stop the dispatch is the continuation
|
|
12
12
|
(``error-continue`` / ``stop-continue``) carrying the send's messages. Every
|
|
13
|
-
initiator acks and settles accepted with its state-visible application
|
|
14
|
-
sends with their insert (lane
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
initiator acks and settles accepted with its state-visible application:
|
|
14
|
+
the verdict certifies the admission write — sends with their insert (lane
|
|
15
|
+
or ``dispatching``), mid-run edit/reload with the stopping window,
|
|
16
|
+
``run/stop`` with ``stopping`` — never future execution.
|
|
17
17
|
|
|
18
18
|
``entry["dispatching"]`` is the dispatch record (absent between an ack and
|
|
19
19
|
the next dispatch or take): the payload the run callback receives.
|
|
@@ -25,8 +25,8 @@ A mid-run stop or rewind opens a stopping window on the entry:
|
|
|
25
25
|
``stopping: {reason}`` plus, for rewinds, the staged dispatch as
|
|
26
26
|
``nextDispatch`` — written in the same envelope as the opener's pending
|
|
27
27
|
answer. On run end one envelope promotes ``nextDispatch`` into
|
|
28
|
-
``dispatching`` and clears the window; a later stop/rewind
|
|
29
|
-
staged
|
|
28
|
+
``dispatching`` and clears the window; a later stop/rewind replaces the
|
|
29
|
+
staged dispatch, whose command already settled at its admission write.
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
import asyncio
|
|
@@ -209,7 +209,7 @@ class RunManager:
|
|
|
209
209
|
self._dispatch_record: dict[str, Any] | None = None
|
|
210
210
|
self._intake: list[Any] = []
|
|
211
211
|
self._outcome: Any = None
|
|
212
|
-
self.
|
|
212
|
+
self._stop_staged = False
|
|
213
213
|
self._stop_reason: str | None = None
|
|
214
214
|
self._staged_rewind: _Rewind | None = None
|
|
215
215
|
self._dispatching: list[tuple[str, dict[str, Any]]] = []
|
|
@@ -354,10 +354,7 @@ class RunManager:
|
|
|
354
354
|
for entry in entries:
|
|
355
355
|
self._apply(entry, fx)
|
|
356
356
|
if self._task is None:
|
|
357
|
-
|
|
358
|
-
if not stop.done():
|
|
359
|
-
stop.set_result(None)
|
|
360
|
-
self._staged_stops.clear()
|
|
357
|
+
self._stop_staged = False
|
|
361
358
|
if isinstance(outcome, RunManager.Complete):
|
|
362
359
|
self._dispatched_ids = ()
|
|
363
360
|
if self._staged_rewind is not None:
|
|
@@ -378,14 +375,12 @@ class RunManager:
|
|
|
378
375
|
)
|
|
379
376
|
if not rewind.acked:
|
|
380
377
|
rewind.ack()
|
|
381
|
-
if not rewind.future.done():
|
|
382
|
-
rewind.future.set_result(None)
|
|
383
378
|
elif outcome is not None:
|
|
384
379
|
self._settle_outcome(outcome)
|
|
385
380
|
else:
|
|
386
381
|
self._idle_action(fx)
|
|
387
382
|
if self._task is not None and (
|
|
388
|
-
self.
|
|
383
|
+
self._stop_staged or self._staged_rewind is not None
|
|
389
384
|
):
|
|
390
385
|
assert self._ctx is not None
|
|
391
386
|
self._ctx.stop_requested.set()
|
|
@@ -401,12 +396,14 @@ class RunManager:
|
|
|
401
396
|
|
|
402
397
|
def _apply(self, entry: Any, fx: _Effects) -> None:
|
|
403
398
|
if isinstance(entry, _Stop):
|
|
404
|
-
self._staged_stops.append(entry.future)
|
|
405
399
|
if self._task is not None:
|
|
400
|
+
self._stop_staged = True
|
|
406
401
|
if self._staged_rewind is not None:
|
|
407
|
-
self.
|
|
402
|
+
self._staged_rewind = None
|
|
408
403
|
self._entry().pop("nextDispatch", None)
|
|
409
404
|
self._entry()["stopping"] = {"reason": "stop"}
|
|
405
|
+
if not entry.future.done():
|
|
406
|
+
entry.future.set_result(None)
|
|
410
407
|
return
|
|
411
408
|
if isinstance(entry, _Continue):
|
|
412
409
|
if not fx.continue_requested:
|
|
@@ -683,17 +680,7 @@ class RunManager:
|
|
|
683
680
|
return True
|
|
684
681
|
return False
|
|
685
682
|
|
|
686
|
-
def _supersede(self) -> None:
|
|
687
|
-
rewind, self._staged_rewind = self._staged_rewind, None
|
|
688
|
-
assert rewind is not None
|
|
689
|
-
if not rewind.future.done():
|
|
690
|
-
rewind.future.set_exception(
|
|
691
|
-
_reject("superseded", "a later stop/rewind replaced this command")
|
|
692
|
-
)
|
|
693
|
-
|
|
694
683
|
def _stage_rewind(self, rewind: _Rewind) -> None:
|
|
695
|
-
if self._staged_rewind is not None:
|
|
696
|
-
self._supersede()
|
|
697
684
|
self._staged_rewind = rewind
|
|
698
685
|
if self._task is not None:
|
|
699
686
|
entry = self._entry()
|
|
@@ -706,6 +693,8 @@ class RunManager:
|
|
|
706
693
|
)
|
|
707
694
|
rewind.ack()
|
|
708
695
|
rewind.acked = True
|
|
696
|
+
if not rewind.future.done():
|
|
697
|
+
rewind.future.set_result(None)
|
|
709
698
|
|
|
710
699
|
def _freeze(self, message: str, payload: Any = None) -> None:
|
|
711
700
|
self._set_status("error")
|
|
@@ -59,30 +59,24 @@ async def test_steer_and_stop_in_one_batch_nets_to_stop():
|
|
|
59
59
|
[("run/steer", add("s1")), ("run/stop", {"runId": await drv.run_id()})]
|
|
60
60
|
)
|
|
61
61
|
assert (await drv.res(first))["type"] == "accepted"
|
|
62
|
-
assert (await drv.res(first + 1
|
|
62
|
+
assert (await drv.res(first + 1))["type"] == "accepted"
|
|
63
63
|
await asyncio.wait_for(call.ctx.stop_requested.wait(), 5)
|
|
64
64
|
call.finish(RunManager.Stop(dispatch_queue=False))
|
|
65
|
-
assert (await drv.res(first + 1))["type"] == "accepted"
|
|
66
65
|
await drv.wait_status("stopped")
|
|
67
66
|
assert queue_ids(drv.replica, "steerQueue") == ["s1"]
|
|
68
67
|
script.no_call()
|
|
69
68
|
|
|
70
69
|
|
|
71
|
-
async def
|
|
70
|
+
async def test_stop_settles_at_the_stopping_write():
|
|
72
71
|
script = Script()
|
|
73
72
|
async with run_host(script) as (drv, host):
|
|
74
73
|
drv.post("run/enqueue", add("m1"))
|
|
75
74
|
call = await script.next_call()
|
|
76
75
|
call.ack()
|
|
77
|
-
|
|
78
|
-
assert
|
|
79
|
-
|
|
80
|
-
await asyncio.sleep(0)
|
|
81
|
-
assert all(
|
|
82
|
-
rsp["type"] == "pending" for rsp in drv._res.get(pending["seq"], [])
|
|
83
|
-
)
|
|
76
|
+
settled = await drv.stop()
|
|
77
|
+
assert settled["type"] == "accepted"
|
|
78
|
+
assert drv.replica["status"] == "running"
|
|
84
79
|
call.finish(RunManager.Stop(dispatch_queue=False))
|
|
85
|
-
assert (await drv.res(pending["seq"]))["type"] == "accepted"
|
|
86
80
|
await drv.wait_status("stopped")
|
|
87
81
|
|
|
88
82
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Contract: the stopping window. Three openers — run/stop, mid-run
|
|
2
2
|
edit/reload, and the enqueue/steer edit-of-dispatched form — write
|
|
3
|
-
``stopping: {reason}`` (plus the staged rewind as ``nextDispatch``)
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
``stopping: {reason}`` (plus the staged rewind as ``nextDispatch``) and
|
|
4
|
+
settle accepted with that write, ``status`` staying "running". On run end
|
|
5
|
+
one envelope promotes ``nextDispatch`` into ``dispatching`` and clears the
|
|
6
|
+
window; a later stop/rewind replaces the staged dispatch, whose command
|
|
7
|
+
already settled at its admission write."""
|
|
8
8
|
|
|
9
9
|
import asyncio
|
|
10
10
|
import copy
|
|
@@ -13,7 +13,6 @@ import pytest
|
|
|
13
13
|
from run_helpers import (
|
|
14
14
|
Script,
|
|
15
15
|
add,
|
|
16
|
-
assert_rejected,
|
|
17
16
|
edit_params,
|
|
18
17
|
msg,
|
|
19
18
|
queue_ids,
|
|
@@ -57,23 +56,22 @@ async def start_run(drv, script, *, parent="p0"):
|
|
|
57
56
|
return call
|
|
58
57
|
|
|
59
58
|
|
|
60
|
-
async def
|
|
59
|
+
async def test_stop_opens_the_window_and_settles_with_its_write():
|
|
61
60
|
script = Script()
|
|
62
61
|
async with run_host(script) as (drv, host):
|
|
63
62
|
call = await start_run(drv, script)
|
|
64
|
-
|
|
65
|
-
assert
|
|
63
|
+
settled = await drv.stop()
|
|
64
|
+
assert settled["type"] == "accepted"
|
|
66
65
|
assert drv.replica["status"] == "running"
|
|
67
66
|
assert drv.run["stopping"] == {"reason": "stop"}
|
|
68
67
|
assert "nextDispatch" not in drv.run
|
|
69
68
|
opened = next(
|
|
70
69
|
state
|
|
71
70
|
for env, state in states(drv)
|
|
72
|
-
if any(r["seq"] ==
|
|
71
|
+
if any(r["seq"] == settled["seq"] for r in env.get("res", []))
|
|
73
72
|
)
|
|
74
73
|
assert window_of(opened) == ({"reason": "stop"}, None)
|
|
75
74
|
call.finish(RunManager.Stop(dispatch_queue=False))
|
|
76
|
-
assert (await drv.res(pending["seq"]))["type"] == "accepted"
|
|
77
75
|
await drv.wait_status("stopped")
|
|
78
76
|
assert "stopping" not in drv.run
|
|
79
77
|
|
|
@@ -178,7 +176,7 @@ async def test_edit_of_dispatched_message_opens_the_window(command):
|
|
|
178
176
|
rerun.finish(RunManager.Complete())
|
|
179
177
|
|
|
180
178
|
|
|
181
|
-
async def
|
|
179
|
+
async def test_later_rewind_replaces_the_staged_dispatch():
|
|
182
180
|
script = Script()
|
|
183
181
|
async with run_host(script, capabilities=("rewind", "rewind-during-run")) as (
|
|
184
182
|
drv,
|
|
@@ -188,7 +186,7 @@ async def test_later_rewind_supersedes_the_staged_one():
|
|
|
188
186
|
first = drv.post("run/edit", edit_params("m1", msg("m2"), anchor="p0"))
|
|
189
187
|
await drv.wait(lambda s: "nextDispatch" in (s["runs"] or [{}])[0])
|
|
190
188
|
second = drv.post("run/reload", reload_params("a1", anchor="m1"))
|
|
191
|
-
|
|
189
|
+
assert (await drv.res(first))["type"] == "accepted"
|
|
192
190
|
await drv.wait(
|
|
193
191
|
lambda s: s["runs"][0]["nextDispatch"]["trigger"] == "message-reload"
|
|
194
192
|
)
|
|
@@ -201,7 +199,7 @@ async def test_later_rewind_supersedes_the_staged_one():
|
|
|
201
199
|
rerun.finish(RunManager.Complete())
|
|
202
200
|
|
|
203
201
|
|
|
204
|
-
async def
|
|
202
|
+
async def test_stop_clears_a_staged_rewind():
|
|
205
203
|
script = Script()
|
|
206
204
|
async with run_host(script, capabilities=("rewind", "rewind-during-run")) as (
|
|
207
205
|
drv,
|
|
@@ -210,12 +208,12 @@ async def test_stop_supersedes_a_staged_rewind():
|
|
|
210
208
|
call = await start_run(drv, script)
|
|
211
209
|
seq = drv.post("run/edit", edit_params("m1", msg("m2"), anchor="p0"))
|
|
212
210
|
await drv.wait(lambda s: "nextDispatch" in (s["runs"] or [{}])[0])
|
|
213
|
-
|
|
214
|
-
|
|
211
|
+
assert (await drv.res(seq))["type"] == "accepted"
|
|
212
|
+
settled = await drv.stop()
|
|
213
|
+
assert settled["type"] == "accepted"
|
|
215
214
|
await drv.wait(lambda s: (s["runs"] or [{}])[0].get("stopping") == {"reason": "stop"})
|
|
216
215
|
assert "nextDispatch" not in drv.run
|
|
217
216
|
call.finish(RunManager.Stop(dispatch_queue=False))
|
|
218
|
-
assert (await drv.res(pending["seq"]))["type"] == "accepted"
|
|
219
217
|
await drv.wait_status("stopped")
|
|
220
218
|
script.no_call()
|
|
221
219
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|