A python node's settings are constants of its own function, so they are passed the way its ports are: by name. The controller binds them to the compiled function, the `params` field is gone from the worker and remote protocols, and a setting sharing a port's name is reported as a node error rather than shadowing it. The panel's scaffold follows suit and keeps the header in step with both ports and settings. The demo's `pace` moves from a flow input to a setting of the training node, which is what it always was.
64 lines
2.0 KiB
Python
64 lines
2.0 KiB
Python
"""A node's settings are arguments of its function, like its ports.
|
|
|
|
What distinguishes them is where the value comes from: a port carries whatever
|
|
the graph last published, a setting is a constant stored with the flow. Both
|
|
arrive by name, so one name cannot mean both.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from app.flow.controller import FlowController
|
|
from app.flow.messages import DType, MessageSpec
|
|
from app.flow.pipeline import Pipeline
|
|
from app.flow.schemas import FlowDef, NodeDef
|
|
from app.flow.state import MemoryState
|
|
from app.flow.store import FlowStore
|
|
|
|
SOURCE = "def process(reading, factor):\n return {'scaled': reading * factor}\n"
|
|
|
|
|
|
def a_flow(**params: object) -> FlowDef:
|
|
return FlowDef(
|
|
name="house",
|
|
nodes=[
|
|
NodeDef(
|
|
id="scale",
|
|
params=dict(params),
|
|
requires=[MessageSpec(name="reading", dtype=DType.FLOAT)],
|
|
provides=[MessageSpec(name="scaled", dtype=DType.FLOAT)],
|
|
)
|
|
],
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def store(tmp_path: Path) -> FlowStore:
|
|
return FlowStore(tmp_path / "flows")
|
|
|
|
|
|
def test_a_setting_reaches_the_function_as_a_keyword_argument(store: FlowStore):
|
|
store.write_flow(a_flow(factor=3))
|
|
store.write_node_source("house", "scale", SOURCE)
|
|
controller = FlowController(store)
|
|
|
|
nodes, _loaded, _initial, _inputs = controller._build_flows(
|
|
[(store.read_flow("house"), False)]
|
|
)
|
|
pipeline = Pipeline(nodes=nodes, state=MemoryState())
|
|
pipeline.run({"house.reading": 2.0})
|
|
|
|
assert pipeline.values()["house.scaled"]["value"] == 6.0
|
|
|
|
|
|
def test_a_setting_named_after_a_port_is_refused(store: FlowStore):
|
|
store.write_draft(a_flow(reading=3), 0)
|
|
store.write_node_source("house", "scale", SOURCE, draft=True)
|
|
controller = FlowController(store)
|
|
|
|
preview = controller.preview("house")
|
|
|
|
assert [node.status for node in preview.nodes] == ["error"]
|
|
assert "both an input and a setting" in (preview.nodes[0].error or "")
|