Version 0.0.7

Add casting and regex
This commit is contained in:
StableLlama
2025-05-08 22:26:22 +02:00
parent c29afc9e38
commit c5b303bde2
7 changed files with 468 additions and 50 deletions
+5 -1
View File
@@ -39,13 +39,17 @@ These nodes are very lightweight and require no additional dependencies.
- Comparison: is_subset, is_superset, is_disjoint
- **data list**: ComfyUI list manipulation nodes (for processing individual items)
- Conversion: convert to LIST
- Creation: create_empty
- Modification: append, filter, extend, insert, remove, pop, clear, set_item
- Access: get_item, slice, index, contains
- Information: length, count
- Operations: sort, reverse, copy, zip, min, max
- **cast**: Type conversion nodes for ComfyUI data types
- Basic conversions: to STRING, to INT, to FLOAT, to BOOLEAN
- Collection conversions: to LIST, to SET, to DICT
- Special conversions: data list to LIST, data list to SET
- **DICT**: Dictionary manipulation nodes
- Creation: create, from_items, from_lists, fromkeys, any_to_DICT
- Access: get, get_multiple, keys, values, items
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "basic_data_handling"
version = "0.0.6"
version = "0.0.7"
description = """Basic Python functions for manipulating data that every programmer is used to.
Currently supported ComfyUI data types: BOOLEAN, FLOAT, INT, STRING and data lists.
Additionally supported Python data types as custom data types: DICT, LIST.
+7 -2
View File
@@ -1,8 +1,10 @@
from . import (boolean_nodes, comparison_nodes, control_flow, data_list_nodes, dict_nodes,
float_nodes, int_nodes, list_nodes, math_nodes, set_nodes, string_nodes)
from . import (boolean_nodes, casting_nodes, comparison_nodes, control_flow,
data_list_nodes, dict_nodes, float_nodes, int_nodes, list_nodes,
math_nodes, regex_nodes, set_nodes, string_nodes)
NODE_CLASS_MAPPINGS = {}
NODE_CLASS_MAPPINGS.update(boolean_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(casting_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(comparison_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(control_flow.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(data_list_nodes.NODE_CLASS_MAPPINGS)
@@ -10,12 +12,14 @@ NODE_CLASS_MAPPINGS.update(dict_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(float_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(int_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(list_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(regex_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(set_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(math_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(string_nodes.NODE_CLASS_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS.update(boolean_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(casting_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(comparison_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(control_flow.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(data_list_nodes.NODE_DISPLAY_NAME_MAPPINGS)
@@ -23,6 +27,7 @@ NODE_DISPLAY_NAME_MAPPINGS.update(dict_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(float_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(int_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(list_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(regex_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(set_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(math_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(string_nodes.NODE_DISPLAY_NAME_MAPPINGS)
+11 -11
View File
@@ -10,8 +10,8 @@ class BooleanAnd:
def INPUT_TYPES(cls):
return {
"required": {
"input1": ("BOOLEAN", {"default": False}),
"input2": ("BOOLEAN", {"default": False}),
"input1": ("BOOLEAN", {"default": False, "forceInput": True}),
"input2": ("BOOLEAN", {"default": False, "forceInput": True}),
}
}
@@ -34,8 +34,8 @@ class BooleanOr:
def INPUT_TYPES(cls):
return {
"required": {
"input1": ("BOOLEAN", {"default": False}),
"input2": ("BOOLEAN", {"default": False}),
"input1": ("BOOLEAN", {"default": False, "forceInput": True}),
"input2": ("BOOLEAN", {"default": False, "forceInput": True}),
}
}
@@ -58,7 +58,7 @@ class BooleanNot:
def INPUT_TYPES(cls):
return {
"required": {
"input": ("BOOLEAN", {"default": False}),
"input": ("BOOLEAN", {"default": False, "forceInput": True}),
}
}
@@ -81,8 +81,8 @@ class BooleanXor:
def INPUT_TYPES(cls):
return {
"required": {
"input1": ("BOOLEAN", {"default": False}),
"input2": ("BOOLEAN", {"default": False}),
"input1": ("BOOLEAN", {"default": False, "forceInput": True}),
"input2": ("BOOLEAN", {"default": False, "forceInput": True}),
}
}
@@ -105,8 +105,8 @@ class BooleanNand:
def INPUT_TYPES(cls):
return {
"required": {
"input1": ("BOOLEAN", {"default": False}),
"input2": ("BOOLEAN", {"default": False}),
"input1": ("BOOLEAN", {"default": False, "forceInput": True}),
"input2": ("BOOLEAN", {"default": False, "forceInput": True}),
}
}
@@ -129,8 +129,8 @@ class BooleanNor:
def INPUT_TYPES(cls):
return {
"required": {
"input1": ("BOOLEAN", {"default": False}),
"input2": ("BOOLEAN", {"default": False}),
"input1": ("BOOLEAN", {"default": False, "forceInput": True}),
"input2": ("BOOLEAN", {"default": False, "forceInput": True}),
}
}
+275
View File
@@ -0,0 +1,275 @@
from typing import Any
from inspect import cleandoc
class CastToString:
"""
Converts any input to a STRING. Non-string values are converted using str().
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": ("*", {})
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert_to_string"
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert_to_string(self, input: Any) -> tuple[str]:
return (str(input),)
class CastToInt:
"""
Converts any numeric input to an INT. Non-numeric or invalid inputs raise a ValueError.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": ("*", {})
}
}
RETURN_TYPES = ("INT",)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert_to_int"
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert_to_int(self, input: Any) -> tuple[int]:
try:
return (int(input),)
except ValueError:
raise ValueError(f"Cannot convert {input} to an INT.")
class CastToFloat:
"""
Converts any numeric input to a FLOAT. Non-numeric or invalid inputs raise a ValueError.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": ("*", {})
}
}
RETURN_TYPES = ("FLOAT",)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert_to_float"
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert_to_float(self, input: Any) -> tuple[float]:
try:
return (float(input),)
except ValueError:
raise ValueError(f"Cannot convert {input} to a FLOAT.")
class CastToBoolean:
"""
Converts any input to a BOOLEAN. Follows standard Python truthy/falsy rules.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": ("*", {})
}
}
RETURN_TYPES = ("BOOLEAN",)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert_to_boolean"
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert_to_boolean(self, input: Any) -> tuple[bool]:
return (bool(input),)
class CastToList:
"""
Converts any input to a LIST. Non-list inputs are wrapped in a list. If input is a ComfyUI data list,
it converts the individual items into a Python LIST.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": ("*", {})
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert_to_list"
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert_to_list(self, input: Any) -> tuple[list]:
if isinstance(input, list):
return (input,)
return ([input],)
class CastDataListToList:
"""
Converts a ComfyUI data list into a LIST object.
This node takes a data list input (which is typically a list of items with the same type)
and converts it to a LIST object (a Python list as a single variable).
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
return (kwargs.get('list', []).copy(),)
class CastToSet:
"""
Converts any input to a SET. Non-set inputs are converted into a set. If input is a ComfyUI data list,
it casts the individual items into a SET.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": ("*", {})
}
}
RETURN_TYPES = ("SET",)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert_to_set"
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert_to_set(self, input: Any) -> tuple[set]:
if isinstance(input, set):
return (input,)
return ({input,} if not isinstance(input, list) else set(input),)
class CastDataListToSet:
"""
Converts a ComfyUI data list into a LIST object.
This node takes a data list input (which is typically a list of items with the same type)
and converts it to a LIST object (a Python list as a single variable).
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*", {}),
}
}
RETURN_TYPES = ("SET",)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
return (set(kwargs.get('list', [])),)
class CastToDict:
"""
Converts compatible inputs to a DICT. Input must be a mapping or a list of key-value pairs.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input": ("*", {})
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/cast"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert_to_dict"
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert_to_dict(self, input: Any) -> tuple[dict]:
try:
return (dict(input),)
except (ValueError, TypeError):
raise ValueError(f"Cannot convert {input} to a DICT. Ensure it is a mapping or list of key-value pairs.")
NODE_CLASS_MAPPINGS = {
"CastToString": CastToString,
"CastToInt": CastToInt,
"CastToFloat": CastToFloat,
"CastToBoolean": CastToBoolean,
"CastToList": CastToList,
"CastDataListToList": CastDataListToList,
"CastToSet": CastToSet,
"CastDataListToSet": CastDataListToSet,
"CastToDict": CastToDict,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"CastToString": "to STRING",
"CastToInt": "to INT",
"CastToFloat": "to FLOAT",
"CastToBoolean": "to BOOLEAN",
"CastToList": "to LIST",
"CastDataListToList": "data list to LIST",
"CastToSet": "to SET",
"CastDataListToSet": "data list to SET",
"CastToDict": "to DICT",
}
+4 -35
View File
@@ -664,7 +664,7 @@ class DataListFilter:
return {
"required": {
"value": ("*", {}),
"filter": ("BOOLEAN", {}),
"filter": ("BOOLEAN", {"forceInput": True}),
}
}
@@ -692,35 +692,6 @@ class DataListFilter:
return (result,)
class DataListToList:
"""
Converts a ComfyUI data list into a LIST object.
This node takes a data list input (which is typically a list of items with the same type)
and converts it to a LIST object (a Python list as a single variable).
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"list": ("*", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/data list"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "convert"
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
return (kwargs.get('list', []).copy(),)
class DataListMin:
"""
Finds the minimum value in a list of numbers.
@@ -825,7 +796,6 @@ NODE_CLASS_MAPPINGS = {
"DataListCreateEmpty": DataListCreateEmpty,
"DataListZip": DataListZip,
"DataListFilter": DataListFilter,
"DataListToList": DataListToList,
"DataListMin": DataListMin,
"DataListMax": DataListMax,
}
@@ -844,13 +814,12 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"DataListCopy": "copy",
"DataListLength": "length",
"DataListSlice": "slice",
"DataListGetItem": "get_item",
"DataListSetItem": "set_item",
"DataListGetItem": "get item",
"DataListSetItem": "set item",
"DataListContains": "contains",
"DataListCreateEmpty": "create_empty",
"DataListCreateEmpty": "create empty",
"DataListZip": "zip",
"DataListFilter": "filter",
"DataListToList": "convert to LIST",
"DataListMin": "min",
"DataListMax": "max",
}
+165
View File
@@ -0,0 +1,165 @@
from typing import Any
from inspect import cleandoc
import re
class RegexSearchGroups:
"""
Searches the string for a match to the pattern and returns a LIST of match groups.
If no match is found, it returns an empty LIST.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pattern": ("STRING", {}),
"string": ("STRING", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "search_groups"
def search_groups(self, pattern: str, string: str) -> tuple[list[str]]:
match = re.search(pattern, string)
if match:
return (list(match.groups()),)
return ([],)
class RegexGroupDict:
"""
Searches the string with the given pattern and returns a DICT of named groups.
If no match is found, it returns an empty DICT.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pattern": ("STRING", {}),
"string": ("STRING", {}),
}
}
RETURN_TYPES = ("DICT",)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "groupdict"
def groupdict(self, pattern: str, string: str) -> tuple[dict]:
match = re.search(pattern, string)
if match:
return (match.groupdict(),)
return ({},)
class RegexFindall:
"""
Returns all non-overlapping matches of a pattern in the string as a list of strings.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pattern": ("STRING", {}),
"string": ("STRING", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "findall"
def findall(self, pattern: str, string: str) -> tuple[list[str]]:
return (re.findall(pattern, string),)
class RegexSplit:
"""
Splits the string at each match of the pattern and returns a list of substrings.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pattern": ("STRING", {}),
"string": ("STRING", {}),
}
}
RETURN_TYPES = ("LIST",)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "split"
def split(self, pattern: str, string: str) -> tuple[list[str]]:
return (re.split(pattern, string),)
class RegexSub:
"""
Substitutes matches of the pattern in the string with a replacement string.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pattern": ("STRING", {}),
"repl": ("STRING", {}),
"string": ("STRING", {}),
"count": ("INT", {"default": 0}), # 0 means replace all occurrences
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "sub"
def sub(self, pattern: str, repl: str, string: str, count: int = 0) -> tuple[str]:
return (re.sub(pattern, repl, string, count),)
class RegexTest:
"""
Tests whether a given regex pattern matches any part of the input string.
Returns True if a match is found, otherwise False.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"pattern": ("STRING", {}),
"string": ("STRING", {}),
}
}
RETURN_TYPES = ("BOOLEAN",)
CATEGORY = "Basic/STRING/regex"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "test"
def test(self, pattern: str, string: str) -> tuple[bool]:
match = re.search(pattern, string)
return (match is not None,)
NODE_CLASS_MAPPINGS = {
"RegexSearchGroups": RegexSearchGroups,
"RegexGroupDict": RegexGroupDict,
"RegexFindall": RegexFindall,
"RegexSplit": RegexSplit,
"RegexSub": RegexSub,
"RegexTest": RegexTest,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RegexSearchGroups": "search groups",
"RegexGroupDict": "search named groups",
"RegexFindall": "find all",
"RegexSplit": "split",
"RegexSub": "substitute",
"RegexTest": "test",
}