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