From e61c02f94fbe5c54854d8c4380bb418d68f5342c Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Tue, 14 Apr 2026 07:35:32 -0300 Subject: [PATCH] =====> switch from v1 to v3 nodes in `__init__` --- __init__.py | 43 +++++--------- enums.py | 37 ------------ old_dict_add_any.py | 65 -------------------- old_dict_add_string.py | 67 --------------------- old_dict_from_text.py | 129 ---------------------------------------- old_dict_key_extract.py | 74 ----------------------- old_dict_preview.py | 96 ------------------------------ old_formatter.py | 89 --------------------------- old_validate_keys.py | 61 ------------------- 9 files changed, 16 insertions(+), 645 deletions(-) delete mode 100644 enums.py delete mode 100644 old_dict_add_any.py delete mode 100644 old_dict_add_string.py delete mode 100644 old_dict_from_text.py delete mode 100644 old_dict_key_extract.py delete mode 100644 old_dict_preview.py delete mode 100644 old_formatter.py delete mode 100644 old_validate_keys.py diff --git a/__init__.py b/__init__.py index 306326b..9242c64 100644 --- a/__init__.py +++ b/__init__.py @@ -2,34 +2,23 @@ """ """ -import typing as _t +from comfy_api.latest import ComfyExtension as _ComfyExtension, io as _io from ._formatter_class import Formatter -from .old_dict_add_any import StringConstructorDictAddAny -from .old_dict_add_string import StringConstructorDictAddString -from .old_dict_key_extract import StringConstructorDictExtractString -from .old_dict_from_text import StringConstructorDictFromText -from .old_dict_preview import StringConstructorDictPreview -from .old_formatter import StringConstructorFormatter -from .old_validate_keys import StringConstructorValidateKeys +from .node_dict_from_text import * +from .node_formatter import * +from .node_validate import * +from .nodes_dict import * -NODE_CLASS_MAPPINGS: _t.Dict[str, type] = { - 'StringConstructorDictAddAny': StringConstructorDictAddAny, - 'StringConstructorDictAddString': StringConstructorDictAddString, - 'StringConstructorDictExtractString': StringConstructorDictExtractString, - 'StringConstructorDictFromText': StringConstructorDictFromText, - 'StringConstructorDictPreview': StringConstructorDictPreview, - 'StringConstructorFormatter': StringConstructorFormatter, - 'StringConstructorValidateKeys': StringConstructorValidateKeys, -} -NODE_DISPLAY_NAME_MAPPINGS: _t.Dict[str, str] = { - 'StringConstructorDictAddAny': "Add ANY to Dict", - 'StringConstructorDictAddString': "Add String to Dict", - 'StringConstructorDictExtractString': "Extract String from Dict", - 'StringConstructorDictFromText': "Dict from Text", - 'StringConstructorDictPreview': "Preview Dict", - 'StringConstructorFormatter': "String Formatter", - 'StringConstructorValidateKeys': "Validate Dict", -} +class StringConstructorExtension(_ComfyExtension): + async def get_node_list(self) -> list[type[_io.ComfyNode]]: + return [ + DictFromText, DictFromTextOld1, + StringFormatter, StringFormatterOld1, + ValidateKeys, ValidateKeysOld1, + DictAddAny, DictAddString, DictExtractString, + DictAddAnyOld1, DictAddStringOld1, DictExtractStringOld1 + ] -__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] +async def comfy_entrypoint() -> _ComfyExtension: + return StringConstructorExtension() diff --git a/enums.py b/enums.py deleted file mode 100644 index bdcceeb..0000000 --- a/enums.py +++ /dev/null @@ -1,37 +0,0 @@ -# encoding: utf-8 -""" -Internal enums. -""" - -import typing as _t - -try: - from enum import StrEnum as _StrEnum -except ImportError: - from comfy.comfy_types.node_typing import StrEnum as _StrEnum - -from .__typing import T as _T - - -class __BaseEnum(_StrEnum): - @classmethod - def all_values(cls) -> _t.Tuple[str, ...]: - return tuple(x.value for x in cls) - - -class DataTypes(__BaseEnum): - """Additional data-types defined byt the node pack.""" - DICT = 'DICT' - - @staticmethod - def __custom_input_type_dict(_dict_args: _t.Tuple[_t.Dict[str, _T], ...], kwargs: _t.Dict[str, _T]) -> _t.Dict[str, _T]: - res_dict: _t.Dict[str, _t.Any] = {"forceInput": True} - for d in _dict_args: - if d is not None: - res_dict.update(d) - res_dict.update(kwargs) - return res_dict - - @classmethod - def input_dict(cls, *_dicts: _t.Dict[str, _T], **kwargs: _T) -> _t.Tuple[_t.Union['DataTypes', str], _t.Dict[str, _T]]: - return str(cls.DICT), cls.__custom_input_type_dict(_dicts, kwargs) diff --git a/old_dict_add_any.py b/old_dict_add_any.py deleted file mode 100644 index 3500f6e..0000000 --- a/old_dict_add_any.py +++ /dev/null @@ -1,65 +0,0 @@ -# encoding: utf-8 -""" -""" - -import typing as _t - -from inspect import cleandoc as _cleandoc - -from frozendict import deepfreeze as _deepfreeze - -from comfy.comfy_types.node_typing import IO as _IO - -from .__meta import category_dict -from ._dict import _new_dict_with_updated_key -from .docstring_formatter import format_docstring as _format_docstring -from .enums import DataTypes as _DataTypes -from .old_dict_add_string import _input_types as _input_types_str - - -_dict = dict - -# A tiny optimization by reusing the same immutable dict: -_input_types = _deepfreeze({ - 'required': { - 'name': (_IO.STRING, {'tooltip': ( - "Name (key) of the non-string item inserted into the dict. " - "It must comprise only of latin letters, digits and underscores + it can't start with a digit." - )}), - }, - 'optional': { - 'dict': _input_types_str['optional']['dict'], - 'value': (_IO.ANY, {"forceInput": True, 'tooltip': "The actual non-string item to add into the dict."}), - }, - # 'hidden': { - # 'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node - # }, -}) - - -class StringConstructorDictAddAny: - """Add/update a non-string item to the Format-Dict - to do some advanced formatting.""" - NODE_NAME = 'StringConstructorDictAddAny' - CATEGORY = category_dict - DESCRIPTION = _format_docstring(_cleandoc(__doc__)) - - FUNCTION = 'main' - RETURN_TYPES = (str(_DataTypes.DICT), ) - RETURN_NAMES = (_DataTypes.DICT.lower(), ) - # OUTPUT_TOOLTIPS = tuple() - - @classmethod - def INPUT_TYPES(cls): - return _input_types - - @staticmethod - def main( - name: str, dict: _t.Dict[str, _t.Any] = None, value: _t.Any = None, - # unique_id: str = None, - ) -> _t.Tuple[_t.Dict[str, _t.Any]]: - """Update/append an item of any type to the dict.""" - if value is None: - if dict is None: - dict = _dict() - return (dict, ) # No need to create another dict instance if we add nothing - return (_new_dict_with_updated_key(dict, name, value), ) diff --git a/old_dict_add_string.py b/old_dict_add_string.py deleted file mode 100644 index fecfe05..0000000 --- a/old_dict_add_string.py +++ /dev/null @@ -1,67 +0,0 @@ -# encoding: utf-8 -""" -""" - -import typing as _t - -from inspect import cleandoc as _cleandoc - -from frozendict import deepfreeze as _deepfreeze - -from comfy.comfy_types.node_typing import IO as _IO - -from .__meta import category_dict -from ._dict import _new_dict_with_updated_key, _T -from .docstring_formatter import format_docstring as _format_docstring -from .enums import DataTypes as _DataTypes - - -# A tiny optimization by reusing the same immutable dict: -_input_types = _deepfreeze({ - 'required': { - 'name': (_IO.STRING, {'tooltip': ( - "Name (key) of the substring inserted into the dict. " - "It must comprise only of latin letters, digits and underscores + it can't start with a digit." - )}), - 'cleanup': (_IO.BOOLEAN, {'default': True, 'label_on': 'leading/trailing spaces', 'label_off': 'no', 'tooltip': ( - "When enabled, each line in the sub-string is stripped from any spaces at its start and end." - )}), - 'string': (_IO.STRING, {'multiline': True, 'tooltip': "The actual sub-string to add into the dict."}), - }, - 'optional': { - 'dict': _DataTypes.input_dict(tooltip="An optional Format-Dictionary to extend/update."), - }, - # 'hidden': { - # 'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node - # }, -}) - - -class StringConstructorDictAddString: - """Add/update a string to the Format-Dict.""" - NODE_NAME = 'StringConstructorDictAddString' - CATEGORY = category_dict - DESCRIPTION = _format_docstring(_cleandoc(__doc__)) - - FUNCTION = 'main' - RETURN_TYPES = (str(_DataTypes.DICT), ) - RETURN_NAMES = (_DataTypes.DICT.lower(), ) - # OUTPUT_TOOLTIPS = tuple() - - @classmethod - def INPUT_TYPES(cls): - return _input_types - - @staticmethod - def main( - name: str, cleanup: bool, string: str, - dict: _t.Dict[str, _T] = None, - # unique_id: str = None, - ) -> _t.Tuple[_t.Dict[str, _t.Union[_T, str]]]: - """Update/append a string to the dict.""" - string = '' if string is None else str(string) - if cleanup: - string = '\n'.join( - x.strip() for x in string.splitlines() - ) - return (_new_dict_with_updated_key(dict, name, string), ) diff --git a/old_dict_from_text.py b/old_dict_from_text.py deleted file mode 100644 index aa3de2b..0000000 --- a/old_dict_from_text.py +++ /dev/null @@ -1,129 +0,0 @@ -# encoding: utf-8 -""" -""" - -import typing as _t - -from inspect import cleandoc as _cleandoc - -from frozendict import frozendict as _frozendict, deepfreeze as _deepfreeze - -from comfy.comfy_types.node_typing import IO as _IO - -from .__meta import category -from ._dict import _show_text_on_node, _new_updated_dict, _T -from .docstring_formatter import format_docstring as _format_docstring -from .enums import DataTypes as _DataTypes -from .old_dict_add_string import _input_types as _input_types_str - - -def _return_line_raw(line_raw: str, line_stripped:str) -> str: - return line_raw - - -def _return_line_stripped(line_raw: str, line_stripped:str) -> str: - return line_stripped - - -def _parsed_kv_pairs_gen(multiline_str: str, strip_lines=True): - """ - Given a multiline string, extract keywords (first non-empty line in each chunk) - and their substrings (all the following non-empty lines). - """ - if not multiline_str: - return - multiline_str = str(multiline_str) - if not multiline_str.strip(): - return - - appended_line_f = _return_line_stripped if strip_lines else _return_line_raw - cur_chunk: _t.List[str] = list() - - def dump_chunk(): - cur_chunk_iter = iter(cur_chunk) # It's more backwards-compatible than `a, *b = x` - chunk_key = next(cur_chunk_iter) - cur_chunk_lines = list(cur_chunk_iter) - # If there are no actual lines, join() would return an empty string: - return chunk_key.strip(), '\n'.join(cur_chunk_lines) - - for line in multiline_str.splitlines(): - line_stripped: str = line.strip() - if line_stripped: - cur_chunk.append(appended_line_f(line, line_stripped)) - continue - - assert not line_stripped - if cur_chunk: - yield dump_chunk() - cur_chunk = list() - - # We simply skip all the empty lines entirely - - if cur_chunk: - yield dump_chunk() - -# -------------------------------------- - -_dict = dict - -_input_types = _deepfreeze({ - 'required': { - 'cleanup': (_IO.BOOLEAN, {'default': True, 'label_on': 'leading/trailing spaces', 'label_off': 'no', 'tooltip': ( - "When enabled, each line in each sub-string is stripped from any spaces at its start and end." - )}), - 'strings': (_IO.STRING, {'multiline': True, 'tooltip': ( - "Sub-string names followed by their text. Different sub-string chunks are separated by empty lines. Example:\n\n" - "char1_short\n1boy, blond, short hair\n\n" - "char1_long\n1boy, smiling, blue eyes, blond, short hair,\nwearing a leather jacket, sitting on a bike" - )}), - 'show_status': (_IO.BOOLEAN, {'default': False, 'label_on': 'detected names', 'label_off': 'no', 'tooltip': ( - "Show detected string names on the node itself?" - )}), - }, - 'optional': { - 'dict': _input_types_str['optional']['dict'], - }, - 'hidden': { - 'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node - }, -}) - - -class StringConstructorDictFromText: - """ - Build a dict of named sub-strings to be used later in string formatting (text construction). - """ - NODE_NAME = 'StringConstructorDictFromText' - CATEGORY = category - DESCRIPTION = _format_docstring(_cleandoc(__doc__)) - - OUTPUT_NODE = True # Just to show the status message even if not connected to anything - - FUNCTION = 'main' - RETURN_TYPES = (str(_DataTypes.DICT), ) - RETURN_NAMES = (_DataTypes.DICT.lower(), ) - # OUTPUT_TOOLTIPS = tuple() - - @classmethod - def INPUT_TYPES(cls): - return _input_types - - @staticmethod - def main( - cleanup: bool, strings: str, show_status: bool = True, - dict: _t.Dict[str, _T] = None, unique_id: str = None, - ) -> _t.Tuple[_t.Dict[str, _t.Union[_T, str]]]: - new_dict = {k: v for k, v in _parsed_kv_pairs_gen(strings, strip_lines=cleanup)} - - if not new_dict: - if dict is None: - dict = _dict() - return (dict, ) # No need to create another dict instance if we add nothing - - out_dict = _new_updated_dict(dict, new_dict) - # These two ↑↓ must be in this specific order: `_new_updated_dict()` also checks both dicts - if show_status and unique_id: - status_text = ','.join(new_dict.keys()) if new_dict else '' - _show_text_on_node(status_text, unique_id) - - return (out_dict, ) diff --git a/old_dict_key_extract.py b/old_dict_key_extract.py deleted file mode 100644 index 78d2ac3..0000000 --- a/old_dict_key_extract.py +++ /dev/null @@ -1,74 +0,0 @@ -# encoding: utf-8 -""" -""" - -import typing as _t - -from inspect import cleandoc as _cleandoc - -from frozendict import deepfreeze as _deepfreeze - -from comfy.comfy_types.node_typing import IO as _IO - -from .__meta import category_dict -from ._dict import _show_text_on_node, _new_dict_with_updated_key, _T -from .docstring_formatter import format_docstring as _format_docstring -from .enums import DataTypes as _DataTypes - - -_input_types = _deepfreeze({ - 'required': { - 'name': (_IO.STRING, {'tooltip': ( - "Name (key) of a string to extract from dictionary.\n" - "If the element with such key isn't a string, it will be turned to one.\n" - "If no such key exists in the dict, an empty string returned." - )}), - 'show_status': (_IO.BOOLEAN, {'default': False, 'label_on': 'value', 'label_off': 'no', 'tooltip': ( - "Show the extracted string on the node itself?" - )}), - }, - 'optional': { - 'dict': _DataTypes.input_dict(tooltip="The dictionary to extract the element from."), - }, - 'hidden': { - 'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node - }, -}) - - -class StringConstructorDictExtractString: - """Extract a single string from the Format-Dict.""" - NODE_NAME = 'StringConstructorDictExtractString' - CATEGORY = category_dict - DESCRIPTION = _format_docstring(_cleandoc(__doc__)) - - FUNCTION = 'main' - RETURN_TYPES = (_IO.STRING, ) - RETURN_NAMES = ('string', ) - # OUTPUT_TOOLTIPS = tuple() - - @classmethod - def INPUT_TYPES(cls): - return _input_types - - @staticmethod - def main( - name: str, show_status: bool = False, dict: _t.Dict[str, _T] = None, - unique_id: str = None, - ) -> _t.Tuple[str]: - """Extract a single string from the Format-Dict.""" - # noinspection PyBroadException - try: - string: str = dict.get(name, '') - except Exception: - string = '' - if string is None: - string = '' - - if not isinstance(string, str): - string = repr(string) - - if show_status and unique_id: - _show_text_on_node(string, unique_id) - - return (string, ) diff --git a/old_dict_preview.py b/old_dict_preview.py deleted file mode 100644 index 75bf1e6..0000000 --- a/old_dict_preview.py +++ /dev/null @@ -1,96 +0,0 @@ -# encoding: utf-8 -""" -""" - -import typing as _t - -from inspect import cleandoc as _cleandoc -from pprint import pformat as _pformat - -from frozendict import deepfreeze as _deepfreeze, frozendict as _frozendict - -from .__meta import category_dict -from ._dict import _show_text_on_node, _verify_input_dict_into_new -from .docstring_formatter import format_docstring as _format_docstring -from .enums import DataTypes as _DataTypes - - -def _to_regular_dict_recursive_copy(input_dict: dict = None) -> dict: - """ - To simplify preview, turns frozen dicts back to regular ones. - Copies the dict in process and does so recursively, but not truly deep: if value is another dict, - it will be processed, too. But dicts within other objects (like lists) won't be found. - """ - if not input_dict: - return dict() - return { - k: ( - _to_regular_dict_recursive_copy(v) - # In py3.10, frozendict isn't a dict, but is a `typing.Mapping`. - # So, this many types to check against: - if isinstance(v, (dict, _frozendict, _t.Mapping)) - else v - ) - for k, v in input_dict.items() - } - - -def _preview_single_entry(key: str, value) -> str: - if isinstance(value, str): - return f"{key}:\n{value}" - value_repr = _pformat(value) - return f"{key}:\n{value_repr}" - - -def _preview_message(input_dict: dict = None): - input_dict = _to_regular_dict_recursive_copy(_verify_input_dict_into_new(input_dict)) - parts: _t.List[str] = [ - _preview_single_entry(k, v) for k, v in input_dict.items() - ] - return '\n\n'.join(parts) - -# -------------------------------------- - -_dict = dict - -_input_types = _deepfreeze({ - 'required': {}, - 'optional': { - 'dict': _DataTypes.input_dict(tooltip="A Format-Dictionary."), - }, - 'hidden': { - 'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node - }, -}) - - -class StringConstructorDictPreview: - """ - Show the contents of a Format-Dict. - """ - NODE_NAME = 'StringConstructorDictPreview' - CATEGORY = category_dict - DESCRIPTION = _format_docstring(_cleandoc(__doc__)) - - OUTPUT_NODE = True - - FUNCTION = 'main' - RETURN_TYPES = (str(_DataTypes.DICT), ) - RETURN_NAMES = (_DataTypes.DICT.lower(), ) - # OUTPUT_TOOLTIPS = tuple() - - @classmethod - def INPUT_TYPES(cls): - return _input_types - - @staticmethod - def main( - dict: _t.Dict[str, _t.Any] = None, - unique_id: str = None, - ): - if dict is None: - dict = _dict() - if unique_id: - status_text = _preview_message(dict) - _show_text_on_node(status_text, unique_id) - return (dict, ) diff --git a/old_formatter.py b/old_formatter.py deleted file mode 100644 index 5ae0261..0000000 --- a/old_formatter.py +++ /dev/null @@ -1,89 +0,0 @@ -# encoding: utf-8 -""" -Code for ``StringConstructorFormatter`` node. -""" - -import typing as _t - -from inspect import cleandoc as _cleandoc - -from frozendict import deepfreeze as _deepfreeze - -from comfy.comfy_types.node_typing import IO as _IO - -from .__meta import category -from ._dict import _show_text_on_node -from ._formatter_class import Formatter as _Formatter -from .docstring_formatter import format_docstring as _format_docstring -from .enums import DataTypes as _DataTypes - -# -------------------------------------- - -_input_types = _deepfreeze({ - 'required': { - 'template': (_IO.STRING, {'multiline': True, 'tooltip': ( - "Type the text template. " - "To reference named substrings from format-dictionary, use this syntax: {substring_name}. For example:\n\n" - "score_9, score_8_up, score_7_up, {char1_short}, standing next to {char2_short},\n" - "{char1_long}\n{char2_long}" - )}), - 'recursive_format': (_IO.BOOLEAN, {'default': False, 'label_on': '❗ yes', 'label_off': 'no', 'tooltip': ( - "Do recursive format - i.e., allow the chunks from the dictionary to reference other chunks." - )}), - 'safe_format': (_IO.BOOLEAN, {'default': True, 'label_on': 'yes', 'label_off': 'no', 'tooltip': ( - "If template contains an invalid {text pattern} which can't be formatted - leave it as-is " - "(instead of throwing an error).\n" - "Safe mode is recommended for templates with JSON, CSS, or other literal curly brackets." - )}), - 'show_status': (_IO.BOOLEAN, {'default': True, 'label_on': 'formatted string', 'label_off': 'no', 'tooltip': ( - "Show the final string constructed from the text-template and format-dictionary?" - )}), - }, - 'optional': { - 'dict': _DataTypes.input_dict(tooltip=( # It's not actually optional, but is here since there's no dict-widget - "The dictionary to take named sub-strings from. It could be left unconnected, if the pattern doesn't reference " - "any sub-strings - then, this node acts exactly the same as a regular string-primitive node." - )), - }, - 'hidden': { - 'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node - }, -}) - - -class StringConstructorFormatter: - """ - Construct the formatted string from template and format-dictionary. - """ - NODE_NAME = 'StringConstructorFormatter' - CATEGORY = category - DESCRIPTION = _format_docstring(_cleandoc(__doc__)) - - OUTPUT_NODE = True - - FUNCTION = 'main' - RETURN_TYPES = (_IO.STRING, ) - RETURN_NAMES = ('string', ) - # OUTPUT_TOOLTIPS = tuple() - - @classmethod - def INPUT_TYPES(cls): - return _input_types - - @staticmethod - def main( - template: str, - recursive_format: bool = False, - safe_format: bool = True, - show_status: bool = False, - dict: _t.Dict[str, _t.Any] = None, #actually, required - but it's here to keep the declared params order - unique_id: str = None - ) -> _t.Tuple[str]: - formatter = _Formatter( - format_dict=dict, - recursive=recursive_format, safe=safe_format, - ) - out_text = formatter(template) - if show_status and unique_id: - _show_text_on_node(out_text, unique_id) - return (out_text, ) diff --git a/old_validate_keys.py b/old_validate_keys.py deleted file mode 100644 index 9fe9708..0000000 --- a/old_validate_keys.py +++ /dev/null @@ -1,61 +0,0 @@ -# encoding: utf-8 -""" -""" - -import typing as _t - -from inspect import cleandoc as _cleandoc - -from frozendict import deepfreeze as _deepfreeze - -from .__meta import category_dict -from ._dict import _verify_input_dict, _show_text_on_node, _T -from .docstring_formatter import format_docstring as _format_docstring -from .enums import DataTypes as _DataTypes - - -_input_types = _deepfreeze({ - 'required': { - 'dict': _DataTypes.input_dict(), - }, - # 'optional': {}, - # 'hidden': { - # 'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node - # }, -}) - - -class StringConstructorValidateKeys: - """ - Verify the dict to have string-formatting-compatible keys. No need to add it before "String Formatter" node, - but it might be handy to verify the dict right after it's created. - """ - NODE_NAME = 'StringConstructorValidateKeys' - CATEGORY = category_dict - DESCRIPTION = _format_docstring(_cleandoc(__doc__)) - - OUTPUT_NODE = True - - FUNCTION = 'main' - RETURN_TYPES = (str(_DataTypes.DICT), ) - RETURN_NAMES = (_DataTypes.DICT.lower(), ) - # OUTPUT_TOOLTIPS = tuple() - - @classmethod - def INPUT_TYPES(cls): - return _input_types - - @staticmethod - def main( - dict: _t.Dict[str, _T], - # unique_id: str = None, - ) -> _t.Tuple[_t.Dict[str, _T]]: - # try: - # _verify_input_dict(dict, error_if_none=True) - # except Exception: - # _show_text_on_node('❌', unique_id) - # raise - # return (Null, ) - # _show_text_on_node('✅', unique_id) - _verify_input_dict(dict, error_if_none=True) - return (dict, )