griptape-nodes 0.59.3__py3-none-any.whl → 0.60.1__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 (42) hide show
  1. griptape_nodes/cli/commands/libraries.py +21 -1
  2. griptape_nodes/common/macro_parser/__init__.py +28 -0
  3. griptape_nodes/common/macro_parser/core.py +230 -0
  4. griptape_nodes/common/macro_parser/exceptions.py +23 -0
  5. griptape_nodes/common/macro_parser/formats.py +170 -0
  6. griptape_nodes/common/macro_parser/matching.py +134 -0
  7. griptape_nodes/common/macro_parser/parsing.py +172 -0
  8. griptape_nodes/common/macro_parser/resolution.py +168 -0
  9. griptape_nodes/common/macro_parser/segments.py +42 -0
  10. griptape_nodes/exe_types/core_types.py +241 -4
  11. griptape_nodes/exe_types/node_types.py +7 -1
  12. griptape_nodes/exe_types/param_components/huggingface/__init__.py +1 -0
  13. griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +168 -0
  14. griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +38 -0
  15. griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_parameter.py +33 -0
  16. griptape_nodes/exe_types/param_components/huggingface/huggingface_utils.py +136 -0
  17. griptape_nodes/exe_types/param_components/log_parameter.py +136 -0
  18. griptape_nodes/exe_types/param_components/seed_parameter.py +59 -0
  19. griptape_nodes/exe_types/param_types/__init__.py +1 -0
  20. griptape_nodes/exe_types/param_types/parameter_bool.py +221 -0
  21. griptape_nodes/exe_types/param_types/parameter_float.py +179 -0
  22. griptape_nodes/exe_types/param_types/parameter_int.py +183 -0
  23. griptape_nodes/exe_types/param_types/parameter_number.py +380 -0
  24. griptape_nodes/exe_types/param_types/parameter_string.py +232 -0
  25. griptape_nodes/node_library/library_registry.py +2 -1
  26. griptape_nodes/retained_mode/events/app_events.py +21 -0
  27. griptape_nodes/retained_mode/events/os_events.py +142 -6
  28. griptape_nodes/retained_mode/events/parameter_events.py +2 -0
  29. griptape_nodes/retained_mode/griptape_nodes.py +14 -0
  30. griptape_nodes/retained_mode/managers/agent_manager.py +5 -3
  31. griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +19 -1
  32. griptape_nodes/retained_mode/managers/library_manager.py +8 -1
  33. griptape_nodes/retained_mode/managers/node_manager.py +14 -1
  34. griptape_nodes/retained_mode/managers/os_manager.py +403 -124
  35. griptape_nodes/retained_mode/managers/user_manager.py +120 -0
  36. griptape_nodes/retained_mode/managers/workflow_manager.py +44 -34
  37. griptape_nodes/traits/multi_options.py +26 -2
  38. griptape_nodes/utils/huggingface_utils.py +136 -0
  39. {griptape_nodes-0.59.3.dist-info → griptape_nodes-0.60.1.dist-info}/METADATA +1 -1
  40. {griptape_nodes-0.59.3.dist-info → griptape_nodes-0.60.1.dist-info}/RECORD +42 -19
  41. {griptape_nodes-0.59.3.dist-info → griptape_nodes-0.60.1.dist-info}/WHEEL +1 -1
  42. {griptape_nodes-0.59.3.dist-info → griptape_nodes-0.60.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,172 @@
1
+ """Parsing logic for macro templates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from griptape_nodes.common.macro_parser.exceptions import MacroSyntaxError
8
+ from griptape_nodes.common.macro_parser.formats import (
9
+ FORMAT_REGISTRY,
10
+ DateFormat,
11
+ FormatSpec,
12
+ NumericPaddingFormat,
13
+ SeparatorFormat,
14
+ )
15
+ from griptape_nodes.common.macro_parser.segments import (
16
+ ParsedSegment,
17
+ ParsedStaticValue,
18
+ ParsedVariable,
19
+ VariableInfo,
20
+ )
21
+
22
+
23
+ def parse_segments(template: str) -> list[ParsedSegment]:
24
+ """Parse template into alternating static/variable segments.
25
+
26
+ Args:
27
+ template: Template string to parse
28
+
29
+ Returns:
30
+ List of ParsedSegment (static and variable)
31
+
32
+ Raises:
33
+ MacroSyntaxError: If template syntax is invalid
34
+ """
35
+ segments: list[ParsedSegment] = []
36
+ current_pos = 0
37
+
38
+ while current_pos < len(template):
39
+ # Find next opening brace
40
+ brace_start = template.find("{", current_pos)
41
+
42
+ if brace_start == -1:
43
+ # No more variables, rest is static text
44
+ static_text = template[current_pos:]
45
+ if static_text:
46
+ # Check for unmatched closing braces in remaining text
47
+ if "}" in static_text:
48
+ closing_pos = current_pos + static_text.index("}")
49
+ msg = f"Unmatched closing brace at position {closing_pos}"
50
+ raise MacroSyntaxError(msg)
51
+ segments.append(ParsedStaticValue(text=static_text))
52
+ break
53
+
54
+ # Add static text before the brace (if any)
55
+ if brace_start > current_pos:
56
+ static_text = template[current_pos:brace_start]
57
+ # Check for unmatched closing braces in static text
58
+ if "}" in static_text:
59
+ closing_pos = current_pos + static_text.index("}")
60
+ msg = f"Unmatched closing brace at position {closing_pos}"
61
+ raise MacroSyntaxError(msg)
62
+ segments.append(ParsedStaticValue(text=static_text))
63
+
64
+ # Find matching closing brace
65
+ brace_end = template.find("}", brace_start)
66
+ if brace_end == -1:
67
+ msg = f"Unclosed brace at position {brace_start}"
68
+ raise MacroSyntaxError(msg)
69
+
70
+ # Check for nested braces (opening brace before closing brace)
71
+ next_open = template.find("{", brace_start + 1)
72
+ if next_open != -1 and next_open < brace_end:
73
+ msg = f"Nested braces are not allowed at position {next_open}"
74
+ raise MacroSyntaxError(msg)
75
+
76
+ # Extract and parse the variable content
77
+ variable_content = template[brace_start + 1 : brace_end]
78
+ if not variable_content:
79
+ msg = f"Empty variable at position {brace_start}"
80
+ raise MacroSyntaxError(msg)
81
+
82
+ variable = parse_variable(variable_content)
83
+ segments.append(variable)
84
+
85
+ # Move past the closing brace
86
+ current_pos = brace_end + 1
87
+
88
+ return segments
89
+
90
+
91
+ def parse_variable(variable_content: str) -> ParsedVariable:
92
+ """Parse a variable from its content (text between braces).
93
+
94
+ Args:
95
+ variable_content: Content between braces (e.g., "workflow_name?:_:lower")
96
+
97
+ Returns:
98
+ ParsedVariable with name, format specs, and default value
99
+
100
+ Raises:
101
+ MacroSyntaxError: If variable syntax is invalid
102
+ """
103
+ # Parse variable content: name[?][:format[:format...]][|default]
104
+
105
+ # Check for default value (|)
106
+ default_value = None
107
+ if "|" in variable_content:
108
+ parts = variable_content.split("|", 1)
109
+ variable_content = parts[0]
110
+ default_value = parts[1]
111
+
112
+ # Check for format specifiers (:)
113
+ format_specs: list[FormatSpec] = []
114
+ if ":" in variable_content:
115
+ parts = variable_content.split(":")
116
+ variable_part = parts[0]
117
+ format_parts = parts[1:]
118
+
119
+ # Parse format specifiers
120
+ for format_part in format_parts:
121
+ format_spec = parse_format_spec(format_part)
122
+ format_specs.append(format_spec)
123
+ else:
124
+ variable_part = variable_content
125
+
126
+ # Check for optional marker (?)
127
+ if variable_part.endswith("?"):
128
+ name = variable_part[:-1]
129
+ is_required = False
130
+ else:
131
+ name = variable_part
132
+ is_required = True
133
+
134
+ info = VariableInfo(name=name, is_required=is_required)
135
+ return ParsedVariable(info=info, format_specs=format_specs, default_value=default_value)
136
+
137
+
138
+ def parse_format_spec(format_text: str) -> FormatSpec:
139
+ """Parse a single format specifier.
140
+
141
+ Args:
142
+ format_text: Format specifier text (e.g., "lower", "03", "_")
143
+
144
+ Returns:
145
+ Appropriate FormatSpec subclass instance
146
+
147
+ Raises:
148
+ MacroSyntaxError: If format specifier is invalid
149
+ """
150
+ # Remove quotes if present (for explicit separators like 'lower')
151
+ if format_text.startswith("'") and format_text.endswith("'"):
152
+ # Quoted text is always a separator, even if it matches other keywords
153
+ return SeparatorFormat(separator=format_text[1:-1])
154
+
155
+ # Check for date format (starts with %)
156
+ if format_text.startswith("%"):
157
+ # Date format pattern like %Y-%m-%d
158
+ return DateFormat(pattern=format_text)
159
+
160
+ # Check for numeric padding (e.g., "03", "04")
161
+ if re.match(r"^\d+$", format_text):
162
+ width = int(format_text)
163
+ # Numeric padding like 03 means pad to 3 digits with zeros
164
+ return NumericPaddingFormat(width=width)
165
+
166
+ # Check for known transformations
167
+ if format_text in FORMAT_REGISTRY:
168
+ # Known transformation keyword (lower, upper, slug)
169
+ return FORMAT_REGISTRY[format_text]
170
+
171
+ # Otherwise, treat as separator (unquoted text that doesn't match any format)
172
+ return SeparatorFormat(separator=format_text)
@@ -0,0 +1,168 @@
1
+ """Resolution logic for macro templates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING
7
+
8
+ from griptape_nodes.common.macro_parser.exceptions import MacroResolutionError
9
+ from griptape_nodes.common.macro_parser.segments import ParsedSegment, ParsedStaticValue, ParsedVariable
10
+
11
+ if TYPE_CHECKING:
12
+ from griptape_nodes.retained_mode.managers.secrets_manager import SecretsManager
13
+
14
+
15
+ @dataclass
16
+ class PartiallyResolvedMacro:
17
+ """Result of partially resolving a macro with known variables.
18
+
19
+ Contains both resolved segments (known variables → static text) and
20
+ unresolved segments (unknown variables still as variables).
21
+ """
22
+
23
+ original_template: str
24
+ segments: list[ParsedSegment]
25
+ known_variables: dict[str, str | int]
26
+
27
+ def is_fully_resolved(self) -> bool:
28
+ """Check if all variables have been resolved."""
29
+ return all(isinstance(seg, ParsedStaticValue) for seg in self.segments)
30
+
31
+ def to_string(self) -> str:
32
+ """Convert to string (only valid if fully resolved)."""
33
+ if not self.is_fully_resolved():
34
+ msg = "Cannot convert partially resolved macro to string - unresolved variables remain"
35
+ raise MacroResolutionError(msg)
36
+ # All segments are ParsedStaticValue at this point
37
+ return "".join(seg.text for seg in self.segments if isinstance(seg, ParsedStaticValue))
38
+
39
+ def get_unresolved_variables(self) -> list[ParsedVariable]:
40
+ """Get list of unresolved variables."""
41
+ return [seg for seg in self.segments if isinstance(seg, ParsedVariable)]
42
+
43
+
44
+ def partial_resolve(
45
+ template: str,
46
+ segments: list[ParsedSegment],
47
+ variables: dict[str, str | int],
48
+ secrets_manager: SecretsManager,
49
+ ) -> PartiallyResolvedMacro:
50
+ """Partially resolve the macro template with known variables.
51
+
52
+ Resolves known variables (including env vars and format specs) into static
53
+ text, leaving unknown variables as-is. This is the core resolution logic
54
+ used by both resolve() and find_matches().
55
+
56
+ Args:
57
+ template: Original template string
58
+ segments: Parsed segments from template
59
+ variables: Variable name -> value mapping for known variables
60
+ secrets_manager: SecretsManager instance for resolving env vars
61
+
62
+ Returns:
63
+ PartiallyResolvedMacro with resolved and unresolved segments
64
+
65
+ Raises:
66
+ MacroResolutionError: If:
67
+ - Required variable is provided but env var resolution fails
68
+ - Format specifier cannot be applied to value type
69
+ """
70
+ resolved_segments: list[ParsedSegment] = []
71
+
72
+ for segment in segments:
73
+ match segment:
74
+ case ParsedStaticValue():
75
+ resolved_segments.append(segment)
76
+ case ParsedVariable():
77
+ if segment.info.name in variables:
78
+ # Known variable - resolve it
79
+ resolved_value = resolve_variable(segment, variables, secrets_manager)
80
+ if resolved_value is not None:
81
+ # Variable was resolved, add as static
82
+ resolved_segments.append(ParsedStaticValue(text=resolved_value))
83
+ # else: Optional variable provided as None, skip it
84
+ continue
85
+
86
+ if segment.info.is_required:
87
+ # Required variable not in variables dict - keep as unresolved
88
+ resolved_segments.append(segment)
89
+ continue
90
+
91
+ # Optional variable not in variables dict - skip it
92
+ continue
93
+ case _:
94
+ msg = f"Unexpected segment type: {type(segment).__name__}"
95
+ raise MacroResolutionError(msg)
96
+
97
+ return PartiallyResolvedMacro(
98
+ original_template=template,
99
+ segments=resolved_segments,
100
+ known_variables=variables,
101
+ )
102
+
103
+
104
+ def resolve_variable(
105
+ variable: ParsedVariable, variables: dict[str, str | int], secrets_manager: SecretsManager
106
+ ) -> str | None:
107
+ """Resolve a single variable with format specs and env var resolution.
108
+
109
+ Args:
110
+ variable: The parsed variable to resolve
111
+ variables: Variable name -> value mapping
112
+ secrets_manager: SecretsManager instance for resolving env vars
113
+
114
+ Returns:
115
+ Resolved string value, or None if optional variable not provided
116
+
117
+ Raises:
118
+ MacroResolutionError: If required variable missing or env var not found
119
+ """
120
+ variable_name = variable.info.name
121
+
122
+ if variable_name not in variables:
123
+ if variable.info.is_required:
124
+ msg = f"Required variable '{variable_name}' not found in variables dict"
125
+ raise MacroResolutionError(msg)
126
+ # Optional variable not provided, return None to signal it should be skipped
127
+ return None
128
+
129
+ value = variables[variable_name]
130
+ resolved_value: str | int = resolve_env_var(value, secrets_manager)
131
+
132
+ for format_spec in variable.format_specs:
133
+ resolved_value = format_spec.apply(resolved_value)
134
+
135
+ # Return fully resolved value as string
136
+ return str(resolved_value)
137
+
138
+
139
+ def resolve_env_var(value: str | int, secrets_manager: SecretsManager) -> str | int:
140
+ """Resolve environment variables in a value.
141
+
142
+ Args:
143
+ value: Value that may contain env var reference (e.g., "$VAR")
144
+ secrets_manager: SecretsManager instance for resolving env vars
145
+
146
+ Returns:
147
+ Resolved value (env var substituted if found)
148
+
149
+ Raises:
150
+ MacroResolutionError: If value starts with $ but env var not found
151
+ """
152
+ if not isinstance(value, str):
153
+ # Integer values don't contain env vars, return as-is
154
+ return value
155
+
156
+ if not value.startswith("$"):
157
+ # String doesn't reference an env var, return as-is
158
+ return value
159
+
160
+ env_var_name = value[1:]
161
+ env_value = secrets_manager.get_secret(env_var_name, should_error_on_not_found=False)
162
+
163
+ if env_value is None:
164
+ msg = f"Environment variable '{env_var_name}' not found"
165
+ raise MacroResolutionError(msg)
166
+
167
+ # Return resolved env var value
168
+ return env_value
@@ -0,0 +1,42 @@
1
+ """Segment classes for representing parsed macro templates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING, NamedTuple
7
+
8
+ if TYPE_CHECKING:
9
+ from griptape_nodes.common.macro_parser.formats import FormatSpec
10
+
11
+
12
+ class VariableInfo(NamedTuple):
13
+ """Metadata about a variable in a macro template.
14
+
15
+ Attributes:
16
+ name: Variable name (e.g., "workflow_name", "file_name")
17
+ is_required: True if variable is required (not marked with ?)
18
+ """
19
+
20
+ name: str
21
+ is_required: bool
22
+
23
+
24
+ @dataclass
25
+ class ParsedSegment:
26
+ """Base class for template segments."""
27
+
28
+
29
+ @dataclass
30
+ class ParsedStaticValue(ParsedSegment):
31
+ """Static text segment in template."""
32
+
33
+ text: str
34
+
35
+
36
+ @dataclass
37
+ class ParsedVariable(ParsedSegment):
38
+ """Variable segment in template."""
39
+
40
+ info: VariableInfo # name + is_required
41
+ format_specs: list[FormatSpec] # Applied in order during resolution
42
+ default_value: str | None # From {var|default} syntax