Add eval and format nodes

This commit is contained in:
Sean Lynch
2023-09-28 12:00:27 -04:00
parent ce67b5c4fb
commit 7235a8e9d9
5 changed files with 130 additions and 8 deletions
+31 -3
View File
@@ -4,8 +4,36 @@ This is a collection of nodes I find useful.
# Nodes
## Conditional Interrupt
## SRL Conditional Interrupt
Interrupts the currently running prompt if the "interrupt" input is true.
![Screenshot of SrlConditionalInterrupt](screenshots/SrlConditionalInterrupt.png)
Also passes through an item of any type for sequencing purposes.
Interrupts the currently running prompt if the "interrupt" input is
true. Also passes through an item of any type for sequencing purposes.
## SRL Format String
![Screenshot of SrlFormatString](screenshots/SrlFormatString.png)
Format a string using Python's string format syntax. You can use both
positional and named argument syntax.
## SRL Eval
![Screenshot of SrlEval](screenshots/SrlEval.png)
Evaluate arbitrary Python code as a function. Code is evaluated as:
```def func({parameters}):
code\_line1
code\_line2
...
```
Right now this uses the globals dictionary from the module, so be
careful about anything that might mutate globals, such as importing
modules. I don't have any intention to try to make it secure because
ComfyUI itself isn't remotely secure, but I will probably try to
improve isolation at some point in the future to prevent unintended
problems. PRs gladly accepted if you have need for this.
+99 -5
View File
@@ -1,3 +1,6 @@
import inspect
import textwrap
import nodes
@@ -9,9 +12,8 @@ class AnyType(str):
any_typ = AnyType("*")
class ConditionalInterrupt:
def __init__(self):
pass
class SrlConditionalInterrupt:
"""Interrupt processing if the boolean input is true. Pass through the other input."""
@classmethod
def INPUT_TYPES(cls):
@@ -34,14 +36,106 @@ class ConditionalInterrupt:
return (inp,)
class SrlFormatString:
"""Use Python f-string syntax to generate a string using the inputs as the arguments."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"format": ("STRING", {
"multiline": False,
"default": "first input via str(): {}, second input via repr(): {!r}, third input by index: {2}, fifth input by name: {in4}",
}),
},
"optional": {
"in0": (any_typ,),
"in1": (any_typ,),
"in2": (any_typ,),
"in3": (any_typ,),
"in4": (any_typ,),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "doit"
CATEGORY = "utils"
def doit(self, format, **kwargs):
# Allow referencing arguments both by name and index.
return (format.format(*kwargs.values(), **kwargs),)
class SrlEval:
"""Evaluate any Python code as a function with the given inputs."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"parameters": ("STRING", {"multiline": False, "default": "a, b=None, c=\"foo\", *rest"}),
"code": ("STRING", {"multiline": True, "default": "code goes here\nreturn a + b"}),
},
"optional": {
"arg0": (any_typ,),
"arg1": (any_typ,),
"arg2": (any_typ,),
"arg3": (any_typ,),
"arg4": (any_typ,),
}
}
RETURN_TYPES = (any_typ,)
FUNCTION = "doit"
CATEGORY = "utils"
def doit(self, parameters, code, **kw):
# Indent the code for the main body of the function
func_code = textwrap.indent(code, " ")
source = f"def func({parameters}):\n{func_code}"
# The provided code can mutate globals or really do anything, but ComfyUI isn't secure to begin with.
loc = {}
exec(source, globals(), loc)
func = loc["func"]
argspec = inspect.getfullargspec(func)
# We don't allow variable keyword arguments or keyword only arguments, but we do allow varargs
assert argspec.varkw is None
assert not argspec.kwonlyargs
input_names = list(self.INPUT_TYPES()["optional"].keys())
parameter_names = argspec.args
# Convert the list of defaults into a dictionary to make it easier to use
default_list = argspec.defaults if argspec.defaults is not None else []
defaults = {parameter_name: default for parameter_name, default in zip(parameter_names[-len(default_list):], default_list)}
# We handle substituting default values ourselves in order to support *args
args = [kw[input_name] if input_name in kw else defaults[parameter_name] for parameter_name, input_name in zip(parameter_names, input_names)]
# Support *args
if argspec.varargs is not None:
unnamed_inputs = input_names[len(argspec.args):]
# I considered requiring the remaining inputs to be contiguous, but I don't think it's helpful.
args += [kw[input_name] for input_name in unnamed_inputs if input_name in kw]
ret = func(*args)
return (ret,)
# A dictionary that contains all nodes you want to export with their names
# NOTE: names should be globally unique
NODE_CLASS_MAPPINGS = {
"Conditional Interrrupt": ConditionalInterrupt,
"SRL Conditional Interrrupt": SrlConditionalInterrupt,
"SRL Format String": SrlFormatString,
"SRL Eval": SrlEval,
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"ConditionalInterrupt": "Conditional Interrupt",
"SrlConditionalInterrupt": "SRL Conditional Interrupt",
"SrlFormatString": "SRL Format String",
"SrlEval": "SRL Eval",
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB