Add nodes for flexible typing and conversion to Any

This commit adds several new nodes to enable flexible typing and conversion between types:

- ValueToAny - Converts primitive values like ints, floats, strings, bools to Any type
- IterableToAny - Converts iterable values like lists, dicts to Any
- CallableToAny - Converts callables like functions to Any
- ObjectToAny - Converts tensor objects like images, latents etc to Any

The motivation is to allow passing different types flexibly into functions using Any typing. This enables writing functions in a more generic way by accepting Any instead of specific types.

The nodes convert the input values to Any and return debug info on what was converted. This allows tracing what types entered a function even if the function uses generic Any types.

No breaking changes, new nodes added:
- ValueToAny
- IterableToAny
- CallableToAny
- ObjectToAny

These provide building blocks for more flexible typing, opening up possibilities like:

- Writing utility and processing functions accepting Any instead of specific types
- Calling functions dynamically with values of different types
- Tracing and logging what types passed through a function

Next steps could include AnyToValue nodes, more specialized Any converters, and exploring uses for generic functions.
This commit is contained in:
Tasha Upchurch
2023-09-18 17:18:35 -04:00
parent bd888f31b3
commit 35b3ec4f66
6 changed files with 794 additions and 165 deletions
+129
View File
@@ -97,6 +97,7 @@ class StrBreakout:
pass
return ("\n".join(notes), out_bool, out_int, out_float, out_list, out_lines, out_comma,)
class IntBreakout:
""" node that breaks out an int into a:
- Boolean
@@ -172,8 +173,136 @@ class IntBreakout:
return ("\n".join(notes), out_bool, out_str, out_float, out_list, out_range, out_none,)
class ValueToAny:
"""takes INT,STRING,FLOAT,BOOL, and returns the value coded as *"""
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"INT": ("INT", {"default": None, }),
"FLOAT": ("FLOAT", {"default": None, }),
"STRING": ("STRING", {"default": None, }),
"BOOL": ("BOOL", {"default": None, }),
}
}
RETURN_TYPES = ("*", "STRING",)
RETURN_NAMES = ("*", "debug info",)
FUNCTION = "get_value"
CATEGORY = "conversion"
def get_value(self, INT=None, FLOAT=None, STRING=None, BOOL=None):
if INT:
dbg = f"found INT: {INT}"
return (INT, dbg,)
if FLOAT:
dbg = f"found FLOAT: {FLOAT}"
return (FLOAT, dbg,)
if STRING:
dbg = f"found STRING: {STRING}"
return (STRING, dbg,)
if BOOL:
dbg = f"found BOOL: {BOOL}"
return (BOOL, dbg,)
dbg = f"found nothing, will return the first value that is not None; from INT,FLOAT,STRING,BOOL"
return (None, dbg)
class IterableToAny:
"""takes LIST,DICT, and returns the value coded as *"""
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"LIST": ("LIST", {"default": None, }),
"DICT": ("DICT", {"default": None, }),
}
}
RETURN_TYPES = ("*", "STRING",)
RETURN_NAMES = ("*", "debug info",)
FUNCTION = "get_value"
CATEGORY = "conversion"
def get_value(self, LIST=None, DICT=None):
if LIST:
dbg = f"found LIST: {LIST}"
return (LIST, dbg,)
if DICT:
dbg = f"found DICT: {DICT}"
return (DICT, dbg,)
dbg = f"found nothing, will return the first value that is not None; from LIST,DICT"
return (None, dbg)
class CallableToAny:
"""takes FUNC, (others soon) and returns the value coded as *"""
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"FUNC": ("FUNC", {"default": None, }),
}
}
RETURN_TYPES = ("*", "STRING",)
RETURN_NAMES = ("*", "debug info",)
FUNCTION = "get_value"
CATEGORY = "conversion"
def get_value(self, FUNC=None):
if FUNC:
dbg = f"found FUNC: {FUNC}"
return (FUNC, dbg,)
dbg = f"found nothing, will return the first value that is not None; from FUNC"
return (None, dbg)
class ObjectToAny:
""" takes non-primitive objects like TENSORS and returns the value coded as *"""
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"LATENT": ("LATENT", {"default": None, }),
"CONDITIONING": ("CONDITIONING", {"default": None, }),
"IMAGE": ("IMAGE", {"default": None, }),
"MASK": ("MASK", {"default": None, }),
}
}
RETURN_TYPES = ("*", "STRING",)
RETURN_NAMES = ("*", "debug info",)
FUNCTION = "get_value"
CATEGORY = "conversion"
def get_value(self, LATENT=None, CONDITIONING=None, IMAGE=None, MASK=None):
if LATENT:
dbg = f"found LATENT: {LATENT}"
return (LATENT, dbg,)
if CONDITIONING:
dbg = f"found CONDITIONING: {CONDITIONING}"
return (CONDITIONING, dbg,)
if IMAGE:
dbg = f"found IMAGE: {IMAGE}"
return (IMAGE, dbg,)
if MASK:
dbg = f"found MASK: {MASK}"
return (MASK, dbg,)
dbg = f"found nothing, will return the first value that is not None; from LATENT,CONDITIONING,IMAGE,MASK"
return (None, dbg)
NODE_CLASS_MAPPINGS = {}
NODE_CLASS_MAPPINGS["MultilineStringNode"] = MultilineStringNode
NODE_CLASS_MAPPINGS["StrBreakout"] = StrBreakout
NODE_CLASS_MAPPINGS["IntBreakout"] = IntBreakout
NODE_CLASS_MAPPINGS["ValueToAny"] = ValueToAny
NODE_CLASS_MAPPINGS["IterableToAny"] = IterableToAny
NODE_CLASS_MAPPINGS["CallableToAny"] = CallableToAny
NODE_CLASS_MAPPINGS["ObjectToAny"] = ObjectToAny