griptape-nodes 0.59.3__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.
- griptape_nodes/common/macro_parser/__init__.py +28 -0
- griptape_nodes/common/macro_parser/core.py +230 -0
- griptape_nodes/common/macro_parser/exceptions.py +23 -0
- griptape_nodes/common/macro_parser/formats.py +170 -0
- griptape_nodes/common/macro_parser/matching.py +134 -0
- griptape_nodes/common/macro_parser/parsing.py +172 -0
- griptape_nodes/common/macro_parser/resolution.py +168 -0
- griptape_nodes/common/macro_parser/segments.py +42 -0
- griptape_nodes/exe_types/core_types.py +241 -4
- griptape_nodes/exe_types/node_types.py +7 -1
- griptape_nodes/exe_types/param_components/huggingface/__init__.py +1 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +168 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +38 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_parameter.py +33 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_utils.py +136 -0
- griptape_nodes/exe_types/param_components/log_parameter.py +136 -0
- griptape_nodes/exe_types/param_components/seed_parameter.py +59 -0
- griptape_nodes/exe_types/param_types/__init__.py +1 -0
- griptape_nodes/exe_types/param_types/parameter_bool.py +221 -0
- griptape_nodes/exe_types/param_types/parameter_float.py +179 -0
- griptape_nodes/exe_types/param_types/parameter_int.py +183 -0
- griptape_nodes/exe_types/param_types/parameter_number.py +380 -0
- griptape_nodes/exe_types/param_types/parameter_string.py +232 -0
- griptape_nodes/node_library/library_registry.py +2 -1
- griptape_nodes/retained_mode/events/app_events.py +21 -0
- griptape_nodes/retained_mode/events/os_events.py +142 -6
- griptape_nodes/retained_mode/events/parameter_events.py +2 -0
- griptape_nodes/retained_mode/griptape_nodes.py +14 -0
- griptape_nodes/retained_mode/managers/agent_manager.py +5 -3
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +19 -1
- griptape_nodes/retained_mode/managers/library_manager.py +8 -1
- griptape_nodes/retained_mode/managers/node_manager.py +14 -1
- griptape_nodes/retained_mode/managers/os_manager.py +403 -124
- griptape_nodes/retained_mode/managers/user_manager.py +120 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +44 -34
- griptape_nodes/traits/multi_options.py +26 -2
- griptape_nodes/utils/huggingface_utils.py +136 -0
- {griptape_nodes-0.59.3.dist-info → griptape_nodes-0.60.0.dist-info}/METADATA +1 -1
- {griptape_nodes-0.59.3.dist-info → griptape_nodes-0.60.0.dist-info}/RECORD +41 -18
- {griptape_nodes-0.59.3.dist-info → griptape_nodes-0.60.0.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.59.3.dist-info → griptape_nodes-0.60.0.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
|