griptape-nodes 0.59.2__py3-none-any.whl → 0.60.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. griptape_nodes/common/macro_parser/__init__.py +28 -0
  2. griptape_nodes/common/macro_parser/core.py +230 -0
  3. griptape_nodes/common/macro_parser/exceptions.py +23 -0
  4. griptape_nodes/common/macro_parser/formats.py +170 -0
  5. griptape_nodes/common/macro_parser/matching.py +134 -0
  6. griptape_nodes/common/macro_parser/parsing.py +172 -0
  7. griptape_nodes/common/macro_parser/resolution.py +168 -0
  8. griptape_nodes/common/macro_parser/segments.py +42 -0
  9. griptape_nodes/exe_types/core_types.py +241 -4
  10. griptape_nodes/exe_types/node_types.py +7 -1
  11. griptape_nodes/exe_types/param_components/huggingface/__init__.py +1 -0
  12. griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +168 -0
  13. griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +38 -0
  14. griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_parameter.py +33 -0
  15. griptape_nodes/exe_types/param_components/huggingface/huggingface_utils.py +136 -0
  16. griptape_nodes/exe_types/param_components/log_parameter.py +136 -0
  17. griptape_nodes/exe_types/param_components/seed_parameter.py +59 -0
  18. griptape_nodes/exe_types/param_types/__init__.py +1 -0
  19. griptape_nodes/exe_types/param_types/parameter_bool.py +221 -0
  20. griptape_nodes/exe_types/param_types/parameter_float.py +179 -0
  21. griptape_nodes/exe_types/param_types/parameter_int.py +183 -0
  22. griptape_nodes/exe_types/param_types/parameter_number.py +380 -0
  23. griptape_nodes/exe_types/param_types/parameter_string.py +232 -0
  24. griptape_nodes/node_library/library_registry.py +2 -1
  25. griptape_nodes/retained_mode/events/app_events.py +21 -0
  26. griptape_nodes/retained_mode/events/os_events.py +142 -6
  27. griptape_nodes/retained_mode/events/parameter_events.py +2 -0
  28. griptape_nodes/retained_mode/griptape_nodes.py +14 -0
  29. griptape_nodes/retained_mode/managers/agent_manager.py +5 -3
  30. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +19 -1
  31. griptape_nodes/retained_mode/managers/library_manager.py +27 -32
  32. griptape_nodes/retained_mode/managers/node_manager.py +14 -1
  33. griptape_nodes/retained_mode/managers/os_manager.py +403 -124
  34. griptape_nodes/retained_mode/managers/user_manager.py +120 -0
  35. griptape_nodes/retained_mode/managers/workflow_manager.py +44 -34
  36. griptape_nodes/traits/multi_options.py +26 -2
  37. griptape_nodes/utils/huggingface_utils.py +136 -0
  38. {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/METADATA +1 -1
  39. {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/RECORD +41 -18
  40. {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/WHEEL +1 -1
  41. {griptape_nodes-0.59.2.dist-info → griptape_nodes-0.60.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,136 @@
1
+ import logging
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ from huggingface_hub import scan_cache_dir
6
+ from huggingface_hub.constants import HF_HUB_CACHE
7
+
8
+ logger = logging.getLogger("griptape_nodes")
9
+
10
+
11
+ def list_all_repo_revisions_in_cache() -> list[tuple[str, str]]:
12
+ """Returns a list of (repo_id, revision) tuples for all repos in the huggingface cache."""
13
+ # Use quick scan for diffuser repos, fallback to scan_cache_dir only on errors
14
+ try:
15
+ repos = quick_scan_diffuser_repos(HF_HUB_CACHE)
16
+ results = [(repo["name"], repo["hash"]) for repo in repos]
17
+ except Exception:
18
+ logger.exception("Failed to quick scan diffuser repos, falling back to scan_cache_dir.")
19
+ else:
20
+ return results
21
+
22
+ # Fallback to original implementation
23
+ cache_info = scan_cache_dir()
24
+ results = []
25
+ for repo in cache_info.repos:
26
+ for revision in repo.revisions:
27
+ results.append((repo.repo_id, revision.commit_hash))
28
+ return results
29
+
30
+
31
+ def list_repo_revisions_in_cache(repo_id: str) -> list[tuple[str, str]]:
32
+ """Returns a list of (repo_id, revision) tuples matching repo_id in the huggingface cache."""
33
+ # Use quick scan for diffuser repos, fallback to scan_cache_dir only on errors
34
+ try:
35
+ repos = quick_scan_diffuser_repos(HF_HUB_CACHE)
36
+ results = [(repo["name"], repo["hash"]) for repo in repos if repo["name"] == repo_id]
37
+ except Exception:
38
+ logger.exception("Failed to quick scan diffuser repos, falling back to scan_cache_dir.")
39
+ else:
40
+ return results
41
+
42
+ # Fallback to original implementation
43
+ cache_info = scan_cache_dir()
44
+ results = []
45
+ for repo in cache_info.repos:
46
+ if repo.repo_id == repo_id:
47
+ for revision in repo.revisions:
48
+ results.append((repo.repo_id, revision.commit_hash))
49
+ return results
50
+
51
+
52
+ def list_repo_revisions_with_file_in_cache(repo_id: str, file: str) -> list[tuple[str, str]]:
53
+ """Returns a list of (repo_id, revision) tuples matching repo_id in the huggingface cache if it contains file."""
54
+ # Use quick scan for diffuser repos, check if file exists
55
+ try:
56
+ repos = quick_scan_diffuser_repos(HF_HUB_CACHE)
57
+ results = [
58
+ (repo["name"], repo["hash"])
59
+ for repo in repos
60
+ if repo["name"] == repo_id and (Path(repo["path"]) / file).exists()
61
+ ]
62
+ except Exception:
63
+ logger.exception("Failed to quick scan diffuser repos, falling back to scan_cache_dir.")
64
+ else:
65
+ return results
66
+
67
+ # Fallback to original implementation
68
+ cache_info = scan_cache_dir()
69
+ results = []
70
+ for repo in cache_info.repos:
71
+ if repo.repo_id == repo_id:
72
+ for revision in repo.revisions:
73
+ if any(f.file_name == file for f in revision.files):
74
+ results.append((repo.repo_id, revision.commit_hash))
75
+ return results
76
+
77
+
78
+ def quick_scan_diffuser_repos(cache_dir: str) -> list[dict[str, Any]]:
79
+ """Quick scan of diffuser repositories in huggingface cache directory.
80
+
81
+ Adapted from https://github.com/huggingface/huggingface_hub/issues/1564#issuecomment-1710764361
82
+
83
+ Args:
84
+ cache_dir: Path to huggingface cache directory
85
+
86
+ Returns:
87
+ List of dictionaries containing repo info with keys:
88
+ - name: Repository name
89
+ - filename: Repository name (alias for name)
90
+ - path: Path to the snapshot folder
91
+ - hash: Commit hash
92
+ - mtime: Modification time
93
+ - model_info: Path to model_info.json file
94
+ """
95
+ diffuser_repos = []
96
+ cache_path = Path(cache_dir)
97
+
98
+ if not cache_path.exists():
99
+ return diffuser_repos
100
+
101
+ for folder_path in cache_path.iterdir():
102
+ folder = folder_path.name
103
+ if not folder_path.is_dir() or "--" not in folder:
104
+ continue
105
+ _, name = folder.split("--", maxsplit=1)
106
+ name = name.replace("--", "/")
107
+
108
+ snapshots_dir = folder_path / "snapshots"
109
+ if not snapshots_dir.exists():
110
+ continue
111
+
112
+ snapshots = [p.name for p in snapshots_dir.iterdir() if p.is_dir()]
113
+ if len(snapshots) == 0:
114
+ continue
115
+
116
+ commit = snapshots[-1]
117
+ snapshot_path = snapshots_dir / commit
118
+
119
+ if (snapshot_path / "hidden").exists():
120
+ continue
121
+
122
+ mtime = snapshot_path.stat().st_mtime
123
+ info = snapshot_path / "model_info.json"
124
+
125
+ diffuser_repos.append(
126
+ {
127
+ "name": name,
128
+ "filename": name,
129
+ "path": str(snapshot_path),
130
+ "hash": commit,
131
+ "mtime": mtime,
132
+ "model_info": str(info),
133
+ }
134
+ )
135
+
136
+ return diffuser_repos
@@ -0,0 +1,136 @@
1
+ import contextlib
2
+ import logging
3
+ import sys
4
+ import time
5
+ from collections.abc import Callable, Iterator
6
+ from types import TracebackType
7
+
8
+ from griptape_nodes.exe_types.core_types import Parameter, ParameterMode
9
+ from griptape_nodes.exe_types.node_types import BaseNode
10
+
11
+
12
+ class LogParameter:
13
+ def __init__(self, node: BaseNode):
14
+ self._node = node
15
+
16
+ def add_output_parameters(self) -> None:
17
+ self._node.add_parameter(
18
+ Parameter(
19
+ name="logs",
20
+ output_type="str",
21
+ allowed_modes={ParameterMode.OUTPUT},
22
+ tooltip="logs",
23
+ ui_options={"multiline": True},
24
+ )
25
+ )
26
+
27
+ @contextlib.contextmanager
28
+ def append_stdout_to_logs(self) -> Iterator[None]:
29
+ def callback(data: str) -> None:
30
+ self.append_to_logs(data)
31
+
32
+ with StdoutCapture(callback):
33
+ yield
34
+
35
+ @contextlib.contextmanager
36
+ def append_logs_to_logs(self, logger: logging.Logger) -> Iterator[None]:
37
+ def callback(data: str) -> None:
38
+ self.append_to_logs(data)
39
+
40
+ with LoggerCapture(logger, callback):
41
+ yield
42
+
43
+ @contextlib.contextmanager
44
+ def append_profile_to_logs(self, label: str) -> Iterator[None]:
45
+ start = time.perf_counter()
46
+ yield
47
+ seconds = time.perf_counter() - start
48
+ human_readable_duration = seconds_to_human_readable(seconds)
49
+ self.append_to_logs(f"{label} took {human_readable_duration}\n")
50
+
51
+ def append_to_logs(self, text: str) -> None:
52
+ self._node.append_value_to_parameter("logs", text)
53
+
54
+ def clear_logs(self) -> None:
55
+ self._node.publish_update_to_parameter("logs", "")
56
+
57
+
58
+ class StdoutCapture:
59
+ def __init__(self, callback: Callable[[str], None]) -> None:
60
+ self.callback: Callable[[str], None] = callback
61
+ self._original_stdout = sys.stdout
62
+
63
+ def write(self, data: str) -> None:
64
+ self._original_stdout.write(data)
65
+ self._original_stdout.flush()
66
+ self.callback(data)
67
+
68
+ def flush(self) -> None:
69
+ self._original_stdout.flush()
70
+
71
+ def __enter__(self) -> "StdoutCapture":
72
+ sys.stdout = self
73
+ return self
74
+
75
+ def __exit__(
76
+ self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
77
+ ) -> None:
78
+ sys.stdout = self._original_stdout
79
+
80
+
81
+ class CallbackHandler(logging.Handler):
82
+ def __init__(self, callback: Callable[[str], None]) -> None:
83
+ super().__init__(level=logging.INFO)
84
+ self.callback = callback
85
+ self.setFormatter(logging.Formatter("%(message)s\n"))
86
+
87
+ def emit(self, record: logging.LogRecord) -> None:
88
+ message = self.format(record)
89
+ self.callback(message)
90
+
91
+
92
+ class LoggerCapture:
93
+ def __init__(
94
+ self, logger: logging.Logger, callback: Callable[[str], None], level: int | str = logging.INFO
95
+ ) -> None:
96
+ self.logger = logger
97
+ self.target_level = level
98
+ self._handler = CallbackHandler(callback)
99
+
100
+ def __enter__(self) -> "LoggerCapture":
101
+ self.original_level = self.logger.level
102
+ self.logger.setLevel(self.target_level)
103
+ self.logger.addHandler(self._handler)
104
+ return self
105
+
106
+ def __exit__(
107
+ self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
108
+ ) -> None:
109
+ self.logger.removeHandler(self._handler)
110
+ self.logger.setLevel(self.original_level)
111
+
112
+
113
+ def seconds_to_human_readable(seconds: float) -> str:
114
+ """Convert seconds to a human-readable format.
115
+
116
+ Args:
117
+ seconds (float): The number of seconds to convert.
118
+
119
+ Returns:
120
+ str: A human-readable string representing the time duration.
121
+ """
122
+ intervals = (
123
+ ("year", 31536000),
124
+ ("month", 2592000),
125
+ ("day", 86400),
126
+ ("hour", 3600),
127
+ ("minute", 60),
128
+ ("second", 1),
129
+ ("millisecond", 0.001),
130
+ )
131
+
132
+ for name, count in intervals:
133
+ if seconds >= count:
134
+ value = seconds / count
135
+ return f"{value:.2f} {name}{'s' if value != 1 else ''}"
136
+ return "0.00 milliseconds"
@@ -0,0 +1,59 @@
1
+ import random
2
+ from typing import Any
3
+
4
+ from griptape_nodes.exe_types.core_types import Parameter, ParameterMode
5
+ from griptape_nodes.exe_types.node_types import BaseNode
6
+
7
+
8
+ class SeedParameter:
9
+ def __init__(self, node: BaseNode):
10
+ self._node = node
11
+
12
+ def add_input_parameters(self) -> None:
13
+ self._node.add_parameter(
14
+ Parameter(
15
+ name="randomize_seed",
16
+ type="bool",
17
+ output_type="bool",
18
+ tooltip="randomize the seed on each run",
19
+ default_value=False,
20
+ )
21
+ )
22
+ self._node.add_parameter(
23
+ Parameter(
24
+ name="seed",
25
+ type="int",
26
+ tooltip="seed",
27
+ default_value=42,
28
+ )
29
+ )
30
+
31
+ def remove_input_parameters(self) -> None:
32
+ self._node.remove_parameter_element_by_name("randomize_seed")
33
+ self._node.remove_parameter_element_by_name("seed")
34
+
35
+ def after_value_set(self, parameter: Parameter, value: Any) -> None:
36
+ if parameter.name != "randomize_seed":
37
+ return
38
+
39
+ seed_parameter = self._node.get_parameter_by_name("seed")
40
+ if not seed_parameter:
41
+ msg = "Seed parameter not found"
42
+ raise RuntimeError(msg)
43
+
44
+ if value:
45
+ # Disable editing the seed if randomize_seed is True
46
+ seed_parameter.allowed_modes = {ParameterMode.OUTPUT}
47
+ else:
48
+ # Enable editing the seed if randomize_seed is False
49
+ seed_parameter.allowed_modes = {ParameterMode.PROPERTY, ParameterMode.INPUT, ParameterMode.OUTPUT}
50
+
51
+ def preprocess(self) -> None:
52
+ if self._node.get_parameter_value("randomize_seed"):
53
+ # Not using for cryptographic purposes
54
+ seed = random.randint(0, 2**32 - 1) # noqa: S311
55
+ self._node.set_parameter_value("seed", seed)
56
+ self._node.publish_update_to_parameter("seed", seed)
57
+
58
+ def get_seed(self) -> int:
59
+ return int(self._node.get_parameter_value("seed"))
@@ -0,0 +1 @@
1
+ """Parameter types for simple parameter creation."""
@@ -0,0 +1,221 @@
1
+ """ParameterBool component for boolean inputs with enhanced UI options."""
2
+
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+ from griptape_nodes.exe_types.core_types import Parameter, ParameterMode, Trait
7
+
8
+
9
+ class ParameterBool(Parameter):
10
+ """A specialized Parameter class for boolean inputs with enhanced UI options.
11
+
12
+ This class provides a convenient way to create boolean parameters with common
13
+ UI customizations like custom on/off labels. It exposes these UI options as
14
+ direct properties for easy runtime modification.
15
+
16
+ Example:
17
+ param = ParameterBool(
18
+ name="enabled",
19
+ tooltip="Enable this feature",
20
+ on_label="Yes",
21
+ off_label="No",
22
+ default_value=True
23
+ )
24
+ param.on_label = "Enable" # Change labels at runtime
25
+ """
26
+
27
+ def __init__( # noqa: PLR0913
28
+ self,
29
+ name: str,
30
+ tooltip: str | None = None,
31
+ *,
32
+ type: str = "bool", # noqa: A002, ARG002
33
+ default_value: Any = None,
34
+ tooltip_as_input: str | None = None,
35
+ tooltip_as_property: str | None = None,
36
+ tooltip_as_output: str | None = None,
37
+ allowed_modes: set[ParameterMode] | None = None,
38
+ traits: set[type[Trait] | Trait] | None = None,
39
+ converters: list[Callable[[Any], Any]] | None = None,
40
+ validators: list[Callable[[Parameter, Any], None]] | None = None,
41
+ ui_options: dict | None = None,
42
+ on_label: str | None = None,
43
+ off_label: str | None = None,
44
+ accept_any: bool = True,
45
+ hide: bool = False,
46
+ hide_label: bool = False,
47
+ hide_property: bool = False,
48
+ allow_input: bool = True,
49
+ allow_property: bool = True,
50
+ allow_output: bool = True,
51
+ settable: bool = True,
52
+ serializable: bool = True,
53
+ user_defined: bool = False,
54
+ element_id: str | None = None,
55
+ element_type: str | None = None,
56
+ parent_container_name: str | None = None,
57
+ ) -> None:
58
+ """Initialize a boolean parameter with enhanced UI options.
59
+
60
+ Args:
61
+ name: Parameter name
62
+ tooltip: Parameter tooltip
63
+ type: Parameter type (ignored, always "bool" for ParameterBool)
64
+ default_value: Default parameter value
65
+ tooltip_as_input: Tooltip for input mode
66
+ tooltip_as_property: Tooltip for property mode
67
+ tooltip_as_output: Tooltip for output mode
68
+ allowed_modes: Allowed parameter modes
69
+ traits: Parameter traits
70
+ converters: Parameter converters
71
+ validators: Parameter validators
72
+ ui_options: Dictionary of UI options
73
+ on_label: Label for the "on" state
74
+ off_label: Label for the "off" state
75
+ accept_any: Whether to accept any input type and convert to boolean (default: True)
76
+ hide: Whether to hide the entire parameter
77
+ hide_label: Whether to hide the parameter label
78
+ hide_property: Whether to hide the parameter in property mode
79
+ allow_input: Whether to allow input mode
80
+ allow_property: Whether to allow property mode
81
+ allow_output: Whether to allow output mode
82
+ settable: Whether the parameter is settable
83
+ serializable: Whether the parameter is serializable
84
+ user_defined: Whether the parameter is user-defined
85
+ element_id: Element ID
86
+ element_type: Element type
87
+ parent_container_name: Name of parent container
88
+ """
89
+ # Build ui_options dictionary from the provided UI-specific parameters
90
+ if ui_options is None:
91
+ ui_options = {}
92
+ else:
93
+ ui_options = ui_options.copy()
94
+
95
+ # Add boolean-specific UI options if they have values
96
+ if on_label is not None:
97
+ ui_options["on_label"] = on_label
98
+ if off_label is not None:
99
+ ui_options["off_label"] = off_label
100
+
101
+ # Set up boolean conversion based on accept_any setting
102
+ if converters is None:
103
+ existing_converters = []
104
+ else:
105
+ existing_converters = converters
106
+
107
+ if accept_any:
108
+ final_input_types = ["any"]
109
+ final_converters = [self._convert_to_bool, *existing_converters]
110
+ else:
111
+ final_input_types = ["bool"]
112
+ final_converters = existing_converters
113
+
114
+ # Call parent with explicit parameters, following ControlParameter pattern
115
+ super().__init__(
116
+ name=name,
117
+ tooltip=tooltip,
118
+ type="bool", # Always a boolean type for ParameterBool
119
+ input_types=final_input_types,
120
+ output_type="bool", # Always output as boolean
121
+ default_value=default_value,
122
+ tooltip_as_input=tooltip_as_input,
123
+ tooltip_as_property=tooltip_as_property,
124
+ tooltip_as_output=tooltip_as_output,
125
+ allowed_modes=allowed_modes,
126
+ traits=traits,
127
+ converters=final_converters,
128
+ validators=validators,
129
+ ui_options=ui_options,
130
+ hide=hide,
131
+ hide_label=hide_label,
132
+ hide_property=hide_property,
133
+ allow_input=allow_input,
134
+ allow_property=allow_property,
135
+ allow_output=allow_output,
136
+ settable=settable,
137
+ serializable=serializable,
138
+ user_defined=user_defined,
139
+ element_id=element_id,
140
+ element_type=element_type,
141
+ parent_container_name=parent_container_name,
142
+ )
143
+
144
+ def _convert_to_bool(self, value: Any) -> bool:
145
+ """Safely convert any input value to a boolean.
146
+
147
+ Handles various input types including strings, numbers, and other objects.
148
+ Uses Python's built-in bool() conversion with proper handling of common
149
+ string representations.
150
+
151
+ Args:
152
+ value: The value to convert to boolean
153
+
154
+ Returns:
155
+ Boolean representation of the value
156
+ """
157
+ if value is None:
158
+ return False
159
+
160
+ # Handle boolean inputs
161
+ if isinstance(value, bool):
162
+ return value
163
+
164
+ # Handle string inputs with common boolean representations
165
+ if isinstance(value, str):
166
+ value_lower = value.lower().strip()
167
+ if value_lower in ("true", "yes", "on", "1", "enable", "enabled"):
168
+ return True
169
+ if value_lower in ("false", "no", "off", "0", "disable", "disabled"):
170
+ return False
171
+ # For other strings, use truthiness
172
+ return bool(value)
173
+
174
+ # For all other types (including numeric), use Python's built-in bool conversion
175
+ return bool(value)
176
+
177
+ @property
178
+ def on_label(self) -> str | None:
179
+ """Get the label for the "on" state.
180
+
181
+ Returns:
182
+ The on label if set, None otherwise
183
+ """
184
+ return self.ui_options.get("on_label")
185
+
186
+ @on_label.setter
187
+ def on_label(self, value: str | None) -> None:
188
+ """Set the label for the "on" state.
189
+
190
+ Args:
191
+ value: The on label to use, or None to remove it
192
+ """
193
+ if value is None:
194
+ ui_options = self.ui_options.copy()
195
+ ui_options.pop("on_label", None)
196
+ self.ui_options = ui_options
197
+ else:
198
+ self.update_ui_options_key("on_label", value)
199
+
200
+ @property
201
+ def off_label(self) -> str | None:
202
+ """Get the label for the "off" state.
203
+
204
+ Returns:
205
+ The off label if set, None otherwise
206
+ """
207
+ return self.ui_options.get("off_label")
208
+
209
+ @off_label.setter
210
+ def off_label(self, value: str | None) -> None:
211
+ """Set the label for the "off" state.
212
+
213
+ Args:
214
+ value: The off label to use, or None to remove it
215
+ """
216
+ if value is None:
217
+ ui_options = self.ui_options.copy()
218
+ ui_options.pop("off_label", None)
219
+ self.ui_options = ui_options
220
+ else:
221
+ self.update_ui_options_key("off_label", value)