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.
Files changed (35) hide show
  1. griptape_nodes/cli/commands/init.py +88 -0
  2. griptape_nodes/cli/commands/models.py +2 -0
  3. griptape_nodes/cli/shared.py +1 -0
  4. griptape_nodes/exe_types/core_types.py +104 -0
  5. griptape_nodes/exe_types/node_types.py +9 -12
  6. griptape_nodes/machines/control_flow.py +10 -0
  7. griptape_nodes/machines/dag_builder.py +21 -2
  8. griptape_nodes/machines/parallel_resolution.py +25 -10
  9. griptape_nodes/node_library/workflow_registry.py +73 -3
  10. griptape_nodes/retained_mode/events/execution_events.py +12 -2
  11. griptape_nodes/retained_mode/events/flow_events.py +58 -0
  12. griptape_nodes/retained_mode/events/resource_events.py +290 -0
  13. griptape_nodes/retained_mode/events/workflow_events.py +57 -2
  14. griptape_nodes/retained_mode/griptape_nodes.py +9 -1
  15. griptape_nodes/retained_mode/managers/flow_manager.py +678 -12
  16. griptape_nodes/retained_mode/managers/library_manager.py +13 -19
  17. griptape_nodes/retained_mode/managers/model_manager.py +184 -83
  18. griptape_nodes/retained_mode/managers/node_manager.py +3 -3
  19. griptape_nodes/retained_mode/managers/os_manager.py +118 -1
  20. griptape_nodes/retained_mode/managers/resource_components/__init__.py +1 -0
  21. griptape_nodes/retained_mode/managers/resource_components/capability_field.py +41 -0
  22. griptape_nodes/retained_mode/managers/resource_components/comparator.py +18 -0
  23. griptape_nodes/retained_mode/managers/resource_components/resource_instance.py +236 -0
  24. griptape_nodes/retained_mode/managers/resource_components/resource_type.py +79 -0
  25. griptape_nodes/retained_mode/managers/resource_manager.py +306 -0
  26. griptape_nodes/retained_mode/managers/resource_types/__init__.py +1 -0
  27. griptape_nodes/retained_mode/managers/resource_types/cpu_resource.py +108 -0
  28. griptape_nodes/retained_mode/managers/resource_types/os_resource.py +87 -0
  29. griptape_nodes/retained_mode/managers/settings.py +5 -0
  30. griptape_nodes/retained_mode/managers/sync_manager.py +10 -3
  31. griptape_nodes/retained_mode/managers/workflow_manager.py +359 -261
  32. {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/METADATA +1 -1
  33. {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/RECORD +35 -25
  34. {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/WHEEL +1 -1
  35. {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."""