griptape-nodes 0.60.3__py3-none-any.whl → 0.61.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/bootstrap/workflow_publishers/local_workflow_publisher.py +0 -1
- griptape_nodes/common/macro_parser/__init__.py +16 -1
- griptape_nodes/common/macro_parser/core.py +15 -3
- griptape_nodes/common/macro_parser/exceptions.py +99 -0
- griptape_nodes/common/macro_parser/formats.py +13 -4
- griptape_nodes/common/macro_parser/matching.py +5 -2
- griptape_nodes/common/macro_parser/parsing.py +48 -8
- griptape_nodes/common/macro_parser/resolution.py +23 -5
- griptape_nodes/common/project_templates/__init__.py +49 -0
- griptape_nodes/common/project_templates/default_project_template.py +92 -0
- griptape_nodes/common/project_templates/defaults/README.md +36 -0
- griptape_nodes/common/project_templates/defaults/project_template.yml +89 -0
- griptape_nodes/common/project_templates/directory.py +67 -0
- griptape_nodes/common/project_templates/loader.py +341 -0
- griptape_nodes/common/project_templates/project.py +252 -0
- griptape_nodes/common/project_templates/situation.py +155 -0
- griptape_nodes/common/project_templates/validation.py +140 -0
- griptape_nodes/exe_types/core_types.py +36 -3
- griptape_nodes/exe_types/node_types.py +4 -2
- griptape_nodes/exe_types/param_components/progress_bar_component.py +57 -0
- griptape_nodes/exe_types/param_types/parameter_audio.py +243 -0
- griptape_nodes/exe_types/param_types/parameter_image.py +243 -0
- griptape_nodes/exe_types/param_types/parameter_three_d.py +215 -0
- griptape_nodes/exe_types/param_types/parameter_video.py +243 -0
- griptape_nodes/node_library/workflow_registry.py +1 -1
- griptape_nodes/retained_mode/events/execution_events.py +41 -0
- griptape_nodes/retained_mode/events/node_events.py +90 -1
- griptape_nodes/retained_mode/events/os_events.py +108 -0
- griptape_nodes/retained_mode/events/parameter_events.py +1 -1
- griptape_nodes/retained_mode/events/project_events.py +413 -0
- griptape_nodes/retained_mode/events/workflow_events.py +19 -1
- griptape_nodes/retained_mode/griptape_nodes.py +9 -1
- griptape_nodes/retained_mode/managers/agent_manager.py +18 -24
- griptape_nodes/retained_mode/managers/event_manager.py +6 -9
- griptape_nodes/retained_mode/managers/flow_manager.py +63 -0
- griptape_nodes/retained_mode/managers/library_manager.py +55 -42
- griptape_nodes/retained_mode/managers/mcp_manager.py +14 -6
- griptape_nodes/retained_mode/managers/node_manager.py +232 -0
- griptape_nodes/retained_mode/managers/os_manager.py +346 -1
- griptape_nodes/retained_mode/managers/project_manager.py +617 -0
- griptape_nodes/retained_mode/managers/settings.py +6 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +17 -71
- griptape_nodes/traits/button.py +18 -0
- {griptape_nodes-0.60.3.dist-info → griptape_nodes-0.61.0.dist-info}/METADATA +5 -3
- {griptape_nodes-0.60.3.dist-info → griptape_nodes-0.61.0.dist-info}/RECORD +47 -31
- {griptape_nodes-0.60.3.dist-info → griptape_nodes-0.61.0.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.60.3.dist-info → griptape_nodes-0.61.0.dist-info}/entry_points.txt +0 -0
|
@@ -32,7 +32,6 @@ class LocalWorkflowPublisher(LocalWorkflowExecutor):
|
|
|
32
32
|
publish_workflow_request = PublishWorkflowRequest(
|
|
33
33
|
workflow_name=workflow_name,
|
|
34
34
|
publisher_name=publisher_name,
|
|
35
|
-
execute_on_publish=False,
|
|
36
35
|
published_workflow_file_name=published_workflow_file_name,
|
|
37
36
|
pickle_control_flow_result=pickle_control_flow_result,
|
|
38
37
|
)
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"""Macro language parser for template-based path generation."""
|
|
2
2
|
|
|
3
3
|
from griptape_nodes.common.macro_parser.core import ParsedMacro
|
|
4
|
-
from griptape_nodes.common.macro_parser.exceptions import
|
|
4
|
+
from griptape_nodes.common.macro_parser.exceptions import (
|
|
5
|
+
MacroMatchFailure,
|
|
6
|
+
MacroMatchFailureReason,
|
|
7
|
+
MacroParseFailure,
|
|
8
|
+
MacroParseFailureReason,
|
|
9
|
+
MacroResolutionError,
|
|
10
|
+
MacroResolutionFailure,
|
|
11
|
+
MacroResolutionFailureReason,
|
|
12
|
+
MacroSyntaxError,
|
|
13
|
+
)
|
|
5
14
|
from griptape_nodes.common.macro_parser.formats import (
|
|
6
15
|
DateFormat,
|
|
7
16
|
LowerCaseFormat,
|
|
@@ -15,7 +24,13 @@ from griptape_nodes.common.macro_parser.segments import ParsedStaticValue, Parse
|
|
|
15
24
|
__all__ = [
|
|
16
25
|
"DateFormat",
|
|
17
26
|
"LowerCaseFormat",
|
|
27
|
+
"MacroMatchFailure",
|
|
28
|
+
"MacroMatchFailureReason",
|
|
29
|
+
"MacroParseFailure",
|
|
30
|
+
"MacroParseFailureReason",
|
|
18
31
|
"MacroResolutionError",
|
|
32
|
+
"MacroResolutionFailure",
|
|
33
|
+
"MacroResolutionFailureReason",
|
|
19
34
|
"MacroSyntaxError",
|
|
20
35
|
"NumericPaddingFormat",
|
|
21
36
|
"ParsedMacro",
|
|
@@ -4,7 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from griptape_nodes.common.macro_parser.exceptions import
|
|
7
|
+
from griptape_nodes.common.macro_parser.exceptions import (
|
|
8
|
+
MacroResolutionError,
|
|
9
|
+
MacroResolutionFailureReason,
|
|
10
|
+
MacroSyntaxError,
|
|
11
|
+
)
|
|
8
12
|
from griptape_nodes.common.macro_parser.matching import extract_unknown_variables
|
|
9
13
|
from griptape_nodes.common.macro_parser.parsing import parse_segments
|
|
10
14
|
from griptape_nodes.common.macro_parser.resolution import partial_resolve
|
|
@@ -28,7 +32,11 @@ class ParsedMacro:
|
|
|
28
32
|
segments = parse_segments(template)
|
|
29
33
|
except MacroSyntaxError as err:
|
|
30
34
|
msg = f"Attempted to parse template string '{template}'. Failed due to: {err}"
|
|
31
|
-
raise MacroSyntaxError(
|
|
35
|
+
raise MacroSyntaxError(
|
|
36
|
+
msg,
|
|
37
|
+
failure_reason=err.failure_reason,
|
|
38
|
+
error_position=err.error_position,
|
|
39
|
+
) from err
|
|
32
40
|
|
|
33
41
|
if not segments:
|
|
34
42
|
segments.append(ParsedStaticValue(text=""))
|
|
@@ -52,7 +60,11 @@ class ParsedMacro:
|
|
|
52
60
|
unresolved = partial.get_unresolved_variables()
|
|
53
61
|
unresolved_names = [var.info.name for var in unresolved]
|
|
54
62
|
msg = f"Cannot fully resolve macro - missing required variables: {', '.join(unresolved_names)}"
|
|
55
|
-
raise MacroResolutionError(
|
|
63
|
+
raise MacroResolutionError(
|
|
64
|
+
msg,
|
|
65
|
+
failure_reason=MacroResolutionFailureReason.MISSING_REQUIRED_VARIABLES,
|
|
66
|
+
missing_variables=unresolved_names,
|
|
67
|
+
)
|
|
56
68
|
|
|
57
69
|
# Convert to string
|
|
58
70
|
return partial.to_string()
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
|
|
5
8
|
|
|
6
9
|
class MacroSyntaxError(Exception):
|
|
7
10
|
"""Raised when macro template has invalid syntax.
|
|
@@ -12,6 +15,23 @@ class MacroSyntaxError(Exception):
|
|
|
12
15
|
- Nested braces: "{outer_{inner}}"
|
|
13
16
|
"""
|
|
14
17
|
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
message: str,
|
|
21
|
+
failure_reason: MacroParseFailureReason | None = None,
|
|
22
|
+
error_position: int | None = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Initialize MacroSyntaxError with optional structured fields.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
message: Human-readable error message
|
|
28
|
+
failure_reason: Specific reason for the syntax error
|
|
29
|
+
error_position: Character position where error occurred
|
|
30
|
+
"""
|
|
31
|
+
super().__init__(message)
|
|
32
|
+
self.failure_reason = failure_reason
|
|
33
|
+
self.error_position = error_position
|
|
34
|
+
|
|
15
35
|
|
|
16
36
|
class MacroResolutionError(Exception):
|
|
17
37
|
"""Raised when macro cannot be resolved with provided variables.
|
|
@@ -21,3 +41,82 @@ class MacroResolutionError(Exception):
|
|
|
21
41
|
- Environment variable referenced but not found in environment
|
|
22
42
|
- Format specifier cannot be applied to value type (e.g., :03 on string)
|
|
23
43
|
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
message: str,
|
|
48
|
+
failure_reason: MacroResolutionFailureReason | None = None,
|
|
49
|
+
variable_name: str | None = None,
|
|
50
|
+
missing_variables: list[str] | None = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Initialize MacroResolutionError with optional structured fields.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
message: Human-readable error message
|
|
56
|
+
failure_reason: Specific reason for the resolution error
|
|
57
|
+
variable_name: Name of the problematic variable (if applicable)
|
|
58
|
+
missing_variables: List of missing variable names (for MISSING_REQUIRED_VARIABLES)
|
|
59
|
+
"""
|
|
60
|
+
super().__init__(message)
|
|
61
|
+
self.failure_reason = failure_reason
|
|
62
|
+
self.variable_name = variable_name
|
|
63
|
+
self.missing_variables = missing_variables
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class MacroMatchFailureReason(StrEnum):
|
|
67
|
+
"""Reason why path matching failed."""
|
|
68
|
+
|
|
69
|
+
STATIC_TEXT_MISMATCH = "STATIC_TEXT_MISMATCH"
|
|
70
|
+
DELIMITER_NOT_FOUND = "DELIMITER_NOT_FOUND"
|
|
71
|
+
FORMAT_REVERSAL_FAILED = "FORMAT_REVERSAL_FAILED"
|
|
72
|
+
INVALID_MACRO_SYNTAX = "INVALID_MACRO_SYNTAX"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class MacroParseFailureReason(StrEnum):
|
|
76
|
+
"""Reason why macro parsing failed."""
|
|
77
|
+
|
|
78
|
+
UNMATCHED_CLOSING_BRACE = "UNMATCHED_CLOSING_BRACE"
|
|
79
|
+
UNCLOSED_BRACE = "UNCLOSED_BRACE"
|
|
80
|
+
NESTED_BRACES = "NESTED_BRACES"
|
|
81
|
+
EMPTY_VARIABLE = "EMPTY_VARIABLE"
|
|
82
|
+
UNEXPECTED_SEGMENT_TYPE = "UNEXPECTED_SEGMENT_TYPE"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class MacroResolutionFailureReason(StrEnum):
|
|
86
|
+
"""Reason why macro resolution failed."""
|
|
87
|
+
|
|
88
|
+
NUMERIC_PADDING_ON_NON_NUMERIC = "NUMERIC_PADDING_ON_NON_NUMERIC"
|
|
89
|
+
INVALID_INTEGER_PARSE = "INVALID_INTEGER_PARSE"
|
|
90
|
+
DATE_FORMAT_NOT_IMPLEMENTED = "DATE_FORMAT_NOT_IMPLEMENTED"
|
|
91
|
+
MISSING_REQUIRED_VARIABLES = "MISSING_REQUIRED_VARIABLES"
|
|
92
|
+
ENVIRONMENT_VARIABLE_NOT_FOUND = "ENVIRONMENT_VARIABLE_NOT_FOUND"
|
|
93
|
+
UNEXPECTED_SEGMENT_TYPE = "UNEXPECTED_SEGMENT_TYPE"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class MacroMatchFailure:
|
|
98
|
+
"""Details about why a macro match failed."""
|
|
99
|
+
|
|
100
|
+
failure_reason: MacroMatchFailureReason
|
|
101
|
+
expected_pattern: str
|
|
102
|
+
known_variables_used: dict[str, str | int]
|
|
103
|
+
error_details: str
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class MacroParseFailure:
|
|
108
|
+
"""Details about why macro parsing failed."""
|
|
109
|
+
|
|
110
|
+
failure_reason: MacroParseFailureReason
|
|
111
|
+
error_position: int | None
|
|
112
|
+
error_details: str
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class MacroResolutionFailure:
|
|
117
|
+
"""Details about why macro resolution failed."""
|
|
118
|
+
|
|
119
|
+
failure_reason: MacroResolutionFailureReason
|
|
120
|
+
variable_name: str | None
|
|
121
|
+
missing_variables: list[str] | None
|
|
122
|
+
error_details: str
|
|
@@ -6,7 +6,7 @@ import re
|
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
|
|
9
|
-
from griptape_nodes.common.macro_parser.exceptions import MacroResolutionError
|
|
9
|
+
from griptape_nodes.common.macro_parser.exceptions import MacroResolutionError, MacroResolutionFailureReason
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@dataclass
|
|
@@ -89,7 +89,10 @@ class NumericPaddingFormat(FormatSpec):
|
|
|
89
89
|
f"Numeric padding format :{self.width:0{self.width}d} "
|
|
90
90
|
f"cannot be applied to non-numeric value: {value}"
|
|
91
91
|
)
|
|
92
|
-
raise MacroResolutionError(
|
|
92
|
+
raise MacroResolutionError(
|
|
93
|
+
msg,
|
|
94
|
+
failure_reason=MacroResolutionFailureReason.NUMERIC_PADDING_ON_NON_NUMERIC,
|
|
95
|
+
)
|
|
93
96
|
value = int(value)
|
|
94
97
|
return f"{value:0{self.width}d}"
|
|
95
98
|
|
|
@@ -99,7 +102,10 @@ class NumericPaddingFormat(FormatSpec):
|
|
|
99
102
|
return int(value)
|
|
100
103
|
except ValueError as e:
|
|
101
104
|
msg = f"Cannot parse '{value}' as integer"
|
|
102
|
-
raise MacroResolutionError(
|
|
105
|
+
raise MacroResolutionError(
|
|
106
|
+
msg,
|
|
107
|
+
failure_reason=MacroResolutionFailureReason.INVALID_INTEGER_PARSE,
|
|
108
|
+
) from e
|
|
103
109
|
|
|
104
110
|
|
|
105
111
|
@dataclass
|
|
@@ -154,7 +160,10 @@ class DateFormat(FormatSpec):
|
|
|
154
160
|
"""Apply date formatting."""
|
|
155
161
|
# TODO(https://github.com/griptape-ai/griptape-nodes/issues/2717): Implement date formatting
|
|
156
162
|
msg = "DateFormat not yet fully implemented"
|
|
157
|
-
raise MacroResolutionError(
|
|
163
|
+
raise MacroResolutionError(
|
|
164
|
+
msg,
|
|
165
|
+
failure_reason=MacroResolutionFailureReason.DATE_FORMAT_NOT_IMPLEMENTED,
|
|
166
|
+
)
|
|
158
167
|
|
|
159
168
|
def reverse(self, value: str) -> str:
|
|
160
169
|
"""Attempt to parse date string."""
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from griptape_nodes.common.macro_parser.exceptions import MacroSyntaxError
|
|
7
|
+
from griptape_nodes.common.macro_parser.exceptions import MacroParseFailureReason, MacroSyntaxError
|
|
8
8
|
from griptape_nodes.common.macro_parser.segments import (
|
|
9
9
|
ParsedSegment,
|
|
10
10
|
ParsedStaticValue,
|
|
@@ -49,7 +49,10 @@ def extract_unknown_variables(
|
|
|
49
49
|
current_pos = new_pos
|
|
50
50
|
case _:
|
|
51
51
|
msg = f"Unexpected segment type: {type(segment).__name__}"
|
|
52
|
-
raise MacroSyntaxError(
|
|
52
|
+
raise MacroSyntaxError(
|
|
53
|
+
msg,
|
|
54
|
+
failure_reason=MacroParseFailureReason.UNEXPECTED_SEGMENT_TYPE,
|
|
55
|
+
)
|
|
53
56
|
|
|
54
57
|
return current_match
|
|
55
58
|
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import re
|
|
6
6
|
|
|
7
|
-
from griptape_nodes.common.macro_parser.exceptions import MacroSyntaxError
|
|
7
|
+
from griptape_nodes.common.macro_parser.exceptions import MacroParseFailureReason, MacroSyntaxError
|
|
8
8
|
from griptape_nodes.common.macro_parser.formats import (
|
|
9
9
|
FORMAT_REGISTRY,
|
|
10
10
|
DateFormat,
|
|
@@ -47,7 +47,11 @@ def parse_segments(template: str) -> list[ParsedSegment]:
|
|
|
47
47
|
if "}" in static_text:
|
|
48
48
|
closing_pos = current_pos + static_text.index("}")
|
|
49
49
|
msg = f"Unmatched closing brace at position {closing_pos}"
|
|
50
|
-
raise MacroSyntaxError(
|
|
50
|
+
raise MacroSyntaxError(
|
|
51
|
+
msg,
|
|
52
|
+
failure_reason=MacroParseFailureReason.UNMATCHED_CLOSING_BRACE,
|
|
53
|
+
error_position=closing_pos,
|
|
54
|
+
)
|
|
51
55
|
segments.append(ParsedStaticValue(text=static_text))
|
|
52
56
|
break
|
|
53
57
|
|
|
@@ -58,26 +62,42 @@ def parse_segments(template: str) -> list[ParsedSegment]:
|
|
|
58
62
|
if "}" in static_text:
|
|
59
63
|
closing_pos = current_pos + static_text.index("}")
|
|
60
64
|
msg = f"Unmatched closing brace at position {closing_pos}"
|
|
61
|
-
raise MacroSyntaxError(
|
|
65
|
+
raise MacroSyntaxError(
|
|
66
|
+
msg,
|
|
67
|
+
failure_reason=MacroParseFailureReason.UNMATCHED_CLOSING_BRACE,
|
|
68
|
+
error_position=closing_pos,
|
|
69
|
+
)
|
|
62
70
|
segments.append(ParsedStaticValue(text=static_text))
|
|
63
71
|
|
|
64
72
|
# Find matching closing brace
|
|
65
73
|
brace_end = template.find("}", brace_start)
|
|
66
74
|
if brace_end == -1:
|
|
67
75
|
msg = f"Unclosed brace at position {brace_start}"
|
|
68
|
-
raise MacroSyntaxError(
|
|
76
|
+
raise MacroSyntaxError(
|
|
77
|
+
msg,
|
|
78
|
+
failure_reason=MacroParseFailureReason.UNCLOSED_BRACE,
|
|
79
|
+
error_position=brace_start,
|
|
80
|
+
)
|
|
69
81
|
|
|
70
82
|
# Check for nested braces (opening brace before closing brace)
|
|
71
83
|
next_open = template.find("{", brace_start + 1)
|
|
72
84
|
if next_open != -1 and next_open < brace_end:
|
|
73
85
|
msg = f"Nested braces are not allowed at position {next_open}"
|
|
74
|
-
raise MacroSyntaxError(
|
|
86
|
+
raise MacroSyntaxError(
|
|
87
|
+
msg,
|
|
88
|
+
failure_reason=MacroParseFailureReason.NESTED_BRACES,
|
|
89
|
+
error_position=next_open,
|
|
90
|
+
)
|
|
75
91
|
|
|
76
92
|
# Extract and parse the variable content
|
|
77
93
|
variable_content = template[brace_start + 1 : brace_end]
|
|
78
94
|
if not variable_content:
|
|
79
95
|
msg = f"Empty variable at position {brace_start}"
|
|
80
|
-
raise MacroSyntaxError(
|
|
96
|
+
raise MacroSyntaxError(
|
|
97
|
+
msg,
|
|
98
|
+
failure_reason=MacroParseFailureReason.EMPTY_VARIABLE,
|
|
99
|
+
error_position=brace_start,
|
|
100
|
+
)
|
|
81
101
|
|
|
82
102
|
variable = parse_variable(variable_content)
|
|
83
103
|
segments.append(variable)
|
|
@@ -111,6 +131,7 @@ def parse_variable(variable_content: str) -> ParsedVariable:
|
|
|
111
131
|
|
|
112
132
|
# Check for format specifiers (:)
|
|
113
133
|
format_specs: list[FormatSpec] = []
|
|
134
|
+
is_required = True
|
|
114
135
|
if ":" in variable_content:
|
|
115
136
|
parts = variable_content.split(":")
|
|
116
137
|
variable_part = parts[0]
|
|
@@ -120,16 +141,35 @@ def parse_variable(variable_content: str) -> ParsedVariable:
|
|
|
120
141
|
for format_part in format_parts:
|
|
121
142
|
format_spec = parse_format_spec(format_part)
|
|
122
143
|
format_specs.append(format_spec)
|
|
144
|
+
|
|
145
|
+
# Check if last format spec ends with unquoted ?
|
|
146
|
+
if format_parts:
|
|
147
|
+
last_format_part = format_parts[-1]
|
|
148
|
+
|
|
149
|
+
# Check if it's quoted (quoted formats preserve ? as literal)
|
|
150
|
+
is_quoted = last_format_part.startswith("'") and last_format_part.endswith("'")
|
|
151
|
+
|
|
152
|
+
if not is_quoted and last_format_part.endswith("?"):
|
|
153
|
+
# Strip the ? and re-parse the format
|
|
154
|
+
stripped_format = last_format_part[:-1]
|
|
155
|
+
if stripped_format:
|
|
156
|
+
# Re-parse without the ?
|
|
157
|
+
format_specs[-1] = parse_format_spec(stripped_format)
|
|
158
|
+
else:
|
|
159
|
+
# Format was just "?", remove it entirely
|
|
160
|
+
format_specs.pop()
|
|
161
|
+
|
|
162
|
+
# Mark variable as optional
|
|
163
|
+
is_required = False
|
|
123
164
|
else:
|
|
124
165
|
variable_part = variable_content
|
|
125
166
|
|
|
126
|
-
# Check for optional marker (?)
|
|
167
|
+
# Check for optional marker (?) after variable name
|
|
127
168
|
if variable_part.endswith("?"):
|
|
128
169
|
name = variable_part[:-1]
|
|
129
170
|
is_required = False
|
|
130
171
|
else:
|
|
131
172
|
name = variable_part
|
|
132
|
-
is_required = True
|
|
133
173
|
|
|
134
174
|
info = VariableInfo(name=name, is_required=is_required)
|
|
135
175
|
return ParsedVariable(info=info, format_specs=format_specs, default_value=default_value)
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from griptape_nodes.common.macro_parser.exceptions import MacroResolutionError
|
|
8
|
+
from griptape_nodes.common.macro_parser.exceptions import MacroResolutionError, MacroResolutionFailureReason
|
|
9
9
|
from griptape_nodes.common.macro_parser.segments import ParsedSegment, ParsedStaticValue, ParsedVariable
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
@@ -31,8 +31,14 @@ class PartiallyResolvedMacro:
|
|
|
31
31
|
def to_string(self) -> str:
|
|
32
32
|
"""Convert to string (only valid if fully resolved)."""
|
|
33
33
|
if not self.is_fully_resolved():
|
|
34
|
+
unresolved = self.get_unresolved_variables()
|
|
35
|
+
unresolved_names = [var.info.name for var in unresolved]
|
|
34
36
|
msg = "Cannot convert partially resolved macro to string - unresolved variables remain"
|
|
35
|
-
raise MacroResolutionError(
|
|
37
|
+
raise MacroResolutionError(
|
|
38
|
+
msg,
|
|
39
|
+
failure_reason=MacroResolutionFailureReason.MISSING_REQUIRED_VARIABLES,
|
|
40
|
+
missing_variables=unresolved_names,
|
|
41
|
+
)
|
|
36
42
|
# All segments are ParsedStaticValue at this point
|
|
37
43
|
return "".join(seg.text for seg in self.segments if isinstance(seg, ParsedStaticValue))
|
|
38
44
|
|
|
@@ -92,7 +98,10 @@ def partial_resolve(
|
|
|
92
98
|
continue
|
|
93
99
|
case _:
|
|
94
100
|
msg = f"Unexpected segment type: {type(segment).__name__}"
|
|
95
|
-
raise MacroResolutionError(
|
|
101
|
+
raise MacroResolutionError(
|
|
102
|
+
msg,
|
|
103
|
+
failure_reason=MacroResolutionFailureReason.UNEXPECTED_SEGMENT_TYPE,
|
|
104
|
+
)
|
|
96
105
|
|
|
97
106
|
return PartiallyResolvedMacro(
|
|
98
107
|
original_template=template,
|
|
@@ -122,7 +131,12 @@ def resolve_variable(
|
|
|
122
131
|
if variable_name not in variables:
|
|
123
132
|
if variable.info.is_required:
|
|
124
133
|
msg = f"Required variable '{variable_name}' not found in variables dict"
|
|
125
|
-
raise MacroResolutionError(
|
|
134
|
+
raise MacroResolutionError(
|
|
135
|
+
msg,
|
|
136
|
+
failure_reason=MacroResolutionFailureReason.MISSING_REQUIRED_VARIABLES,
|
|
137
|
+
variable_name=variable_name,
|
|
138
|
+
missing_variables=[variable_name],
|
|
139
|
+
)
|
|
126
140
|
# Optional variable not provided, return None to signal it should be skipped
|
|
127
141
|
return None
|
|
128
142
|
|
|
@@ -162,7 +176,11 @@ def resolve_env_var(value: str | int, secrets_manager: SecretsManager) -> str |
|
|
|
162
176
|
|
|
163
177
|
if env_value is None:
|
|
164
178
|
msg = f"Environment variable '{env_var_name}' not found"
|
|
165
|
-
raise MacroResolutionError(
|
|
179
|
+
raise MacroResolutionError(
|
|
180
|
+
msg,
|
|
181
|
+
failure_reason=MacroResolutionFailureReason.ENVIRONMENT_VARIABLE_NOT_FOUND,
|
|
182
|
+
variable_name=env_var_name,
|
|
183
|
+
)
|
|
166
184
|
|
|
167
185
|
# Return resolved env var value
|
|
168
186
|
return env_value
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Project template system for managing project.yml files and situations."""
|
|
2
|
+
|
|
3
|
+
from griptape_nodes.common.project_templates.default_project_template import DEFAULT_PROJECT_TEMPLATE
|
|
4
|
+
from griptape_nodes.common.project_templates.directory import DirectoryDefinition
|
|
5
|
+
from griptape_nodes.common.project_templates.loader import (
|
|
6
|
+
ProjectOverlayData,
|
|
7
|
+
YAMLLineInfo,
|
|
8
|
+
YAMLParseResult,
|
|
9
|
+
load_partial_project_template,
|
|
10
|
+
load_project_template_from_yaml,
|
|
11
|
+
load_yaml_with_line_tracking,
|
|
12
|
+
)
|
|
13
|
+
from griptape_nodes.common.project_templates.project import ProjectTemplate
|
|
14
|
+
from griptape_nodes.common.project_templates.situation import (
|
|
15
|
+
SituationFilePolicy,
|
|
16
|
+
SituationPolicy,
|
|
17
|
+
SituationTemplate,
|
|
18
|
+
)
|
|
19
|
+
from griptape_nodes.common.project_templates.validation import (
|
|
20
|
+
ProjectOverride,
|
|
21
|
+
ProjectOverrideAction,
|
|
22
|
+
ProjectOverrideCategory,
|
|
23
|
+
ProjectValidationInfo,
|
|
24
|
+
ProjectValidationProblem,
|
|
25
|
+
ProjectValidationProblemSeverity,
|
|
26
|
+
ProjectValidationStatus,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"DEFAULT_PROJECT_TEMPLATE",
|
|
31
|
+
"DirectoryDefinition",
|
|
32
|
+
"ProjectOverlayData",
|
|
33
|
+
"ProjectOverride",
|
|
34
|
+
"ProjectOverrideAction",
|
|
35
|
+
"ProjectOverrideCategory",
|
|
36
|
+
"ProjectTemplate",
|
|
37
|
+
"ProjectValidationInfo",
|
|
38
|
+
"ProjectValidationProblem",
|
|
39
|
+
"ProjectValidationProblemSeverity",
|
|
40
|
+
"ProjectValidationStatus",
|
|
41
|
+
"SituationFilePolicy",
|
|
42
|
+
"SituationPolicy",
|
|
43
|
+
"SituationTemplate",
|
|
44
|
+
"YAMLLineInfo",
|
|
45
|
+
"YAMLParseResult",
|
|
46
|
+
"load_partial_project_template",
|
|
47
|
+
"load_project_template_from_yaml",
|
|
48
|
+
"load_yaml_with_line_tracking",
|
|
49
|
+
]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Default project template defined in Python using Pydantic models."""
|
|
2
|
+
|
|
3
|
+
from griptape_nodes.common.project_templates.directory import DirectoryDefinition
|
|
4
|
+
from griptape_nodes.common.project_templates.project import ProjectTemplate
|
|
5
|
+
from griptape_nodes.common.project_templates.situation import (
|
|
6
|
+
SituationFilePolicy,
|
|
7
|
+
SituationPolicy,
|
|
8
|
+
SituationTemplate,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# Default project template matching the values from project_template.yml
|
|
12
|
+
DEFAULT_PROJECT_TEMPLATE = ProjectTemplate(
|
|
13
|
+
project_template_schema_version="0.1.0",
|
|
14
|
+
name="Default Project",
|
|
15
|
+
description="System default configuration",
|
|
16
|
+
directories={
|
|
17
|
+
"inputs": DirectoryDefinition(
|
|
18
|
+
name="inputs",
|
|
19
|
+
path_schema="inputs",
|
|
20
|
+
),
|
|
21
|
+
"outputs": DirectoryDefinition(
|
|
22
|
+
name="outputs",
|
|
23
|
+
path_schema="outputs",
|
|
24
|
+
),
|
|
25
|
+
"temp": DirectoryDefinition(
|
|
26
|
+
name="temp",
|
|
27
|
+
path_schema="temp",
|
|
28
|
+
),
|
|
29
|
+
"previews": DirectoryDefinition(
|
|
30
|
+
name="previews",
|
|
31
|
+
path_schema="previews",
|
|
32
|
+
),
|
|
33
|
+
},
|
|
34
|
+
environment={},
|
|
35
|
+
situations={
|
|
36
|
+
"save_file": SituationTemplate(
|
|
37
|
+
name="save_file",
|
|
38
|
+
situation_template_schema_version="0.1.0",
|
|
39
|
+
description="Generic file save operation",
|
|
40
|
+
macro="{file_name_base}{_index?:03}.{file_extension}",
|
|
41
|
+
policy=SituationPolicy(
|
|
42
|
+
on_collision=SituationFilePolicy.CREATE_NEW,
|
|
43
|
+
create_dirs=True,
|
|
44
|
+
),
|
|
45
|
+
fallback=None,
|
|
46
|
+
),
|
|
47
|
+
"copy_external_file": SituationTemplate(
|
|
48
|
+
name="copy_external_file",
|
|
49
|
+
situation_template_schema_version="0.1.0",
|
|
50
|
+
description="User copies external file to project",
|
|
51
|
+
macro="{inputs}/{node_name?:_}{parameter_name?:_}{file_name_base}{_index?:03}.{file_extension}",
|
|
52
|
+
policy=SituationPolicy(
|
|
53
|
+
on_collision=SituationFilePolicy.CREATE_NEW,
|
|
54
|
+
create_dirs=True,
|
|
55
|
+
),
|
|
56
|
+
fallback="save_file",
|
|
57
|
+
),
|
|
58
|
+
"download_url": SituationTemplate(
|
|
59
|
+
name="download_url",
|
|
60
|
+
situation_template_schema_version="0.1.0",
|
|
61
|
+
description="Download file from URL",
|
|
62
|
+
macro="{inputs}/{sanitized_url}",
|
|
63
|
+
policy=SituationPolicy(
|
|
64
|
+
on_collision=SituationFilePolicy.OVERWRITE,
|
|
65
|
+
create_dirs=True,
|
|
66
|
+
),
|
|
67
|
+
fallback="save_file",
|
|
68
|
+
),
|
|
69
|
+
"save_node_output": SituationTemplate(
|
|
70
|
+
name="save_node_output",
|
|
71
|
+
situation_template_schema_version="0.1.0",
|
|
72
|
+
description="Node generates and saves output",
|
|
73
|
+
macro="{outputs}/{node_name?:_}{file_name_base}{_index?:03}.{file_extension}",
|
|
74
|
+
policy=SituationPolicy(
|
|
75
|
+
on_collision=SituationFilePolicy.CREATE_NEW,
|
|
76
|
+
create_dirs=True,
|
|
77
|
+
),
|
|
78
|
+
fallback="save_file",
|
|
79
|
+
),
|
|
80
|
+
"save_preview": SituationTemplate(
|
|
81
|
+
name="save_preview",
|
|
82
|
+
situation_template_schema_version="0.1.0",
|
|
83
|
+
description="Generate preview/thumbnail",
|
|
84
|
+
macro="{previews}/{original_file_path}",
|
|
85
|
+
policy=SituationPolicy(
|
|
86
|
+
on_collision=SituationFilePolicy.OVERWRITE,
|
|
87
|
+
create_dirs=True,
|
|
88
|
+
),
|
|
89
|
+
fallback="save_file",
|
|
90
|
+
),
|
|
91
|
+
},
|
|
92
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# System Default Project Template
|
|
2
|
+
|
|
3
|
+
This directory contains the system default project template used when no `project.yml` exists in a project.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- `project_template.yml` - Complete default project configuration with all situations
|
|
8
|
+
|
|
9
|
+
## Validation
|
|
10
|
+
|
|
11
|
+
The default template is loaded and validated at module import time. If validation fails, the system will raise a RuntimeError with details about what's broken.
|
|
12
|
+
|
|
13
|
+
This ensures:
|
|
14
|
+
|
|
15
|
+
- Default template is always valid
|
|
16
|
+
- Typos/errors are caught immediately in CI/tests
|
|
17
|
+
- Users can rely on defaults being correct
|
|
18
|
+
|
|
19
|
+
## Modifying Defaults
|
|
20
|
+
|
|
21
|
+
When modifying `project_template.yml`:
|
|
22
|
+
|
|
23
|
+
1. Ensure all syntax is correct (YAML, macro templates, etc.)
|
|
24
|
+
1. Run tests to validate changes
|
|
25
|
+
1. Update version numbers if making breaking changes
|
|
26
|
+
1. Update comments to keep documentation in sync
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
Users can copy this file to their project root as `project.yml` and customize it:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cp defaults/project_template.yml /path/to/project/project.yml
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The system will automatically use the project-specific version instead of these defaults.
|