Add eval and format nodes
This commit is contained in:
@@ -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.
|
||||

|
||||
|
||||
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
|
||||
|
||||

|
||||
|
||||
Format a string using Python's string format syntax. You can use both
|
||||
positional and named argument syntax.
|
||||
|
||||
## SRL Eval
|
||||
|
||||

|
||||
|
||||
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
@@ -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 |
Reference in New Issue
Block a user