griptape-nodes 0.61.0__py3-none-any.whl → 0.62.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/core.py +4 -4
- griptape_nodes/common/macro_parser/exceptions.py +3 -3
- griptape_nodes/common/macro_parser/resolution.py +2 -2
- griptape_nodes/common/project_templates/default_project_template.py +5 -10
- griptape_nodes/common/project_templates/directory.py +5 -5
- griptape_nodes/common/project_templates/loader.py +8 -7
- griptape_nodes/common/project_templates/project.py +1 -1
- griptape_nodes/common/project_templates/situation.py +5 -17
- griptape_nodes/common/project_templates/validation.py +3 -3
- griptape_nodes/node_library/workflow_registry.py +1 -1
- griptape_nodes/retained_mode/events/project_events.py +208 -93
- griptape_nodes/retained_mode/managers/os_manager.py +54 -6
- griptape_nodes/retained_mode/managers/project_manager.py +709 -259
- griptape_nodes/retained_mode/managers/sync_manager.py +4 -1
- griptape_nodes/retained_mode/managers/workflow_manager.py +2 -10
- griptape_nodes/traits/button.py +2 -1
- {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.0.dist-info}/METADATA +1 -1
- {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.0.dist-info}/RECORD +20 -21
- griptape_nodes/common/project_templates/defaults/project_template.yml +0 -89
- {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.0.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
"""Events for project template management."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
from dataclasses import dataclass
|
|
4
6
|
from enum import StrEnum
|
|
5
|
-
from
|
|
6
|
-
from typing import Any
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
7
8
|
|
|
8
|
-
from griptape_nodes.common.macro_parser import MacroMatchFailure, MacroParseFailure, VariableInfo
|
|
9
|
-
from griptape_nodes.common.project_templates import ProjectTemplate, ProjectValidationInfo
|
|
10
9
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
11
10
|
RequestPayload,
|
|
12
11
|
ResultPayloadFailure,
|
|
@@ -15,6 +14,13 @@ from griptape_nodes.retained_mode.events.base_events import (
|
|
|
15
14
|
)
|
|
16
15
|
from griptape_nodes.retained_mode.events.payload_registry import PayloadRegistry
|
|
17
16
|
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from griptape_nodes.common.macro_parser import MacroMatchFailure, ParsedMacro, VariableInfo
|
|
21
|
+
from griptape_nodes.common.project_templates import ProjectTemplate, ProjectValidationInfo, SituationTemplate
|
|
22
|
+
from griptape_nodes.retained_mode.managers.project_manager import ProjectID, ProjectInfo
|
|
23
|
+
|
|
18
24
|
# Type alias for macro variable dictionaries (used by ParsedMacro)
|
|
19
25
|
MacroVariables = dict[str, str | int]
|
|
20
26
|
|
|
@@ -49,12 +55,12 @@ class LoadProjectTemplateResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuc
|
|
|
49
55
|
"""Project template loaded successfully.
|
|
50
56
|
|
|
51
57
|
Args:
|
|
52
|
-
|
|
58
|
+
project_id: The identifier for the loaded project
|
|
53
59
|
template: The merged ProjectTemplate (system defaults + user customizations)
|
|
54
60
|
validation: Validation info with status and any problems encountered
|
|
55
61
|
"""
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
project_id: ProjectID
|
|
58
64
|
template: ProjectTemplate
|
|
59
65
|
validation: ProjectValidationInfo
|
|
60
66
|
|
|
@@ -65,28 +71,26 @@ class LoadProjectTemplateResultFailure(WorkflowNotAlteredMixin, ResultPayloadFai
|
|
|
65
71
|
"""Project template loading failed.
|
|
66
72
|
|
|
67
73
|
Args:
|
|
68
|
-
project_path: Path to the project.yml that failed to load
|
|
69
74
|
validation: Validation info with error details
|
|
70
75
|
"""
|
|
71
76
|
|
|
72
|
-
project_path: Path
|
|
73
77
|
validation: ProjectValidationInfo
|
|
74
78
|
|
|
75
79
|
|
|
76
80
|
@dataclass
|
|
77
81
|
@PayloadRegistry.register
|
|
78
82
|
class GetProjectTemplateRequest(RequestPayload):
|
|
79
|
-
"""Get cached project template for a
|
|
83
|
+
"""Get cached project template for a project ID.
|
|
80
84
|
|
|
81
85
|
Use when: Querying current project configuration, checking validation status.
|
|
82
86
|
|
|
83
87
|
Args:
|
|
84
|
-
|
|
88
|
+
project_id: Identifier of the project
|
|
85
89
|
|
|
86
90
|
Results: GetProjectTemplateResultSuccess | GetProjectTemplateResultFailure
|
|
87
91
|
"""
|
|
88
92
|
|
|
89
|
-
|
|
93
|
+
project_id: ProjectID
|
|
90
94
|
|
|
91
95
|
|
|
92
96
|
@dataclass
|
|
@@ -109,40 +113,80 @@ class GetProjectTemplateResultFailure(WorkflowNotAlteredMixin, ResultPayloadFail
|
|
|
109
113
|
"""Project template retrieval failed (not loaded yet)."""
|
|
110
114
|
|
|
111
115
|
|
|
116
|
+
@dataclass
|
|
117
|
+
class ProjectTemplateInfo:
|
|
118
|
+
"""Information about a loaded or failed project template."""
|
|
119
|
+
|
|
120
|
+
project_id: ProjectID
|
|
121
|
+
validation: ProjectValidationInfo
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
@PayloadRegistry.register
|
|
126
|
+
class ListProjectTemplatesRequest(RequestPayload):
|
|
127
|
+
"""List all project templates that have been loaded or attempted to load.
|
|
128
|
+
|
|
129
|
+
Use when: Displaying available projects, checking which projects are loaded.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
include_system_builtins: Whether to include system builtin templates like SYSTEM_DEFAULTS_KEY
|
|
133
|
+
|
|
134
|
+
Results: ListProjectTemplatesResultSuccess
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
include_system_builtins: bool = False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass
|
|
141
|
+
@PayloadRegistry.register
|
|
142
|
+
class ListProjectTemplatesResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
143
|
+
"""List of all project templates retrieved.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
successfully_loaded: List of templates that loaded successfully
|
|
147
|
+
failed_to_load: List of templates that failed to load with validation errors
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
successfully_loaded: list[ProjectTemplateInfo]
|
|
151
|
+
failed_to_load: list[ProjectTemplateInfo]
|
|
152
|
+
|
|
153
|
+
|
|
112
154
|
@dataclass
|
|
113
155
|
@PayloadRegistry.register
|
|
114
|
-
class
|
|
115
|
-
"""Get the
|
|
156
|
+
class GetSituationRequest(RequestPayload):
|
|
157
|
+
"""Get the full situation template for a specific situation.
|
|
158
|
+
|
|
159
|
+
Returns the complete SituationTemplate including macro and policy.
|
|
116
160
|
|
|
117
|
-
Use when: Need
|
|
161
|
+
Use when: Need situation macro and/or policy for file operations.
|
|
162
|
+
Uses the current project for context.
|
|
118
163
|
|
|
119
164
|
Args:
|
|
120
|
-
project_path: Path to the project.yml to use
|
|
121
165
|
situation_name: Name of the situation template (e.g., "save_node_output")
|
|
122
166
|
|
|
123
|
-
Results:
|
|
167
|
+
Results: GetSituationResultSuccess | GetSituationResultFailure
|
|
124
168
|
"""
|
|
125
169
|
|
|
126
|
-
project_path: Path
|
|
127
170
|
situation_name: str
|
|
128
171
|
|
|
129
172
|
|
|
130
173
|
@dataclass
|
|
131
174
|
@PayloadRegistry.register
|
|
132
|
-
class
|
|
133
|
-
"""Situation
|
|
175
|
+
class GetSituationResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
176
|
+
"""Situation template retrieved successfully.
|
|
134
177
|
|
|
135
178
|
Args:
|
|
136
|
-
|
|
179
|
+
situation: The complete situation template including macro and policy.
|
|
180
|
+
Access via situation.macro, situation.policy.create_dirs, etc.
|
|
137
181
|
"""
|
|
138
182
|
|
|
139
|
-
|
|
183
|
+
situation: SituationTemplate
|
|
140
184
|
|
|
141
185
|
|
|
142
186
|
@dataclass
|
|
143
187
|
@PayloadRegistry.register
|
|
144
|
-
class
|
|
145
|
-
"""Situation
|
|
188
|
+
class GetSituationResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
189
|
+
"""Situation template retrieval failed (situation not found or template not loaded)."""
|
|
146
190
|
|
|
147
191
|
|
|
148
192
|
@dataclass
|
|
@@ -152,16 +196,17 @@ class GetPathForMacroRequest(RequestPayload):
|
|
|
152
196
|
|
|
153
197
|
Use when: Resolving paths, saving files. Works with any macro string, not tied to situations.
|
|
154
198
|
|
|
199
|
+
Uses the current project for context. Caller must parse the macro string
|
|
200
|
+
into a ParsedMacro before creating this request.
|
|
201
|
+
|
|
155
202
|
Args:
|
|
156
|
-
|
|
157
|
-
macro_schema: The macro template string to resolve (e.g., "{inputs}/{file_name}.{file_ext}")
|
|
203
|
+
parsed_macro: The parsed macro to resolve
|
|
158
204
|
variables: Variable values for macro substitution (e.g., {"file_name": "output", "file_ext": "png"})
|
|
159
205
|
|
|
160
206
|
Results: GetPathForMacroResultSuccess | GetPathForMacroResultFailure
|
|
161
207
|
"""
|
|
162
208
|
|
|
163
|
-
|
|
164
|
-
macro_schema: str
|
|
209
|
+
parsed_macro: ParsedMacro
|
|
165
210
|
variables: MacroVariables
|
|
166
211
|
|
|
167
212
|
|
|
@@ -171,10 +216,12 @@ class GetPathForMacroResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess
|
|
|
171
216
|
"""Path resolved successfully from macro.
|
|
172
217
|
|
|
173
218
|
Args:
|
|
174
|
-
resolved_path: The
|
|
219
|
+
resolved_path: The relative project path after macro substitution (e.g., "outputs/file.png")
|
|
220
|
+
absolute_path: The absolute filesystem path (e.g., "/workspace/outputs/file.png")
|
|
175
221
|
"""
|
|
176
222
|
|
|
177
223
|
resolved_path: Path
|
|
224
|
+
absolute_path: Path
|
|
178
225
|
|
|
179
226
|
|
|
180
227
|
@dataclass
|
|
@@ -186,29 +233,27 @@ class GetPathForMacroResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure
|
|
|
186
233
|
failure_reason: Specific reason for failure
|
|
187
234
|
missing_variables: List of required variable names that were not provided (for MISSING_REQUIRED_VARIABLES)
|
|
188
235
|
conflicting_variables: List of variables that conflict with directory names (for DIRECTORY_OVERRIDE_ATTEMPTED)
|
|
189
|
-
error_details: Additional error details from ParsedMacro (for MACRO_RESOLUTION_ERROR)
|
|
190
236
|
"""
|
|
191
237
|
|
|
192
238
|
failure_reason: PathResolutionFailureReason
|
|
193
|
-
missing_variables:
|
|
194
|
-
conflicting_variables:
|
|
195
|
-
error_details: str | None = None
|
|
239
|
+
missing_variables: set[str] | None = None
|
|
240
|
+
conflicting_variables: set[str] | None = None
|
|
196
241
|
|
|
197
242
|
|
|
198
243
|
@dataclass
|
|
199
244
|
@PayloadRegistry.register
|
|
200
245
|
class SetCurrentProjectRequest(RequestPayload):
|
|
201
|
-
"""Set which project
|
|
246
|
+
"""Set which project user has currently selected.
|
|
202
247
|
|
|
203
248
|
Use when: User switches between projects, opens a new workspace.
|
|
204
249
|
|
|
205
250
|
Args:
|
|
206
|
-
|
|
251
|
+
project_id: Identifier of the project to set as current (None to clear)
|
|
207
252
|
|
|
208
253
|
Results: SetCurrentProjectResultSuccess
|
|
209
254
|
"""
|
|
210
255
|
|
|
211
|
-
|
|
256
|
+
project_id: ProjectID | None
|
|
212
257
|
|
|
213
258
|
|
|
214
259
|
@dataclass
|
|
@@ -224,7 +269,7 @@ class GetCurrentProjectRequest(RequestPayload):
|
|
|
224
269
|
|
|
225
270
|
Use when: Need to know which project user is working with.
|
|
226
271
|
|
|
227
|
-
Results: GetCurrentProjectResultSuccess
|
|
272
|
+
Results: GetCurrentProjectResultSuccess | GetCurrentProjectResultFailure
|
|
228
273
|
"""
|
|
229
274
|
|
|
230
275
|
|
|
@@ -234,10 +279,16 @@ class GetCurrentProjectResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSucce
|
|
|
234
279
|
"""Current project retrieved.
|
|
235
280
|
|
|
236
281
|
Args:
|
|
237
|
-
|
|
282
|
+
project_info: Complete information about the current project
|
|
238
283
|
"""
|
|
239
284
|
|
|
240
|
-
|
|
285
|
+
project_info: ProjectInfo
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@dataclass
|
|
289
|
+
@PayloadRegistry.register
|
|
290
|
+
class GetCurrentProjectResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
291
|
+
"""No current project is set."""
|
|
241
292
|
|
|
242
293
|
|
|
243
294
|
@dataclass
|
|
@@ -261,13 +312,7 @@ class SaveProjectTemplateRequest(RequestPayload):
|
|
|
261
312
|
@dataclass
|
|
262
313
|
@PayloadRegistry.register
|
|
263
314
|
class SaveProjectTemplateResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
264
|
-
"""Project template saved successfully.
|
|
265
|
-
|
|
266
|
-
Args:
|
|
267
|
-
project_path: Path where project.yml was saved
|
|
268
|
-
"""
|
|
269
|
-
|
|
270
|
-
project_path: Path
|
|
315
|
+
"""Project template saved successfully."""
|
|
271
316
|
|
|
272
317
|
|
|
273
318
|
@dataclass
|
|
@@ -281,122 +326,192 @@ class SaveProjectTemplateResultFailure(WorkflowNotAlteredMixin, ResultPayloadFai
|
|
|
281
326
|
- Disk full
|
|
282
327
|
"""
|
|
283
328
|
|
|
284
|
-
project_path: Path
|
|
285
|
-
|
|
286
329
|
|
|
287
330
|
@dataclass
|
|
288
331
|
@PayloadRegistry.register
|
|
289
|
-
class
|
|
290
|
-
"""
|
|
332
|
+
class AttemptMatchPathAgainstMacroRequest(RequestPayload):
|
|
333
|
+
"""Attempt to match a path against a macro schema and extract variables.
|
|
291
334
|
|
|
292
335
|
Use when: Validating paths, extracting info from file paths,
|
|
293
336
|
identifying which schema produced a file.
|
|
294
337
|
|
|
338
|
+
Uses the current project for context. Caller must parse the macro string
|
|
339
|
+
into a ParsedMacro before creating this request.
|
|
340
|
+
|
|
341
|
+
Pattern non-matches are returned as success with match_failure populated.
|
|
342
|
+
Only true system errors (missing SecretsManager, etc.) return failure.
|
|
343
|
+
|
|
295
344
|
Args:
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
file_path: Path to test
|
|
345
|
+
parsed_macro: Parsed macro template to match against
|
|
346
|
+
file_path: Path string to test
|
|
299
347
|
known_variables: Variables we already know
|
|
300
348
|
|
|
301
|
-
Results:
|
|
349
|
+
Results: AttemptMatchPathAgainstMacroResultSuccess | AttemptMatchPathAgainstMacroResultFailure
|
|
302
350
|
"""
|
|
303
351
|
|
|
304
|
-
|
|
305
|
-
macro_schema: str
|
|
352
|
+
parsed_macro: ParsedMacro
|
|
306
353
|
file_path: str
|
|
307
354
|
known_variables: MacroVariables
|
|
308
355
|
|
|
309
356
|
|
|
310
357
|
@dataclass
|
|
311
358
|
@PayloadRegistry.register
|
|
312
|
-
class
|
|
313
|
-
"""
|
|
359
|
+
class AttemptMatchPathAgainstMacroResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
360
|
+
"""Attempt completed (match succeeded or pattern didn't match).
|
|
314
361
|
|
|
315
|
-
|
|
362
|
+
Check match_failure to determine outcome:
|
|
363
|
+
- match_failure is None: Pattern matched, extracted_variables contains results
|
|
364
|
+
- match_failure is not None: Pattern didn't match (normal case, not an error)
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
extracted_variables: MacroVariables | None
|
|
368
|
+
match_failure: MacroMatchFailure | None
|
|
316
369
|
|
|
317
370
|
|
|
318
371
|
@dataclass
|
|
319
372
|
@PayloadRegistry.register
|
|
320
|
-
class
|
|
321
|
-
"""
|
|
322
|
-
|
|
323
|
-
match_failure: MacroMatchFailure
|
|
373
|
+
class AttemptMatchPathAgainstMacroResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
374
|
+
"""System error occurred (missing SecretsManager, invalid configuration, etc.)."""
|
|
324
375
|
|
|
325
376
|
|
|
326
377
|
@dataclass
|
|
327
378
|
@PayloadRegistry.register
|
|
328
|
-
class
|
|
329
|
-
"""
|
|
379
|
+
class GetStateForMacroRequest(RequestPayload):
|
|
380
|
+
"""Analyze a macro and return comprehensive state information.
|
|
330
381
|
|
|
331
|
-
Use when: Building UI forms,
|
|
332
|
-
|
|
382
|
+
Use when: Building UI forms, real-time validation, checking if resolution
|
|
383
|
+
would succeed before actually resolving.
|
|
384
|
+
|
|
385
|
+
Uses the current project for context. Caller must parse the macro string
|
|
386
|
+
into a ParsedMacro before creating this request.
|
|
333
387
|
|
|
334
388
|
Args:
|
|
335
|
-
|
|
389
|
+
parsed_macro: The parsed macro to analyze
|
|
390
|
+
variables: Currently provided variable values
|
|
336
391
|
|
|
337
|
-
Results:
|
|
392
|
+
Results: GetStateForMacroResultSuccess | GetStateForMacroResultFailure
|
|
338
393
|
"""
|
|
339
394
|
|
|
340
|
-
|
|
395
|
+
parsed_macro: ParsedMacro
|
|
396
|
+
variables: MacroVariables
|
|
341
397
|
|
|
342
398
|
|
|
343
399
|
@dataclass
|
|
344
400
|
@PayloadRegistry.register
|
|
345
|
-
class
|
|
346
|
-
"""
|
|
401
|
+
class GetStateForMacroResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
402
|
+
"""Macro state analysis completed successfully.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
all_variables: All variables found in the macro
|
|
406
|
+
satisfied_variables: Variables that have values (from user, directories, or builtins)
|
|
407
|
+
missing_required_variables: Required variables that are missing values
|
|
408
|
+
conflicting_variables: Variables that conflict (e.g., user overriding builtin with different value)
|
|
409
|
+
can_resolve: Whether the macro can be fully resolved (no missing required vars, no conflicts)
|
|
410
|
+
"""
|
|
347
411
|
|
|
348
|
-
|
|
412
|
+
all_variables: set[VariableInfo]
|
|
413
|
+
satisfied_variables: set[str]
|
|
414
|
+
missing_required_variables: set[str]
|
|
415
|
+
conflicting_variables: set[str]
|
|
416
|
+
can_resolve: bool
|
|
349
417
|
|
|
350
418
|
|
|
351
419
|
@dataclass
|
|
352
420
|
@PayloadRegistry.register
|
|
353
|
-
class
|
|
354
|
-
"""
|
|
421
|
+
class GetStateForMacroResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
422
|
+
"""Macro state analysis failed.
|
|
355
423
|
|
|
356
|
-
|
|
424
|
+
Failure occurs when:
|
|
425
|
+
- No current project is set
|
|
426
|
+
- Current project template is not loaded
|
|
427
|
+
- A builtin variable cannot be resolved (RuntimeError or NotImplementedError)
|
|
428
|
+
"""
|
|
357
429
|
|
|
358
430
|
|
|
359
431
|
@dataclass
|
|
360
432
|
@PayloadRegistry.register
|
|
361
|
-
class
|
|
362
|
-
"""
|
|
433
|
+
class AttemptMapAbsolutePathToProjectRequest(RequestPayload):
|
|
434
|
+
"""Find out if an absolute path exists anywhere within a Project directory.
|
|
435
|
+
|
|
436
|
+
Use when: User selects or types an absolute path via FilePicker and you need to know:
|
|
437
|
+
1. Is this path inside any project directory?
|
|
438
|
+
2. If yes, what's the macro form (e.g., {outputs}/file.png)?
|
|
363
439
|
|
|
364
|
-
|
|
365
|
-
|
|
440
|
+
This enables automatic conversion of absolute paths to portable macro form for workflow portability.
|
|
441
|
+
|
|
442
|
+
Uses longest prefix matching to find the most specific directory match.
|
|
443
|
+
Returns Success with mapped_path if inside project, or Success with None if outside.
|
|
444
|
+
Returns Failure if operation cannot be performed (no project loaded, secrets unavailable).
|
|
366
445
|
|
|
367
446
|
Args:
|
|
368
|
-
|
|
447
|
+
absolute_path: The absolute filesystem path to check
|
|
448
|
+
|
|
449
|
+
Results: AttemptMapAbsolutePathToProjectResultSuccess | AttemptMapAbsolutePathToProjectResultFailure
|
|
369
450
|
|
|
370
|
-
|
|
451
|
+
Examples:
|
|
452
|
+
Path inside project directory:
|
|
453
|
+
Request: absolute_path = /Users/james/project/outputs/renders/image.png
|
|
454
|
+
Result: mapped_path = "{outputs}/renders/image.png"
|
|
455
|
+
|
|
456
|
+
Path outside project:
|
|
457
|
+
Request: absolute_path = /Users/james/Downloads/image.png
|
|
458
|
+
Result: mapped_path = None
|
|
459
|
+
|
|
460
|
+
Path at directory root:
|
|
461
|
+
Request: absolute_path = /Users/james/project/outputs
|
|
462
|
+
Result: mapped_path = "{outputs}"
|
|
371
463
|
"""
|
|
372
464
|
|
|
373
|
-
|
|
465
|
+
absolute_path: Path
|
|
374
466
|
|
|
375
467
|
|
|
376
468
|
@dataclass
|
|
377
469
|
@PayloadRegistry.register
|
|
378
|
-
class
|
|
379
|
-
"""
|
|
470
|
+
class AttemptMapAbsolutePathToProjectResultSuccess(WorkflowNotAlteredMixin, ResultPayloadSuccess):
|
|
471
|
+
"""Path check completed successfully.
|
|
472
|
+
|
|
473
|
+
Success means the check was performed (not necessarily that a match was found).
|
|
474
|
+
- mapped_path is NOT None: Path is inside a project directory (macro form returned)
|
|
475
|
+
- mapped_path is None: Path is outside all project directories (valid answer)
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
mapped_path: The macro form if path is inside a project directory (e.g., "{outputs}/file.png"),
|
|
479
|
+
or None if path is outside all project directories
|
|
480
|
+
|
|
481
|
+
Examples:
|
|
482
|
+
Path inside project:
|
|
483
|
+
mapped_path = "{outputs}/renders/image.png"
|
|
484
|
+
result_details = "Successfully mapped absolute path to '{outputs}/renders/image.png'"
|
|
380
485
|
|
|
381
|
-
|
|
382
|
-
|
|
486
|
+
Path outside project:
|
|
487
|
+
mapped_path = None
|
|
488
|
+
result_details = "Attempted to map absolute path '/Users/james/Downloads/image.png'. Path is outside all project directories"
|
|
489
|
+
"""
|
|
490
|
+
|
|
491
|
+
mapped_path: str | None
|
|
383
492
|
|
|
384
493
|
|
|
385
494
|
@dataclass
|
|
386
495
|
@PayloadRegistry.register
|
|
387
|
-
class
|
|
388
|
-
"""
|
|
496
|
+
class AttemptMapAbsolutePathToProjectResultFailure(WorkflowNotAlteredMixin, ResultPayloadFailure):
|
|
497
|
+
"""Path mapping attempt failed.
|
|
389
498
|
|
|
390
|
-
|
|
391
|
-
|
|
499
|
+
Returned when the operation cannot be performed (no current project, secrets manager unavailable).
|
|
500
|
+
This is distinct from "path is outside project" which returns Success with None values.
|
|
501
|
+
|
|
502
|
+
Examples:
|
|
503
|
+
No current project:
|
|
504
|
+
result_details = "Attempted to map absolute path. Failed because no current project is set"
|
|
505
|
+
|
|
506
|
+
Secrets manager unavailable:
|
|
507
|
+
result_details = "Attempted to map absolute path. Failed because SecretsManager not available"
|
|
508
|
+
"""
|
|
392
509
|
|
|
393
510
|
|
|
394
511
|
@dataclass
|
|
395
512
|
@PayloadRegistry.register
|
|
396
513
|
class GetAllSituationsForProjectRequest(RequestPayload):
|
|
397
|
-
"""Get all situation names and schemas from
|
|
398
|
-
|
|
399
|
-
project_path: Path
|
|
514
|
+
"""Get all situation names and schemas from current project template."""
|
|
400
515
|
|
|
401
516
|
|
|
402
517
|
@dataclass
|
|
@@ -155,7 +155,49 @@ class OSManager:
|
|
|
155
155
|
"""
|
|
156
156
|
# Expand environment variables first, then tilde
|
|
157
157
|
expanded_vars = os.path.expandvars(path_str)
|
|
158
|
-
return Path(expanded_vars).expanduser()
|
|
158
|
+
return self.resolve_path_safely(Path(expanded_vars).expanduser())
|
|
159
|
+
|
|
160
|
+
def resolve_path_safely(self, path: Path) -> Path:
|
|
161
|
+
"""Resolve a path consistently across platforms.
|
|
162
|
+
|
|
163
|
+
Unlike Path.resolve() which behaves differently on Windows vs Unix
|
|
164
|
+
for non-existent paths, this method provides consistent behavior:
|
|
165
|
+
- Converts relative paths to absolute (using CWD as base)
|
|
166
|
+
- Normalizes path separators and removes . and ..
|
|
167
|
+
- Does NOT resolve symlinks if path doesn't exist
|
|
168
|
+
- Does NOT change path based on CWD for absolute paths
|
|
169
|
+
|
|
170
|
+
Use this instead of .resolve() when:
|
|
171
|
+
- Path might not exist (file creation, validation, user input)
|
|
172
|
+
- You need consistent cross-platform comparison
|
|
173
|
+
- You're about to create the file/directory
|
|
174
|
+
|
|
175
|
+
Use .resolve() when:
|
|
176
|
+
- Path definitely exists and you need symlink resolution
|
|
177
|
+
- You're checking actual file locations
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
path: Path to resolve (relative or absolute, existing or not)
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Absolute, normalized Path object
|
|
184
|
+
|
|
185
|
+
Examples:
|
|
186
|
+
# Relative path
|
|
187
|
+
resolve_path_safely(Path("relative/file.txt"))
|
|
188
|
+
→ Path("/current/dir/relative/file.txt")
|
|
189
|
+
|
|
190
|
+
# Absolute non-existent path (Windows safe)
|
|
191
|
+
resolve_path_safely(Path("/abs/nonexistent/path"))
|
|
192
|
+
→ Path("/abs/nonexistent/path") # NOT resolved relative to CWD
|
|
193
|
+
"""
|
|
194
|
+
# Convert to absolute if relative
|
|
195
|
+
if not path.is_absolute():
|
|
196
|
+
path = Path.cwd() / path
|
|
197
|
+
|
|
198
|
+
# Normalize (remove . and .., collapse slashes) without resolving symlinks
|
|
199
|
+
# This works consistently even for non-existent paths on Windows
|
|
200
|
+
return Path(os.path.normpath(path))
|
|
159
201
|
|
|
160
202
|
def _resolve_file_path(self, path_str: str, *, workspace_only: bool = False) -> Path:
|
|
161
203
|
"""Resolve a file path, handling absolute, relative, and tilde paths.
|
|
@@ -172,7 +214,7 @@ class OSManager:
|
|
|
172
214
|
# Expand tilde and environment variables for absolute paths or paths starting with ~
|
|
173
215
|
return self._expand_path(path_str)
|
|
174
216
|
# Both workspace and system-wide modes resolve relative to current directory
|
|
175
|
-
return (self._get_workspace_path() / path_str)
|
|
217
|
+
return self.resolve_path_safely(self._get_workspace_path() / path_str)
|
|
176
218
|
except (ValueError, RuntimeError):
|
|
177
219
|
if workspace_only:
|
|
178
220
|
msg = f"Path '{path_str}' not found, using workspace directory: {self._get_workspace_path()}"
|
|
@@ -193,8 +235,11 @@ class OSManager:
|
|
|
193
235
|
workspace = GriptapeNodes.ConfigManager().workspace_path
|
|
194
236
|
|
|
195
237
|
# Ensure both paths are resolved for comparison
|
|
238
|
+
# Both path and workspace should use .resolve() to follow symlinks consistently
|
|
239
|
+
# (e.g., /var -> /private/var on macOS). Even if path doesn't exist yet,
|
|
240
|
+
# .resolve() will resolve parent directories and symlinks in the path.
|
|
196
241
|
path = path.resolve()
|
|
197
|
-
workspace = workspace.resolve()
|
|
242
|
+
workspace = workspace.resolve() # Workspace should always exist
|
|
198
243
|
|
|
199
244
|
msg = f"Validating path: {path} against workspace: {workspace}"
|
|
200
245
|
logger.debug(msg)
|
|
@@ -217,6 +262,9 @@ class OSManager:
|
|
|
217
262
|
need the \\?\ prefix to work correctly. This method transparently adds
|
|
218
263
|
the prefix when needed on Windows.
|
|
219
264
|
|
|
265
|
+
Note: This method assumes the path exists or will exist. For non-existent
|
|
266
|
+
paths that need cross-platform normalization, use resolve_path_safely() first.
|
|
267
|
+
|
|
220
268
|
Args:
|
|
221
269
|
path: Path object to convert to string
|
|
222
270
|
|
|
@@ -443,7 +491,7 @@ class OSManager:
|
|
|
443
491
|
directory = self._expand_path(request.directory_path)
|
|
444
492
|
else:
|
|
445
493
|
# Both workspace and system-wide modes resolve relative to current directory
|
|
446
|
-
directory = (self._get_workspace_path() / request.directory_path)
|
|
494
|
+
directory = self.resolve_path_safely(self._get_workspace_path() / request.directory_path)
|
|
447
495
|
|
|
448
496
|
# Check if directory exists
|
|
449
497
|
if not directory.exists():
|
|
@@ -1056,9 +1104,9 @@ class OSManager:
|
|
|
1056
1104
|
|
|
1057
1105
|
# Resolve path - if absolute, use as-is; if relative, align to workspace
|
|
1058
1106
|
if is_absolute:
|
|
1059
|
-
file_path = Path(full_path_str)
|
|
1107
|
+
file_path = self.resolve_path_safely(Path(full_path_str))
|
|
1060
1108
|
else:
|
|
1061
|
-
file_path = (self._get_workspace_path() / full_path_str)
|
|
1109
|
+
file_path = self.resolve_path_safely(self._get_workspace_path() / full_path_str)
|
|
1062
1110
|
|
|
1063
1111
|
# Check if it already exists - warn but treat as success
|
|
1064
1112
|
if file_path.exists():
|