Refuse a __main__ node where its body is written, not at import

A module defining nodes could not be run directly: the decorator refused
`__main__` while the module body was still executing, so a `if __name__ ==
"__main__"` self-check beside the nodes was impossible and the checks had
to live in a separate pytest file. The refusal now fires where the
generated body is written — document() and shims(), both, since sync writes
the document first — and the decorator hands the function back as it always
did. Syncing a __main__-defined node is still refused, with the same words.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Hra4ndWMCLU5F3KjUuVAc
This commit is contained in:
2026-08-29 13:50:35 +02:00
co-authored by Claude Opus 5
parent cfb3941cf3
commit 81649dbfca
2 changed files with 42 additions and 6 deletions
+25 -1
View File
@@ -6,7 +6,7 @@ from fluksio.flow.schemas import FlowDef
from fluksio.sdk import MARKER, Flow, Port, SyncError, node, use
# The functions live here rather than in each test: a node's module is what the
# generated body imports, and `__main__` is refused for exactly that reason.
# generated body imports, and `__main__` is refused at sync for that reason.
@node(provides=[Port("dataset", "artifact"), Port("rows", "int")])
@@ -316,3 +316,27 @@ def test_a_negative_timeout_is_refused():
def test_a_zero_timeout_means_no_limit():
decorated = node(requires=["a"], timeout=0)(one_default)
assert decorated.__fluksio__.timeout == 0
def test_a_node_in_a_script_run_directly_is_refused_at_sync_not_at_import():
"""A study module has to be runnable as a script for a self-check.
The refusal belongs where the body is generated: the decorator hands the
function back untouched, so `python study.py` declares its nodes, calls
them and checks itself. What cannot be done is syncing them, because
nothing could import `__main__`.
"""
def probe(rows=1):
return {"score": float(rows)}
probe.__module__ = "__main__"
decorated = node(provides=[Port("score", "float")])(probe)
# Declared and callable, which is the whole point of running the file.
assert decorated(rows=2) == {"score": 2.0}
flow = Flow("mainflow", nodes=[decorated], outputs=["score"])
with pytest.raises(SyncError, match="run directly"):
flow.document()
with pytest.raises(SyncError, match="run directly"):
flow.shims()