stageflow-framework 0.1.2__tar.gz → 0.1.4__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.
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/PKG-INFO +1 -1
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/pyproject.toml +1 -1
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/core/session.py +12 -3
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/core/stage.py +20 -4
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow_framework.egg-info/PKG-INFO +1 -1
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow_framework.egg-info/SOURCES.txt +2 -0
- stageflow_framework-0.1.4/tests/test_session_wait_input.py +40 -0
- stageflow_framework-0.1.4/tests/test_stage_wait_input.py +64 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/README.md +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/setup.cfg +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/__init__.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/builtins/__init__.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/builtins/dicts.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/builtins/lists.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/builtins/lists_extra.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/builtins/logic.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/builtins/strings.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/builtins/vars.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/core/__init__.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/core/context.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/core/event.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/core/jsonlogic.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/core/node.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/core/pipeline.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/core/utils.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/docs/__init__.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/docs/html.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/docs/schema.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/docs/schemas/pipeline.json +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/py.typed +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/testing.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow_framework.egg-info/dependency_links.txt +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow_framework.egg-info/requires.txt +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow_framework.egg-info/top_level.txt +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/tests/test_core_flow.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/tests/test_docs_schema.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/tests/test_full_pipeline.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/tests/test_jsonlogic.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/tests/test_payload_validation.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/tests/test_pipeline_tester.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/tests/test_snapshot.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/tests/test_std_stages.py +0 -0
- {stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/tests/test_subpipeline.py +0 -0
|
@@ -87,15 +87,20 @@ class Session:
|
|
|
87
87
|
self._pending_inputs.setdefault(type_, []).append(entry)
|
|
88
88
|
return entry
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
# Deliver buffered input if it arrived before waiter.
|
|
90
|
+
def start_wait_input(self, type_: str) -> asyncio.Future:
|
|
92
91
|
pending = self._pending_inputs.get(type_)
|
|
93
92
|
if pending:
|
|
94
|
-
|
|
93
|
+
loop = asyncio.get_running_loop()
|
|
94
|
+
fut = loop.create_future()
|
|
95
|
+
fut.set_result(pending.pop(0))
|
|
96
|
+
return fut
|
|
95
97
|
loop = asyncio.get_running_loop()
|
|
96
98
|
fut = loop.create_future()
|
|
97
99
|
self._waiting.setdefault(type_, []).append(fut)
|
|
98
100
|
self.emit(Event(type="waiting_for_input", session_id=self.id, payload={"type": type_}))
|
|
101
|
+
return fut
|
|
102
|
+
|
|
103
|
+
async def finish_wait_input(self, type_: str, fut: asyncio.Future, timeout: float | None = None):
|
|
99
104
|
try:
|
|
100
105
|
return await asyncio.wait_for(fut, timeout=timeout)
|
|
101
106
|
except asyncio.TimeoutError:
|
|
@@ -108,6 +113,10 @@ class Session:
|
|
|
108
113
|
if not waiters:
|
|
109
114
|
del self._waiting[type_]
|
|
110
115
|
|
|
116
|
+
async def wait_input(self, type_: str, timeout: float | None = None):
|
|
117
|
+
fut = self.start_wait_input(type_)
|
|
118
|
+
return await self.finish_wait_input(type_, fut, timeout=timeout)
|
|
119
|
+
|
|
111
120
|
def last_input(self, type_: str | None = None):
|
|
112
121
|
if not self.input_history:
|
|
113
122
|
return None
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import copy
|
|
2
3
|
from typing import Any, TYPE_CHECKING
|
|
3
4
|
import yaml
|
|
@@ -121,14 +122,29 @@ class BaseStage:
|
|
|
121
122
|
payload=payload or {},
|
|
122
123
|
))
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
def _get_allowed_input(self, type_: str) -> InputSpec | None:
|
|
125
126
|
if self.allowed_inputs:
|
|
126
127
|
allowed = {spec.type for spec in self.allowed_inputs if spec.type}
|
|
127
128
|
if allowed and type_ not in allowed:
|
|
128
129
|
raise ValueError(f"Input type '{type_}' is not allowed for stage '{self.stage_name}'")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
return next((spec for spec in self.allowed_inputs if spec.type == type_), None)
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
def start_wait_input(self, type_: str) -> asyncio.Future:
|
|
134
|
+
self._get_allowed_input(type_)
|
|
135
|
+
return self.session.start_wait_input(type_)
|
|
136
|
+
|
|
137
|
+
async def finish_wait_input(self, type_: str, fut: asyncio.Future, timeout: float | None = None):
|
|
138
|
+
matching = self._get_allowed_input(type_)
|
|
139
|
+
result = await self.session.finish_wait_input(type_, fut, timeout=timeout)
|
|
140
|
+
if result is None:
|
|
141
|
+
return None
|
|
142
|
+
if matching and matching.payload_schema is not None:
|
|
143
|
+
validate_schema(result.get("payload", {}), matching.payload_schema, "Input payload")
|
|
144
|
+
return result
|
|
145
|
+
|
|
146
|
+
async def wait_input(self, type_: str, timeout: float | None = None):
|
|
147
|
+
matching = self._get_allowed_input(type_)
|
|
132
148
|
result = await self.session.wait_input(type_, timeout=timeout)
|
|
133
149
|
if result is None:
|
|
134
150
|
return None
|
{stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow_framework.egg-info/SOURCES.txt
RENAMED
|
@@ -34,6 +34,8 @@ tests/test_full_pipeline.py
|
|
|
34
34
|
tests/test_jsonlogic.py
|
|
35
35
|
tests/test_payload_validation.py
|
|
36
36
|
tests/test_pipeline_tester.py
|
|
37
|
+
tests/test_session_wait_input.py
|
|
37
38
|
tests/test_snapshot.py
|
|
39
|
+
tests/test_stage_wait_input.py
|
|
38
40
|
tests/test_std_stages.py
|
|
39
41
|
tests/test_subpipeline.py
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from stageflow.core.context import Context
|
|
4
|
+
from stageflow.core.node import TerminalNode
|
|
5
|
+
from stageflow.core.pipeline import Pipeline
|
|
6
|
+
from stageflow.core.session import Session
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _make_pipeline():
|
|
10
|
+
end = TerminalNode(id="end", type="terminal")
|
|
11
|
+
return Pipeline(entry="end", nodes=[end])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SessionWaitInputTests(unittest.IsolatedAsyncioTestCase):
|
|
15
|
+
async def test_start_and_finish_wait_input_success(self):
|
|
16
|
+
session = Session(id="s1", pipeline=_make_pipeline(), context=Context())
|
|
17
|
+
waiter = session.start_wait_input("user")
|
|
18
|
+
|
|
19
|
+
await session.input("user", {"id": 1})
|
|
20
|
+
result = await session.finish_wait_input("user", waiter, timeout=1)
|
|
21
|
+
|
|
22
|
+
self.assertEqual(result["payload"]["id"], 1)
|
|
23
|
+
self.assertIn("waiting_for_input", [e.type for e in session.event_history])
|
|
24
|
+
|
|
25
|
+
async def test_start_finish_timeout_and_pending_consumption(self):
|
|
26
|
+
session = Session(id="s2", pipeline=_make_pipeline(), context=Context())
|
|
27
|
+
waiter = session.start_wait_input("later")
|
|
28
|
+
result = await session.finish_wait_input("later", waiter, timeout=0.01)
|
|
29
|
+
|
|
30
|
+
self.assertIsNone(result)
|
|
31
|
+
self.assertIn("input_timeout", [e.type for e in session.event_history])
|
|
32
|
+
|
|
33
|
+
await session.input("later", {"value": 2})
|
|
34
|
+
waiter2 = session.start_wait_input("later")
|
|
35
|
+
result2 = await session.finish_wait_input("later", waiter2, timeout=1)
|
|
36
|
+
self.assertEqual(result2["payload"]["value"], 2)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
unittest.main()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from stageflow.core.context import Context
|
|
4
|
+
from stageflow.core.event import InputSpec
|
|
5
|
+
from stageflow.core.node import TerminalNode
|
|
6
|
+
from stageflow.core.pipeline import Pipeline
|
|
7
|
+
from stageflow.core.session import Session
|
|
8
|
+
from stageflow.core.stage import BaseStage
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _make_pipeline():
|
|
12
|
+
end = TerminalNode(id="end", type="terminal")
|
|
13
|
+
return Pipeline(entry="end", nodes=[end])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StageWaitInputTests(unittest.IsolatedAsyncioTestCase):
|
|
17
|
+
async def test_start_and_finish_wait_input_success(self):
|
|
18
|
+
session = Session(id="stage1", pipeline=_make_pipeline(), context=Context())
|
|
19
|
+
|
|
20
|
+
class WaitStage(BaseStage):
|
|
21
|
+
allowed_inputs = [InputSpec(type="user", payload_schema={"id": int})]
|
|
22
|
+
|
|
23
|
+
async def run(self):
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
stage = WaitStage(stage_id="s1", config={}, arguments={}, outputs={}, session=session)
|
|
27
|
+
fut = stage.start_wait_input("user")
|
|
28
|
+
await session.input("user", {"id": 10})
|
|
29
|
+
|
|
30
|
+
result = await stage.finish_wait_input("user", fut, timeout=1)
|
|
31
|
+
self.assertEqual(result["payload"]["id"], 10)
|
|
32
|
+
|
|
33
|
+
async def test_finish_wait_input_validates_payload(self):
|
|
34
|
+
session = Session(id="stage2", pipeline=_make_pipeline(), context=Context())
|
|
35
|
+
|
|
36
|
+
class WaitStage(BaseStage):
|
|
37
|
+
allowed_inputs = [InputSpec(type="user", payload_schema={"id": int})]
|
|
38
|
+
|
|
39
|
+
async def run(self):
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
stage = WaitStage(stage_id="s1", config={}, arguments={}, outputs={}, session=session)
|
|
43
|
+
fut = stage.start_wait_input("user")
|
|
44
|
+
await session.input("user", {"id": "bad"})
|
|
45
|
+
|
|
46
|
+
with self.assertRaises(ValueError):
|
|
47
|
+
await stage.finish_wait_input("user", fut, timeout=1)
|
|
48
|
+
|
|
49
|
+
async def test_start_wait_input_rejects_disallowed_type(self):
|
|
50
|
+
session = Session(id="stage3", pipeline=_make_pipeline(), context=Context())
|
|
51
|
+
|
|
52
|
+
class LimitedStage(BaseStage):
|
|
53
|
+
allowed_inputs = [InputSpec(type="only")]
|
|
54
|
+
|
|
55
|
+
async def run(self):
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
stage = LimitedStage(stage_id="s1", config={}, arguments={}, outputs={}, session=session)
|
|
59
|
+
with self.assertRaises(ValueError):
|
|
60
|
+
stage.start_wait_input("other")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
unittest.main()
|
|
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
|
{stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow/docs/schemas/pipeline.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow_framework.egg-info/requires.txt
RENAMED
|
File without changes
|
{stageflow_framework-0.1.2 → stageflow_framework-0.1.4}/stageflow_framework.egg-info/top_level.txt
RENAMED
|
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
|