griptape-nodes 0.61.0__py3-none-any.whl → 0.62.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 (26) hide show
  1. griptape_nodes/common/macro_parser/core.py +4 -4
  2. griptape_nodes/common/macro_parser/exceptions.py +3 -3
  3. griptape_nodes/common/macro_parser/resolution.py +2 -2
  4. griptape_nodes/common/project_templates/default_project_template.py +5 -10
  5. griptape_nodes/common/project_templates/directory.py +5 -5
  6. griptape_nodes/common/project_templates/loader.py +8 -7
  7. griptape_nodes/common/project_templates/project.py +1 -1
  8. griptape_nodes/common/project_templates/situation.py +5 -17
  9. griptape_nodes/common/project_templates/validation.py +3 -3
  10. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +2 -2
  11. griptape_nodes/drivers/storage/local_storage_driver.py +2 -2
  12. griptape_nodes/node_library/workflow_registry.py +1 -1
  13. griptape_nodes/retained_mode/events/project_events.py +208 -93
  14. griptape_nodes/retained_mode/managers/event_manager.py +24 -9
  15. griptape_nodes/retained_mode/managers/library_manager.py +12 -21
  16. griptape_nodes/retained_mode/managers/os_manager.py +54 -6
  17. griptape_nodes/retained_mode/managers/project_manager.py +709 -259
  18. griptape_nodes/retained_mode/managers/static_files_manager.py +1 -5
  19. griptape_nodes/retained_mode/managers/sync_manager.py +4 -1
  20. griptape_nodes/retained_mode/managers/workflow_manager.py +2 -10
  21. griptape_nodes/traits/button.py +2 -1
  22. {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.1.dist-info}/METADATA +1 -1
  23. {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.1.dist-info}/RECORD +25 -26
  24. {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.1.dist-info}/WHEEL +1 -1
  25. griptape_nodes/common/project_templates/defaults/project_template.yml +0 -89
  26. {griptape_nodes-0.61.0.dist-info → griptape_nodes-0.62.1.dist-info}/entry_points.txt +0 -0
@@ -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().resolve()
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).resolve()
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).resolve()
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).resolve()
1107
+ file_path = self.resolve_path_safely(Path(full_path_str))
1060
1108
  else:
1061
- file_path = (self._get_workspace_path() / full_path_str).resolve()
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():