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.
- griptape_nodes/common/node_executor.py +352 -27
- griptape_nodes/drivers/storage/base_storage_driver.py +12 -3
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +18 -2
- griptape_nodes/drivers/storage/local_storage_driver.py +42 -5
- griptape_nodes/exe_types/base_iterative_nodes.py +0 -1
- griptape_nodes/exe_types/connections.py +42 -0
- griptape_nodes/exe_types/core_types.py +2 -2
- griptape_nodes/exe_types/node_groups/__init__.py +2 -1
- griptape_nodes/exe_types/node_groups/base_iterative_node_group.py +177 -0
- griptape_nodes/exe_types/node_groups/base_node_group.py +1 -0
- griptape_nodes/exe_types/node_groups/subflow_node_group.py +35 -2
- griptape_nodes/exe_types/param_types/parameter_audio.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_bool.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_button.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_float.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_image.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_int.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_number.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_string.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_three_d.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_video.py +1 -1
- griptape_nodes/machines/control_flow.py +5 -4
- griptape_nodes/machines/dag_builder.py +121 -55
- griptape_nodes/machines/fsm.py +10 -0
- griptape_nodes/machines/parallel_resolution.py +39 -38
- griptape_nodes/machines/sequential_resolution.py +29 -3
- griptape_nodes/node_library/library_registry.py +41 -2
- griptape_nodes/retained_mode/events/library_events.py +147 -8
- griptape_nodes/retained_mode/events/os_events.py +12 -4
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +2 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/incompatible_requirements_problem.py +34 -0
- griptape_nodes/retained_mode/managers/flow_manager.py +133 -20
- griptape_nodes/retained_mode/managers/library_manager.py +1324 -564
- griptape_nodes/retained_mode/managers/node_manager.py +9 -3
- griptape_nodes/retained_mode/managers/os_manager.py +429 -65
- griptape_nodes/retained_mode/managers/resource_types/compute_resource.py +82 -0
- griptape_nodes/retained_mode/managers/resource_types/os_resource.py +17 -0
- griptape_nodes/retained_mode/managers/static_files_manager.py +21 -8
- griptape_nodes/retained_mode/managers/version_compatibility_manager.py +3 -3
- griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +5 -5
- griptape_nodes/version_compatibility/versions/v0_65_4/__init__.py +5 -0
- griptape_nodes/version_compatibility/versions/v0_65_4/run_in_parallel_to_run_in_order.py +79 -0
- griptape_nodes/version_compatibility/versions/v0_65_5/__init__.py +5 -0
- griptape_nodes/version_compatibility/versions/v0_65_5/flux_2_removed_parameters.py +85 -0
- {griptape_nodes-0.65.6.dist-info → griptape_nodes-0.66.1.dist-info}/METADATA +1 -1
- {griptape_nodes-0.65.6.dist-info → griptape_nodes-0.66.1.dist-info}/RECORD +48 -53
- griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +0 -45
- griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +0 -191
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +0 -346
- griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +0 -439
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +0 -17
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +0 -82
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +0 -116
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +0 -367
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +0 -104
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +0 -155
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +0 -18
- griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +0 -12
- {griptape_nodes-0.65.6.dist-info → griptape_nodes-0.66.1.dist-info}/WHEEL +0 -0
- {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
|