griptape-nodes 0.55.1__py3-none-any.whl → 0.56.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/cli/commands/init.py +88 -0
- griptape_nodes/cli/commands/models.py +2 -0
- griptape_nodes/cli/shared.py +1 -0
- griptape_nodes/exe_types/core_types.py +104 -0
- griptape_nodes/exe_types/node_types.py +9 -12
- griptape_nodes/machines/control_flow.py +10 -0
- griptape_nodes/machines/dag_builder.py +21 -2
- griptape_nodes/machines/parallel_resolution.py +25 -10
- griptape_nodes/node_library/workflow_registry.py +73 -3
- griptape_nodes/retained_mode/events/execution_events.py +12 -2
- griptape_nodes/retained_mode/events/flow_events.py +58 -0
- griptape_nodes/retained_mode/events/resource_events.py +290 -0
- griptape_nodes/retained_mode/events/workflow_events.py +57 -2
- griptape_nodes/retained_mode/griptape_nodes.py +9 -1
- griptape_nodes/retained_mode/managers/flow_manager.py +678 -12
- griptape_nodes/retained_mode/managers/library_manager.py +13 -19
- griptape_nodes/retained_mode/managers/model_manager.py +184 -83
- griptape_nodes/retained_mode/managers/node_manager.py +3 -3
- griptape_nodes/retained_mode/managers/os_manager.py +118 -1
- griptape_nodes/retained_mode/managers/resource_components/__init__.py +1 -0
- griptape_nodes/retained_mode/managers/resource_components/capability_field.py +41 -0
- griptape_nodes/retained_mode/managers/resource_components/comparator.py +18 -0
- griptape_nodes/retained_mode/managers/resource_components/resource_instance.py +236 -0
- griptape_nodes/retained_mode/managers/resource_components/resource_type.py +79 -0
- griptape_nodes/retained_mode/managers/resource_manager.py +306 -0
- griptape_nodes/retained_mode/managers/resource_types/__init__.py +1 -0
- griptape_nodes/retained_mode/managers/resource_types/cpu_resource.py +108 -0
- griptape_nodes/retained_mode/managers/resource_types/os_resource.py +87 -0
- griptape_nodes/retained_mode/managers/settings.py +5 -0
- griptape_nodes/retained_mode/managers/sync_manager.py +10 -3
- griptape_nodes/retained_mode/managers/workflow_manager.py +359 -261
- {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/METADATA +1 -1
- {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/RECORD +35 -25
- {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from griptape_nodes.retained_mode.managers.resource_components.comparator import Comparator
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from griptape_nodes.retained_mode.managers.resource_components.resource_type import ResourceType
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("griptape_nodes")
|
|
13
|
+
|
|
14
|
+
# Type aliases for resource requirements
|
|
15
|
+
RequirementValue = (
|
|
16
|
+
Any # Simple values: "present", 8, True
|
|
17
|
+
| tuple[Any, str] # Tuple format: (8, ">"), ("cuda", "present")
|
|
18
|
+
| tuple[Any, Comparator] # Enum format: (8, Comparator.GREATER_THAN)
|
|
19
|
+
)
|
|
20
|
+
Requirements = dict[str, RequirementValue]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ResourceInstance(ABC):
|
|
24
|
+
"""Base class for resource instances that can be locked and managed."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, resource_type: "ResourceType", instance_id_prefix: str, capabilities: dict[str, Any]):
|
|
27
|
+
self._resource_type = resource_type
|
|
28
|
+
self._capabilities = deepcopy(capabilities)
|
|
29
|
+
self._instance_id = f"{instance_id_prefix}_{uuid4()}"
|
|
30
|
+
self._locked_by = None
|
|
31
|
+
|
|
32
|
+
def get_resource_type(self) -> "ResourceType":
|
|
33
|
+
"""Get the resource type of this instance."""
|
|
34
|
+
return self._resource_type
|
|
35
|
+
|
|
36
|
+
def get_capabilities(self) -> list[str]:
|
|
37
|
+
"""Get the capability keys available for this resource."""
|
|
38
|
+
return list(self._capabilities.keys())
|
|
39
|
+
|
|
40
|
+
def has_capability(self, key: str) -> bool:
|
|
41
|
+
"""Check if resource has a specific capability key."""
|
|
42
|
+
return key in self._capabilities
|
|
43
|
+
|
|
44
|
+
def get_capability_value(self, key: str) -> Any:
|
|
45
|
+
"""Get a specific capability value.
|
|
46
|
+
|
|
47
|
+
This method can be overridden by subclasses to support volatile resources
|
|
48
|
+
that require real-time queries (e.g., current memory usage, GPU temperature)
|
|
49
|
+
or resources that need external API calls to retrieve current values.
|
|
50
|
+
|
|
51
|
+
The default implementation returns the static value from the capabilities dict.
|
|
52
|
+
"""
|
|
53
|
+
return self._capabilities[key]
|
|
54
|
+
|
|
55
|
+
def get_instance_id(self) -> str:
|
|
56
|
+
"""Unique handle/ID for this instance."""
|
|
57
|
+
return self._instance_id
|
|
58
|
+
|
|
59
|
+
def acquire_lock(self, owner_id: str) -> None:
|
|
60
|
+
"""Acquire exclusive lock.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
owner_id: The ID of the entity requesting the lock
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
ValueError: If the resource is already locked by another owner
|
|
67
|
+
"""
|
|
68
|
+
if self._locked_by is not None:
|
|
69
|
+
if self._locked_by == owner_id:
|
|
70
|
+
# Already locked by the same owner - this is fine
|
|
71
|
+
return
|
|
72
|
+
msg = f"Resource {self._instance_id} is already locked by {self._locked_by}, cannot lock for {owner_id}"
|
|
73
|
+
raise ValueError(msg)
|
|
74
|
+
|
|
75
|
+
self._locked_by = owner_id
|
|
76
|
+
logger.debug("Resource %s locked by %s", self._instance_id, owner_id)
|
|
77
|
+
|
|
78
|
+
def release_lock(self, owner_id: str) -> None:
|
|
79
|
+
"""Release lock.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
owner_id: The ID of the entity releasing the lock
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
ValueError: If the resource is not locked by the specified owner
|
|
86
|
+
"""
|
|
87
|
+
if self._locked_by != owner_id:
|
|
88
|
+
if self._locked_by is None:
|
|
89
|
+
msg = f"Resource {self._instance_id} is not locked, cannot release for {owner_id}"
|
|
90
|
+
else:
|
|
91
|
+
msg = f"Resource {self._instance_id} is locked by {self._locked_by}, cannot release for {owner_id}"
|
|
92
|
+
raise ValueError(msg)
|
|
93
|
+
|
|
94
|
+
self._locked_by = None
|
|
95
|
+
logger.debug("Resource %s unlocked by %s", self._instance_id, owner_id)
|
|
96
|
+
|
|
97
|
+
def is_locked(self) -> bool:
|
|
98
|
+
"""Check if resource is currently locked."""
|
|
99
|
+
return self._locked_by is not None
|
|
100
|
+
|
|
101
|
+
def is_locked_by(self, owner_id: str) -> bool:
|
|
102
|
+
"""Check if resource is locked by specific owner."""
|
|
103
|
+
return self._locked_by == owner_id
|
|
104
|
+
|
|
105
|
+
def get_lock_owner(self) -> str | None:
|
|
106
|
+
"""Get the current lock owner, if any."""
|
|
107
|
+
return self._locked_by
|
|
108
|
+
|
|
109
|
+
def force_unlock(self) -> None:
|
|
110
|
+
"""Force unlock this resource instance (for administrative operations)."""
|
|
111
|
+
self._locked_by = None
|
|
112
|
+
|
|
113
|
+
def get_all_capabilities_and_current_values(self) -> dict[str, Any]:
|
|
114
|
+
"""Get the complete capabilities dictionary with current values.
|
|
115
|
+
|
|
116
|
+
This method returns all capability keys and their current values, which may
|
|
117
|
+
include real-time queries for volatile resources that override get_capability_value().
|
|
118
|
+
Returns a deep copy to prevent external modification of nested mutable objects.
|
|
119
|
+
"""
|
|
120
|
+
# First get current values (may trigger real-time queries for volatile resources)
|
|
121
|
+
result = {key: self.get_capability_value(key) for key in self._capabilities}
|
|
122
|
+
# Then deep copy to protect against modification of nested mutable objects
|
|
123
|
+
return deepcopy(result)
|
|
124
|
+
|
|
125
|
+
def is_compatible_with(self, requirements: Requirements | None) -> bool:
|
|
126
|
+
"""Check if this instance can satisfy the requirements."""
|
|
127
|
+
if requirements is None:
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
for key, req_spec in requirements.items():
|
|
131
|
+
# Handle both tuple and non-tuple formats
|
|
132
|
+
if isinstance(req_spec, tuple):
|
|
133
|
+
required_value, comparator_str = req_spec
|
|
134
|
+
else:
|
|
135
|
+
required_value, comparator_str = req_spec, Comparator.EQUALS
|
|
136
|
+
|
|
137
|
+
# Convert string to StrEnum
|
|
138
|
+
comparator = Comparator(comparator_str)
|
|
139
|
+
|
|
140
|
+
if not self._compare_values(key, required_value, comparator):
|
|
141
|
+
return False
|
|
142
|
+
return True
|
|
143
|
+
|
|
144
|
+
def _compare_values(self, key: str, required: Any, comparator: Comparator) -> bool:
|
|
145
|
+
"""Compare actual capability value against required value using comparator."""
|
|
146
|
+
match comparator:
|
|
147
|
+
case Comparator.NOT_PRESENT:
|
|
148
|
+
return key not in self._capabilities
|
|
149
|
+
case _:
|
|
150
|
+
# For all other comparators, key must exist
|
|
151
|
+
if key not in self._capabilities:
|
|
152
|
+
return False
|
|
153
|
+
actual = self._capabilities[key]
|
|
154
|
+
return self._apply_comparator(actual, required, comparator, key)
|
|
155
|
+
|
|
156
|
+
def _apply_comparator(self, actual: Any, required: Any, comparator: Comparator, key: str) -> bool:
|
|
157
|
+
"""Apply the comparator operation."""
|
|
158
|
+
match comparator:
|
|
159
|
+
case Comparator.EQUALS:
|
|
160
|
+
return actual == required
|
|
161
|
+
case Comparator.NOT_EQUALS:
|
|
162
|
+
return actual != required
|
|
163
|
+
case (
|
|
164
|
+
Comparator.GREATER_THAN_OR_EQUAL
|
|
165
|
+
| Comparator.GREATER_THAN
|
|
166
|
+
| Comparator.LESS_THAN_OR_EQUAL
|
|
167
|
+
| Comparator.LESS_THAN
|
|
168
|
+
):
|
|
169
|
+
return self._apply_numeric_comparator(actual, required, comparator)
|
|
170
|
+
case Comparator.STARTS_WITH | Comparator.INCLUDES:
|
|
171
|
+
return self._apply_string_comparator(actual, required, comparator)
|
|
172
|
+
case Comparator.HAS_ANY | Comparator.HAS_ALL:
|
|
173
|
+
return self._apply_container_comparator(actual, required, comparator, key)
|
|
174
|
+
case Comparator.CUSTOM:
|
|
175
|
+
return self._resource_type.handle_custom_requirement(
|
|
176
|
+
_instance=self,
|
|
177
|
+
_key=key,
|
|
178
|
+
_requirement_value=required,
|
|
179
|
+
_actual_value=actual,
|
|
180
|
+
_capabilities=self._capabilities,
|
|
181
|
+
)
|
|
182
|
+
case _:
|
|
183
|
+
msg = f"Unknown comparator: {comparator}"
|
|
184
|
+
raise ValueError(msg)
|
|
185
|
+
|
|
186
|
+
def _apply_numeric_comparator(self, actual: Any, required: Any, comparator: Comparator) -> bool:
|
|
187
|
+
"""Apply numeric comparison operators."""
|
|
188
|
+
match comparator:
|
|
189
|
+
case Comparator.GREATER_THAN_OR_EQUAL:
|
|
190
|
+
return actual >= required
|
|
191
|
+
case Comparator.GREATER_THAN:
|
|
192
|
+
return actual > required
|
|
193
|
+
case Comparator.LESS_THAN_OR_EQUAL:
|
|
194
|
+
return actual <= required
|
|
195
|
+
case Comparator.LESS_THAN:
|
|
196
|
+
return actual < required
|
|
197
|
+
case _:
|
|
198
|
+
msg = f"Invalid numeric comparator: {comparator}"
|
|
199
|
+
raise ValueError(msg)
|
|
200
|
+
|
|
201
|
+
def _apply_string_comparator(self, actual: Any, required: Any, comparator: Comparator) -> bool:
|
|
202
|
+
"""Apply string comparison operators."""
|
|
203
|
+
match comparator:
|
|
204
|
+
case Comparator.STARTS_WITH:
|
|
205
|
+
return str(actual).startswith(str(required))
|
|
206
|
+
case Comparator.INCLUDES:
|
|
207
|
+
return str(required) in str(actual)
|
|
208
|
+
case _:
|
|
209
|
+
msg = f"Invalid string comparator: {comparator}"
|
|
210
|
+
raise ValueError(msg)
|
|
211
|
+
|
|
212
|
+
def _apply_container_comparator(self, actual: Any, required: Any, comparator: Comparator, key: str) -> bool:
|
|
213
|
+
"""Apply container comparison operators."""
|
|
214
|
+
if not hasattr(actual, "__iter__") or isinstance(actual, str):
|
|
215
|
+
msg = f"{comparator} comparator requires capability '{key}' to be a container, got {type(actual)}"
|
|
216
|
+
raise ValueError(msg)
|
|
217
|
+
if not hasattr(required, "__iter__") or isinstance(required, str):
|
|
218
|
+
msg = f"{comparator} comparator requires requirement value to be a container, got {type(required)}"
|
|
219
|
+
raise ValueError(msg)
|
|
220
|
+
|
|
221
|
+
match comparator:
|
|
222
|
+
case Comparator.HAS_ANY:
|
|
223
|
+
return any(item in actual for item in required)
|
|
224
|
+
case Comparator.HAS_ALL:
|
|
225
|
+
return all(item in actual for item in required)
|
|
226
|
+
case _:
|
|
227
|
+
msg = f"Invalid container comparator: {comparator}"
|
|
228
|
+
raise ValueError(msg)
|
|
229
|
+
|
|
230
|
+
@abstractmethod
|
|
231
|
+
def can_be_freed(self) -> bool:
|
|
232
|
+
"""Check if this resource can be safely freed."""
|
|
233
|
+
|
|
234
|
+
@abstractmethod
|
|
235
|
+
def free(self) -> None:
|
|
236
|
+
"""Free this resource instance."""
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import TYPE_CHECKING, Any
|
|
3
|
+
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from griptape_nodes.retained_mode.managers.resource_components.capability_field import CapabilityField
|
|
6
|
+
from griptape_nodes.retained_mode.managers.resource_components.resource_instance import (
|
|
7
|
+
Requirements,
|
|
8
|
+
ResourceInstance,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ResourceType(ABC):
|
|
13
|
+
"""Base class for resource type handlers."""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def get_capability_schema(self) -> list["CapabilityField"]:
|
|
17
|
+
"""Get the capability schema for this resource type."""
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def create_instance(self, capabilities: dict[str, Any]) -> "ResourceInstance":
|
|
21
|
+
"""Create a resource instance with the specified capabilities."""
|
|
22
|
+
|
|
23
|
+
def select_best_compatible_instance(
|
|
24
|
+
self, compatible_instances: list["ResourceInstance"], _requirements: "Requirements | None" = None
|
|
25
|
+
) -> "ResourceInstance | None":
|
|
26
|
+
"""Select the best instance from pre-filtered compatible instances.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
compatible_instances: List of instances that are already known to be compatible
|
|
30
|
+
_requirements: The original requirements dict for context
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The best instance to use, or None if none are suitable
|
|
34
|
+
|
|
35
|
+
Note:
|
|
36
|
+
All instances in compatible_instances are guaranteed to satisfy the requirements.
|
|
37
|
+
This method allows ResourceType implementations to apply their own selection
|
|
38
|
+
logic (e.g., prefer instances with more resources, least used, etc.).
|
|
39
|
+
"""
|
|
40
|
+
# Default behavior: return the first compatible instance if available
|
|
41
|
+
# Implementors can override this method to provide more optimized instance selection
|
|
42
|
+
return compatible_instances[0] if compatible_instances else None
|
|
43
|
+
|
|
44
|
+
def handle_custom_requirement(
|
|
45
|
+
self,
|
|
46
|
+
*,
|
|
47
|
+
_instance: "ResourceInstance",
|
|
48
|
+
_key: str,
|
|
49
|
+
_requirement_value: Any,
|
|
50
|
+
_actual_value: Any,
|
|
51
|
+
_capabilities: dict[str, Any],
|
|
52
|
+
) -> bool:
|
|
53
|
+
"""Handle custom requirement logic with full context.
|
|
54
|
+
|
|
55
|
+
This method is called when a requirement uses the Comparator.CUSTOM comparator,
|
|
56
|
+
allowing ResourceType implementations to define complex comparison logic that
|
|
57
|
+
goes beyond the standard built-in comparators.
|
|
58
|
+
|
|
59
|
+
Use cases for custom comparators:
|
|
60
|
+
- Complex mathematical relationships (e.g., GPU memory + system memory > threshold)
|
|
61
|
+
- Version compatibility checks (e.g., CUDA version compatibility matrices)
|
|
62
|
+
- Hardware compatibility logic (e.g., CPU instruction set compatibility)
|
|
63
|
+
- Multi-field validation (e.g., ensuring RAM and CPU are balanced)
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
_instance: The resource instance being evaluated
|
|
67
|
+
_key: The requirement key being evaluated
|
|
68
|
+
_requirement_value: The value from the requirements dict
|
|
69
|
+
_actual_value: The actual capability value
|
|
70
|
+
_capabilities: Full capabilities dict for context
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
True if the custom requirement is satisfied, False otherwise
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
NotImplementedError: If the ResourceType doesn't implement custom logic
|
|
77
|
+
"""
|
|
78
|
+
msg = f"Custom requirement handling not implemented for {type(self).__name__}"
|
|
79
|
+
raise NotImplementedError(msg)
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from griptape_nodes.retained_mode.events.base_events import ResultPayload
|
|
6
|
+
from griptape_nodes.retained_mode.events.resource_events import (
|
|
7
|
+
AcquireResourceInstanceLockRequest,
|
|
8
|
+
AcquireResourceInstanceLockResultFailure,
|
|
9
|
+
AcquireResourceInstanceLockResultSuccess,
|
|
10
|
+
CreateResourceInstanceRequest,
|
|
11
|
+
CreateResourceInstanceResultFailure,
|
|
12
|
+
CreateResourceInstanceResultSuccess,
|
|
13
|
+
FreeResourceInstanceRequest,
|
|
14
|
+
FreeResourceInstanceResultFailure,
|
|
15
|
+
FreeResourceInstanceResultSuccess,
|
|
16
|
+
GetResourceInstanceStatusRequest,
|
|
17
|
+
GetResourceInstanceStatusResultFailure,
|
|
18
|
+
GetResourceInstanceStatusResultSuccess,
|
|
19
|
+
ListCompatibleResourceInstancesRequest,
|
|
20
|
+
ListCompatibleResourceInstancesResultFailure,
|
|
21
|
+
ListCompatibleResourceInstancesResultSuccess,
|
|
22
|
+
ListRegisteredResourceTypesRequest,
|
|
23
|
+
ListRegisteredResourceTypesResultSuccess,
|
|
24
|
+
ListResourceInstancesByTypeRequest,
|
|
25
|
+
ListResourceInstancesByTypeResultFailure,
|
|
26
|
+
ListResourceInstancesByTypeResultSuccess,
|
|
27
|
+
RegisterResourceTypeRequest,
|
|
28
|
+
RegisterResourceTypeResultSuccess,
|
|
29
|
+
ReleaseResourceInstanceLockRequest,
|
|
30
|
+
ReleaseResourceInstanceLockResultFailure,
|
|
31
|
+
ReleaseResourceInstanceLockResultSuccess,
|
|
32
|
+
)
|
|
33
|
+
from griptape_nodes.retained_mode.managers.event_manager import EventManager
|
|
34
|
+
from griptape_nodes.retained_mode.managers.resource_components.resource_type import ResourceType
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from griptape_nodes.retained_mode.managers.resource_components.resource_instance import ResourceInstance
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger("griptape_nodes")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class ResourceStatus:
|
|
44
|
+
resource_type: ResourceType
|
|
45
|
+
instance_id: str
|
|
46
|
+
owner_of_lock: str | None
|
|
47
|
+
capabilities: dict[str, Any]
|
|
48
|
+
|
|
49
|
+
def is_locked(self) -> bool:
|
|
50
|
+
"""Check if this resource is currently locked."""
|
|
51
|
+
return self.owner_of_lock is not None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ResourceManager:
|
|
55
|
+
"""Manager for resource allocation, locking, and lifecycle management."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, event_manager: EventManager) -> None:
|
|
58
|
+
self._resource_types: set[ResourceType] = set()
|
|
59
|
+
# Maps instance_id to ResourceInstance objects
|
|
60
|
+
self._instances: dict[str, ResourceInstance] = {}
|
|
61
|
+
|
|
62
|
+
# Register event handlers
|
|
63
|
+
event_manager.assign_manager_to_request_type(
|
|
64
|
+
request_type=ListRegisteredResourceTypesRequest, callback=self.on_list_registered_resource_types_request
|
|
65
|
+
)
|
|
66
|
+
event_manager.assign_manager_to_request_type(
|
|
67
|
+
request_type=RegisterResourceTypeRequest, callback=self.on_register_resource_type_request
|
|
68
|
+
)
|
|
69
|
+
event_manager.assign_manager_to_request_type(
|
|
70
|
+
request_type=CreateResourceInstanceRequest, callback=self.on_create_resource_instance_request
|
|
71
|
+
)
|
|
72
|
+
event_manager.assign_manager_to_request_type(
|
|
73
|
+
request_type=FreeResourceInstanceRequest, callback=self.on_free_resource_instance_request
|
|
74
|
+
)
|
|
75
|
+
event_manager.assign_manager_to_request_type(
|
|
76
|
+
request_type=AcquireResourceInstanceLockRequest, callback=self.on_acquire_resource_instance_lock_request
|
|
77
|
+
)
|
|
78
|
+
event_manager.assign_manager_to_request_type(
|
|
79
|
+
request_type=ReleaseResourceInstanceLockRequest, callback=self.on_release_resource_instance_lock_request
|
|
80
|
+
)
|
|
81
|
+
event_manager.assign_manager_to_request_type(
|
|
82
|
+
request_type=ListCompatibleResourceInstancesRequest,
|
|
83
|
+
callback=self.on_list_compatible_resource_instances_request,
|
|
84
|
+
)
|
|
85
|
+
event_manager.assign_manager_to_request_type(
|
|
86
|
+
request_type=GetResourceInstanceStatusRequest, callback=self.on_get_resource_instance_status_request
|
|
87
|
+
)
|
|
88
|
+
event_manager.assign_manager_to_request_type(
|
|
89
|
+
request_type=ListResourceInstancesByTypeRequest, callback=self.on_list_resource_instances_by_type_request
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Public Event Handlers
|
|
93
|
+
def on_list_registered_resource_types_request(self, _request: ListRegisteredResourceTypesRequest) -> ResultPayload:
|
|
94
|
+
"""Handle request to list all registered resource types."""
|
|
95
|
+
type_names = []
|
|
96
|
+
for rt in self._resource_types:
|
|
97
|
+
type_names.append(type(rt).__name__) # noqa: PERF401
|
|
98
|
+
|
|
99
|
+
return ListRegisteredResourceTypesResultSuccess(
|
|
100
|
+
resource_type_names=type_names, result_details="Successfully listed registered resource types"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def on_register_resource_type_request(self, request: RegisterResourceTypeRequest) -> ResultPayload:
|
|
104
|
+
"""Handle request to register a new resource type."""
|
|
105
|
+
self._resource_types.add(request.resource_type)
|
|
106
|
+
|
|
107
|
+
return RegisterResourceTypeResultSuccess(
|
|
108
|
+
result_details=f"Successfully registered resource type {type(request.resource_type).__name__}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def on_create_resource_instance_request(self, request: CreateResourceInstanceRequest) -> ResultPayload:
|
|
112
|
+
"""Handle request to create a new resource instance."""
|
|
113
|
+
resource_type = self._get_resource_type_by_name(request.resource_type_name)
|
|
114
|
+
if not resource_type:
|
|
115
|
+
return CreateResourceInstanceResultFailure(
|
|
116
|
+
result_details=f"Attempted to create resource instance with resource type {request.resource_type_name} and capabilities {request.capabilities}. Failed due to resource type not found."
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
new_instance = resource_type.create_instance(request.capabilities)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
return CreateResourceInstanceResultFailure(
|
|
123
|
+
result_details=f"Attempted to create resource instance with resource type {request.resource_type_name} and capabilities {request.capabilities}. Failed due to resource type creation failed: {e}."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
instance_id = new_instance.get_instance_id()
|
|
127
|
+
self._instances[instance_id] = new_instance
|
|
128
|
+
|
|
129
|
+
return CreateResourceInstanceResultSuccess(
|
|
130
|
+
instance_id=instance_id, result_details=f"Successfully created resource instance {instance_id}"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def on_free_resource_instance_request(self, request: FreeResourceInstanceRequest) -> ResultPayload:
|
|
134
|
+
"""Handle request to free a resource instance."""
|
|
135
|
+
instance = self._instances.get(request.instance_id)
|
|
136
|
+
if instance is None:
|
|
137
|
+
return FreeResourceInstanceResultFailure(
|
|
138
|
+
result_details=f"Attempted to free resource instance {request.instance_id} with force_unlock={request.force_unlock}. Failed due to resource instance does not exist."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Check if resource can be safely freed before touching locks
|
|
142
|
+
if not instance.can_be_freed():
|
|
143
|
+
return FreeResourceInstanceResultFailure(
|
|
144
|
+
result_details=f"Resource instance {request.instance_id} cannot be freed and therefore cannot be deleted."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
if instance.is_locked():
|
|
148
|
+
if not request.force_unlock:
|
|
149
|
+
owner = instance.get_lock_owner()
|
|
150
|
+
return FreeResourceInstanceResultFailure(
|
|
151
|
+
result_details=f"Attempted to free resource instance {request.instance_id} with force_unlock={request.force_unlock}. Failed due to resource instance is locked by {owner}."
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
owner = instance.get_lock_owner()
|
|
155
|
+
instance.force_unlock()
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
instance.free()
|
|
159
|
+
except Exception as e:
|
|
160
|
+
return FreeResourceInstanceResultFailure(
|
|
161
|
+
result_details=f"Attempted to free resource instance {request.instance_id} with force_unlock={request.force_unlock}. Failed to free: {e}."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
del self._instances[request.instance_id]
|
|
165
|
+
|
|
166
|
+
return FreeResourceInstanceResultSuccess(
|
|
167
|
+
result_details=f"Successfully freed resource instance {request.instance_id}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def on_acquire_resource_instance_lock_request(self, request: AcquireResourceInstanceLockRequest) -> ResultPayload:
|
|
171
|
+
"""Handle request to acquire a resource instance lock."""
|
|
172
|
+
resource_type = self._get_resource_type_by_name(request.resource_type_name)
|
|
173
|
+
if not resource_type:
|
|
174
|
+
return AcquireResourceInstanceLockResultFailure(
|
|
175
|
+
result_details=f"Attempted to acquire resource instance lock for owner {request.owner_id} with resource type {request.resource_type_name} and requirements {request.requirements}. Failed due to resource type not found."
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Get compatible unlocked instances
|
|
179
|
+
compatible_instances = []
|
|
180
|
+
for instance in self._instances.values():
|
|
181
|
+
if instance.is_locked():
|
|
182
|
+
continue
|
|
183
|
+
if instance.get_resource_type() != resource_type:
|
|
184
|
+
continue
|
|
185
|
+
if request.requirements is None:
|
|
186
|
+
compatible_instances.append(instance)
|
|
187
|
+
continue
|
|
188
|
+
if instance.is_compatible_with(request.requirements):
|
|
189
|
+
compatible_instances.append(instance)
|
|
190
|
+
|
|
191
|
+
best_instance = resource_type.select_best_compatible_instance(compatible_instances, request.requirements)
|
|
192
|
+
if not best_instance:
|
|
193
|
+
return AcquireResourceInstanceLockResultFailure(
|
|
194
|
+
result_details=f"Attempted to acquire resource instance lock for owner {request.owner_id} with resource type {request.resource_type_name} and requirements {request.requirements}. Failed due to no compatible resource instances available."
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
best_instance.acquire_lock(request.owner_id)
|
|
199
|
+
except Exception as e:
|
|
200
|
+
return AcquireResourceInstanceLockResultFailure(
|
|
201
|
+
result_details=f"Attempted to acquire resource instance lock for owner {request.owner_id} with resource type {request.resource_type_name} and requirements {request.requirements}. Failed due to lock acquisition failed: {e}."
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
instance_id = best_instance.get_instance_id()
|
|
205
|
+
|
|
206
|
+
return AcquireResourceInstanceLockResultSuccess(
|
|
207
|
+
instance_id=instance_id,
|
|
208
|
+
result_details=f"Successfully acquired lock on resource instance {instance_id} for {request.owner_id}",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def on_release_resource_instance_lock_request(self, request: ReleaseResourceInstanceLockRequest) -> ResultPayload:
|
|
212
|
+
"""Handle request to release a resource instance lock."""
|
|
213
|
+
instance = self._instances.get(request.instance_id)
|
|
214
|
+
if instance is None:
|
|
215
|
+
return ReleaseResourceInstanceLockResultFailure(
|
|
216
|
+
result_details=f"Attempted to release resource instance lock on {request.instance_id} for owner {request.owner_id}. Failed due to resource instance does not exist."
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
instance.release_lock(request.owner_id)
|
|
221
|
+
except Exception as e:
|
|
222
|
+
return ReleaseResourceInstanceLockResultFailure(
|
|
223
|
+
result_details=f"Attempted to release resource instance lock on {request.instance_id} for owner {request.owner_id}. Failed due to lock release failed: {e}."
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
return ReleaseResourceInstanceLockResultSuccess(
|
|
227
|
+
result_details=f"Successfully released lock on resource instance {request.instance_id} from {request.owner_id}"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
def on_list_compatible_resource_instances_request(
|
|
231
|
+
self, request: ListCompatibleResourceInstancesRequest
|
|
232
|
+
) -> ResultPayload:
|
|
233
|
+
"""Handle request to list compatible resource instances."""
|
|
234
|
+
resource_type = self._get_resource_type_by_name(request.resource_type_name)
|
|
235
|
+
if not resource_type:
|
|
236
|
+
return ListCompatibleResourceInstancesResultFailure(
|
|
237
|
+
result_details=f"Attempted to list compatible resource instances with resource type {request.resource_type_name}, requirements {request.requirements}, and include_locked={request.include_locked}. Failed due to resource type not found."
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Get compatible instances (with optional locked instances)
|
|
241
|
+
instance_ids = []
|
|
242
|
+
for instance in self._instances.values():
|
|
243
|
+
if instance.is_locked() and not request.include_locked:
|
|
244
|
+
continue
|
|
245
|
+
if instance.get_resource_type() != resource_type:
|
|
246
|
+
continue
|
|
247
|
+
if request.requirements is None:
|
|
248
|
+
instance_ids.append(instance.get_instance_id())
|
|
249
|
+
continue
|
|
250
|
+
if instance.is_compatible_with(request.requirements):
|
|
251
|
+
instance_ids.append(instance.get_instance_id())
|
|
252
|
+
|
|
253
|
+
return ListCompatibleResourceInstancesResultSuccess(
|
|
254
|
+
instance_ids=instance_ids,
|
|
255
|
+
result_details=f"Successfully found {len(instance_ids)} compatible resource instances",
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def on_get_resource_instance_status_request(self, request: GetResourceInstanceStatusRequest) -> ResultPayload:
|
|
259
|
+
"""Handle request to get resource instance status."""
|
|
260
|
+
instance = self._instances.get(request.instance_id)
|
|
261
|
+
if instance is None:
|
|
262
|
+
return GetResourceInstanceStatusResultFailure(
|
|
263
|
+
result_details=f"Attempted to get resource instance status for {request.instance_id}. Failed due to resource instance not found."
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
status = ResourceStatus(
|
|
267
|
+
resource_type=instance.get_resource_type(),
|
|
268
|
+
instance_id=request.instance_id,
|
|
269
|
+
owner_of_lock=instance.get_lock_owner(),
|
|
270
|
+
capabilities=instance.get_all_capabilities_and_current_values(),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
return GetResourceInstanceStatusResultSuccess(
|
|
274
|
+
status=status,
|
|
275
|
+
result_details=f"Successfully retrieved status for resource instance {request.instance_id}",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
def on_list_resource_instances_by_type_request(self, request: ListResourceInstancesByTypeRequest) -> ResultPayload:
|
|
279
|
+
"""Handle request to list resource instances by type."""
|
|
280
|
+
resource_type = self._get_resource_type_by_name(request.resource_type_name)
|
|
281
|
+
if not resource_type:
|
|
282
|
+
return ListResourceInstancesByTypeResultFailure(
|
|
283
|
+
result_details=f"Attempted to list resource instances by type {request.resource_type_name} with include_locked={request.include_locked}. Failed due to resource type not found."
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
matching_instances = []
|
|
287
|
+
for instance in self._instances.values():
|
|
288
|
+
if instance.get_resource_type() != resource_type:
|
|
289
|
+
continue
|
|
290
|
+
if not request.include_locked and instance.is_locked():
|
|
291
|
+
continue
|
|
292
|
+
matching_instances.append(instance.get_instance_id())
|
|
293
|
+
|
|
294
|
+
return ListResourceInstancesByTypeResultSuccess(
|
|
295
|
+
instance_ids=matching_instances,
|
|
296
|
+
result_details=f"Successfully found {len(matching_instances)} resource instances of specified type",
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# Private Implementation Methods
|
|
300
|
+
|
|
301
|
+
def _get_resource_type_by_name(self, name: str) -> ResourceType | None:
|
|
302
|
+
"""Get a registered resource type by its class name."""
|
|
303
|
+
for resource_type in self._resource_types:
|
|
304
|
+
if type(resource_type).__name__ == name:
|
|
305
|
+
return resource_type
|
|
306
|
+
return None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Resource types package containing concrete resource type implementations."""
|