perspective-python 3.0.0rc1__cp39-abi3-win_amd64.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 (70) hide show
  1. perspective/__init__.py +78 -0
  2. perspective/extension/finos-perspective-nbextension.json +5 -0
  3. perspective/handlers/__init__.py +26 -0
  4. perspective/handlers/aiohttp.py +54 -0
  5. perspective/handlers/starlette.py +51 -0
  6. perspective/handlers/tornado.py +61 -0
  7. perspective/perspective.pyd +0 -0
  8. perspective/templates/exported_widget.html.jinja +35 -0
  9. perspective/tests/__init__.py +11 -0
  10. perspective/tests/conftest.py +268 -0
  11. perspective/tests/core/__init__.py +11 -0
  12. perspective/tests/core/test_async.py +436 -0
  13. perspective/tests/core/test_threadpool.py +48 -0
  14. perspective/tests/server/__init__.py +11 -0
  15. perspective/tests/server/test_server.py +1062 -0
  16. perspective/tests/server/test_session.py +55 -0
  17. perspective/tests/single_threaded/test_single_threaded.py +61 -0
  18. perspective/tests/table/__init__.py +11 -0
  19. perspective/tests/table/arrow/date32.arrow +0 -0
  20. perspective/tests/table/arrow/date64.arrow +0 -0
  21. perspective/tests/table/arrow/dict.arrow +0 -0
  22. perspective/tests/table/arrow/dict_update.arrow +0 -0
  23. perspective/tests/table/arrow/int_float_str.arrow +0 -0
  24. perspective/tests/table/arrow/int_float_str_file.arrow +0 -0
  25. perspective/tests/table/arrow/int_float_str_update.arrow +0 -0
  26. perspective/tests/table/object_sequence.py +402 -0
  27. perspective/tests/table/test_delete.py +124 -0
  28. perspective/tests/table/test_exception.py +53 -0
  29. perspective/tests/table/test_leaks.py +54 -0
  30. perspective/tests/table/test_ports.py +178 -0
  31. perspective/tests/table/test_remove.py +102 -0
  32. perspective/tests/table/test_table.py +610 -0
  33. perspective/tests/table/test_table_arrow.py +452 -0
  34. perspective/tests/table/test_table_datetime.py +2409 -0
  35. perspective/tests/table/test_table_infer.py +201 -0
  36. perspective/tests/table/test_table_limit.py +43 -0
  37. perspective/tests/table/test_table_numpy.py +1022 -0
  38. perspective/tests/table/test_table_pandas.py +1018 -0
  39. perspective/tests/table/test_to_arrow.py +414 -0
  40. perspective/tests/table/test_to_arrow_lz4.py +33 -0
  41. perspective/tests/table/test_to_format.py +1024 -0
  42. perspective/tests/table/test_update.py +545 -0
  43. perspective/tests/table/test_update_arrow.py +980 -0
  44. perspective/tests/table/test_update_numpy.py +252 -0
  45. perspective/tests/table/test_update_pandas.py +211 -0
  46. perspective/tests/table/test_view.py +2235 -0
  47. perspective/tests/table/test_view_expression.py +1940 -0
  48. perspective/tests/viewer/__init__.py +11 -0
  49. perspective/tests/viewer/test_validate.py +70 -0
  50. perspective/tests/viewer/test_viewer.py +245 -0
  51. perspective/tests/widget/__init__.py +11 -0
  52. perspective/tests/widget/test_widget.py +278 -0
  53. perspective/tests/widget/test_widget_pandas.py +453 -0
  54. perspective/viewer/__init__.py +15 -0
  55. perspective/viewer/validate.py +22 -0
  56. perspective/viewer/viewer.py +331 -0
  57. perspective/viewer/viewer_traitlets.py +101 -0
  58. perspective/widget/__init__.py +16 -0
  59. perspective/widget/widget.py +269 -0
  60. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/install.json +5 -0
  61. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/package.json +81 -0
  62. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/253.6f17b87bb4eb1e656365.js +18 -0
  63. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/253.6f17b87bb4eb1e656365.js.LICENSE.txt +59 -0
  64. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/905.d3bbc3d5954582d507bb.js +1 -0
  65. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/remoteEntry.7044010cbbf2a7208035.js +1 -0
  66. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/style.js +4 -0
  67. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/third-party-licenses.json +16 -0
  68. perspective_python-3.0.0rc1.dist-info/METADATA +13 -0
  69. perspective_python-3.0.0rc1.dist-info/RECORD +70 -0
  70. perspective_python-3.0.0rc1.dist-info/WHEEL +4 -0
@@ -0,0 +1,1062 @@
1
+ # ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ # ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ # ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ # ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ # ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ # ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ # ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ # ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ # ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ # ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ import json
14
+ import numpy as np
15
+ import pyarrow as pa
16
+ from functools import partial
17
+ from pytest import raises, mark
18
+ from perspective import Server, Client, PerspectiveError
19
+
20
+
21
+ client = Server().new_local_client()
22
+ Table = client.table
23
+
24
+
25
+ data = {"a": [1, 2, 3], "b": ["a", "b", "c"]}
26
+
27
+
28
+ class TestServer(object):
29
+ def post(self, msg):
30
+ """boilerplate callback to simulate a client's `post()` method."""
31
+ msg = json.loads(msg)
32
+ assert msg["id"] is not None
33
+
34
+ def validate_post(self, msg, expected=None):
35
+ msg = json.loads(msg)
36
+ if expected:
37
+ assert msg == expected
38
+
39
+ def test_server_host_table(self):
40
+ server = Server()
41
+ client = Client.from_server(server)
42
+ table = client.table(data, name="table1")
43
+ table2 = client.open_table("table1")
44
+ assert table2.schema() == {"a": "integer", "b": "string"}
45
+
46
+ def test_server_host(self):
47
+ server = Server()
48
+ client = Client.from_server(server)
49
+ table = client.table(data)
50
+ table.update({"a": [4, 5, 6], "b": ["d", "e", "f"]})
51
+ names = client.get_hosted_table_names()
52
+ assert client.open_table(names[0]).size() == 6
53
+
54
+ def test_server_host_table_transitive(self):
55
+ server = Server()
56
+ client = Client.from_server(server)
57
+ table = client.table(data, name="table1")
58
+ table.update({"a": [4, 5, 6], "b": ["d", "e", "f"]})
59
+ assert client.open_table("table1").size() == 6
60
+
61
+ def test_server_get_hosted_table_names(self):
62
+ server = Server()
63
+ client = Client.from_server(server)
64
+ client.table(data, name="table1")
65
+ assert client.get_hosted_table_names() == ["table1"]
66
+
67
+ client.table(data, name="table2")
68
+ assert client.get_hosted_table_names() == ["table1", "table2"]
69
+
70
+ def test_server_get_hosted_table_names_with_cmd(self):
71
+ server = Server()
72
+ client = Client.from_server(server)
73
+ client.table(data, name="table1")
74
+ assert client.get_hosted_table_names() == ["table1"]
75
+
76
+ @mark.skip(reason="Have not implemented locking yet")
77
+ def test_locked_client_create_table(self):
78
+ post_callback = partial(
79
+ self.validate_post,
80
+ expected={"id": 1, "error": "`table` failed - access denied"},
81
+ )
82
+ message = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
83
+ client = Server(lock=True)
84
+ client._process(message, post_callback)
85
+
86
+ def test_server_create_indexed_table(self):
87
+ server = Server()
88
+ client = Client.from_server(server)
89
+ client.table(data, name="table1", index="a")
90
+ table = client.open_table("table1")
91
+ assert table.schema() == {"a": "integer", "b": "string"}
92
+ assert table.get_index() == "a"
93
+
94
+ def test_server_create_indexed_table_and_update(self):
95
+ server = Server()
96
+ client = Client.from_server(server)
97
+ client.table(data, name="table1", index="a")
98
+ table = client.open_table("table1")
99
+
100
+ assert table.schema() == {"a": "integer", "b": "string"}
101
+ assert table.get_index() == "a"
102
+
103
+ table.update({"a": [1, 2, 3], "b": ["str1", "str2", "str3"]})
104
+ assert table.view().to_columns() == {
105
+ "a": [1, 2, 3],
106
+ "b": ["str1", "str2", "str3"],
107
+ }
108
+
109
+ def test_server_create_indexed_table_and_remove(self):
110
+ server = Server()
111
+ client = Client.from_server(server)
112
+ client.table(data, name="table1", index="a")
113
+ table = client.open_table("table1")
114
+ assert table.schema() == {"a": "integer", "b": "string"}
115
+ assert table.get_index() == "a"
116
+ table.remove([1, 2])
117
+ assert table.view().to_columns() == {"a": [3], "b": ["c"]}
118
+
119
+ @mark.skip(reason="Have not implemented locking yet")
120
+ def test_locked_client_create_view(self):
121
+ message = {"id": 1, "table_name": "table1", "view_name": "view1", "cmd": "view"}
122
+ client = Server(lock=True)
123
+ table = Table(data)
124
+ client.host_table("table1", table)
125
+ client._process(message, self.post)
126
+ assert client._get_view("view1").schema() == {"a": "integer", "b": "string"}
127
+
128
+ def test_server_create_view_zero(self):
129
+ message = {"id": 1, "table_name": "table1", "view_name": "view1", "cmd": "view"}
130
+ server = Server()
131
+ client = Client.from_server(server)
132
+ client.table(data, name="table1")
133
+ table = client.open_table("table1")
134
+ print(f"XXX: {dir(table)}")
135
+ assert table.view().dimensions()["num_view_rows"] == 3
136
+ # client._process(message, self.post)
137
+ # assert client._views["view1"].num_rows() == 3
138
+
139
+ def test_server_create_view_one(self):
140
+ server = Server()
141
+ client = Client.from_server(server)
142
+ client.table(data, name="table1")
143
+ table = client.open_table("table1")
144
+ view = table.view(group_by=["a"])
145
+ assert view.to_columns() == {
146
+ "__ROW_PATH__": [[], [1], [2], [3]],
147
+ "a": [6, 1, 2, 3],
148
+ "b": [3, 1, 1, 1],
149
+ }
150
+
151
+ def test_server_create_view_two(self):
152
+ server = Server()
153
+ client = Client.from_server(server)
154
+ client.table(data, name="table1")
155
+ table = client.open_table("table1")
156
+ view = table.view(group_by=["a"], split_by=["b"])
157
+ assert view.to_columns() == {
158
+ "__ROW_PATH__": [[], [1], [2], [3]],
159
+ "a|a": [1, 1, None, None],
160
+ "a|b": [1, 1, None, None],
161
+ "b|a": [2, None, 2, None],
162
+ "b|b": [1, None, 1, None],
163
+ "c|a": [3, None, None, 3],
164
+ "c|b": [1, None, None, 1],
165
+ }
166
+
167
+ # clear views
168
+
169
+ @mark.skip("Need to implement clear_views")
170
+ def test_server_clear_view(self):
171
+ messages = [
172
+ {"id": 1, "table_name": "table1", "view_name": "view1", "cmd": "view"},
173
+ {"id": 2, "table_name": "table1", "view_name": "view2", "cmd": "view"},
174
+ {"id": 3, "table_name": "table1", "view_name": "view3", "cmd": "view"},
175
+ ]
176
+ server = Server()
177
+ client = Client.from_server(server)
178
+ table = Table(data)
179
+ client.host_table("table1", table)
180
+ for message in messages:
181
+ client._process(message, self.post, client_id=1)
182
+ client.clear_views(1)
183
+ assert client._views == {}
184
+
185
+ @mark.skip("Need to implement clear_views")
186
+ def test_server_clear_view_nonseq(self):
187
+ messages = [
188
+ {"id": 1, "table_name": "table1", "view_name": "view1", "cmd": "view"},
189
+ {"id": 2, "table_name": "table1", "view_name": "view2", "cmd": "view"},
190
+ {"id": 3, "table_name": "table1", "view_name": "view3", "cmd": "view"},
191
+ ]
192
+ server = Server()
193
+ client = Client.from_server(server)
194
+ table = Table(data)
195
+ client.host_table("table1", table)
196
+ for i, message in enumerate(messages, 1):
197
+ client._process(message, self.post, client_id=i)
198
+ client.clear_views(1)
199
+ client.clear_views(3)
200
+ assert "view1" not in client._views
201
+ assert "view3" not in client._views
202
+ assert "view2" in client._views
203
+
204
+ @mark.skip("Need to implement clear_views")
205
+ def test_server_clear_view_no_client_id(self):
206
+ messages = [
207
+ {"id": 1, "table_name": "table1", "view_name": "view1", "cmd": "view"},
208
+ {"id": 2, "table_name": "table1", "view_name": "view2", "cmd": "view"},
209
+ {"id": 3, "table_name": "table1", "view_name": "view3", "cmd": "view"},
210
+ ]
211
+ server = Server()
212
+ client = Client.from_server(server)
213
+ table = Table(data)
214
+ client.host_table("table1", table)
215
+ for message in messages:
216
+ client._process(message, self.post)
217
+ with raises(PerspectiveError):
218
+ client.clear_views(None)
219
+
220
+ # locked client
221
+ @mark.skip(reason="Have not implemented locking yet")
222
+ def test_locked_client_update_table(self):
223
+ post_callback = partial(
224
+ self.validate_post,
225
+ expected={"id": 1, "error": "`table_method.update` failed - access denied"},
226
+ )
227
+ message = {
228
+ "id": 1,
229
+ "name": "table1",
230
+ "cmd": "table_method",
231
+ "method": "update",
232
+ "args": [data],
233
+ }
234
+ client = Server(lock=True)
235
+ client._process(message, post_callback)
236
+
237
+ @mark.skip(reason="Have not implemented locking yet")
238
+ def test_locked_client_clear_table(self):
239
+ post_callback = partial(
240
+ self.validate_post,
241
+ expected={"id": 1, "error": "`table_method.clear` failed - access denied"},
242
+ )
243
+ message = {
244
+ "id": 1,
245
+ "name": "table1",
246
+ "cmd": "table_method",
247
+ "method": "clear",
248
+ "args": [],
249
+ }
250
+ client = Server(lock=True)
251
+ client._process(message, post_callback)
252
+
253
+ @mark.skip(reason="Have not implemented locking yet")
254
+ def test_locked_client_replace_table(self):
255
+ post_callback = partial(
256
+ self.validate_post,
257
+ expected={
258
+ "id": 1,
259
+ "error": "`table_method.replace` failed - access denied",
260
+ },
261
+ )
262
+ message = {
263
+ "id": 1,
264
+ "name": "table1",
265
+ "cmd": "table_method",
266
+ "method": "replace",
267
+ "args": [data],
268
+ }
269
+ client = Server(lock=True)
270
+ client._process(message, post_callback)
271
+
272
+ @mark.skip(reason="Have not implemented locking yet")
273
+ def test_locked_client_remove_table(self):
274
+ post_callback = partial(
275
+ self.validate_post,
276
+ expected={"id": 1, "error": "`table_method.remove` failed - access denied"},
277
+ )
278
+ message = {
279
+ "id": 1,
280
+ "name": "table1",
281
+ "cmd": "table_method",
282
+ "method": "remove",
283
+ "args": [],
284
+ }
285
+ client = Server(lock=True)
286
+ client._process(message, post_callback)
287
+
288
+ @mark.skip(reason="Have not implemented locking yet")
289
+ def test_locked_client_delete_table(self):
290
+ post_callback = partial(
291
+ self.validate_post,
292
+ expected={"id": 1, "error": "`table_method.delete` failed - access denied"},
293
+ )
294
+ message = {
295
+ "id": 1,
296
+ "name": "table1",
297
+ "cmd": "table_method",
298
+ "method": "delete",
299
+ "args": [],
300
+ }
301
+ client = Server(lock=True)
302
+ client._process(message, post_callback)
303
+
304
+ @mark.skip(reason="Have not implemented locking yet")
305
+ def test_arbitary_lock_unlock_client(self):
306
+ client = Server(lock=True)
307
+ make_table_message = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
308
+ client._process(
309
+ make_table_message,
310
+ partial(
311
+ self.validate_post,
312
+ expected={"id": 1, "error": "`table` failed - access denied"},
313
+ ),
314
+ )
315
+ client.unlock()
316
+ client._process(make_table_message, self.post)
317
+ assert client._tables["table1"].schema() == {"a": "integer", "b": "string"}
318
+ update_message = {
319
+ "id": 2,
320
+ "name": "table1",
321
+ "cmd": "table_method",
322
+ "method": "update",
323
+ "args": [data],
324
+ }
325
+ client._process(update_message, self.post)
326
+ assert client._tables["table1"].size() == 6
327
+ client.lock()
328
+ update_message_new = {
329
+ "id": 3,
330
+ "name": "table1",
331
+ "cmd": "table_method",
332
+ "method": "update",
333
+ "args": [data],
334
+ }
335
+ client._process(
336
+ update_message_new,
337
+ partial(
338
+ self.validate_post,
339
+ expected={
340
+ "id": 3,
341
+ "error": "`table_method.update` failed - access denied",
342
+ },
343
+ ),
344
+ )
345
+
346
+ # serialization
347
+
348
+ def test_server_to_dict(self):
349
+ server = Server()
350
+ client = Client.from_server(server)
351
+ client.table(data, name="table1")
352
+ table = client.open_table("table1")
353
+ view = table.view()
354
+ assert view.to_columns() == data
355
+
356
+ @mark.skip(reason="Have not implemented locking yet")
357
+ def test_locked_client_to_dict(self, sentinel):
358
+ s = sentinel(False)
359
+
360
+ def handle_to_dict(msg):
361
+ s.set(True)
362
+ message = json.loads(msg)
363
+ assert message["data"] == data
364
+
365
+ message = {"id": 1, "table_name": "table1", "view_name": "view1", "cmd": "view"}
366
+ client = Server(lock=True)
367
+ table = Table(data)
368
+ client.host_table("table1", table)
369
+ client._process(message, self.post)
370
+ to_dict_message = {
371
+ "id": 2,
372
+ "name": "view1",
373
+ "cmd": "view_method",
374
+ "method": "to_dict",
375
+ }
376
+ client._process(to_dict_message, handle_to_dict)
377
+ assert s.get() is True
378
+
379
+ def test_server_to_dict_with_options(self, sentinel):
380
+ server = Server()
381
+ client = Client.from_server(server)
382
+ client.table(data, name="table1")
383
+ table = client.open_table("table1")
384
+ view = table.view()
385
+ d = view.to_columns(start_row=0, end_row=1)
386
+ assert d == {"a": [1], "b": ["a"]}
387
+
388
+ def test_server_to_dict_with_nan(self, util, sentinel):
389
+ data = util.make_arrow(
390
+ ["a"], [[1.5, np.nan, 2.5, np.nan]], types=[pa.float64()]
391
+ )
392
+ server = Server()
393
+ client = Client.from_server(server)
394
+ client.table(data, name="table1")
395
+ table = client.open_table("table1")
396
+ view = table.view()
397
+ assert view.to_columns() == {"a": [1.5, None, 2.5, None]}
398
+
399
+ def test_server_to_dict_unix_timestamps(self, sentinel):
400
+ """The conversion from `datetime` to a Unix timestamp should not
401
+ alter the timestamp in any way if both are in local time."""
402
+ timestamp_data = {"a": [1580515140000]}
403
+
404
+ server = Server()
405
+ client = Client.from_server(server)
406
+ schema = {"a": "datetime"}
407
+ table = client.table(schema, name="table1")
408
+ table.update(timestamp_data)
409
+
410
+ table = client.open_table("table1")
411
+ view = table.view()
412
+ assert view.to_columns() == timestamp_data
413
+
414
+ def test_server_create_view_and_update_table(self):
415
+ server = Server()
416
+ client = Client.from_server(server)
417
+ client.table(data, name="table1")
418
+ table = client.open_table("table1")
419
+ view = table.view()
420
+ table.update([{"a": 4, "b": "d"}])
421
+ assert view.dimensions()["num_view_rows"] == 4
422
+
423
+ def test_server_on_update_rows(self, sentinel):
424
+ s = sentinel(0)
425
+
426
+ def update_callback(port_id, delta):
427
+ table = Table(delta)
428
+ assert table.size() == 1
429
+ assert table.schema() == {"a": "integer", "b": "string"}
430
+ table.delete()
431
+ s.set(s.get() + 1)
432
+
433
+ # create a table and view using client
434
+ server = Server()
435
+ client = Client.from_server(server)
436
+ client.table(data, name="table1")
437
+ table = client.open_table("table1")
438
+ view = table.view()
439
+ view.on_update(update_callback, mode="row")
440
+ table.update({"a": [4], "b": ["d"]})
441
+ table.update({"a": [5], "b": ["e"]})
442
+ assert s.get() == 2
443
+
444
+ def test_repro(self, sentinel):
445
+ s = sentinel(0)
446
+
447
+ def update_callback(*args, **kwargs):
448
+ s.set(s.get() + 1)
449
+
450
+ table = Table(data)
451
+ view = table.view()
452
+ view.on_update(update_callback)
453
+ table.update({"a": [5], "b": ["e"]})
454
+ assert s.get() == 1
455
+
456
+ @mark.skip
457
+ def test_server_on_update_rows_with_port_id(self, sentinel):
458
+ s = sentinel(0)
459
+
460
+ def update_callback(port_id, delta):
461
+ table = Table(delta)
462
+ assert table.size() == 1
463
+ assert table.schema() == {"a": "integer", "b": "string"}
464
+ table.delete()
465
+ s.set(s.get() + 1)
466
+
467
+ # create a table and view using client
468
+ make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
469
+ server = Server()
470
+ client = Client.from_server(server)
471
+ client._process(make_table, self.post)
472
+ make_view = {
473
+ "id": 2,
474
+ "table_name": "table1",
475
+ "view_name": "view1",
476
+ "cmd": "view",
477
+ }
478
+ client._process(make_view, self.post)
479
+
480
+ # Get two ports on the table
481
+ make_port = {
482
+ "id": 3,
483
+ "name": "table1",
484
+ "cmd": "table_method",
485
+ "method": "make_port",
486
+ }
487
+ make_port2 = {
488
+ "id": 4,
489
+ "name": "table1",
490
+ "cmd": "table_method",
491
+ "method": "make_port",
492
+ }
493
+
494
+ client._process(make_port, self.post)
495
+ client._process(make_port2, self.post)
496
+
497
+ # hook into the created view and pass it the callback
498
+ view = client._views["view1"]
499
+ view.on_update(update_callback, mode="row")
500
+
501
+ # call updates
502
+ update1 = {
503
+ "id": 5,
504
+ "name": "table1",
505
+ "cmd": "table_method",
506
+ "method": "update",
507
+ "args": [{"a": [4], "b": ["d"]}, {"port_id": 1}],
508
+ }
509
+ update2 = {
510
+ "id": 6,
511
+ "name": "table1",
512
+ "cmd": "table_method",
513
+ "method": "update",
514
+ "args": [{"a": [5], "b": ["e"]}, {"port_id": 2}],
515
+ }
516
+
517
+ client._process(update1, self.post)
518
+ client._process(update2, self.post)
519
+
520
+ assert s.get() == 2
521
+
522
+ @mark.skip
523
+ def test_server_remove_update(self, sentinel):
524
+ s = sentinel(0)
525
+
526
+ def update_callback(port_id, delta):
527
+ s.set(s.get() + 1)
528
+
529
+ # create a table and view using client
530
+ make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
531
+ server = Server()
532
+ client = Client.from_server(server)
533
+ client._process(make_table, self.post)
534
+ make_view = {
535
+ "id": 2,
536
+ "table_name": "table1",
537
+ "view_name": "view1",
538
+ "cmd": "view",
539
+ }
540
+ client._process(make_view, self.post)
541
+
542
+ # hook into the created view and pass it the callback
543
+ view = client._views["view1"]
544
+ view.on_update(update_callback)
545
+ view.remove_update(update_callback)
546
+
547
+ # call updates
548
+ update1 = {
549
+ "id": 4,
550
+ "name": "table1",
551
+ "cmd": "table_method",
552
+ "method": "update",
553
+ "args": [{"a": [4], "b": ["d"]}],
554
+ }
555
+ update2 = {
556
+ "id": 5,
557
+ "name": "table1",
558
+ "cmd": "table_method",
559
+ "method": "update",
560
+ "args": [{"a": [5], "b": ["e"]}],
561
+ }
562
+ client._process(update1, self.post)
563
+ client._process(update2, self.post)
564
+ assert s.get() == 0
565
+
566
+ @mark.skip
567
+ def test_server_on_update_through_wire_API(self, sentinel):
568
+ s = sentinel(0)
569
+
570
+ # create a table and view using client
571
+ make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
572
+ server = Server()
573
+ client = Client.from_server(server)
574
+ client._process(make_table, self.post)
575
+ make_view = {
576
+ "id": 2,
577
+ "table_name": "table1",
578
+ "view_name": "view1",
579
+ "cmd": "view",
580
+ }
581
+ client._process(make_view, self.post)
582
+
583
+ def callback(updated):
584
+ assert updated["port_id"] == 0
585
+ s.set(s.get() + 100)
586
+
587
+ # simulate a client that holds callbacks by id
588
+ callbacks = {3: callback}
589
+
590
+ def post_update(msg):
591
+ # when `on_update` is triggered, this callback gets the message
592
+ # and has to decide which callback to trigger.
593
+ message = json.loads(msg)
594
+ assert message["id"] is not None
595
+ if message["id"] == 3:
596
+ # trigger callback
597
+ assert message["data"] == {"port_id": 0}
598
+ callbacks[message["id"]](message["data"])
599
+
600
+ # hook into the created view and pass it the callback
601
+ make_on_update = {
602
+ "id": 3,
603
+ "name": "view1",
604
+ "cmd": "view_method",
605
+ "subscribe": True,
606
+ "method": "on_update",
607
+ "callback_id": "callback_1",
608
+ }
609
+ client._process(make_on_update, post_update)
610
+
611
+ # call updates
612
+ update1 = {
613
+ "id": 4,
614
+ "name": "table1",
615
+ "cmd": "table_method",
616
+ "method": "update",
617
+ "args": [{"a": [4], "b": ["d"]}],
618
+ }
619
+ update2 = {
620
+ "id": 5,
621
+ "name": "table1",
622
+ "cmd": "table_method",
623
+ "method": "update",
624
+ "args": [{"a": [5], "b": ["e"]}],
625
+ }
626
+ client._process(update1, self.post)
627
+ client._process(update2, self.post)
628
+ assert s.get() == 200
629
+
630
+ @mark.skip
631
+ def test_server_on_update_rows_through_wire_API(self, sentinel):
632
+ s = sentinel(0)
633
+
634
+ # create a table and view using client
635
+ make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
636
+ server = Server()
637
+ client = Client.from_server(server)
638
+ client._process(make_table, self.post)
639
+ make_view = {
640
+ "id": 2,
641
+ "table_name": "table1",
642
+ "view_name": "view1",
643
+ "cmd": "view",
644
+ }
645
+ client._process(make_view, self.post)
646
+
647
+ def callback(delta):
648
+ table = Table(delta)
649
+ assert table.size() == 1
650
+ assert table.schema() == {"a": "integer", "b": "string"}
651
+ table.delete()
652
+ s.set(s.get() + 100)
653
+
654
+ # simulate a client that holds callbacks by id
655
+ callbacks = {3: callback}
656
+
657
+ def post_update(msg, binary=False):
658
+ # when `on_update` is triggered, this callback gets the message
659
+ # and has to decide which callback to trigger.
660
+ if binary:
661
+ # trigger callback - "msg" here is binary
662
+ callbacks[3](msg)
663
+ return
664
+
665
+ message = json.loads(msg)
666
+ assert message["id"] is not None
667
+ if message["id"] == 3:
668
+ # wait for transferable
669
+ assert message["data"]["port_id"] == 0
670
+ return
671
+
672
+ # hook into the created view and pass it the callback
673
+ make_on_update = {
674
+ "id": 3,
675
+ "name": "view1",
676
+ "cmd": "view_method",
677
+ "subscribe": True,
678
+ "method": "on_update",
679
+ "callback_id": "callback_1",
680
+ "args": [{"mode": "row"}],
681
+ }
682
+ client._process(make_on_update, post_update)
683
+
684
+ # call updates
685
+ update1 = {
686
+ "id": 4,
687
+ "name": "table1",
688
+ "cmd": "table_method",
689
+ "method": "update",
690
+ "args": [{"a": [4], "b": ["d"]}],
691
+ }
692
+ update2 = {
693
+ "id": 5,
694
+ "name": "table1",
695
+ "cmd": "table_method",
696
+ "method": "update",
697
+ "args": [{"a": [5], "b": ["e"]}],
698
+ }
699
+ client._process(update1, self.post)
700
+ client._process(update2, self.post)
701
+ assert s.get() == 200
702
+
703
+ @mark.skip
704
+ def test_server_on_update_rows_with_port_id_through_wire_API(self, sentinel):
705
+ s = sentinel(0)
706
+
707
+ def update_callback(port_id, delta):
708
+ table = Table(delta)
709
+ assert table.size() == 1
710
+ assert table.schema() == {"a": "integer", "b": "string"}
711
+ table.delete()
712
+ s.set(s.get() + 1)
713
+
714
+ # create a table and view using client
715
+ make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
716
+ server = Server()
717
+ client = Client.from_server(server)
718
+ client._process(make_table, self.post)
719
+ make_view = {
720
+ "id": 2,
721
+ "table_name": "table1",
722
+ "view_name": "view1",
723
+ "cmd": "view",
724
+ }
725
+ client._process(make_view, self.post)
726
+
727
+ # Get two ports on the table
728
+ make_port = {
729
+ "id": 3,
730
+ "name": "table1",
731
+ "cmd": "table_method",
732
+ "method": "make_port",
733
+ }
734
+ make_port2 = {
735
+ "id": 4,
736
+ "name": "table1",
737
+ "cmd": "table_method",
738
+ "method": "make_port",
739
+ }
740
+
741
+ client._process(make_port, self.post)
742
+ client._process(make_port2, self.post)
743
+
744
+ # define an update callback
745
+ def callback(delta):
746
+ table = Table(delta)
747
+ assert table.size() == 1
748
+ assert table.schema() == {"a": "integer", "b": "string"}
749
+ table.delete()
750
+ s.set(s.get() + 100)
751
+
752
+ # simulate a client that holds callbacks by id
753
+ callbacks = {3: callback}
754
+
755
+ def post_update(msg, binary=False):
756
+ # when `on_update` is triggered, this callback gets the message
757
+ # and has to decide which callback to trigger.
758
+ if binary:
759
+ # trigger callback - "msg" here is binary
760
+ callbacks[3](msg)
761
+ return
762
+
763
+ message = json.loads(msg)
764
+
765
+ assert message["id"] is not None
766
+
767
+ if message["id"] == 3:
768
+ # wait for transferable
769
+ assert message["data"]["port_id"] in (1, 2)
770
+ return
771
+
772
+ # hook into the created view and pass it the callback
773
+ make_on_update = {
774
+ "id": 5,
775
+ "name": "view1",
776
+ "cmd": "view_method",
777
+ "subscribe": True,
778
+ "method": "on_update",
779
+ "callback_id": "callback_1",
780
+ "args": [{"mode": "row"}],
781
+ }
782
+ client._process(make_on_update, post_update)
783
+
784
+ # call updates
785
+ update1 = {
786
+ "id": 6,
787
+ "name": "table1",
788
+ "cmd": "table_method",
789
+ "method": "update",
790
+ "args": [{"a": [4], "b": ["d"]}, {"port_id": 1}],
791
+ }
792
+ update2 = {
793
+ "id": 7,
794
+ "name": "table1",
795
+ "cmd": "table_method",
796
+ "method": "update",
797
+ "args": [{"a": [5], "b": ["e"]}, {"port_id": 2}],
798
+ }
799
+
800
+ client._process(update1, self.post)
801
+ client._process(update2, self.post)
802
+
803
+ assert s.get() == 200
804
+
805
+ @mark.skip
806
+ def test_server_remove_update_through_wire_API(self, sentinel):
807
+ s = sentinel(0)
808
+
809
+ def update_callback(port_id, delta):
810
+ s.set(s.get() + 1)
811
+
812
+ # create a table and view using client
813
+ make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
814
+ server = Server()
815
+ client = Client.from_server(server)
816
+ client._process(make_table, self.post)
817
+ make_view = {
818
+ "id": 2,
819
+ "table_name": "table1",
820
+ "view_name": "view1",
821
+ "cmd": "view",
822
+ }
823
+ client._process(make_view, self.post)
824
+
825
+ def callback(updated):
826
+ assert updated["port_id"] == 0
827
+ s.set(s.get() + 100)
828
+
829
+ # simulate a client that holds callbacks by id
830
+ callbacks = {3: callback}
831
+
832
+ def post_update(msg):
833
+ # when `on_update` is triggered, this callback gets the message
834
+ # and has to decide which callback to trigger.
835
+ message = json.loads(msg)
836
+ assert message["id"] is not None
837
+ if message["id"] == 3:
838
+ # trigger callback
839
+ assert message["data"] == {"port_id": 0}
840
+ callbacks[message["id"]](message["data"])
841
+
842
+ # create an on_update callback
843
+ make_on_update = {
844
+ "id": 3,
845
+ "name": "view1",
846
+ "cmd": "view_method",
847
+ "subscribe": True,
848
+ "method": "on_update",
849
+ "callback_id": "callback_1",
850
+ }
851
+ client._process(make_on_update, post_update)
852
+
853
+ # call updates
854
+ update1 = {
855
+ "id": 4,
856
+ "name": "table1",
857
+ "cmd": "table_method",
858
+ "method": "update",
859
+ "args": [{"a": [4], "b": ["d"]}],
860
+ }
861
+ client._process(update1, self.post)
862
+
863
+ # remove update callback
864
+ remove_on_update = {
865
+ "id": 5,
866
+ "name": "view1",
867
+ "cmd": "view_method",
868
+ "subscribe": True,
869
+ "method": "remove_update",
870
+ "callback_id": "callback_1",
871
+ }
872
+ client._process(remove_on_update, self.post)
873
+
874
+ update2 = {
875
+ "id": 6,
876
+ "name": "table1",
877
+ "cmd": "table_method",
878
+ "method": "update",
879
+ "args": [{"a": [5], "b": ["e"]}],
880
+ }
881
+ client._process(update2, self.post)
882
+ assert s.get() == 100
883
+
884
+ @mark.skip
885
+ def test_server_delete_table_should_fail(self):
886
+ post_callback = partial(
887
+ self.validate_post,
888
+ expected={
889
+ "id": 2,
890
+ "error": "table.delete() cannot be called on a remote table, as the remote has full ownership.",
891
+ },
892
+ )
893
+ make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
894
+ server = Server()
895
+ client = Client.from_server(server)
896
+ client._process(make_table, self.post)
897
+ delete_table = {
898
+ "id": 2,
899
+ "name": "table1",
900
+ "cmd": "table_method",
901
+ "method": "delete",
902
+ "args": [],
903
+ }
904
+ client._process(delete_table, post_callback)
905
+ assert len(client._tables) == 1
906
+
907
+ @mark.skip
908
+ def test_server_delete_view(self):
909
+ make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
910
+ server = Server()
911
+ client = Client.from_server(server)
912
+ client._process(make_table, self.post)
913
+ make_view = {
914
+ "id": 2,
915
+ "table_name": "table1",
916
+ "view_name": "view1",
917
+ "cmd": "view",
918
+ }
919
+ client._process(make_view, self.post)
920
+ delete_view = {
921
+ "id": 3,
922
+ "name": "view1",
923
+ "cmd": "view_method",
924
+ "method": "delete",
925
+ }
926
+ client._process(delete_view, self.post)
927
+ assert len(client._views) == 0
928
+
929
+ @mark.skip
930
+ def test_server_on_delete_view(self, sentinel):
931
+ s = sentinel(False)
932
+
933
+ def delete_callback():
934
+ s.set(True)
935
+
936
+ make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
937
+ server = Server()
938
+ client = Client.from_server(server)
939
+ client._process(make_table, self.post)
940
+ make_view = {
941
+ "id": 2,
942
+ "table_name": "table1",
943
+ "view_name": "view1",
944
+ "cmd": "view",
945
+ }
946
+ client._process(make_view, self.post)
947
+
948
+ view = client._get_view("view1")
949
+ view.on_delete(delete_callback)
950
+
951
+ delete_view = {
952
+ "id": 3,
953
+ "name": "view1",
954
+ "cmd": "view_method",
955
+ "method": "delete",
956
+ }
957
+
958
+ client._process(delete_view, self.post)
959
+
960
+ assert len(client._views) == 0
961
+ assert s.get() is True
962
+
963
+ @mark.skip
964
+ def test_server_remove_delete_view(self, sentinel):
965
+ s = sentinel(False)
966
+
967
+ def delete_callback():
968
+ s.set(True)
969
+
970
+ make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]}
971
+ server = Server()
972
+ client = Client.from_server(server)
973
+ client._process(make_table, self.post)
974
+ make_view = {
975
+ "id": 2,
976
+ "table_name": "table1",
977
+ "view_name": "view1",
978
+ "cmd": "view",
979
+ }
980
+ client._process(make_view, self.post)
981
+
982
+ view = client._get_view("view1")
983
+ view.on_delete(delete_callback)
984
+ view.remove_delete(delete_callback)
985
+
986
+ delete_view = {
987
+ "id": 3,
988
+ "name": "view1",
989
+ "cmd": "view_method",
990
+ "method": "delete",
991
+ }
992
+
993
+ client._process(delete_view, self.post)
994
+
995
+ assert len(client._views) == 0
996
+ assert s.get() is False
997
+
998
+ def test_server_set_queue_process(self, sentinel):
999
+ s = sentinel(0)
1000
+
1001
+ def fake_queue_process(f, *args, **kwargs):
1002
+ s.set(s.get() + 1)
1003
+ f(*args, **kwargs)
1004
+
1005
+ server = Server()
1006
+ client = Client.from_server(server, loop_callback=fake_queue_process)
1007
+ table = client.table({"a": [1, 2, 3]}, name="tbl")
1008
+ table.update({"a": [4, 5, 6]})
1009
+ assert table.view().to_columns() == {"a": [1, 2, 3, 4, 5, 6]}
1010
+
1011
+ table.update({"a": [7, 8, 9]})
1012
+ assert s.get() == 5
1013
+
1014
+ @mark.skip(
1015
+ reason="This method no longer dispatches to the loop_cb because it is sync without an on_update callback"
1016
+ )
1017
+ def test_server_set_queue_process_before_host_table(self, sentinel):
1018
+ s = sentinel(0)
1019
+ server = Server()
1020
+ client = Client.from_server(server)
1021
+ table = client.table({"a": [1, 2, 3]}, name="tbl")
1022
+
1023
+ def fake_queue_process(f, *args, **kwargs):
1024
+ s.set(s.get() + 1)
1025
+ f(*args, **kwargs)
1026
+
1027
+ client.set_loop_callback(fake_queue_process)
1028
+ table.update({"a": [4, 5, 6]})
1029
+ table.update({"a": [4, 5, 6]})
1030
+
1031
+ assert s.get() == 2
1032
+
1033
+ @mark.skip(
1034
+ reason="This method no longer dispatches to the loop_cb because it is sync without an on_update callback"
1035
+ )
1036
+ def test_server_set_queue_process_multiple(self, sentinel):
1037
+ # client2's queue process should not affect client1,
1038
+ # provided they manage different tables
1039
+ s = sentinel(0)
1040
+ s2 = sentinel(0)
1041
+ server = Server()
1042
+ client = Client.from_server(server)
1043
+ client2 = Client.from_server(server)
1044
+ table = client.table({"a": [1, 2, 3]}, name="tbl")
1045
+ table2 = client2.table({"a": [1, 2, 3]}, name="tbl2")
1046
+
1047
+ def fake_queue_process(f, *args, **kwargs):
1048
+ s2.set(s2.get() + 1)
1049
+ f(*args, **kwargs)
1050
+
1051
+ client2.set_loop_callback(fake_queue_process)
1052
+
1053
+ table.update({"a": [4, 5, 6]})
1054
+ assert table.view().to_columns() == {"a": [1, 2, 3, 4, 5, 6]}
1055
+
1056
+ table2.update({"a": [7, 8, 9]})
1057
+ table.update({"a": [7, 8, 9]})
1058
+
1059
+ assert table.view().to_columns() == {"a": [1, 2, 3, 4, 5, 6, 7, 8, 9]}
1060
+ assert table2.view().to_columns() == {"a": [1, 2, 3, 7, 8, 9]}
1061
+ assert s.get() == 0
1062
+ assert s2.get() == 3