diff --git a/CHANGELOG.md b/CHANGELOG.md index f13a8ae..47de817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ ## `TODO` -- Multi-item nodes. -- Bulk `Search & Replace` within **keys** in dictionary. +The pack has already started to sip into dictionary-editing domain. Only the validator and the actual formatting node needs to stay. The rest need to go to their own repo. + +- ~~Multi-item nodes.~~ +- ~~Bulk `Search & Replace` within **keys** in dictionary.~~ + +# v1.0.2 + +- New node: `Validate Format-Dict` # v1.0.1 diff --git a/__init__.py b/__init__.py index 4c613d2..0779f7d 100644 --- a/__init__.py +++ b/__init__.py @@ -9,6 +9,7 @@ from .node_dict_add_string import StringConstructorDictAddString from .node_dict_from_text import StringConstructorDictFromText from .node_dict_preview import StringConstructorDictPreview from .node_formatter import StringConstructorFormatter +from .node_validate_keys import StringConstructorValidateKeys NODE_CLASS_MAPPINGS: _t.Dict[str, type] = { 'StringConstructorDictAddAny': StringConstructorDictAddAny, @@ -16,6 +17,7 @@ NODE_CLASS_MAPPINGS: _t.Dict[str, type] = { 'StringConstructorDictFromText': StringConstructorDictFromText, 'StringConstructorDictPreview': StringConstructorDictPreview, 'StringConstructorFormatter': StringConstructorFormatter, + 'StringConstructorValidateKeys': StringConstructorValidateKeys, } NODE_DISPLAY_NAME_MAPPINGS: _t.Dict[str, str] = { 'StringConstructorDictAddAny': "Add ANY to Format-Dict (Advanced)", @@ -23,6 +25,7 @@ NODE_DISPLAY_NAME_MAPPINGS: _t.Dict[str, str] = { 'StringConstructorDictFromText': "Format-Dict from Text", 'StringConstructorDictPreview': "Preview Format-Dict", 'StringConstructorFormatter': "String Formatter", + 'StringConstructorValidateKeys': "Validate Format-Dict", } __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] diff --git a/funcs_common.py b/funcs_common.py index c05db3e..0ffd27b 100644 --- a/funcs_common.py +++ b/funcs_common.py @@ -60,13 +60,15 @@ def _raise_from_errors_dict( raise KeyError(msg) -def _verify_input_dict(input_dict: _t.Dict[str, _T] = None) -> _t.Dict[str, _T]: +def _verify_input_dict(input_dict: _t.Dict[str, _T] = None, error_if_none=False): """ Verify input dict to have only valid keys. Raises errors if invalid ones found. - Always returns a new dict instance (shallow copy if non-empty dict passed). """ if input_dict is None: - return dict() + if error_if_none: + raise TypeError("No input-dict") + return + if not isinstance(input_dict, dict): raise TypeError(f"Input-dict isn't a dict. Got: {input_dict!r}") @@ -78,4 +80,14 @@ def _verify_input_dict(input_dict: _t.Dict[str, _T] = None) -> _t.Dict[str, _T]: errors_dict, "Invalid input-dict:\n{}", "Invalid keys (string names) in input-dict:\n{}" ) + + +def _verify_input_dict_into_new(input_dict: _t.Dict[str, _T] = None) -> _t.Dict[str, _T]: + """ + Verify input dict to have only valid keys. Raises errors if invalid ones found. + Always returns a new dict instance (shallow copy if non-empty dict passed). + """ + if input_dict is None: + return dict() + _verify_input_dict(input_dict) return dict(input_dict) diff --git a/funcs_dict_add_any.py b/funcs_dict_add_any.py index 67b4d63..aa00f3d 100644 --- a/funcs_dict_add_any.py +++ b/funcs_dict_add_any.py @@ -7,7 +7,7 @@ import typing as _t from frozendict import deepfreeze as _deepfreeze -from .funcs_common import _raise_from_errors_dict, _validate_key, _verify_input_dict +from .funcs_common import _raise_from_errors_dict, _validate_key, _verify_input_dict_into_new def set_dict_any_type_item( @@ -15,7 +15,7 @@ def set_dict_any_type_item( # unique_id: str = None, ) -> _t.Tuple[_t.Dict[str, _t.Any]]: """Update/append an item of any type to the dict.""" - out_dict: _t.Dict[str, _t.Any] = _verify_input_dict(in_dict) + out_dict: _t.Dict[str, _t.Any] = _verify_input_dict_into_new(in_dict) if value is None: return (_deepfreeze(in_dict), ) # No need to create another dict instance if it's already frozen diff --git a/funcs_dict_add_string.py b/funcs_dict_add_string.py index 902d003..a396d32 100644 --- a/funcs_dict_add_string.py +++ b/funcs_dict_add_string.py @@ -8,7 +8,7 @@ import typing as _t from frozendict import deepfreeze as _deepfreeze from .enums import T as _T -from .funcs_common import _raise_from_errors_dict, _validate_key, _verify_input_dict +from .funcs_common import _raise_from_errors_dict, _validate_key, _verify_input_dict_into_new def set_dict_string( @@ -16,7 +16,7 @@ def set_dict_string( # unique_id: str = None, ) -> _t.Tuple[_t.Dict[str, _t.Union[_T, str]]]: """Update/append a string to the dict.""" - out_dict: _t.Dict[str, _t.Union[_T, str]] = _verify_input_dict(in_dict) + out_dict: _t.Dict[str, _t.Union[_T, str]] = _verify_input_dict_into_new(in_dict) errors_dict: _t.Dict[_t.Any, str] = dict() name = _validate_key(name, errors_dict) _raise_from_errors_dict(errors_dict) diff --git a/funcs_dict_from_text.py b/funcs_dict_from_text.py index 0a22aba..5ed9bac 100644 --- a/funcs_dict_from_text.py +++ b/funcs_dict_from_text.py @@ -8,7 +8,7 @@ import typing as _t from frozendict import frozendict as _frozendict, deepfreeze as _deepfreeze from .enums import T as _T -from .funcs_common import _show_text_on_node, _raise_from_errors_dict, _validate_key, _verify_input_dict +from .funcs_common import _show_text_on_node, _raise_from_errors_dict, _validate_key, _verify_input_dict_into_new def _return_line_raw(line_raw: str, line_stripped:str) -> str: @@ -61,7 +61,7 @@ def parse_dict_from_text( multiline_str: str, strip_lines=True, show: bool = True, in_dict: _t.Dict[str, _T] = None, unique_id: str = None ) -> _t.Tuple[_t.Dict[str, _T]]: - out_dict: _t.Dict[str, _T] = _verify_input_dict(in_dict) + out_dict: _t.Dict[str, _T] = _verify_input_dict_into_new(in_dict) errors_dict: _t.Dict[_t.Any, str] = dict() new_dict: _t.Dict[str, str] = { diff --git a/funcs_dict_preview.py b/funcs_dict_preview.py index 0285211..de1ab0e 100644 --- a/funcs_dict_preview.py +++ b/funcs_dict_preview.py @@ -7,7 +7,7 @@ import typing as _t from pprint import pformat as _pformat -from .funcs_common import _show_text_on_node, _verify_input_dict +from .funcs_common import _show_text_on_node, _verify_input_dict_into_new def _to_regular_dict_recursive_copy(input_dict: dict = None) -> dict: @@ -32,7 +32,7 @@ def _preview_single_entry(key: str, value) -> str: def _preview_message(input_dict: dict = None): - input_dict = _to_regular_dict_recursive_copy(_verify_input_dict(input_dict)) + 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() ] diff --git a/funcs_formatter.py b/funcs_formatter.py index 46d4646..9b36a37 100644 --- a/funcs_formatter.py +++ b/funcs_formatter.py @@ -7,7 +7,7 @@ import typing as _t import sys as _sys -from .funcs_common import _show_text_on_node, _verify_input_dict +from .funcs_common import _show_text_on_node, _verify_input_dict_into_new _RECURSION_LIMIT = max(int(_sys.getrecursionlimit()), 10) # You can externally monkey-patch it... but if it blows up, your fault 🤷🏻‍♂️ @@ -45,7 +45,7 @@ def formatter( template: str, recursive: bool = False, format_dict: _t.Dict[str, _t.Any] = None, show: bool = True, unique_id: str = None ) -> _t.Tuple[str]: - format_dict = _verify_input_dict(format_dict) + format_dict = _verify_input_dict_into_new(format_dict) if not isinstance(template, str): raise TypeError(f"Not a string: {template!r}") diff --git a/node_validate_keys.py b/node_validate_keys.py new file mode 100644 index 0000000..5ab0b58 --- /dev/null +++ b/node_validate_keys.py @@ -0,0 +1,61 @@ +# encoding: utf-8 +""" +""" + +import typing as _t + +from inspect import cleandoc as _cleandoc + +from frozendict import deepfreeze as _deepfreeze + +from . import _meta +from .docstring_formatter import format_docstring as _format_docstring +from .enums import DataTypes as _DataTypes +from .funcs_common import _verify_input_dict, _show_text_on_node, _T + + +_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 = _meta.category + DESCRIPTION = _format_docstring(_cleandoc(__doc__)) + + OUTPUT_NODE = True + + FUNCTION = 'main' + RETURN_TYPES = (_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, ) diff --git a/pyproject.toml b/pyproject.toml index d129d83..2c1abcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "string-constructor" -description = "Composing prompt variants from the same text pieces with ease:\n• Build your \"library\" (dictionary) of named text chunks (sub-strings) to use it across the entire workflow.\n• Compile these snippets into different prompts in-place - with just one string formatting node.\n• Freely update the dictionary down the line - get different prompts.\n• Reference text chunks within each other to build dependent hierarchies of less/more detailed descriptions.\n• A real life-saver for regional prompting (aka area composition)." -version = "1.0.1" +description = "String formatting (compiling text from pieces) made for humans:\n• Turn parts of prompt into a dictionary - to share them all across the entire workflow as a single connection.\n• Compose your prompt in-place from these snippets and regular text - with just one text field.\n• Easily update the dictionary down the line - get different prompts from the same template.\n• Reference text chunks within each other to build complex hierarchies of self-updating prompts.\n• A true time-saver for regional prompting (aka area composition)." +version = "1.0.2" authors = [ {name = "Lex Darlog"} ]