inorbit-edge-executor 3.2.0__tar.gz → 3.2.3__tar.gz
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.
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/PKG-INFO +1 -1
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/behavior_tree.py +42 -5
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor.egg-info/PKG-INFO +1 -1
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor.egg-info/SOURCES.txt +1 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/pyproject.toml +2 -2
- inorbit_edge_executor-3.2.3/tests/test_run_action_node.py +288 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/LICENSE +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/README.md +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/__init__.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/datatypes.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/db.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/dummy_backend.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/exceptions.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/inorbit.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/logger.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/mission.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/observable.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/sqlite_backend.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/worker.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/worker_pool.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor.egg-info/dependency_links.txt +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor.egg-info/requires.txt +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor.egg-info/top_level.txt +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/setup.cfg +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/tests/test_behavior_tree.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/tests/test_behavior_tree_error_handler.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/tests/test_set_data_node.py +0 -0
- {inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/tests/test_wait_expression_node.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: inorbit_edge_executor
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.3
|
|
4
4
|
Summary: InOrbit Edge Missions Executor
|
|
5
5
|
Author-email: Mariano Cereda <mariano.cereda@inorbit.ai>, Hernan Badenes <herchu@inorbit.ai>, Miguel Garcia <mike@inorbit.ai>
|
|
6
6
|
License: MIT
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/behavior_tree.py
RENAMED
|
@@ -557,6 +557,8 @@ class RunActionNode(BehaviorTree):
|
|
|
557
557
|
action_id,
|
|
558
558
|
arguments,
|
|
559
559
|
target: Target = None,
|
|
560
|
+
max_retries: int = 3,
|
|
561
|
+
retry_wait_seconds: float = 5.0,
|
|
560
562
|
**kwargs,
|
|
561
563
|
):
|
|
562
564
|
super().__init__(**kwargs)
|
|
@@ -564,6 +566,8 @@ class RunActionNode(BehaviorTree):
|
|
|
564
566
|
self.action_id = action_id
|
|
565
567
|
self.arguments = arguments
|
|
566
568
|
self.target = target
|
|
569
|
+
self.max_retries = max_retries
|
|
570
|
+
self.retry_wait_seconds = retry_wait_seconds
|
|
567
571
|
if self.target is None:
|
|
568
572
|
self.robot = context.robot_api
|
|
569
573
|
else:
|
|
@@ -571,9 +575,29 @@ class RunActionNode(BehaviorTree):
|
|
|
571
575
|
|
|
572
576
|
async def _execute(self):
|
|
573
577
|
arguments = await self.mt.resolve_arguments(self.arguments)
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
578
|
+
|
|
579
|
+
last_exception = None
|
|
580
|
+
for attempt in range(self.max_retries + 1):
|
|
581
|
+
try:
|
|
582
|
+
resp = await self.robot.execute_action(self.action_id, arguments=arguments)
|
|
583
|
+
# TODO track action execution, as done in the app. This JSON response only guarantees
|
|
584
|
+
# the action was *started*.
|
|
585
|
+
return # Success, exit retry loop
|
|
586
|
+
except Exception as e:
|
|
587
|
+
last_exception = e
|
|
588
|
+
if attempt < self.max_retries:
|
|
589
|
+
logger.warning(
|
|
590
|
+
f"Action execution failed (attempt {attempt + 1}/{self.max_retries + 1}): {e}. "
|
|
591
|
+
f"Retrying in {self.retry_wait_seconds} seconds..."
|
|
592
|
+
)
|
|
593
|
+
await asyncio.sleep(self.retry_wait_seconds)
|
|
594
|
+
else:
|
|
595
|
+
logger.error(
|
|
596
|
+
f"Action execution failed after {self.max_retries + 1} attempts: {e}"
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
# All retries exhausted, raise the last exception
|
|
600
|
+
raise last_exception
|
|
577
601
|
|
|
578
602
|
async def on_pause(self):
|
|
579
603
|
# TODO (Elvio): Here goes the logic to stop an action when a Mission is paused
|
|
@@ -584,15 +608,28 @@ class RunActionNode(BehaviorTree):
|
|
|
584
608
|
object = super().dump_object()
|
|
585
609
|
object["action_id"] = self.action_id
|
|
586
610
|
object["arguments"] = self.arguments
|
|
611
|
+
object["max_retries"] = self.max_retries
|
|
612
|
+
object["retry_wait_seconds"] = self.retry_wait_seconds
|
|
587
613
|
if self.target is not None:
|
|
588
614
|
object["target"] = self.target.dump_object()
|
|
589
615
|
return object
|
|
590
616
|
|
|
591
617
|
@classmethod
|
|
592
|
-
def from_object(
|
|
618
|
+
def from_object(
|
|
619
|
+
cls,
|
|
620
|
+
context,
|
|
621
|
+
action_id,
|
|
622
|
+
arguments,
|
|
623
|
+
target=None,
|
|
624
|
+
max_retries=3,
|
|
625
|
+
retry_wait_seconds=5.0,
|
|
626
|
+
**kwargs,
|
|
627
|
+
):
|
|
593
628
|
if target is not None:
|
|
594
629
|
target = Target.from_object(**target)
|
|
595
|
-
return RunActionNode(
|
|
630
|
+
return RunActionNode(
|
|
631
|
+
context, action_id, arguments, target, max_retries, retry_wait_seconds, **kwargs
|
|
632
|
+
)
|
|
596
633
|
|
|
597
634
|
|
|
598
635
|
class WaitExpressionNode(BehaviorTree):
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: inorbit_edge_executor
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.3
|
|
4
4
|
Summary: InOrbit Edge Missions Executor
|
|
5
5
|
Author-email: Mariano Cereda <mariano.cereda@inorbit.ai>, Hernan Badenes <herchu@inorbit.ai>, Miguel Garcia <mike@inorbit.ai>
|
|
6
6
|
License: MIT
|
|
@@ -21,5 +21,6 @@ inorbit_edge_executor.egg-info/requires.txt
|
|
|
21
21
|
inorbit_edge_executor.egg-info/top_level.txt
|
|
22
22
|
tests/test_behavior_tree.py
|
|
23
23
|
tests/test_behavior_tree_error_handler.py
|
|
24
|
+
tests/test_run_action_node.py
|
|
24
25
|
tests/test_set_data_node.py
|
|
25
26
|
tests/test_wait_expression_node.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "inorbit_edge_executor"
|
|
7
|
-
version = "3.2.
|
|
7
|
+
version = "3.2.3"
|
|
8
8
|
description = "InOrbit Edge Missions Executor"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -47,7 +47,7 @@ target-version = ['py310']
|
|
|
47
47
|
line-length = 100
|
|
48
48
|
|
|
49
49
|
[tool.bumpversion]
|
|
50
|
-
current_version = "3.2.
|
|
50
|
+
current_version = "3.2.3"
|
|
51
51
|
commit = true
|
|
52
52
|
tag = true
|
|
53
53
|
tag_name = "v{new_version}"
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import AsyncMock, Mock
|
|
3
|
+
|
|
4
|
+
from inorbit_edge_executor.behavior_tree import RunActionNode, BehaviorTreeBuilderContext
|
|
5
|
+
from inorbit_edge_executor.datatypes import Target
|
|
6
|
+
from inorbit_edge_executor.inorbit import RobotApi, RobotApiFactory
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestRunActionNodeRetry:
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def mock_robot_api(self):
|
|
12
|
+
"""Mock RobotApi for testing"""
|
|
13
|
+
mock_api = Mock(spec=RobotApi)
|
|
14
|
+
mock_api.robot_id = "test_robot"
|
|
15
|
+
mock_api.execute_action = AsyncMock()
|
|
16
|
+
return mock_api
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def mock_mission_tracking(self):
|
|
20
|
+
"""Mock mission tracking for testing"""
|
|
21
|
+
mock_mt = Mock()
|
|
22
|
+
mock_mt.resolve_arguments = AsyncMock(return_value={"param": "value"})
|
|
23
|
+
return mock_mt
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def context(self, mock_robot_api, mock_mission_tracking):
|
|
27
|
+
"""Create a test context with mocked dependencies"""
|
|
28
|
+
context = BehaviorTreeBuilderContext()
|
|
29
|
+
context.robot_api = mock_robot_api
|
|
30
|
+
context.mt = mock_mission_tracking
|
|
31
|
+
return context
|
|
32
|
+
|
|
33
|
+
@pytest.mark.asyncio
|
|
34
|
+
async def test_execute_action_success_first_try(self, context, mock_robot_api):
|
|
35
|
+
"""Test successful action execution on first attempt"""
|
|
36
|
+
# Setup
|
|
37
|
+
mock_robot_api.execute_action.return_value = {"status": "success"}
|
|
38
|
+
|
|
39
|
+
node = RunActionNode(
|
|
40
|
+
context=context,
|
|
41
|
+
action_id="test_action",
|
|
42
|
+
arguments={"param": "value"},
|
|
43
|
+
max_retries=3,
|
|
44
|
+
retry_wait_seconds=0.01,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Execute
|
|
48
|
+
await node._execute()
|
|
49
|
+
|
|
50
|
+
# Verify
|
|
51
|
+
mock_robot_api.execute_action.assert_called_once_with(
|
|
52
|
+
"test_action", arguments={"param": "value"}
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
@pytest.mark.asyncio
|
|
56
|
+
async def test_execute_action_success_after_retries(self, context, mock_robot_api):
|
|
57
|
+
"""Test successful action execution after some failures"""
|
|
58
|
+
# Setup - fail first 2 attempts, succeed on 3rd
|
|
59
|
+
mock_robot_api.execute_action.side_effect = [
|
|
60
|
+
Exception("Network timeout"),
|
|
61
|
+
Exception("Connection refused"),
|
|
62
|
+
{"status": "success"}, # Success on third try
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
node = RunActionNode(
|
|
66
|
+
context=context,
|
|
67
|
+
action_id="test_action",
|
|
68
|
+
arguments={"param": "value"},
|
|
69
|
+
max_retries=3,
|
|
70
|
+
retry_wait_seconds=0.01,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Execute
|
|
74
|
+
await node._execute()
|
|
75
|
+
|
|
76
|
+
# Verify
|
|
77
|
+
assert mock_robot_api.execute_action.call_count == 3
|
|
78
|
+
|
|
79
|
+
@pytest.mark.asyncio
|
|
80
|
+
async def test_execute_action_fail_all_retries(self, context, mock_robot_api):
|
|
81
|
+
"""Test action execution failure after exhausting all retries"""
|
|
82
|
+
# Setup - all attempts fail
|
|
83
|
+
test_exception = Exception("Persistent network error")
|
|
84
|
+
mock_robot_api.execute_action.side_effect = test_exception
|
|
85
|
+
|
|
86
|
+
node = RunActionNode(
|
|
87
|
+
context=context,
|
|
88
|
+
action_id="test_action",
|
|
89
|
+
arguments={"param": "value"},
|
|
90
|
+
max_retries=2,
|
|
91
|
+
retry_wait_seconds=0.01,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Execute and verify exception is raised
|
|
95
|
+
with pytest.raises(Exception, match="Persistent network error"):
|
|
96
|
+
await node._execute()
|
|
97
|
+
|
|
98
|
+
# Verify all retry attempts were made (max_retries + 1 = 3 attempts)
|
|
99
|
+
assert mock_robot_api.execute_action.call_count == 3
|
|
100
|
+
|
|
101
|
+
@pytest.mark.asyncio
|
|
102
|
+
async def test_execute_action_different_exception_types(self, context, mock_robot_api):
|
|
103
|
+
"""Test that different types of exceptions are handled consistently"""
|
|
104
|
+
exceptions_to_test = [
|
|
105
|
+
ValueError("Invalid parameter"),
|
|
106
|
+
ConnectionError("Network unreachable"),
|
|
107
|
+
TimeoutError("Request timeout"),
|
|
108
|
+
RuntimeError("Service unavailable"),
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
for exception in exceptions_to_test:
|
|
112
|
+
# Reset mock
|
|
113
|
+
mock_robot_api.execute_action.reset_mock()
|
|
114
|
+
mock_robot_api.execute_action.side_effect = exception
|
|
115
|
+
|
|
116
|
+
node = RunActionNode(
|
|
117
|
+
context=context,
|
|
118
|
+
action_id="test_action",
|
|
119
|
+
arguments={"param": "value"},
|
|
120
|
+
max_retries=1,
|
|
121
|
+
retry_wait_seconds=0.01,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Execute and verify exception is raised
|
|
125
|
+
with pytest.raises(type(exception)):
|
|
126
|
+
await node._execute()
|
|
127
|
+
|
|
128
|
+
# Verify retries were attempted
|
|
129
|
+
assert mock_robot_api.execute_action.call_count == 2
|
|
130
|
+
|
|
131
|
+
@pytest.mark.asyncio
|
|
132
|
+
async def test_execute_action_with_custom_max_retries(self, context, mock_robot_api):
|
|
133
|
+
"""Test action execution with different max_retries values"""
|
|
134
|
+
test_cases = [0, 1, 5, 10]
|
|
135
|
+
|
|
136
|
+
for max_retries in test_cases:
|
|
137
|
+
# Reset mock
|
|
138
|
+
mock_robot_api.execute_action.reset_mock()
|
|
139
|
+
mock_robot_api.execute_action.side_effect = Exception("Always fail")
|
|
140
|
+
|
|
141
|
+
node = RunActionNode(
|
|
142
|
+
context=context,
|
|
143
|
+
action_id="test_action",
|
|
144
|
+
arguments={"param": "value"},
|
|
145
|
+
max_retries=max_retries,
|
|
146
|
+
retry_wait_seconds=0.01,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Execute and verify exception is raised
|
|
150
|
+
with pytest.raises(Exception):
|
|
151
|
+
await node._execute()
|
|
152
|
+
|
|
153
|
+
# Verify correct number of attempts (max_retries + 1)
|
|
154
|
+
expected_attempts = max_retries + 1
|
|
155
|
+
assert mock_robot_api.execute_action.call_count == expected_attempts
|
|
156
|
+
|
|
157
|
+
@pytest.mark.asyncio
|
|
158
|
+
async def test_retry_delay_timing(self, context, mock_robot_api):
|
|
159
|
+
"""Test that retry delays are working (approximate timing)"""
|
|
160
|
+
# Setup
|
|
161
|
+
mock_robot_api.execute_action.side_effect = [
|
|
162
|
+
Exception("Fail 1"),
|
|
163
|
+
Exception("Fail 2"),
|
|
164
|
+
{"status": "success"},
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
node = RunActionNode(
|
|
168
|
+
context=context,
|
|
169
|
+
action_id="test_action",
|
|
170
|
+
arguments={"param": "value"},
|
|
171
|
+
max_retries=2,
|
|
172
|
+
retry_wait_seconds=0.01,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Execute with timing
|
|
176
|
+
import time
|
|
177
|
+
|
|
178
|
+
start_time = time.time()
|
|
179
|
+
await node._execute()
|
|
180
|
+
end_time = time.time()
|
|
181
|
+
|
|
182
|
+
# Should take at least 0.02 seconds (2 retries * 0.01 seconds each)
|
|
183
|
+
# but allow some tolerance for test execution overhead
|
|
184
|
+
assert end_time - start_time >= 0.015
|
|
185
|
+
|
|
186
|
+
@pytest.mark.asyncio
|
|
187
|
+
async def test_execute_action_with_target_robot(self, mock_mission_tracking):
|
|
188
|
+
"""Test action execution with a different target robot"""
|
|
189
|
+
# Setup target robot
|
|
190
|
+
target = Target(robotId="target_robot")
|
|
191
|
+
|
|
192
|
+
# Create mock robot API factory
|
|
193
|
+
mock_target_robot_api = Mock(spec=RobotApi)
|
|
194
|
+
mock_target_robot_api.execute_action = AsyncMock(return_value={"status": "success"})
|
|
195
|
+
|
|
196
|
+
mock_factory = Mock(spec=RobotApiFactory)
|
|
197
|
+
mock_factory.build.return_value = mock_target_robot_api
|
|
198
|
+
|
|
199
|
+
context = BehaviorTreeBuilderContext()
|
|
200
|
+
context.robot_api_factory = mock_factory
|
|
201
|
+
context.mt = mock_mission_tracking
|
|
202
|
+
|
|
203
|
+
node = RunActionNode(
|
|
204
|
+
context=context,
|
|
205
|
+
action_id="test_action",
|
|
206
|
+
arguments={"param": "value"},
|
|
207
|
+
target=target,
|
|
208
|
+
max_retries=2,
|
|
209
|
+
retry_wait_seconds=0.01,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Execute
|
|
213
|
+
await node._execute()
|
|
214
|
+
|
|
215
|
+
# Verify
|
|
216
|
+
mock_factory.build.assert_called_once_with("target_robot")
|
|
217
|
+
mock_target_robot_api.execute_action.assert_called_once_with(
|
|
218
|
+
"test_action", arguments={"param": "value"}
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def test_dump_object_includes_max_retries(self, context):
|
|
222
|
+
"""Test that dump_object includes max_retries parameter"""
|
|
223
|
+
node = RunActionNode(
|
|
224
|
+
context=context,
|
|
225
|
+
action_id="test_action",
|
|
226
|
+
arguments={"param": "value"},
|
|
227
|
+
max_retries=5,
|
|
228
|
+
retry_wait_seconds=0.01,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
dumped = node.dump_object()
|
|
232
|
+
|
|
233
|
+
assert dumped["max_retries"] == 5
|
|
234
|
+
assert dumped["retry_wait_seconds"] == 0.01
|
|
235
|
+
assert dumped["action_id"] == "test_action"
|
|
236
|
+
assert dumped["arguments"] == {"param": "value"}
|
|
237
|
+
|
|
238
|
+
def test_from_object_creates_node_with_max_retries(self, context):
|
|
239
|
+
"""Test that from_object correctly handles max_retries parameter"""
|
|
240
|
+
node = RunActionNode.from_object(
|
|
241
|
+
context=context,
|
|
242
|
+
action_id="test_action",
|
|
243
|
+
arguments={"param": "value"},
|
|
244
|
+
max_retries=7,
|
|
245
|
+
retry_wait_seconds=0.01,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
assert node.max_retries == 7
|
|
249
|
+
assert node.retry_wait_seconds == 0.01
|
|
250
|
+
assert node.action_id == "test_action"
|
|
251
|
+
assert node.arguments == {"param": "value"}
|
|
252
|
+
|
|
253
|
+
def test_from_object_defaults_max_retries(self, context):
|
|
254
|
+
"""Test that from_object uses default max_retries when not specified"""
|
|
255
|
+
node = RunActionNode.from_object(
|
|
256
|
+
context=context, action_id="test_action", arguments={"param": "value"}
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
assert node.max_retries == 3 # Default value
|
|
260
|
+
assert node.retry_wait_seconds == 5.0 # Default value
|
|
261
|
+
|
|
262
|
+
def test_serialization_round_trip(self, context):
|
|
263
|
+
"""Test that serialization and deserialization preserve max_retries"""
|
|
264
|
+
original_node = RunActionNode(
|
|
265
|
+
context=context,
|
|
266
|
+
action_id="test_action",
|
|
267
|
+
arguments={"param": "value"},
|
|
268
|
+
max_retries=4,
|
|
269
|
+
retry_wait_seconds=0.01,
|
|
270
|
+
label="test_label",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Serialize
|
|
274
|
+
dumped = original_node.dump_object()
|
|
275
|
+
|
|
276
|
+
# Remove type field that's used by the serialization system
|
|
277
|
+
dumped_for_restore = dumped.copy()
|
|
278
|
+
del dumped_for_restore["type"]
|
|
279
|
+
|
|
280
|
+
# Deserialize
|
|
281
|
+
restored_node = RunActionNode.from_object(context=context, **dumped_for_restore)
|
|
282
|
+
|
|
283
|
+
# Verify all properties are preserved
|
|
284
|
+
assert restored_node.max_retries == 4
|
|
285
|
+
assert restored_node.retry_wait_seconds == 0.01
|
|
286
|
+
assert restored_node.action_id == "test_action"
|
|
287
|
+
assert restored_node.arguments == {"param": "value"}
|
|
288
|
+
assert restored_node.label == "test_label"
|
|
File without changes
|
|
File without changes
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/__init__.py
RENAMED
|
File without changes
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/datatypes.py
RENAMED
|
File without changes
|
|
File without changes
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/dummy_backend.py
RENAMED
|
File without changes
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/exceptions.py
RENAMED
|
File without changes
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/inorbit.py
RENAMED
|
File without changes
|
|
File without changes
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/mission.py
RENAMED
|
File without changes
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/observable.py
RENAMED
|
File without changes
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/sqlite_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/inorbit_edge_executor/worker_pool.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{inorbit_edge_executor-3.2.0 → inorbit_edge_executor-3.2.3}/tests/test_wait_expression_node.py
RENAMED
|
File without changes
|