Merge pull request #1 from StableLlama/more_tests

Add more unit tests
This commit is contained in:
StableLlama
2025-05-10 00:46:52 +02:00
committed by GitHub
15 changed files with 446 additions and 602 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "basic_data_handling"
version = "0.0.8"
version = "0.0.9"
description = """NOTE: Still in development! Expect breaking changes!
Basic Python functions for manipulating data that every programmer is used to.
Currently supported ComfyUI data types: BOOLEAN, FLOAT, INT, STRING and data lists.
+3 -3
View File
@@ -1,4 +1,4 @@
from . import (boolean_nodes, casting_nodes, comparison_nodes, control_flow,
from . import (boolean_nodes, casting_nodes, comparison_nodes, control_flow_nodes,
data_list_nodes, dict_nodes, float_nodes, int_nodes, list_nodes,
math_nodes, path_nodes, regex_nodes, set_nodes, string_nodes)
@@ -6,7 +6,7 @@ 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(control_flow_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(data_list_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(dict_nodes.NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(float_nodes.NODE_CLASS_MAPPINGS)
@@ -22,7 +22,7 @@ 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(control_flow_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(data_list_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(dict_nodes.NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(float_nodes.NODE_DISPLAY_NAME_MAPPINGS)
+4 -4
View File
@@ -51,7 +51,7 @@ class CastToInt:
def convert_to_int(self, input: Any) -> tuple[int]:
try:
return (int(input),)
except ValueError:
except (ValueError, TypeError):
raise ValueError(f"Cannot convert {input} to an INT.")
@@ -79,7 +79,7 @@ class CastToFloat:
def convert_to_float(self, input: Any) -> tuple[float]:
try:
return (float(input),)
except ValueError:
except (ValueError, TypeError):
raise ValueError(f"Cannot convert {input} to a FLOAT.")
@@ -161,7 +161,7 @@ class CastDataListToList:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def convert(self, **kwargs: list[Any]) -> tuple[list[Any]]:
return (kwargs.get('list', []).copy(),)
@@ -218,7 +218,7 @@ class CastDataListToSet:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def convert(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def convert(self, **kwargs: list[Any]) -> tuple[set[Any]]:
return (set(kwargs.get('list', [])),)
@@ -1,4 +1,4 @@
from typing import Any, Dict, List, Union
from typing import Any
from inspect import cleandoc
class IfElse:
+29 -29
View File
@@ -30,7 +30,7 @@ class DataListAppend:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def append(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def append(self, **kwargs: list[Any]) -> tuple[list[Any]]:
result = kwargs.get('list', []).copy()
item = kwargs.get('item', [])
if len(item) > 0:
@@ -66,7 +66,7 @@ class DataListExtend:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def extend(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def extend(self, **kwargs: list[Any]) -> tuple[list[Any]]:
return (kwargs.get('list_a', []) + kwargs.get('list_b', []),)
@@ -99,7 +99,7 @@ class DataListInsert:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def insert(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def insert(self, **kwargs: list[Any]) -> tuple[list[Any]]:
result = kwargs.get('list', []).copy()
result.insert(kwargs.get('index', [0])[0], kwargs.get('item', [None])[0])
return (result,)
@@ -133,7 +133,7 @@ class DataListRemove:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def remove(self, **kwargs: dict[str, list]) -> tuple[list[Any], bool]:
def remove(self, **kwargs: list[Any]) -> tuple[list[Any], bool]:
result = kwargs.get('list', []).copy()
value = kwargs.get('value', [])
try:
@@ -172,12 +172,12 @@ class DataListPop:
OUTPUT_IS_LIST = (True, False)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool|str:
if input_types[0].get("index", "INT") not in ("INT"):
def VALIDATE_INPUTS(cls, input_types: list[dict[str, str]]) -> bool|str:
if input_types[0].get("index", "INT") != "INT":
return "index must be an INT type"
return True
def pop(self, **kwargs: dict[str, list]) -> tuple[list[Any], Any]:
def pop(self, **kwargs: list[Any]) -> tuple[list[Any], Any]:
result = kwargs.get('list', []).copy()
index = kwargs.get('index', [-1])[0]
try:
@@ -213,7 +213,7 @@ class DataListClear:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def clear(self, **kwargs: dict[str, list]) -> tuple[[]]:
def clear(self, **kwargs: list[Any]) -> tuple[list]:
# Return a new empty list rather than modifying the input
return ([],)
@@ -250,7 +250,7 @@ class DataListIndex:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def list_index(self, **kwargs: dict[str, list]) -> tuple[int]:
def list_index(self, **kwargs: list[Any]) -> tuple[int]:
input_list = kwargs.get('list', [])
value = kwargs.get('value', [None])[0]
start = kwargs.get('start', [0])[0]
@@ -291,7 +291,7 @@ class DataListCount:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def count(self, **kwargs: dict[str, list]) -> tuple[int]:
def count(self, **kwargs: list[Any]) -> tuple[int]:
value = kwargs.get('value', [None])[0]
return (kwargs.get('list', []).count(value),)
@@ -326,7 +326,7 @@ class DataListSort:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def sort(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def sort(self, **kwargs: list[Any]) -> tuple[list[Any]]:
# Convert string to boolean
reverse = kwargs.get('reverse', ["False"])[0] == "True"
@@ -360,7 +360,7 @@ class DataListReverse:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def reverse(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def reverse(self, **kwargs: list[Any]) -> tuple[list[Any]]:
result = kwargs.get('list', []).copy()
result.reverse()
return (result,)
@@ -392,7 +392,7 @@ class DataListCopy:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def copy(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def copy(self, **kwargs: list[Any]) -> tuple[list[Any]]:
return (kwargs.get('list', []).copy(),)
@@ -421,7 +421,7 @@ class DataListLength:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def length(self, **kwargs: dict[str, list]) -> tuple[int]:
def length(self, **kwargs: list[Any]) -> tuple[int]:
return (len(kwargs.get('list', [])),)
@@ -457,7 +457,7 @@ class DataListSlice:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def slice(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def slice(self, **kwargs: list[Any]) -> tuple[list[Any]]:
input_list = kwargs.get('list', [])
start = kwargs.get('start', [0])[0]
stop = kwargs.get('stop', [-1])[0]
@@ -481,7 +481,7 @@ class DataListGetItem:
def INPUT_TYPES(cls):
return {
"required": {
"input_list": ("*",),
"list": ("*",),
"index": ("INT", {"default": 0}),
}
}
@@ -497,7 +497,7 @@ class DataListGetItem:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def get_item(self, **kwargs: dict[str, list]) -> tuple[Any]:
def get_item(self, **kwargs: list[Any]) -> tuple[Any]:
index = kwargs.get('index', [0])[0]
try:
return (kwargs.get('list', [])[index],)
@@ -534,7 +534,7 @@ class DataListSetItem:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def set_item(self, **kwargs: dict[str, list]) -> tuple[Any]:
def set_item(self, **kwargs: list[Any]) -> tuple[Any]:
input_list = kwargs.get('list', [])
index = kwargs.get('index', [0])[0]
value = kwargs.get('value', [None])[0]
@@ -557,7 +557,7 @@ class DataListContains:
def INPUT_TYPES(cls):
return {
"required": {
"input_list": ("*",),
"list": ("*",),
"value": ("*",),
}
}
@@ -573,7 +573,7 @@ class DataListContains:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def contains(self, **kwargs: dict[str, list]) -> tuple[bool]:
def contains(self, **kwargs: list[Any]) -> tuple[bool]:
value = kwargs.get('value', [])
if len(value) == 0:
return (False,)
@@ -597,7 +597,7 @@ class DataListCreateEmpty:
FUNCTION = "create_empty"
OUTPUT_IS_LIST = (True,)
def create_empty(self) -> tuple[[]]:
def create_empty(self) -> tuple[list]:
return ([],)
@@ -634,7 +634,7 @@ class DataListZip:
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool:
return True
def zip_lists(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def zip_lists(self, **kwargs: list[Any]) -> tuple[list[Any]]:
lists = [kwargs.get('list1', []), kwargs.get('list2', [])]
if 'list3' in kwargs:
@@ -677,12 +677,12 @@ class DataListFilter:
OUTPUT_IS_LIST = (True,)
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool|str:
if input_types[0]["filter"] not in ("BOOLEAN"):
def VALIDATE_INPUTS(cls, input_types: list[dict[str, str]]) -> bool|str:
if input_types[0]["filter"] != "BOOLEAN":
return "filter must be a BOOLEAN type"
return True
def filter_data(self, **kwargs: dict[str, list]) -> tuple[list[Any]]:
def filter_data(self, **kwargs: list[Any]) -> tuple[list[Any]]:
values = kwargs.get('value', [])
filters = kwargs.get('filter', [])
@@ -716,12 +716,12 @@ class DataListMin:
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool|str:
def VALIDATE_INPUTS(cls, input_types: list[dict[str, str]]) -> bool|str:
if input_types[0]["list"] not in ("FLOAT", "INT"):
return "list must contain FLOAT or INT types"
return True
def find_min(self, **kwargs: dict[str, list]) -> tuple[Any]:
def find_min(self, **kwargs: list[Any]) -> tuple[Any]:
values = kwargs.get('list', [])
if not values:
return (None,)
@@ -758,12 +758,12 @@ class DataListMax:
INPUT_IS_LIST = True
@classmethod
def VALIDATE_INPUTS(cls, input_types: dict[str, str]) -> bool|str:
def VALIDATE_INPUTS(cls, input_types: list[dict[str, str]]) -> bool|str:
if input_types[0]["list"] not in ("FLOAT", "INT"):
return "list must contain FLOAT or INT types"
return True
def find_max(self, **kwargs: dict[str, list]) -> tuple[Any]:
def find_max(self, **kwargs: list[Any]) -> tuple[Any]:
values = kwargs.get('list', [])
if not values:
return (None,)
+1 -1
View File
@@ -353,7 +353,7 @@ class DictPopItem:
return result, key, value, True
else:
return result, "", None, False
except Exception as e:
except:
return result, "", None, False
+1 -1
View File
@@ -201,7 +201,7 @@ class IntToBytes:
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "to_bytes"
def to_bytes(self, int_value: int, length: int, byteorder: Literal["big", "little"], signed: Literal["True", "False"]) -> tuple[int]:
def to_bytes(self, int_value: int, length: int, byteorder: Literal["big", "little"], signed: Literal["True", "False"]) -> tuple[bytes]:
signed_bool = (signed == "True")
return (int_value.to_bytes(length, byteorder=byteorder, signed=signed_bool),)
-1
View File
@@ -1,4 +1,3 @@
from typing import Any
from inspect import cleandoc
import re
+3 -557
View File
@@ -79,7 +79,7 @@ class StringCenter:
return (string.center(width, fillchar),)
class StringConcat:
class DataStringConcat:
"""Combines two text strings together, joining them end-to-end."""
@classmethod
def INPUT_TYPES(cls):
@@ -696,29 +696,6 @@ class StringLjust:
return (string.ljust(width, fillchar),)
class StringLower:
"""
Converts the string to lowercase.
This node returns a copy of the string with all uppercase characters converted to lowercase.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "lower"
def lower(self, string):
return (string.lower(),)
class StringLstrip:
"""
Removes leading characters from the string.
@@ -749,36 +726,6 @@ class StringLstrip:
return (string.lstrip(chars),)
class StringReplace:
"""
Replaces occurrences of a substring with another substring.
This node returns a copy of the string with all occurrences of substring
old replaced by new. If the optional argument count is given, only the
first count occurrences are replaced.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
"old": ("STRING", {"default": ""}),
"new": ("STRING", {"default": ""}),
},
"optional": {
"count": ("INT", {"default": -1, "min": -1}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "replace"
def replace(self, string, old, new, count=-1):
return (string.replace(old, new, count),)
class StringRemoveprefix:
"""
Removes prefix from the string if present.
@@ -829,71 +776,6 @@ class StringRemovesuffix:
return (string.removesuffix(suffix),)
class StringRfind:
"""
Finds the highest index of the substring in the string.
This node searches for the last occurrence of the specified substring within the input string.
Returns the highest index where the substring is found. If the substring is not found, it returns -1.
Optional start and end parameters allow you to limit the search to a specific portion of the string.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
"substring": ("STRING", {"default": ""}),
},
"optional": {
"start": ("INT", {"default": 0, "min": 0}),
"end": ("INT", {"default": 0, "min": 0}),
}
}
RETURN_TYPES = ("INT",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rfind"
def rfind(self, string, substring, start=0, end=0):
# If end is 0 or negative, search to the end of the string
if end <= 0:
end = len(string)
return (string.rfind(substring, start, end),)
class StringRjust:
"""
Right-aligns the string within a field of a given width.
This node returns a string right-aligned in a field of the specified width.
If fillchar is provided, it is used as the padding character instead of a space.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
"width": ("INT", {"default": 10, "min": 0}),
},
"optional": {
"fillchar": ("STRING", {"default": " "}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rjust"
def rjust(self, string, width, fillchar=" "):
# Ensure fillchar is only one character
if len(fillchar) > 0:
fillchar = fillchar[0]
return (string.rjust(width, fillchar),)
class StringRsplit:
"""
Splits the string at the specified separator from the right.
@@ -926,68 +808,6 @@ class StringRsplit:
return (string.rsplit(sep, maxsplit),)
class StringRstrip:
"""
Removes trailing characters from the string.
This node returns a copy of the string with trailing characters removed.
If chars is provided, it specifies the set of characters to be removed.
If chars is not provided, whitespace characters are removed.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
},
"optional": {
"chars": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rstrip"
def rstrip(self, string, chars=None):
if chars == "":
chars = None
return (string.rstrip(chars),)
class StringLjust:
"""
Left-aligns the string within a field of a given width.
This node returns a string left-aligned in a field of the specified width.
If fillchar is provided, it is used as the padding character instead of a space.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
"width": ("INT", {"default": 10, "min": 0}),
},
"optional": {
"fillchar": ("STRING", {"default": " "}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "ljust"
def ljust(self, string, width, fillchar=" "):
# Ensure fillchar is only one character
if len(fillchar) > 0:
fillchar = fillchar[0]
return (string.ljust(width, fillchar),)
class StringLower:
"""
Converts the string to lowercase.
@@ -1011,36 +831,6 @@ class StringLower:
return (string.lower(),)
class StringLstrip:
"""
Removes leading characters from the string.
This node returns a copy of the string with leading characters removed.
If chars is provided, it specifies the set of characters to be removed.
If chars is not provided, whitespace characters are removed.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
},
"optional": {
"chars": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "lstrip"
def lstrip(self, string, chars=None):
if chars == "":
chars = None
return (string.lstrip(chars),)
class StringReplace:
"""
Replaces occurrences of a substring with another substring.
@@ -1071,90 +861,6 @@ class StringReplace:
return (string.replace(old, new, count),)
class StringRemoveprefix:
"""
Removes prefix from the string if present.
This node returns a copy of the string with the specified prefix removed
if the string starts with that prefix, otherwise returns the original string.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
"prefix": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "removeprefix"
def removeprefix(self, string, prefix):
return (string.removeprefix(prefix),)
class StringRemovesuffix:
"""
Removes suffix from the string if present.
This node returns a copy of the string with the specified suffix removed
if the string ends with that suffix, otherwise returns the original string.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
"suffix": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "removesuffix"
def removesuffix(self, string, suffix):
return (string.removesuffix(suffix),)
class StringRfind:
"""
Finds the highest index of the substring in the string.
This node searches for the last occurrence of the specified substring within the input string.
Returns the highest index where the substring is found. If the substring is not found, it returns -1.
Optional start and end parameters allow you to limit the search to a specific portion of the string.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
"substring": ("STRING", {"default": ""}),
},
"optional": {
"start": ("INT", {"default": 0, "min": 0}),
"end": ("INT", {"default": 0, "min": 0}),
}
}
RETURN_TYPES = ("INT",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rfind"
def rfind(self, string, substring, start=0, end=0):
# If end is 0 or negative, search to the end of the string
if end <= 0:
end = len(string)
return (string.rfind(substring, start, end),)
class StringRjust:
"""
Right-aligns the string within a field of a given width.
@@ -1186,69 +892,6 @@ class StringRjust:
return (string.rjust(width, fillchar),)
class StringRsplit:
"""
Splits the string at the specified separator from the right.
This node returns a list of strings by splitting the input string at the specified separator,
starting from the right. If maxsplit is provided, at most maxsplit splits are done.
If the separator is not specified or is None, any whitespace string is a separator.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
},
"optional": {
"sep": ("STRING", {"default": ""}),
"maxsplit": ("INT", {"default": -1, "min": -1}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rsplit"
OUTPUT_IS_LIST = (True,)
def rsplit(self, string, sep=None, maxsplit=-1):
if sep == "":
sep = None
return (string.rsplit(sep, maxsplit),)
class StringRstrip:
"""
Removes trailing characters from the string.
This node returns a copy of the string with trailing characters removed.
If chars is provided, it specifies the set of characters to be removed.
If chars is not provided, whitespace characters are removed.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
},
"optional": {
"chars": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rstrip"
def rstrip(self, string, chars=None):
if chars == "":
chars = None
return (string.rstrip(chars),)
class StringJoin:
"""
Joins strings from a data list with a specified separator.
@@ -1277,140 +920,6 @@ class StringJoin:
return (separator.join(strings),)
class StringLjust:
"""
Left-aligns the string within a field of a given width.
This node returns a string left-aligned in a field of the specified width.
If fillchar is provided, it is used as the padding character instead of a space.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
"width": ("INT", {"default": 10, "min": 0}),
},
"optional": {
"fillchar": ("STRING", {"default": " "}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "ljust"
def ljust(self, string, width, fillchar=" "):
# Ensure fillchar is only one character
if len(fillchar) > 0:
fillchar = fillchar[0]
return (string.ljust(width, fillchar),)
class StringLower:
"""
Converts the string to lowercase.
This node returns a copy of the string with all uppercase characters converted to lowercase.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "lower"
def lower(self, string):
return (string.lower(),)
class StringLstrip:
"""
Removes leading characters from the string.
This node returns a copy of the string with leading characters removed.
If chars is provided, it specifies the set of characters to be removed.
If chars is not provided, whitespace characters are removed.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
},
"optional": {
"chars": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "lstrip"
def lstrip(self, string, chars=None):
if chars == "":
chars = None
return (string.lstrip(chars),)
class StringRemoveprefix:
"""
Removes prefix from the string if present.
This node returns a copy of the string with the specified prefix removed
if the string starts with that prefix, otherwise returns the original string.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
"prefix": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "removeprefix"
def removeprefix(self, string, prefix):
return (string.removeprefix(prefix),)
class StringRemovesuffix:
"""
Removes suffix from the string if present.
This node returns a copy of the string with the specified suffix removed
if the string ends with that suffix, otherwise returns the original string.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
"suffix": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "removesuffix"
def removesuffix(self, string, suffix):
return (string.removesuffix(suffix),)
class StringRfind:
"""
Finds the highest index of the substring in the string.
@@ -1445,69 +954,6 @@ class StringRfind:
return (string.rfind(substring, start, end),)
class StringRjust:
"""
Right-aligns the string within a field of a given width.
This node returns a string right-aligned in a field of the specified width.
If fillchar is provided, it is used as the padding character instead of a space.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
"width": ("INT", {"default": 10, "min": 0}),
},
"optional": {
"fillchar": ("STRING", {"default": " "}),
}
}
RETURN_TYPES = ("STRING",)
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rjust"
def rjust(self, string, width, fillchar=" "):
# Ensure fillchar is only one character
if len(fillchar) > 0:
fillchar = fillchar[0]
return (string.rjust(width, fillchar),)
class StringRsplit:
"""
Splits the string at the specified separator from the right.
This node returns a list of strings by splitting the input string at the specified separator,
starting from the right. If maxsplit is provided, at most maxsplit splits are done.
If the separator is not specified or is None, any whitespace string is a separator.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"string": ("STRING", {"default": "", "defaultInput": True}),
},
"optional": {
"sep": ("STRING", {"default": ""}),
"maxsplit": ("INT", {"default": -1, "min": -1}),
}
}
RETURN_TYPES = ("STRING",) # Changed from "LIST" to "STRING"
CATEGORY = "Basic/STRING"
DESCRIPTION = cleandoc(__doc__ or "")
FUNCTION = "rsplit"
OUTPUT_IS_LIST = (True,) # This indicates that the output is a data list
def rsplit(self, string, sep=None, maxsplit=-1):
if sep == "":
sep = None
return (string.rsplit(sep, maxsplit),)
class StringRstrip:
"""
Removes trailing characters from the string.
@@ -1771,7 +1217,7 @@ NODE_CLASS_MAPPINGS = {
"StringCapitalize": StringCapitalize,
"StringCasefold": StringCasefold,
"StringCenter": StringCenter,
"StringConcat": StringConcat,
"DataStringConcat": DataStringConcat,
"StringCount": StringCount,
"StringDecode": StringDecode,
"StringEncode": StringEncode,
@@ -1816,7 +1262,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"StringCapitalize": "capitalize",
"StringCasefold": "casefold",
"StringCenter": "center",
"StringConcat": "concat",
"DataStringConcat": "concat",
"StringCount": "count",
"StringDecode": "decode",
"StringEncode": "encode",
+1 -1
View File
@@ -1,4 +1,4 @@
import pytest
#import pytest
from src.basic_data_handling.boolean_nodes import (
BooleanAnd,
+81
View File
@@ -0,0 +1,81 @@
import pytest
from src.basic_data_handling.casting_nodes import (CastToString, CastToInt, CastToFloat, CastToBoolean,
CastToList, CastDataListToList, CastToSet, CastDataListToSet,
CastToDict)
def test_cast_to_string():
node = CastToString()
assert node.convert_to_string(123) == ("123",)
assert node.convert_to_string(None) == ("None",)
assert node.convert_to_string(True) == ("True",)
assert node.convert_to_string("hello") == ("hello",)
def test_cast_to_int():
node = CastToInt()
assert node.convert_to_int("123") == (123,)
assert node.convert_to_int(456) == (456,)
with pytest.raises(ValueError):
node.convert_to_int("abc")
with pytest.raises(ValueError):
node.convert_to_int(None)
def test_cast_to_float():
node = CastToFloat()
assert node.convert_to_float("123.45") == (123.45,)
assert node.convert_to_float(67.89) == (67.89,)
assert node.convert_to_float(123) == (123.0,)
with pytest.raises(ValueError):
node.convert_to_float("abc")
with pytest.raises(ValueError):
node.convert_to_float(None)
def test_cast_to_boolean():
node = CastToBoolean()
assert node.convert_to_boolean(0) == (False,)
assert node.convert_to_boolean(1) == (True,)
assert node.convert_to_boolean("") == (False,)
assert node.convert_to_boolean("non_empty_string") == (True,)
def test_cast_to_list():
node = CastToList()
assert node.convert_to_list(123) == ([123],)
assert node.convert_to_list([1, 2, 3]) == ([1, 2, 3],)
assert node.convert_to_list("hello") == (["hello"],)
assert node.convert_to_list(None) == ([None],)
def test_cast_data_list_to_list():
node = CastDataListToList()
assert node.convert(list=[1, 2, 3]) == ([1, 2, 3],)
assert node.convert(list=[]) == ([],)
assert node.convert(list=["a", "b", "c"]) == (["a", "b", "c"],)
def test_cast_to_set():
node = CastToSet()
assert node.convert_to_set(123) == ({123},)
assert node.convert_to_set([1, 2, 2]) == ({1, 2},)
assert node.convert_to_set("abc") == ({"abc"},)
assert node.convert_to_set(None) == ({None},)
def test_cast_data_list_to_set():
node = CastDataListToSet()
assert node.convert(list=[1, 2, 3, 3]) == ({1, 2, 3},)
assert node.convert(list=["a", "b", "b"]) == ({"a", "b"},)
assert node.convert(list=[]) == (set(),)
def test_cast_to_dict():
node = CastToDict()
assert node.convert_to_dict({"key": "value"}) == ({"key": "value"},)
assert node.convert_to_dict([("key1", "value1"), ("key2", "value2")]) == ({"key1": "value1", "key2": "value2"},)
with pytest.raises(ValueError):
node.convert_to_dict(123)
with pytest.raises(ValueError):
node.convert_to_dict("string")
+115
View File
@@ -0,0 +1,115 @@
#import pytest
from src.basic_data_handling.comparison_nodes import (
Equal,
NotEqual,
GreaterThan,
LessThan,
GreaterThanOrEqual,
LessThanOrEqual,
StringComparison,
NumberInRange,
IsNull,
CompareLength,
)
def test_equal():
node = Equal()
assert node.compare(10, 10) == (True,)
assert node.compare(10, 20) == (False,)
assert node.compare("hello", "hello") == (True,)
assert node.compare([1, 2], [1, 2]) == (True,)
assert node.compare(None, None) == (True,)
def test_not_equal():
node = NotEqual()
assert node.compare(10, 10) == (False,)
assert node.compare(10, 20) == (True,)
assert node.compare("hello", "world") == (True,)
assert node.compare([1, 2], [1, 3]) == (True,)
assert node.compare(None, []) == (True,)
def test_greater_than():
node = GreaterThan()
assert node.compare(10, 5) == (True,)
assert node.compare(5, 10) == (False,)
assert node.compare(10, 10) == (False,)
assert node.compare(5.6, 2.3) == (True,)
def test_less_than():
node = LessThan()
assert node.compare(5, 10) == (True,)
assert node.compare(10, 5) == (False,)
assert node.compare(10, 10) == (False,)
assert node.compare(2.3, 5.6) == (True,)
def test_greater_than_or_equal():
node = GreaterThanOrEqual()
assert node.compare(10, 10) == (True,)
assert node.compare(15, 10) == (True,)
assert node.compare(5, 10) == (False,)
assert node.compare(5.5, 5.5) == (True,)
def test_less_than_or_equal():
node = LessThanOrEqual()
assert node.compare(10, 10) == (True,)
assert node.compare(5, 15) == (True,)
assert node.compare(15, 5) == (False,)
assert node.compare(3.1, 3.1) == (True,)
def test_string_comparison():
node = StringComparison()
# Case-sensitive tests
assert node.compare("hello", "hello", "==", "True") == (True,)
assert node.compare("apple", "banana", "<", "True") == (True,)
assert node.compare("apple", "banana", ">", "True") == (False,)
assert node.compare("apple", "apple", "!=", "True") == (False,)
# Case-insensitive tests
assert node.compare("Hello", "hello", "==", "False") == (True,)
assert node.compare("Apple", "BANANA", "<", "False") == (True,)
assert node.compare("Grape", "GRAPE", ">=", "False") == (True,)
def test_number_in_range():
node = NumberInRange()
# Inclusive range tests
assert node.check_range(50, 0, 100) == (True,)
assert node.check_range(0, 0, 100) == (True,)
# Exclusive range tests
assert node.check_range(0, 0, 100, include_min="False") == (False,)
assert node.check_range(100, 0, 100, include_max="False") == (False,)
# Out of range tests
assert node.check_range(-1, 0, 100) == (False,)
assert node.check_range(101, 0, 100) == (False,)
def test_is_null():
node = IsNull()
assert node.check_null(None) == (True,)
assert node.check_null(0) == (False,)
assert node.check_null("") == (False,)
assert node.check_null([]) == (False,)
def test_compare_length():
node = CompareLength()
# Valid comparisons
assert node.compare_length([1, 2, 3], "==", 3) == (True, 3)
assert node.compare_length("hello", "!=", 4) == (True, 5)
assert node.compare_length((1, 2), ">", 1) == (True, 2)
# Invalid comparisons
assert node.compare_length({"key": "value"}, "<", 1) == (False, 1)
assert node.compare_length(123, "==", 0) == (False, -1) # Non-container input edge case
+39
View File
@@ -0,0 +1,39 @@
#import pytest
from src.basic_data_handling.control_flow_nodes import IfElse, SwitchCase
def test_if_else():
node = IfElse()
# Test where condition is True
assert node.execute(True, "Value if True", "Value if False") == ("Value if True",)
# Test where condition is False
assert node.execute(False, "Value if True", "Value if False") == ("Value if False",)
# Test edge cases
assert node.execute(True, 1, 2) == (1,) # Integers
assert node.execute(False, [1, 2, 3], None) == (None,) # Complex types
assert node.execute(True, None, "fallback") == (None,) # Fallback to True branch
def test_switch_case():
node = SwitchCase()
# Selector within range, valid cases
assert node.execute(0, "Case 0", "Case 1", "Case 2", "Case 3") == ("Case 0",)
assert node.execute(1, "Case 0", "Case 1", "Case 2", "Case 3") == ("Case 1",)
assert node.execute(3, "Case 0", "Case 1", "Case 2", "Case 3") == ("Case 3",)
# Selector out of range, fallback to default case
assert node.execute(4, "Case 0", "Case 1", "Case 2", "Case 3", default="Default") == ("Default",)
assert node.execute(99, "Case 0", "Case 1", "Case 2", "Case 3", default="Default") == ("Default",)
# Selector out of range, no default provided
assert node.execute(4, "Case 0", "Case 1", "Case 2", "Case 3") == (None,)
# Edge cases
assert node.execute(0, None, "Case 1", "Case 2") == (None,) # Case with None
assert node.execute(2, "Case 0", "Case 1", "Case 2") == ("Case 2",)
assert node.execute(0, 123, False, "Case 2") == (123,) # Different data types
assert node.execute(10, "Case 0", "Case 1", "Case 2", default="Fallback") == ("Fallback",)
+164
View File
@@ -0,0 +1,164 @@
import pytest
from src.basic_data_handling.data_list_nodes import (
DataListAppend,
DataListExtend,
DataListInsert,
DataListRemove,
DataListPop,
DataListClear,
DataListIndex,
DataListCount,
DataListSort,
DataListReverse,
DataListCopy,
DataListLength,
DataListSlice,
DataListGetItem,
DataListSetItem,
DataListContains,
DataListCreateEmpty,
DataListZip,
DataListFilter,
DataListMin,
DataListMax,
)
def test_append():
node = DataListAppend()
assert node.append(list=[1, 2, 3], item=[4]) == ([1, 2, 3, 4],)
assert node.append(list=[], item=["test"]) == (["test"],)
assert node.append(list=[1, 2, 3], item=[]) == ([1, 2, 3],)
def test_extend():
node = DataListExtend()
assert node.extend(list_a=[1, 2], list_b=[3, 4]) == ([1, 2, 3, 4],)
assert node.extend(list_a=[], list_b=["a", "b"]) == (["a", "b"],)
assert node.extend(list_a=[1], list_b=[]) == ([1],)
def test_insert():
node = DataListInsert()
assert node.insert(list=[1, 2, 4], index=[2], item=[3]) == ([1, 2, 3, 4],)
assert node.insert(list=[], index=[0], item=["a"]) == (["a"],)
assert node.insert(list=[1, 2], index=[5], item=[3]) == ([1, 2, 3],) # Out-of-range insert
def test_remove():
node = DataListRemove()
assert node.remove(list=[1, 2, 3], value=[2]) == ([1, 3], True)
assert node.remove(list=["a", "b", "a"], value=["a"]) == (["b", "a"], True)
assert node.remove(list=[1, 2, 3], value=[5]) == ([1, 2, 3], False) # Value not found
def test_pop():
node = DataListPop()
assert node.pop(list=[1, 2, 3], index=[1]) == ([1, 3], 2)
assert node.pop(list=["a", "b", "c"], index=[-1]) == (["a", "b"], "c") # Negative index
assert node.pop(list=[], index=[0]) == ([], None) # Pop from empty list
def test_clear():
node = DataListClear()
assert node.clear(list=[1, 2, 3]) == ([],)
assert node.clear(list=[]) == ([],)
def test_index():
node = DataListIndex()
assert node.list_index(list=["a", "b", "c"], value=["b"]) == (1,)
assert node.list_index(list=[1, 2, 3], value=[5]) == (-1,) # Value not found
assert node.list_index(list=[1, 2, 1, 2], value=[1], start=[1]) == (2,)
def test_count():
node = DataListCount()
assert node.count(list=[1, 2, 1, 2], value=[1]) == (2,)
assert node.count(list=["a", "b", "a"], value=["a"]) == (2,)
assert node.count(list=[], value=["x"]) == (0,)
def test_sort():
node = DataListSort()
assert node.sort(list=[3, 2, 1]) == ([1, 2, 3],)
assert node.sort(list=["c", "a", "b"]) == (["a", "b", "c"],)
assert node.sort(list=[3, 1, 2], reverse=["True"]) == ([3, 2, 1],) # Reverse sort
def test_reverse():
node = DataListReverse()
assert node.reverse(list=[1, 2, 3]) == ([3, 2, 1],)
assert node.reverse(list=[]) == ([],)
def test_copy():
node = DataListCopy()
assert node.copy(list=[1, 2, 3]) == ([1, 2, 3],)
assert node.copy(list=[]) == ([],)
def test_length():
node = DataListLength()
assert node.length(list=[1, 2, 3]) == (3,)
assert node.length(list=[]) == (0,)
def test_slice():
node = DataListSlice()
assert node.slice(list=[1, 2, 3, 4, 5], start=[1], stop=[4]) == ([2, 3, 4],)
assert node.slice(list=[1, 2, 3], start=[0], stop=[2], step=[2]) == ([1],)
assert node.slice(list=["a", "b", "c"], start=[-2], stop=[3]) == (["b", "c"],)
def test_get_item():
node = DataListGetItem()
assert node.get_item(list=[1, 2, 3], index=[1]) == (2,)
assert node.get_item(list=["a", "b", "c"], index=[-1]) == ("c",) # Negative index
assert node.get_item(list=[], index=[0]) == (None,) # Out of range
def test_set_item():
node = DataListSetItem()
assert node.set_item(list=[1, 2, 3], index=[1], value=[9]) == ([1, 9, 3],)
assert node.set_item(list=["a", "b", "c"], index=[-1], value=["z"]) == (["a", "b", "z"],)
with pytest.raises(IndexError):
node.set_item(list=[], index=[0], value=["test"]) # Out of range
def test_contains():
node = DataListContains()
assert node.contains(list=[1, 2, 3], value=[2]) == (True,)
assert node.contains(list=["a", "b"], value=["c"]) == (False,)
assert node.contains(list=[], value=["x"]) == (False,)
def test_create_empty():
node = DataListCreateEmpty()
assert node.create_empty() == ([],)
def test_zip():
node = DataListZip()
assert node.zip_lists(list1=[1, 2], list2=["a", "b"]) == ([[1, "a"], [2, "b"]],)
assert node.zip_lists(list1=[1], list2=["a", "b"]) == ([[1, "a"]],) # Shortest list length
def test_filter():
node = DataListFilter()
assert node.filter_data(value=[1, 2, 3], filter=[False, True, False]) == ([1, 3],)
assert node.filter_data(value=[1, 2], filter=[True, True]) == ([],)
def test_min():
node = DataListMin()
assert node.find_min(list=[3, 1, 2]) == (1,)
assert node.find_min(list=[-1, -5, 0]) == (-5,)
assert node.find_min(list=[]) == (None,)
def test_max():
node = DataListMax()
assert node.find_max(list=[3, 1, 2]) == (3,)
assert node.find_max(list=[-1, -5, 0]) == (0,)
assert node.find_max(list=[]) == (None,)
+3 -3
View File
@@ -1,10 +1,10 @@
import pytest
#import pytest
from src.basic_data_handling.string_nodes import (
StringCapitalize,
StringCasefold,
StringCenter,
StringConcat,
DataStringConcat,
StringCount,
StringDecode,
StringEncode,
@@ -72,7 +72,7 @@ def test_center():
assert node.center("test", 10, "ab") == ("aaatestaaa",)
def test_concat():
node = StringConcat()
node = DataStringConcat()
assert node.concat("hello ", "world") == ("hello world",)
assert node.concat("", "") == ("",) # Empty strings
assert node.concat("123", "456") == ("123456",) # Numbers as strings