griptape-nodes 0.65.6__py3-none-any.whl → 0.66.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 (60) hide show
  1. griptape_nodes/common/node_executor.py +352 -27
  2. griptape_nodes/drivers/storage/base_storage_driver.py +12 -3
  3. griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +18 -2
  4. griptape_nodes/drivers/storage/local_storage_driver.py +42 -5
  5. griptape_nodes/exe_types/base_iterative_nodes.py +0 -1
  6. griptape_nodes/exe_types/connections.py +42 -0
  7. griptape_nodes/exe_types/core_types.py +2 -2
  8. griptape_nodes/exe_types/node_groups/__init__.py +2 -1
  9. griptape_nodes/exe_types/node_groups/base_iterative_node_group.py +177 -0
  10. griptape_nodes/exe_types/node_groups/base_node_group.py +1 -0
  11. griptape_nodes/exe_types/node_groups/subflow_node_group.py +35 -2
  12. griptape_nodes/exe_types/param_types/parameter_audio.py +1 -1
  13. griptape_nodes/exe_types/param_types/parameter_bool.py +1 -1
  14. griptape_nodes/exe_types/param_types/parameter_button.py +1 -1
  15. griptape_nodes/exe_types/param_types/parameter_float.py +1 -1
  16. griptape_nodes/exe_types/param_types/parameter_image.py +1 -1
  17. griptape_nodes/exe_types/param_types/parameter_int.py +1 -1
  18. griptape_nodes/exe_types/param_types/parameter_number.py +1 -1
  19. griptape_nodes/exe_types/param_types/parameter_string.py +1 -1
  20. griptape_nodes/exe_types/param_types/parameter_three_d.py +1 -1
  21. griptape_nodes/exe_types/param_types/parameter_video.py +1 -1
  22. griptape_nodes/machines/control_flow.py +5 -4
  23. griptape_nodes/machines/dag_builder.py +121 -55
  24. griptape_nodes/machines/fsm.py +10 -0
  25. griptape_nodes/machines/parallel_resolution.py +39 -38
  26. griptape_nodes/machines/sequential_resolution.py +29 -3
  27. griptape_nodes/node_library/library_registry.py +41 -2
  28. griptape_nodes/retained_mode/events/library_events.py +147 -8
  29. griptape_nodes/retained_mode/events/os_events.py +12 -4
  30. griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +2 -0
  31. griptape_nodes/retained_mode/managers/fitness_problems/libraries/incompatible_requirements_problem.py +34 -0
  32. griptape_nodes/retained_mode/managers/flow_manager.py +133 -20
  33. griptape_nodes/retained_mode/managers/library_manager.py +1324 -564
  34. griptape_nodes/retained_mode/managers/node_manager.py +9 -3
  35. griptape_nodes/retained_mode/managers/os_manager.py +429 -65
  36. griptape_nodes/retained_mode/managers/resource_types/compute_resource.py +82 -0
  37. griptape_nodes/retained_mode/managers/resource_types/os_resource.py +17 -0
  38. griptape_nodes/retained_mode/managers/static_files_manager.py +21 -8
  39. griptape_nodes/retained_mode/managers/version_compatibility_manager.py +3 -3
  40. griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +5 -5
  41. griptape_nodes/version_compatibility/versions/v0_65_4/__init__.py +5 -0
  42. griptape_nodes/version_compatibility/versions/v0_65_4/run_in_parallel_to_run_in_order.py +79 -0
  43. griptape_nodes/version_compatibility/versions/v0_65_5/__init__.py +5 -0
  44. griptape_nodes/version_compatibility/versions/v0_65_5/flux_2_removed_parameters.py +85 -0
  45. {griptape_nodes-0.65.6.dist-info → griptape_nodes-0.66.1.dist-info}/METADATA +1 -1
  46. {griptape_nodes-0.65.6.dist-info → griptape_nodes-0.66.1.dist-info}/RECORD +48 -53
  47. griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -45
  48. griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -191
  49. griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -346
  50. griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -439
  51. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -17
  52. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -82
  53. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -116
  54. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -367
  55. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -104
  56. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -155
  57. griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -18
  58. griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -12
  59. {griptape_nodes-0.65.6.dist-info → griptape_nodes-0.66.1.dist-info}/WHEEL +0 -0
  60. {griptape_nodes-0.65.6.dist-info → griptape_nodes-0.66.1.dist-info}/entry_points.txt +0 -0
@@ -1,45 +0,0 @@
1
- """Library lifecycle management subsystem."""
2
-
3
- from griptape_nodes.retained_mode.managers.library_lifecycle.data_models import (
4
- EvaluationResult,
5
- InspectionResult,
6
- InstallationResult,
7
- LibraryByType,
8
- LibraryEntry,
9
- LibraryLoadedResult,
10
- LibraryPreferences,
11
- LifecycleIssue,
12
- )
13
- from griptape_nodes.retained_mode.managers.library_lifecycle.library_directory import LibraryDirectory
14
- from griptape_nodes.retained_mode.managers.library_lifecycle.library_fsm import (
15
- LibraryLifecycleContext,
16
- LibraryLifecycleFSM,
17
- )
18
- from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance import (
19
- LibraryProvenance,
20
- LibraryProvenanceGitHub,
21
- LibraryProvenanceLocalFile,
22
- LibraryProvenancePackage,
23
- LibraryProvenanceSandbox,
24
- )
25
- from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
26
-
27
- __all__ = [
28
- "EvaluationResult",
29
- "InspectionResult",
30
- "InstallationResult",
31
- "LibraryByType",
32
- "LibraryDirectory",
33
- "LibraryEntry",
34
- "LibraryLifecycleContext",
35
- "LibraryLifecycleFSM",
36
- "LibraryLoadedResult",
37
- "LibraryPreferences",
38
- "LibraryProvenance",
39
- "LibraryProvenanceGitHub",
40
- "LibraryProvenanceLocalFile",
41
- "LibraryProvenancePackage",
42
- "LibraryProvenanceSandbox",
43
- "LibraryStatus",
44
- "LifecycleIssue",
45
- ]
@@ -1,191 +0,0 @@
1
- """Data models for library lifecycle management."""
2
-
3
- from __future__ import annotations
4
-
5
- from dataclasses import dataclass, field
6
- from typing import TYPE_CHECKING, NamedTuple
7
-
8
- from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
9
-
10
- if TYPE_CHECKING:
11
- from griptape_nodes.node_library.library_registry import LibrarySchema
12
- from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance import LibraryProvenance
13
-
14
-
15
- class LibraryByType(NamedTuple):
16
- """A library entry with its name, organized by provenance type."""
17
-
18
- name: str
19
- entry: LibraryEntry
20
-
21
-
22
- class LibraryCandidate(NamedTuple):
23
- """A library candidate with its provenance and entry."""
24
-
25
- provenance: LibraryProvenance
26
- entry: LibraryEntry
27
-
28
-
29
- @dataclass
30
- class LifecycleIssue:
31
- """Represents an issue found during library lifecycle with severity level."""
32
-
33
- message: str
34
- severity: LibraryStatus
35
-
36
-
37
- @dataclass
38
- class Result:
39
- """Base class for all library lifecycle result objects."""
40
-
41
- issues: list[LifecycleIssue] = field(default_factory=list)
42
-
43
- def get_status(self) -> LibraryStatus:
44
- """Determine overall status based on issues."""
45
- if any(issue.severity == LibraryStatus.UNUSABLE for issue in self.issues):
46
- return LibraryStatus.UNUSABLE
47
- if any(issue.severity == LibraryStatus.FLAWED for issue in self.issues):
48
- return LibraryStatus.FLAWED
49
- return LibraryStatus.GOOD
50
-
51
- def is_usable(self) -> bool:
52
- """Check if result is usable despite issues."""
53
- return self.get_status() in [LibraryStatus.GOOD, LibraryStatus.FLAWED]
54
-
55
-
56
- @dataclass
57
- class InspectionResult(Result):
58
- """Result of library inspection with structured issues and severity levels."""
59
-
60
- schema: LibrarySchema | None = None
61
-
62
- def __init__(self, schema: LibrarySchema | None = None, issues: list[LifecycleIssue] | None = None):
63
- super().__init__(issues=issues or [])
64
- self.schema = schema
65
-
66
- def get_status(self) -> LibraryStatus:
67
- """Determine overall status based on issues and schema availability."""
68
- if not self.schema:
69
- return LibraryStatus.UNUSABLE
70
- return super().get_status()
71
-
72
-
73
- @dataclass
74
- class EvaluationResult(Result):
75
- """Result of library evaluation with structured issues and severity levels."""
76
-
77
- def __init__(self, issues: list[LifecycleIssue] | None = None):
78
- super().__init__(issues=issues or [])
79
-
80
-
81
- @dataclass
82
- class InstallationResult(Result):
83
- """Result of library installation with structured issues and severity levels."""
84
-
85
- installation_path: str = "" # Where the library files are
86
- venv_path: str = "" # Where the virtual environment is
87
-
88
- def __init__(self, installation_path: str = "", venv_path: str = "", issues: list[LifecycleIssue] | None = None):
89
- super().__init__(issues=issues or [])
90
- self.installation_path = installation_path
91
- self.venv_path = venv_path
92
-
93
-
94
- @dataclass
95
- class LibraryLoadedResult(Result):
96
- """Result of library loading with structured issues and severity levels."""
97
-
98
- def __init__(self, issues: list[LifecycleIssue] | None = None):
99
- super().__init__(issues=issues or [])
100
-
101
-
102
- @dataclass
103
- class LibraryEntry:
104
- """A library entry combining provenance and user configuration."""
105
-
106
- # Abstract base - concrete implementations in library_provenance.py
107
- active: bool = True
108
- library_name: str | None = None # Set after inspection from metadata
109
-
110
- def get_provenance(self) -> LibraryProvenance:
111
- """Get the provenance for this library entry."""
112
- msg = "Subclasses must implement get_provenance()"
113
- raise NotImplementedError(msg)
114
-
115
- def set_library_name(self, name: str) -> None:
116
- """Set the library name after inspection."""
117
- self.library_name = name
118
-
119
-
120
- @dataclass
121
- class LibraryPreferences:
122
- """User preferences and configuration for libraries."""
123
-
124
- libraries: dict[str, LibraryEntry] = field(default_factory=dict)
125
-
126
- def add_library(self, name: str, library_entry: LibraryEntry) -> None:
127
- """Add or update a library entry."""
128
- library_entry.set_library_name(name)
129
- self.libraries[name] = library_entry
130
-
131
- def add_inspected_library(self, library_entry: LibraryEntry) -> str | None:
132
- """Add a library entry that has been inspected and has a library name.
133
-
134
- Returns the final library name used, or None if there was a conflict.
135
- """
136
- if not library_entry.library_name:
137
- msg = "Library entry must have a library_name set after inspection"
138
- raise ValueError(msg)
139
-
140
- library_name = library_entry.library_name
141
-
142
- # Check for conflicts
143
- if library_name in self.libraries:
144
- # Library name already exists - this is a conflict
145
- existing_entry = self.libraries[library_name]
146
- if existing_entry.get_provenance() != library_entry.get_provenance():
147
- # Different provenance with same name - this is a real conflict
148
- return None
149
-
150
- # No conflict, add the library
151
- self.libraries[library_name] = library_entry
152
- return library_name
153
-
154
- def has_library_entry(self, name: str) -> bool:
155
- """Check if a library entry exists."""
156
- return name in self.libraries
157
-
158
- def get_library_entry(self, name: str) -> LibraryEntry:
159
- """Get the library entry for a library."""
160
- if name not in self.libraries:
161
- msg = f"Library {name} not found in preferences"
162
- raise KeyError(msg)
163
- return self.libraries[name]
164
-
165
- def get_all_library_names(self) -> list[str]:
166
- """Get all library names in preferences."""
167
- return list(self.libraries.keys())
168
-
169
- def remove_library(self, name: str) -> None:
170
- """Remove a library from preferences."""
171
- self.libraries.pop(name, None)
172
-
173
- def get_libraries_by_type(self, provenance_type: type) -> list[LibraryByType]:
174
- """Get all libraries that have a specific provenance type."""
175
- matching_libraries = []
176
-
177
- for name, entry in self.libraries.items():
178
- if isinstance(entry.get_provenance(), provenance_type):
179
- matching_libraries.append(LibraryByType(name=name, entry=entry))
180
-
181
- return matching_libraries
182
-
183
- def get_libraries_by_provenance(self, provenance: LibraryProvenance) -> list[LibraryByType]:
184
- """Get all libraries that have a specific provenance."""
185
- matching_libraries = []
186
-
187
- for name, entry in self.libraries.items():
188
- if entry.get_provenance() == provenance:
189
- matching_libraries.append(LibraryByType(name=name, entry=entry))
190
-
191
- return matching_libraries
@@ -1,346 +0,0 @@
1
- """Library directory for managing library candidates."""
2
-
3
- from __future__ import annotations
4
-
5
- import logging
6
- from typing import TYPE_CHECKING
7
-
8
- from griptape_nodes.retained_mode.managers.library_lifecycle.data_models import LibraryCandidate, LifecycleIssue
9
- from griptape_nodes.retained_mode.managers.library_lifecycle.library_fsm import EvaluatedState, LibraryLifecycleFSM
10
- from griptape_nodes.retained_mode.managers.library_lifecycle.library_status import LibraryStatus
11
-
12
- if TYPE_CHECKING:
13
- from griptape_nodes.retained_mode.managers.library_lifecycle.data_models import LibraryEntry
14
- from griptape_nodes.retained_mode.managers.library_lifecycle.library_provenance import LibraryProvenance
15
-
16
- logger = logging.getLogger("griptape_nodes")
17
-
18
-
19
- class LibraryDirectory:
20
- """Unified registry of all known libraries - both curated and user-added.
21
-
22
- This class manages discovery of libraries and works with LibraryPreferences
23
- for configuration. It's responsible for finding libraries and their provenances,
24
- while LibraryPreferences handles user configuration.
25
- """
26
-
27
- def __init__(self) -> None:
28
- # This is now just a discovery mechanism - the actual configuration
29
- # lives in LibraryPreferences
30
- # Key: provenance, Value: entry
31
- self._discovered_libraries: dict[LibraryProvenance, LibraryEntry] = {}
32
- # Track library name conflicts centrally
33
- # Key: library name, Value: set of provenances with that name
34
- self._library_name_to_provenances: dict[str, set[LibraryProvenance]] = {}
35
- # Own all FSM instances for library lifecycle management
36
- self._provenance_to_fsm: dict[LibraryProvenance, LibraryLifecycleFSM] = {}
37
-
38
- async def discover_library(self, provenance: LibraryProvenance) -> None:
39
- """Discover a library and its provenance.
40
-
41
- Discovery is purely about cataloging - activation state is handled separately.
42
- """
43
- # Check if already discovered
44
- if provenance in self._discovered_libraries:
45
- return
46
-
47
- # Create entry with neutral active state (will be set by specific methods)
48
- entry = provenance.create_library_entry(active=False)
49
- self._discovered_libraries[provenance] = entry
50
-
51
- # Create FSM and run evaluation automatically
52
- await self._create_fsm_and_evaluate(provenance)
53
-
54
- async def add_curated_candidate(self, provenance: LibraryProvenance) -> None:
55
- """Add a curated library candidate.
56
-
57
- Curated libraries default to inactive and need to be activated by user.
58
- """
59
- await self.discover_library(provenance)
60
-
61
- # Set curated library as inactive by default
62
- if provenance in self._discovered_libraries:
63
- entry = self._discovered_libraries[provenance]
64
- entry.active = False
65
-
66
- async def add_user_candidate(self, provenance: LibraryProvenance) -> None:
67
- """Add a user-supplied library candidate.
68
-
69
- User libraries default to active.
70
- """
71
- await self.discover_library(provenance)
72
-
73
- # Set user library as active by default
74
- if provenance in self._discovered_libraries:
75
- entry = self._discovered_libraries[provenance]
76
- entry.active = True
77
-
78
- def get_all_candidates(self) -> list[LibraryCandidate]:
79
- """Get all known library candidates with their entries.
80
-
81
- Returns list of LibraryCandidate named tuples.
82
- """
83
- candidates = []
84
- for provenance, entry in self._discovered_libraries.items():
85
- candidates.append(LibraryCandidate(provenance=provenance, entry=entry))
86
- return candidates
87
-
88
- def get_active_candidates(self) -> list[LibraryCandidate]:
89
- """Get all candidates that should be active.
90
-
91
- Returns list of LibraryCandidate named tuples for active libraries.
92
- """
93
- all_candidates = self.get_all_candidates()
94
- return [candidate for candidate in all_candidates if candidate.entry.active]
95
-
96
- def get_candidate(self, provenance: LibraryProvenance) -> LibraryEntry | None:
97
- """Get a specific library candidate entry by provenance."""
98
- return self._discovered_libraries.get(provenance)
99
-
100
- def remove_candidate(self, provenance: LibraryProvenance) -> None:
101
- """Remove a library candidate from discovery."""
102
- self._discovered_libraries.pop(provenance, None)
103
- # Remove from name mapping
104
- for library_name, provenances in list(self._library_name_to_provenances.items()):
105
- provenances.discard(provenance)
106
- if not provenances: # Remove empty sets
107
- del self._library_name_to_provenances[library_name]
108
- # Remove FSM
109
- self._provenance_to_fsm.pop(provenance, None)
110
-
111
- def clear(self) -> None:
112
- """Clear all library candidates."""
113
- self._discovered_libraries.clear()
114
- self._library_name_to_provenances.clear()
115
- self._provenance_to_fsm.clear()
116
-
117
- def get_discovered_libraries(self) -> dict[LibraryProvenance, LibraryEntry]:
118
- """Get all discovered libraries and their entries.
119
-
120
- Returns dict mapping provenance -> entry.
121
- """
122
- return self._discovered_libraries.copy()
123
-
124
- def get_conflicting_provenances(self, library_name: str) -> set[LibraryProvenance]:
125
- """Get all provenances that have the given library name.
126
-
127
- Returns empty set if library name not found.
128
- """
129
- return self._library_name_to_provenances.get(library_name, set()).copy()
130
-
131
- def has_library_name_conflicts(self, library_name: str) -> bool:
132
- """Check if a library name has conflicts (more than one provenance)."""
133
- return len(self._library_name_to_provenances.get(library_name, set())) > 1
134
-
135
- def get_all_conflicting_library_names(self) -> list[str]:
136
- """Get all library names that have conflicts."""
137
- return [name for name, provenances in self._library_name_to_provenances.items() if len(provenances) > 1]
138
-
139
- def can_install_library(self, provenance: LibraryProvenance, library_name: str) -> bool: # noqa: ARG002
140
- """Check if a library can be installed (no name conflicts)."""
141
- return not self.has_library_name_conflicts(library_name)
142
-
143
- def get_conflicting_library_display_names(
144
- self, library_name: str, excluding_provenance: LibraryProvenance | None = None
145
- ) -> list[str]:
146
- """Get display names of libraries that conflict with the given library name.
147
-
148
- Optionally exclude a specific provenance from the results.
149
- """
150
- conflicting_provenances = self.get_conflicting_provenances(library_name)
151
- if excluding_provenance:
152
- conflicting_provenances.discard(excluding_provenance)
153
- return [p.get_display_name() for p in conflicting_provenances]
154
-
155
- def get_installable_candidates(self) -> list[LibraryCandidate]:
156
- """Get all active candidates that are ready for installation (evaluated, usable, no conflicts)."""
157
- active_candidates = self.get_active_candidates()
158
- return [
159
- candidate for candidate in active_candidates if not self.get_installation_blockers(candidate.provenance)
160
- ]
161
-
162
- def get_installation_blockers(self, provenance: LibraryProvenance) -> list[LifecycleIssue]:
163
- """Get all issues preventing this library from being installed."""
164
- blockers = []
165
- fsm = self._provenance_to_fsm.get(provenance)
166
-
167
- if not fsm:
168
- blockers.append(LifecycleIssue(message="No FSM found for library", severity=LibraryStatus.MISSING))
169
- return blockers
170
-
171
- # Check if library is in evaluated state
172
- if fsm.current_state != EvaluatedState:
173
- blockers.append(
174
- LifecycleIssue(
175
- message=f"Library not in evaluated state: {fsm.get_current_state_name()}",
176
- severity=LibraryStatus.UNUSABLE,
177
- )
178
- )
179
- return blockers
180
-
181
- context = fsm.get_context()
182
-
183
- # Check if inspection result is usable
184
- if not context.inspection_result or not context.inspection_result.is_usable():
185
- blockers.append(LifecycleIssue(message="Library has inspection issues", severity=LibraryStatus.UNUSABLE))
186
- return blockers
187
-
188
- # Check if schema is available
189
- if not context.inspection_result.schema:
190
- blockers.append(LifecycleIssue(message="Library schema not available", severity=LibraryStatus.UNUSABLE))
191
- return blockers
192
-
193
- # Check for name conflicts
194
- library_name = context.inspection_result.schema.name
195
- if self.has_library_name_conflicts(library_name):
196
- conflicting_libraries = self.get_conflicting_library_display_names(library_name, provenance)
197
- blockers.append(
198
- LifecycleIssue(
199
- message=f"Library has name conflicts with: {conflicting_libraries}", severity=LibraryStatus.FLAWED
200
- )
201
- )
202
-
203
- return blockers
204
-
205
- async def _create_fsm_and_evaluate(self, provenance: LibraryProvenance) -> None:
206
- """Create FSM for provenance and run through evaluation phase.
207
-
208
- This method is called automatically when a library is discovered.
209
- """
210
- logger.debug("Creating FSM and starting evaluation for library: %s", provenance.get_display_name())
211
-
212
- # Create FSM instance for this library
213
- fsm = LibraryLifecycleFSM(provenance)
214
- self._provenance_to_fsm[provenance] = fsm
215
-
216
- # Start the lifecycle and run through evaluation
217
- await fsm.start_lifecycle()
218
-
219
- # Progress through inspection
220
- if fsm.can_begin_inspection():
221
- await fsm.begin_inspection()
222
- else:
223
- logger.error(
224
- "Cannot inspect library '%s' - inspection step cannot proceed",
225
- provenance.get_display_name(),
226
- )
227
- return
228
-
229
- # Progress through evaluation
230
- if fsm.can_begin_evaluation():
231
- await fsm.begin_evaluation()
232
- else:
233
- logger.error(
234
- "Cannot evaluate library '%s' - evaluation step cannot proceed",
235
- provenance.get_display_name(),
236
- )
237
- return
238
-
239
- # Update library name mapping after successful inspection and evaluation
240
- # At this point, we know inspection_result and schema are valid since we completed both phases
241
- context = fsm.get_context()
242
- if context.inspection_result and context.inspection_result.schema:
243
- library_name = context.inspection_result.schema.name
244
- if library_name not in self._library_name_to_provenances:
245
- self._library_name_to_provenances[library_name] = set()
246
- self._library_name_to_provenances[library_name].add(provenance)
247
-
248
- logger.debug("Completed FSM evaluation for library: %s", provenance.get_display_name())
249
-
250
- async def install_library(self, provenance: LibraryProvenance) -> bool:
251
- """Install a library by running its FSM through the installation phase.
252
-
253
- Returns True if installation was successful, False otherwise.
254
- """
255
- fsm = self._provenance_to_fsm.get(provenance)
256
- if not fsm:
257
- logger.error("No FSM found for provenance: %s", provenance.get_display_name())
258
- return False
259
-
260
- # Check if library has name conflicts that prevent installation
261
- context = fsm.get_context()
262
- if context.inspection_result and context.inspection_result.schema:
263
- library_name = context.inspection_result.schema.name
264
- if self.has_library_name_conflicts(library_name):
265
- conflicting_libraries = self.get_conflicting_library_display_names(library_name, provenance)
266
- logger.error(
267
- "Cannot install library '%s' due to name conflicts with: %s",
268
- provenance.get_display_name(),
269
- conflicting_libraries,
270
- )
271
- return False
272
-
273
- # Proceed with installation
274
- if fsm.can_begin_installation():
275
- await fsm.begin_installation()
276
- logger.info("Installation completed for library: %s", provenance.get_display_name())
277
- return True
278
- logger.error(
279
- "Cannot install library '%s' - installation step cannot proceed",
280
- provenance.get_display_name(),
281
- )
282
- return False
283
-
284
- async def load_library(self, provenance: LibraryProvenance) -> bool:
285
- """Load a library by running its FSM through the loading phase.
286
-
287
- Returns True if loading was successful, False otherwise.
288
- """
289
- fsm = self._provenance_to_fsm.get(provenance)
290
- if not fsm:
291
- logger.error("No FSM found for provenance: %s", provenance.get_display_name())
292
- return False
293
-
294
- if not fsm.can_begin_loading():
295
- logger.error(
296
- "Cannot load library '%s' - loading step cannot proceed",
297
- provenance.get_display_name(),
298
- )
299
- return False
300
-
301
- # Proceed with loading
302
- await fsm.begin_loading()
303
-
304
- if not fsm.is_loaded():
305
- logger.error(
306
- "Failed to load library '%s' - did not reach loaded state: %s",
307
- provenance.get_display_name(),
308
- fsm.get_current_state_name(),
309
- )
310
- return False
311
-
312
- logger.info("Successfully loaded library '%s'", provenance.get_display_name())
313
- return True
314
-
315
- def get_library_name_from_provenance(self, provenance: LibraryProvenance) -> str | None:
316
- """Get the library name for a given provenance after evaluation.
317
-
318
- Returns None if the provenance hasn't been evaluated or doesn't have a valid schema.
319
- """
320
- fsm = self._provenance_to_fsm.get(provenance)
321
- if not fsm:
322
- return None
323
-
324
- context = fsm.get_context()
325
- if not context.inspection_result or not context.inspection_result.schema:
326
- return None
327
-
328
- return context.inspection_result.schema.name
329
-
330
- def get_provenances_for_library_name(self, library_name: str) -> list[LibraryProvenance]:
331
- """Get all provenances that have the given library name.
332
-
333
- Returns empty list if library name not found.
334
- """
335
- return list(self._library_name_to_provenances.get(library_name, set()))
336
-
337
- def find_provenance_by_library_name(self, library_name: str) -> LibraryProvenance | None:
338
- """Find a single provenance for a library name.
339
-
340
- Returns None if library name not found or if there are multiple provenances
341
- (indicating a conflict that should be resolved first).
342
- """
343
- provenances = self.get_provenances_for_library_name(library_name)
344
- if len(provenances) == 1:
345
- return provenances[0]
346
- return None