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