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:
@@ -1837,6 +1837,93 @@ def _signature_gap(
|
||||
)
|
||||
|
||||
|
||||
def follow_header(node: NodeDef, code: str) -> NodeDef:
|
||||
"""The node with its input ports and settings read off ``def process(...)``.
|
||||
|
||||
The inverse of the canvas's scaffold, which writes the input ports first,
|
||||
in their order, and the settings after them. A parameter already declared
|
||||
keeps its kind. A new one is a setting when it has a default, which becomes
|
||||
its value, or when it follows a setting, and an input port otherwise. One
|
||||
name swapped for one other is a rename, so the port keeps its message and
|
||||
the setting its value. A declared name the header no longer has is dropped.
|
||||
|
||||
A header this cannot read for certain leaves the node as it is: a syntax
|
||||
error, no top-level ``def process``, a decorator, ``*args``, ``**kwargs``,
|
||||
a bare ``*`` or ``/``.
|
||||
"""
|
||||
try:
|
||||
tree = ast.parse(code)
|
||||
except SyntaxError:
|
||||
return node
|
||||
defs = [
|
||||
entry
|
||||
for entry in tree.body
|
||||
if isinstance(entry, ast.FunctionDef) and entry.name == "process"
|
||||
]
|
||||
if not defs:
|
||||
return node
|
||||
args = defs[-1].args
|
||||
if (
|
||||
defs[-1].decorator_list
|
||||
or args.vararg
|
||||
or args.kwarg
|
||||
or args.posonlyargs
|
||||
or args.kwonlyargs
|
||||
):
|
||||
return node
|
||||
# Defaults belong to the last parameters, so they line up from the end.
|
||||
defaults: list[ast.expr | None] = [None] * (
|
||||
len(args.args) - len(args.defaults)
|
||||
) + list(args.defaults)
|
||||
header = [
|
||||
(arg.arg, default)
|
||||
for arg, default in zip(args.args, defaults, strict=True)
|
||||
if arg.arg not in RESERVED_SETTINGS
|
||||
]
|
||||
|
||||
def written(name: str) -> bool:
|
||||
return name.isascii() and name.isidentifier()
|
||||
|
||||
ports = {spec.port: spec for spec in node.requires if written(spec.port)}
|
||||
settings = {
|
||||
key: value
|
||||
for key, value in node.params.items()
|
||||
if key not in RESERVED_SETTINGS and written(key)
|
||||
}
|
||||
names = [name for name, _ in header]
|
||||
gone = [name for name in [*ports, *settings] if name not in names]
|
||||
new = [name for name in names if name not in ports and name not in settings]
|
||||
renamed = {new[0]: gone[0]} if len(gone) == len(new) == 1 else {}
|
||||
|
||||
requires: list[MessageSpec] = []
|
||||
params = {key: value for key, value in node.params.items() if key not in settings}
|
||||
after_setting = False
|
||||
for name, default in header:
|
||||
was = renamed.get(name, name)
|
||||
if was in ports:
|
||||
requires.append(ports[was].model_copy(update={"port": name}))
|
||||
elif was in settings or default is not None or after_setting:
|
||||
params[name] = settings[was] if was in settings else _literal(default)
|
||||
after_setting = True
|
||||
else:
|
||||
requires.append(MessageSpec(port=name))
|
||||
# Ports the header cannot name, such as a row not named yet, stay.
|
||||
requires += [spec for spec in node.requires if not written(spec.port)]
|
||||
return node.model_copy(update={"requires": requires, "params": params})
|
||||
|
||||
|
||||
def _literal(default: ast.expr | None) -> Any:
|
||||
"""A parameter's default as a setting's value, or empty when it is not JSON."""
|
||||
if default is None:
|
||||
return ""
|
||||
try:
|
||||
value = ast.literal_eval(default)
|
||||
json.dumps(value)
|
||||
except (ValueError, TypeError, SyntaxError, RecursionError):
|
||||
return ""
|
||||
return value
|
||||
|
||||
|
||||
def with_settings(
|
||||
function: Callable[..., Any], params: dict[str, Any]
|
||||
) -> Callable[..., Any]:
|
||||
|
||||
Reference in New Issue
Block a user