Header of process and node ports follow each other both ways: a panel edit rewrites only the def line of edited code, a code save reads the header back into input ports and settings (draft and response); Space types in the node editor again
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
@@ -557,6 +557,33 @@ async def test_the_live_socket_ends_when_its_client_goes(
|
||||
assert parked == []
|
||||
|
||||
|
||||
def test_saving_a_header_moves_the_node_ports_in_the_draft(
|
||||
client: TestClient, superuser_token_headers: dict[str, str]
|
||||
) -> None:
|
||||
saved = client.put(
|
||||
f"{PREFIX}/header", headers=superuser_token_headers, json=a_flow("header")
|
||||
).json()
|
||||
|
||||
response = client.put(
|
||||
f"{PREFIX}/header/nodes/logger/source",
|
||||
headers=superuser_token_headers,
|
||||
json={"code": "def process(reading, level, gain=2):\n return {}\n"},
|
||||
).json()
|
||||
|
||||
assert [spec["port"] for spec in response["node"]["requires"]] == [
|
||||
"reading",
|
||||
"level",
|
||||
]
|
||||
assert response["node"]["params"] == {"gain": 2}
|
||||
draft = client.get(f"{PREFIX}/header", headers=superuser_token_headers).json()
|
||||
assert draft["definition"]["version"] == response["version"]
|
||||
assert response["version"] == saved["definition"]["version"] + 1
|
||||
logger = next(n for n in draft["definition"]["nodes"] if n["id"] == "logger")
|
||||
assert logger["params"] == {"gain": 2}
|
||||
|
||||
client.delete(f"{PREFIX}/header", headers=superuser_token_headers)
|
||||
|
||||
|
||||
def test_a_shared_node_in_use_is_retired_only_with_detach(
|
||||
client: TestClient, superuser_token_headers: dict[str, str]
|
||||
) -> None:
|
||||
|
||||
@@ -9,7 +9,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from fluksio.flow.controller import FlowController
|
||||
from fluksio.flow.controller import FlowController, follow_header
|
||||
from fluksio.flow.messages import DType, MessageSpec
|
||||
from fluksio.flow.pipeline import Pipeline
|
||||
from fluksio.flow.schemas import FlowDef, NodeDef
|
||||
@@ -148,3 +148,66 @@ def test_a_shared_body_is_checked_against_each_user(store: FlowStore):
|
||||
|
||||
assert [n.status for n in nodes] == ["error"]
|
||||
assert "'level' is an input of 'scale'" in (nodes[0].error or "")
|
||||
|
||||
|
||||
def _scaffold_header(node: NodeDef) -> str:
|
||||
"""The header the canvas writes: input ports in order, then settings."""
|
||||
names = [spec.port for spec in node.requires] + [
|
||||
key for key in node.params if key != "synchronous"
|
||||
]
|
||||
return f"def process({', '.join(names)}):\n return {{}}\n"
|
||||
|
||||
|
||||
def test_a_scaffold_header_reads_back_as_the_node_it_came_from():
|
||||
node = _flow(
|
||||
[*READING, MessageSpec(name="garden.level", port="depth")],
|
||||
factor=3,
|
||||
synchronous=True,
|
||||
).nodes[0]
|
||||
|
||||
assert follow_header(node, _scaffold_header(node)) == node
|
||||
|
||||
|
||||
def test_a_header_declares_ports_and_settings_that_read_back_the_same():
|
||||
header = "def process(reading, level, gain=1.5, mode='eco'):\n return {}\n"
|
||||
|
||||
node = follow_header(_flow([]).nodes[0], header)
|
||||
|
||||
assert [spec.port for spec in node.requires] == ["reading", "level"]
|
||||
assert node.params == {"gain": 1.5, "mode": "eco"}
|
||||
assert _scaffold_header(node).split(":")[0] == (
|
||||
"def process(reading, level, gain, mode)"
|
||||
)
|
||||
|
||||
|
||||
def test_a_renamed_parameter_keeps_the_message_and_a_dropped_one_goes():
|
||||
node = _flow(READING, factor=3, offset=1).nodes[0]
|
||||
|
||||
renamed = follow_header(node, "def process(value, factor, offset): pass\n")
|
||||
dropped = follow_header(node, "def process(reading, factor): pass\n")
|
||||
|
||||
assert renamed.requires == [READING[0].model_copy(update={"port": "value"})]
|
||||
assert renamed.params == {"factor": 3, "offset": 1}
|
||||
assert dropped.requires == READING
|
||||
assert dropped.params == {"factor": 3}
|
||||
|
||||
|
||||
def test_a_new_parameter_after_a_setting_is_a_setting():
|
||||
node = _flow(READING, factor=3).nodes[0]
|
||||
|
||||
followed = follow_header(node, "def process(reading, factor, offset): pass\n")
|
||||
|
||||
assert followed.requires == READING
|
||||
assert followed.params == {"factor": 3, "offset": ""}
|
||||
|
||||
|
||||
def test_a_header_it_cannot_read_for_certain_leaves_the_node():
|
||||
node = _flow(READING, factor=3).nodes[0]
|
||||
|
||||
for code in (
|
||||
"def process(reading, factor",
|
||||
"def process(**kw): pass\n",
|
||||
"@cache\ndef process(value): pass\n",
|
||||
"def run(value): pass\n",
|
||||
):
|
||||
assert follow_header(node, code) == node
|
||||
|
||||
Reference in New Issue
Block a user