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.
- perspective/__init__.py +396 -0
- perspective/extension/finos-perspective-nbextension.json +5 -0
- perspective/handlers/__init__.py +11 -0
- perspective/handlers/aiohttp.py +61 -0
- perspective/handlers/starlette.py +55 -0
- perspective/handlers/tornado.py +184 -0
- perspective/perspective.pyd +0 -0
- perspective/templates/exported_widget.html.template +35 -0
- perspective/tests/__init__.py +11 -0
- perspective/tests/async/test_async_client.py +83 -0
- perspective/tests/async/test_websocket_client.py +124 -0
- perspective/tests/conftest.py +272 -0
- perspective/tests/core/__init__.py +11 -0
- perspective/tests/core/test_async.py +351 -0
- perspective/tests/multi_threaded/__init__.py +11 -0
- perspective/tests/multi_threaded/test_multi_threaded.py +201 -0
- perspective/tests/server/__init__.py +11 -0
- perspective/tests/server/test_server.py +1016 -0
- perspective/tests/server/test_session.py +110 -0
- perspective/tests/table/__init__.py +11 -0
- perspective/tests/table/arrow/date32.arrow +0 -0
- perspective/tests/table/arrow/date64.arrow +0 -0
- perspective/tests/table/arrow/dict.arrow +0 -0
- perspective/tests/table/arrow/dict_update.arrow +0 -0
- perspective/tests/table/arrow/int_float_str.arrow +0 -0
- perspective/tests/table/arrow/int_float_str_file.arrow +0 -0
- perspective/tests/table/arrow/int_float_str_update.arrow +0 -0
- perspective/tests/table/object_sequence.py +402 -0
- perspective/tests/table/test_column_paths.py +89 -0
- perspective/tests/table/test_delete.py +124 -0
- perspective/tests/table/test_exception.py +65 -0
- perspective/tests/table/test_leaks.py +54 -0
- perspective/tests/table/test_ports.py +178 -0
- perspective/tests/table/test_remove.py +102 -0
- perspective/tests/table/test_table.py +641 -0
- perspective/tests/table/test_table_arrow.py +503 -0
- perspective/tests/table/test_table_datetime.py +2409 -0
- perspective/tests/table/test_table_infer.py +201 -0
- perspective/tests/table/test_table_limit.py +45 -0
- perspective/tests/table/test_table_numpy.py +1022 -0
- perspective/tests/table/test_table_pandas.py +1018 -0
- perspective/tests/table/test_table_polars.py +251 -0
- perspective/tests/table/test_table_view_table.py +130 -0
- perspective/tests/table/test_to_arrow.py +417 -0
- perspective/tests/table/test_to_arrow_lz4.py +32 -0
- perspective/tests/table/test_to_format.py +1024 -0
- perspective/tests/table/test_to_polars.py +26 -0
- perspective/tests/table/test_update.py +545 -0
- perspective/tests/table/test_update_arrow.py +980 -0
- perspective/tests/table/test_update_pandas.py +211 -0
- perspective/tests/table/test_view.py +2261 -0
- perspective/tests/table/test_view_expression.py +1940 -0
- perspective/tests/test_dependencies.py +53 -0
- perspective/tests/viewer/__init__.py +11 -0
- perspective/tests/viewer/test_viewer.py +246 -0
- perspective/tests/widget/__init__.py +11 -0
- perspective/tests/widget/test_widget.py +278 -0
- perspective/tests/widget/test_widget_pandas.py +453 -0
- perspective/virtual_servers/__init__.py +134 -0
- perspective/virtual_servers/clickhouse.py +245 -0
- perspective/virtual_servers/duckdb.py +236 -0
- perspective/widget/__init__.py +349 -0
- perspective/widget/viewer/__init__.py +15 -0
- perspective/widget/viewer/validate.py +22 -0
- perspective/widget/viewer/viewer.py +343 -0
- perspective/widget/viewer/viewer_traitlets.py +101 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/install.json +5 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/package.json +71 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/253.5f5c9e80605aa4106a28.js +2 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/253.5f5c9e80605aa4106a28.js.LICENSE.txt +25 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/523.c030af5d3c4f67ff83f6.js +1 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/remoteEntry.95a8ea1b44d96032833f.js +1 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/style.js +4 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/third-party-licenses.json +16 -0
- perspective_python-4.2.0.dist-info/METADATA +27 -0
- perspective_python-4.2.0.dist-info/RECORD +79 -0
- perspective_python-4.2.0.dist-info/WHEEL +4 -0
- perspective_python-4.2.0.dist-info/licenses/LICENSE.md +193 -0
- 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
|