Skip to content

Commit 66260d2

Browse files
committed
Finish fixing #16. The automatic throttling/batching is built into the interface cleanly, with controls and status.
1 parent 1b19ebf commit 66260d2

File tree

5 files changed

+256
-101
lines changed

5 files changed

+256
-101
lines changed

tests/test_actions.py

Lines changed: 123 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
import json
2222

2323
import mock
24+
import pytest
2425

2526
from conftest import mock_connection_params, MockResponse
26-
from umapi_client import Connection, Action
27+
from umapi_client import Connection, Action, ServerError
2728

2829

2930
def test_action_create():
@@ -56,55 +57,75 @@ def test_action_create_two_dofirst():
5657
'{"a1": "a1 text", "do": [{"com2": {"com2k": "com2v"}}, {"com1": {"com1k": "com1v"}}], "z1": "z1 text"}'
5758

5859

59-
def test_execute_single_success():
60+
def test_execute_single_success_immediate():
6061
with mock.patch("umapi_client.connection.requests.post") as mock_post:
6162
mock_post.return_value = MockResponse(200, {"result": "success"})
6263
conn = Connection(**mock_connection_params)
6364
action = Action(top="top").append(a="a")
64-
assert conn.execute_single(action) == (1, 1)
65+
assert conn.execute_single(action, immediate=True) == (0, 1, 1)
6566

6667

67-
def test_execute_single_dofirst_success():
68+
def test_execute_single_success_queued():
6869
with mock.patch("umapi_client.connection.requests.post") as mock_post:
6970
mock_post.return_value = MockResponse(200, {"result": "success"})
70-
conn = Connection(**mock_connection_params)
71-
action = Action(top="top").insert(a="a")
72-
assert conn.execute_single(action) == (1, 1)
71+
conn = Connection(throttle_actions=2, **mock_connection_params)
72+
action = Action(top="top").append(a="a")
73+
assert conn.execute_single(action) == (1, 0, 0)
74+
assert conn.execute_single(action) == (0, 2, 2)
7375

7476

75-
def test_execute_multiple_success():
77+
def test_execute_single_error_queued_throttled():
7678
with mock.patch("umapi_client.connection.requests.post") as mock_post:
77-
mock_post.return_value = MockResponse(200, {"result": "success"})
78-
conn = Connection(**mock_connection_params)
79-
action0 = Action(top="top0").append(a="a0").append(b="b")
80-
action1 = Action(top="top1").append(a="a1")
81-
assert conn.execute_multiple([action0, action1]) == (2, 2)
79+
mock_post.side_effect = [MockResponse(200, {"result": "success"}),
80+
MockResponse(200, {"result": "partial",
81+
"completed": 1,
82+
"notCompleted": 1,
83+
"errors": [{"index": 1, "step": 0,
84+
"errorCode": "test.error",
85+
"message": "Test error message"}]})]
86+
conn = Connection(throttle_actions=2, throttle_commands=1, **mock_connection_params)
87+
action = Action(top="top").append(a="a").append(b="b").append(c="c").append(d="d")
88+
assert conn.execute_single(action) == (0, 4, 3)
89+
assert action.execution_errors() == [{"command": {"d": "d"},
90+
"errorCode": "test.error",
91+
"message": "Test error message"}]
8292

8393

84-
def test_execute_multiple_dofirst_success():
94+
def test_execute_single_error_immediate_throttled():
95+
with mock.patch("umapi_client.connection.requests.post") as mock_post:
96+
mock_post.return_value = MockResponse(200, {"result": "partial",
97+
"completed": 1,
98+
"notCompleted": 1,
99+
"errors": [{"index": 1, "step": 0, "errorCode": "test"}]})
100+
conn = Connection(throttle_commands=2, **mock_connection_params)
101+
action = Action(top="top0").append(a="a0").append(a="a1").append(a="a2")
102+
assert conn.execute_single(action, immediate=True) == (0, 2, 1)
103+
assert action.execution_errors() == [{"command": {"a": "a2"}, "errorCode": "test"}]
104+
105+
106+
def test_execute_single_dofirst_success_immediate():
85107
with mock.patch("umapi_client.connection.requests.post") as mock_post:
86108
mock_post.return_value = MockResponse(200, {"result": "success"})
87109
conn = Connection(**mock_connection_params)
88-
action0 = Action(top="top0").append(a="a0").insert(b="b")
89-
action1 = Action(top="top1").append(a="a1")
90-
assert conn.execute_multiple([action0, action1]) == (2, 2)
110+
action = Action(top="top").insert(a="a")
111+
assert conn.execute_single(action, immediate=True) == (0, 1, 1)
91112

92113

93-
def test_execute_single_error():
114+
def test_execute_single_error_immediate():
94115
with mock.patch("umapi_client.connection.requests.post") as mock_post:
95116
mock_post.return_value = MockResponse(200, {"result": "error",
96117
"errors": [{"index": 0, "step": 0,
97118
"errorCode": "test.error",
98119
"message": "Test error message"}]})
99120
conn = Connection(**mock_connection_params)
100121
action = Action(top="top").append(a="a")
101-
assert conn.execute_single(action) == (1, 0)
122+
assert conn.execute_single(action, immediate=True) == (0, 1, 0)
102123
assert action.execution_errors() == [{"command": {"a": "a"},
103124
"errorCode": "test.error",
104125
"message": "Test error message"}]
105126

106127

107-
def test_execute_single_multi_error():
128+
def test_execute_single_multi_error_immediate():
108129
with mock.patch("umapi_client.connection.requests.post") as mock_post:
109130
mock_post.return_value = MockResponse(200, {"result": "error",
110131
"errors": [{"index": 0, "step": 0,
@@ -115,7 +136,7 @@ def test_execute_single_multi_error():
115136
"message": "message2"}]})
116137
conn = Connection(**mock_connection_params)
117138
action = Action(top="top").append(a="a")
118-
assert conn.execute_single(action) == (1, 0)
139+
assert conn.execute_single(action, immediate=True) == (0, 1, 0)
119140
assert action.execution_errors() == [{"command": {"a": "a"},
120141
"errorCode": "error1",
121142
"message": "message1"},
@@ -124,20 +145,48 @@ def test_execute_single_multi_error():
124145
"message": "message2"}]
125146

126147

127-
def test_execute_single_dofirst_error():
148+
def test_execute_single_dofirst_error_immediate():
128149
with mock.patch("umapi_client.connection.requests.post") as mock_post:
129150
mock_post.return_value = MockResponse(200, {"result": "error",
130151
"errors": [{"index": 0, "step": 0,
131152
"errorCode": "test.error",
132153
"message": "Test error message"}]})
133154
conn = Connection(**mock_connection_params)
134155
action = Action(top="top").insert(a="a")
135-
assert conn.execute_single(action) == (1, 0)
156+
assert conn.execute_single(action, immediate=True) == (0, 1, 0)
136157
assert action.execution_errors() == [{"command": {"a": "a"},
137158
"errorCode": "test.error",
138159
"message": "Test error message"}]
139160

140161

162+
def test_execute_multiple_success():
163+
with mock.patch("umapi_client.connection.requests.post") as mock_post:
164+
mock_post.return_value = MockResponse(200, {"result": "success"})
165+
conn = Connection(**mock_connection_params)
166+
action0 = Action(top="top0").append(a="a0").append(b="b")
167+
action1 = Action(top="top1").append(a="a1")
168+
assert conn.execute_multiple([action0, action1]) == (0, 2, 2)
169+
170+
171+
def test_execute_multiple_success_queued():
172+
with mock.patch("umapi_client.connection.requests.post") as mock_post:
173+
mock_post.return_value = MockResponse(200, {"result": "success"})
174+
conn = Connection(**mock_connection_params)
175+
action0 = Action(top="top0").append(a="a0").append(b="b")
176+
action1 = Action(top="top1").append(a="a1")
177+
assert conn.execute_multiple([action0, action1], immediate=False) == (2, 0, 0)
178+
assert conn.execute_queued() == (0, 2, 2)
179+
180+
181+
def test_execute_multiple_dofirst_success():
182+
with mock.patch("umapi_client.connection.requests.post") as mock_post:
183+
mock_post.return_value = MockResponse(200, {"result": "success"})
184+
conn = Connection(**mock_connection_params)
185+
action0 = Action(top="top0").append(a="a0").insert(b="b")
186+
action1 = Action(top="top1").append(a="a1")
187+
assert conn.execute_multiple([action0, action1]) == (0, 2, 2)
188+
189+
141190
def test_execute_multiple_error():
142191
with mock.patch("umapi_client.connection.requests.post") as mock_post:
143192
mock_post.return_value = MockResponse(200, {"result": "partial",
@@ -149,7 +198,7 @@ def test_execute_multiple_error():
149198
conn = Connection(**mock_connection_params)
150199
action0 = Action(top="top0").append(a="a0")
151200
action1 = Action(top="top1").append(a="a1").append(b="b")
152-
assert conn.execute_multiple([action0, action1]) == (2, 1)
201+
assert conn.execute_multiple([action0, action1]) == (0, 2, 1)
153202
assert action0.execution_errors() == []
154203
assert action1.execution_errors() == [{"command": {"b": "b"},
155204
"errorCode": "test.error",
@@ -170,7 +219,7 @@ def test_execute_multiple_multi_error():
170219
conn = Connection(**mock_connection_params)
171220
action0 = Action(top="top0").append(a="a0")
172221
action1 = Action(top="top1").append(a="a1").append(b="b")
173-
assert conn.execute_multiple([action0, action1]) == (2, 1)
222+
assert conn.execute_multiple([action0, action1]) == (0, 2, 1)
174223
assert action0.execution_errors() == []
175224
assert action1.execution_errors() == [{"command": {"b": "b"},
176225
"errorCode": "error1",
@@ -191,37 +240,70 @@ def test_execute_multiple_dofirst_error():
191240
conn = Connection(**mock_connection_params)
192241
action0 = Action(top="top0").append(a="a0")
193242
action1 = Action(top="top1").append(a="a1").insert(b="b")
194-
assert conn.execute_multiple([action0, action1]) == (2, 1)
243+
assert conn.execute_multiple([action0, action1]) == (0, 2, 1)
195244
assert action0.execution_errors() == []
196245
assert action1.execution_errors() == [{"command": {"a": "a1"},
197246
"errorCode": "test.error",
198247
"message": "Test error message"}]
199248

200249

201-
def test_execute_single_throttle_commands():
202-
with mock.patch("umapi_client.connection.requests.post") as mock_post:
203-
mock_post.return_value = MockResponse(200, {"result": "partial",
204-
"completed": 1,
205-
"notCompleted": 1,
206-
"errors": [{"index": 1, "step": 0, "errorCode": "test"}]})
207-
conn = Connection(throttle_commands=2, **mock_connection_params)
208-
action = Action(top="top0").append(a="a0").append(a="a1").append(a="a2")
209-
assert conn.execute_single(action) == (2, 1)
210-
assert action.execution_errors() == [{"command": {"a": "a2"}, "errorCode": "test"}]
211-
212-
213-
def test_execute_multiple_throttle_actions():
250+
def test_execute_multiple_single_queued_throttle_actions():
214251
with mock.patch("umapi_client.connection.requests.post") as mock_post:
215252
mock_post.side_effect = [MockResponse(200, {"result": "success"}),
216253
MockResponse(200, {"result": "partial",
217-
"completed": 0,
254+
"completed": 1,
218255
"notCompleted": 1,
219256
"errors": [{"index": 0, "step": 0, "errorCode": "test"}]})]
220257
conn = Connection(throttle_actions=2, **mock_connection_params)
221258
action0 = Action(top="top0").append(a="a0")
222259
action1 = Action(top="top1").append(a="a1")
223260
action2 = Action(top="top2").append(a="a2")
224-
assert conn.execute_multiple([action0, action1, action2]) == (3, 2)
261+
action3 = Action(top="top3").append(a="a3")
262+
assert conn.execute_multiple([action0, action1, action2], immediate=False) == (1, 2, 2)
263+
local_status, server_status = conn.status(remote=False)
264+
assert server_status == {"status": "Never contacted",
265+
"endpoint": conn.endpoint}
266+
assert local_status == {"multiple-query-count": 0,
267+
"single-query-count": 0,
268+
"actions-sent": 2,
269+
"actions-completed": 2,
270+
"actions-queued": 1}
271+
assert conn.execute_single(action3) == (0, 2, 1)
272+
local_status, _ = conn.status(remote=False)
273+
assert local_status == {"multiple-query-count": 0,
274+
"single-query-count": 0,
275+
"actions-sent": 4,
276+
"actions-completed": 3,
277+
"actions-queued": 0}
225278
assert action0.execution_errors() == []
226279
assert action1.execution_errors() == []
227280
assert action2.execution_errors() == [{"command": {"a": "a2"}, "errorCode": "test"}]
281+
assert action3.execution_errors() == []
282+
283+
284+
def test_execute_multiple_queued_throttle_actions_error():
285+
with mock.patch("umapi_client.connection.requests.post") as mock_post:
286+
mock_post.return_value = MockResponse(500)
287+
conn = Connection(throttle_actions=2, **mock_connection_params)
288+
action0 = Action(top="top0").append(a="a0")
289+
action1 = Action(top="top1").append(a="a1")
290+
action2 = Action(top="top2").append(a="a2")
291+
action3 = Action(top="top3").append(a="a3")
292+
action4 = Action(top="top4").append(a="a4")
293+
action5 = Action(top="top5").append(a="a5")
294+
pytest.raises(ServerError, conn.execute_multiple,
295+
[action0, action1, action2, action3, action4, action5], immediate=False)
296+
local_status, _ = conn.status(remote=False)
297+
assert local_status == {"multiple-query-count": 0,
298+
"single-query-count": 0,
299+
"actions-sent": 2,
300+
"actions-completed": 0,
301+
"actions-queued": 4}
302+
mock_post.return_value = MockResponse(200, {"result": "success"})
303+
assert conn.execute_queued() == (0, 4, 4)
304+
local_status, _ = conn.status(remote=False)
305+
assert local_status == {"multiple-query-count": 0,
306+
"single-query-count": 0,
307+
"actions-sent": 6,
308+
"actions-completed": 4,
309+
"actions-queued": 0}

tests/test_connections.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,28 @@
2929
from umapi_client import Connection, UnavailableError, ServerError, RequestError, ClientError
3030

3131

32-
def test_status_success():
32+
def test_remote_status_success():
3333
with mock.patch("umapi_client.connection.requests.get") as mock_get:
3434
mock_get.return_value = MockResponse(200, body={"build": "2559", "version": "2.1.54", "state":"LIVE"})
3535
conn = Connection(**mock_connection_params)
36-
assert conn.status() == {"build": "2559", "version": "2.1.54", "state":"LIVE"}
36+
_, remote_status = conn.status(remote=True)
37+
assert remote_status == {"endpoint": "https://test/", "build": "2559", "version": "2.1.54", "state":"LIVE"}
3738

3839

39-
def test_status_failure():
40+
def test_remote_status_failure():
4041
with mock.patch("umapi_client.connection.requests.get") as mock_get:
4142
mock_get.return_value = MockResponse(404, text="404 Not Found")
4243
conn = Connection(**mock_connection_params)
43-
pytest.raises (ClientError, conn.status)
44+
_, remote_status = conn.status(remote=True)
45+
assert remote_status["status"].startswith("Unexpected")
4446

4547

46-
def test_status_timeout():
48+
def test_remote_status_timeout():
4749
with mock.patch("umapi_client.connection.requests.get") as mock_get:
4850
mock_get.side_effect = requests.Timeout
4951
conn = Connection(**mock_connection_params)
50-
pytest.raises(UnavailableError, conn.status)
52+
_, remote_status = conn.status(remote=True)
53+
assert remote_status["status"].startswith("Unreachable")
5154

5255

5356
def test_get_success():

tests/test_live.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def config():
4949

5050
def test_status(config):
5151
conn, _ = config
52-
status = conn.status()
52+
_, status = conn.status(remote=True)
5353
assert status["state"] == "LIVE"
5454

5555

umapi_client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@
2020

2121
from .connection import Connection
2222
from .api import Action, QuerySingle, QueryMultiple
23-
from .users import UserAction, UserQuery, UsersQuery, IdentityTypes, GroupTypes, RoleTypes
23+
from .users import UserAction, UserQuery, UsersQuery, IdentityTypes, GroupTypes, RoleTypes, IfAlreadyExistsOptions
2424
from .groups import GroupsQuery
2525
from .error import ClientError, RequestError, ServerError, UnavailableError

0 commit comments

Comments
 (0)