From 5eec267a7ed6f6bda9d2507d292f5792d2b6e400 Mon Sep 17 00:00:00 2001 From: stroblme Date: Fri, 6 Nov 2026 14:35:12 +0100 Subject: [PATCH] A code-declared node's shim names only its triggering ports, so a non-triggering input with no value yet reaches the function as its own default instead of failing the call --- backend/fluksio/sdk/__init__.py | 6 +++++- backend/tests/sdk/test_build.py | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/backend/fluksio/sdk/__init__.py b/backend/fluksio/sdk/__init__.py index c41fb5e..d841631 100644 --- a/backend/fluksio/sdk/__init__.py +++ b/backend/fluksio/sdk/__init__.py @@ -877,12 +877,16 @@ def _shim(spec: NodeSpec) -> str: The import is aliased because ``process``'s parameters are the node's port names, and a port may be named after the very function it feeds — which would otherwise shadow the import inside the body. + + Only the triggering ports are named. The engine leaves out an input that + has no value yet, which only a port that does not trigger can lack, so + those travel in ``**settings`` and the function's own default applies. """ _refuse_main(spec) fn = spec.fn where = inspect.getsourcefile(fn) or fn.__module__ repo = import_root(fn) - ports = [port.port for port in spec.requires] + ports = [port.port for port in spec.requires if port.trigger] signature = ", ".join(ports + ["**settings"]) arguments = ", ".join([f"{port}={port}" for port in ports] + ["**settings"]) call = f"_impl({arguments})" diff --git a/backend/tests/sdk/test_build.py b/backend/tests/sdk/test_build.py index 380540d..4bb4b6c 100644 --- a/backend/tests/sdk/test_build.py +++ b/backend/tests/sdk/test_build.py @@ -240,6 +240,28 @@ def test_a_port_named_like_its_function_still_calls_the_function(): assert namespace["process"](scale=2.0) == {"scaled": 4.0} +@node( + requires=["value", Port("factor", "float", trigger=False)], + provides=Port("scaled", "float"), +) +def scale_by(value, factor=3.0): + return value * factor + + +def test_a_port_that_does_not_trigger_may_be_missing_from_the_call(): + """The engine leaves out an input with no value yet, so the default applies.""" + flow = Flow( + "optional", + nodes=[scale_by], + inputs=[Port("value", "float", initial=1.0), Port("factor", "float")], + ) + namespace: dict = {} + exec(compile(flow.shims()["scale_by"], "", "exec"), namespace) + + assert namespace["process"](value=2.0) == {"scaled": 6.0} + assert namespace["process"](value=2.0, factor=0.5) == {"scaled": 1.0} + + def reads_settings(settings): return settings