perspective-python 3.0.0rc1__cp39-abi3-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- perspective/__init__.py +78 -0
- perspective/extension/finos-perspective-nbextension.json +5 -0
- perspective/handlers/__init__.py +26 -0
- perspective/handlers/aiohttp.py +54 -0
- perspective/handlers/starlette.py +51 -0
- perspective/handlers/tornado.py +61 -0
- perspective/perspective.pyd +0 -0
- perspective/templates/exported_widget.html.jinja +35 -0
- perspective/tests/__init__.py +11 -0
- perspective/tests/conftest.py +268 -0
- perspective/tests/core/__init__.py +11 -0
- perspective/tests/core/test_async.py +436 -0
- perspective/tests/core/test_threadpool.py +48 -0
- perspective/tests/server/__init__.py +11 -0
- perspective/tests/server/test_server.py +1062 -0
- perspective/tests/server/test_session.py +55 -0
- perspective/tests/single_threaded/test_single_threaded.py +61 -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_delete.py +124 -0
- perspective/tests/table/test_exception.py +53 -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 +610 -0
- perspective/tests/table/test_table_arrow.py +452 -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 +43 -0
- perspective/tests/table/test_table_numpy.py +1022 -0
- perspective/tests/table/test_table_pandas.py +1018 -0
- perspective/tests/table/test_to_arrow.py +414 -0
- perspective/tests/table/test_to_arrow_lz4.py +33 -0
- perspective/tests/table/test_to_format.py +1024 -0
- perspective/tests/table/test_update.py +545 -0
- perspective/tests/table/test_update_arrow.py +980 -0
- perspective/tests/table/test_update_numpy.py +252 -0
- perspective/tests/table/test_update_pandas.py +211 -0
- perspective/tests/table/test_view.py +2235 -0
- perspective/tests/table/test_view_expression.py +1940 -0
- perspective/tests/viewer/__init__.py +11 -0
- perspective/tests/viewer/test_validate.py +70 -0
- perspective/tests/viewer/test_viewer.py +245 -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/viewer/__init__.py +15 -0
- perspective/viewer/validate.py +22 -0
- perspective/viewer/viewer.py +331 -0
- perspective/viewer/viewer_traitlets.py +101 -0
- perspective/widget/__init__.py +16 -0
- perspective/widget/widget.py +269 -0
- perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/install.json +5 -0
- perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/package.json +81 -0
- perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/253.6f17b87bb4eb1e656365.js +18 -0
- perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/253.6f17b87bb4eb1e656365.js.LICENSE.txt +59 -0
- perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/905.d3bbc3d5954582d507bb.js +1 -0
- perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/remoteEntry.7044010cbbf2a7208035.js +1 -0
- perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/style.js +4 -0
- perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/third-party-licenses.json +16 -0
- perspective_python-3.0.0rc1.dist-info/METADATA +13 -0
- perspective_python-3.0.0rc1.dist-info/RECORD +70 -0
- perspective_python-3.0.0rc1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,436 @@
|
|
|
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 queue
|
|
14
|
+
import random
|
|
15
|
+
import threading
|
|
16
|
+
from functools import partial
|
|
17
|
+
|
|
18
|
+
import tornado.ioloop
|
|
19
|
+
from perspective import (
|
|
20
|
+
Server,
|
|
21
|
+
Client,
|
|
22
|
+
)
|
|
23
|
+
from pytest import mark, raises
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def syncify(f):
|
|
27
|
+
"""Given a function `f` that must be run on `TestAsync.loop`, queue `f` on
|
|
28
|
+
the loop, block until it is evaluated, and return the result.
|
|
29
|
+
"""
|
|
30
|
+
sem = queue.Queue()
|
|
31
|
+
|
|
32
|
+
def _syncify_task():
|
|
33
|
+
assert threading.current_thread().ident == TestAsync.thread.ident
|
|
34
|
+
result = f()
|
|
35
|
+
TestAsync.loop.add_callback(lambda: sem.put(result))
|
|
36
|
+
|
|
37
|
+
def _syncify():
|
|
38
|
+
TestAsync.loop.add_callback(_syncify_task)
|
|
39
|
+
return sem.get()
|
|
40
|
+
|
|
41
|
+
return _syncify
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
data = [{"a": i, "b": i * 0.5, "c": str(i)} for i in range(10)]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class TestAsync(object):
|
|
48
|
+
@classmethod
|
|
49
|
+
def setup_class(cls):
|
|
50
|
+
cls.loop = tornado.ioloop.IOLoop()
|
|
51
|
+
cls.loop.make_current()
|
|
52
|
+
cls.thread = threading.Thread(target=cls.loop.start)
|
|
53
|
+
cls.thread.daemon = True
|
|
54
|
+
cls.thread.start()
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def teardown_class(cls):
|
|
58
|
+
cls.loop.add_callback(lambda: tornado.ioloop.IOLoop.current().stop())
|
|
59
|
+
cls.loop.clear_current()
|
|
60
|
+
cls.thread.join()
|
|
61
|
+
cls.loop.close(all_fds=True)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def loop_is_running(cls):
|
|
65
|
+
return cls.loop.asyncio_loop.is_running()
|
|
66
|
+
|
|
67
|
+
def test_async_queue_process(self):
|
|
68
|
+
server = Server()
|
|
69
|
+
client = Client.from_server(
|
|
70
|
+
server,
|
|
71
|
+
loop_callback=lambda fn, *args: TestAsync.loop.add_callback(fn, *args),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
tbl = client.table({"a": "integer", "b": "float", "c": "string"})
|
|
75
|
+
|
|
76
|
+
@syncify
|
|
77
|
+
def _task():
|
|
78
|
+
assert tbl.size() == 0
|
|
79
|
+
for i in range(5):
|
|
80
|
+
tbl.update([data[i]])
|
|
81
|
+
return tbl.size()
|
|
82
|
+
|
|
83
|
+
assert _task() == 5
|
|
84
|
+
tbl.delete()
|
|
85
|
+
|
|
86
|
+
def test_async_queue_process_csv(self):
|
|
87
|
+
"""Make sure GIL release during CSV loading works"""
|
|
88
|
+
server = Server()
|
|
89
|
+
client = Client.from_server(
|
|
90
|
+
server,
|
|
91
|
+
loop_callback=lambda fn, *args: TestAsync.loop.add_callback(fn, *args),
|
|
92
|
+
)
|
|
93
|
+
tbl = client.table("x,y,z\n1,a,true\n2,b,false\n3,c,true\n4,d,false")
|
|
94
|
+
|
|
95
|
+
@syncify
|
|
96
|
+
def _task():
|
|
97
|
+
assert tbl.size() == 4
|
|
98
|
+
for i in range(5):
|
|
99
|
+
tbl.update("x,y,z\n1,a,true\n2,b,false\n3,c,true\n4,d,false")
|
|
100
|
+
return tbl.size()
|
|
101
|
+
|
|
102
|
+
assert _task() == 24
|
|
103
|
+
|
|
104
|
+
tbl.delete()
|
|
105
|
+
|
|
106
|
+
def test_async_call_loop(self):
|
|
107
|
+
server = Server()
|
|
108
|
+
client = Client.from_server(
|
|
109
|
+
server,
|
|
110
|
+
loop_callback=lambda fn, *args: TestAsync.loop.add_callback(fn, *args),
|
|
111
|
+
)
|
|
112
|
+
tbl = client.table({"a": "integer", "b": "float", "c": "string"})
|
|
113
|
+
tbl.update(data)
|
|
114
|
+
|
|
115
|
+
@syncify
|
|
116
|
+
def _task():
|
|
117
|
+
return tbl.size()
|
|
118
|
+
|
|
119
|
+
assert _task() == 10
|
|
120
|
+
tbl.delete()
|
|
121
|
+
|
|
122
|
+
@mark.skip(reason="We take a loop to construct the client now")
|
|
123
|
+
def test_async_call_loop_error_if_no_loop(self):
|
|
124
|
+
server = Server()
|
|
125
|
+
client = Client.from_server(
|
|
126
|
+
server, lambda fn, *args: TestAsync.loop.add_callback(fn, *args)
|
|
127
|
+
)
|
|
128
|
+
tbl = client.table({"a": "integer", "b": "float", "c": "string"})
|
|
129
|
+
|
|
130
|
+
with raises(PerspectiveError):
|
|
131
|
+
# loop not set - errors
|
|
132
|
+
tbl.update(data)
|
|
133
|
+
|
|
134
|
+
tbl.update(data)
|
|
135
|
+
|
|
136
|
+
@syncify
|
|
137
|
+
def _task():
|
|
138
|
+
return tbl.size()
|
|
139
|
+
|
|
140
|
+
# subsequent calls to call_loop will work if loop_callback is set.
|
|
141
|
+
assert _task() == 10
|
|
142
|
+
|
|
143
|
+
tbl.delete()
|
|
144
|
+
|
|
145
|
+
def test_async_multiple_managers_queue_process(self):
|
|
146
|
+
server = Server()
|
|
147
|
+
client = Client.from_server(
|
|
148
|
+
server, lambda fn, *args: TestAsync.loop.add_callback(fn, *args)
|
|
149
|
+
)
|
|
150
|
+
server2 = Server()
|
|
151
|
+
client2 = Client.from_server(
|
|
152
|
+
server2, lambda fn, *args: TestAsync.loop.add_callback(fn, *args)
|
|
153
|
+
)
|
|
154
|
+
tbl = client.table({"a": "integer", "b": "float", "c": "string"})
|
|
155
|
+
tbl2 = client2.table({"a": "integer", "b": "float", "c": "string"})
|
|
156
|
+
|
|
157
|
+
@syncify
|
|
158
|
+
def _update_task():
|
|
159
|
+
for i in range(5):
|
|
160
|
+
tbl.update([data[i]])
|
|
161
|
+
tbl2.update([data[i]])
|
|
162
|
+
return tbl.size()
|
|
163
|
+
|
|
164
|
+
assert _update_task() == 5
|
|
165
|
+
|
|
166
|
+
@syncify
|
|
167
|
+
def _flush_to_process():
|
|
168
|
+
view = tbl2.view()
|
|
169
|
+
records = view.to_records()
|
|
170
|
+
for i in range(5):
|
|
171
|
+
tbl2.update([data[i]])
|
|
172
|
+
|
|
173
|
+
view.delete()
|
|
174
|
+
return records
|
|
175
|
+
|
|
176
|
+
assert _flush_to_process() == data[:5]
|
|
177
|
+
|
|
178
|
+
@syncify
|
|
179
|
+
def _delete_task():
|
|
180
|
+
tbl2.delete()
|
|
181
|
+
tbl.delete()
|
|
182
|
+
|
|
183
|
+
_delete_task()
|
|
184
|
+
|
|
185
|
+
@mark.skip(
|
|
186
|
+
reason="This test is failing because we're not calling process after each update like before"
|
|
187
|
+
)
|
|
188
|
+
def test_async_multiple_managers_mixed_queue_process(self):
|
|
189
|
+
sentinel = {"called": 0}
|
|
190
|
+
|
|
191
|
+
def sync_queue_process(f, *args, **kwargs):
|
|
192
|
+
sentinel["called"] += 1
|
|
193
|
+
f(*args, **kwargs)
|
|
194
|
+
|
|
195
|
+
server = Server()
|
|
196
|
+
client = Client.from_server(
|
|
197
|
+
server, lambda fn, *args: TestAsync.loop.add_callback(fn, *args)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
server2 = Server()
|
|
201
|
+
client2 = Client.from_server(server2, sync_queue_process)
|
|
202
|
+
tbl = client.table({"a": "integer", "b": "float", "c": "string"})
|
|
203
|
+
tbl2 = client2.table({"a": "integer", "b": "float", "c": "string"})
|
|
204
|
+
|
|
205
|
+
@syncify
|
|
206
|
+
def _tbl_task():
|
|
207
|
+
for i in range(5):
|
|
208
|
+
tbl.update([data[i]])
|
|
209
|
+
return tbl.size()
|
|
210
|
+
|
|
211
|
+
assert _tbl_task() == 5
|
|
212
|
+
|
|
213
|
+
for i in range(5):
|
|
214
|
+
tbl2.update([data[i]])
|
|
215
|
+
|
|
216
|
+
assert sentinel["called"] == 5
|
|
217
|
+
|
|
218
|
+
@syncify
|
|
219
|
+
def _tbl_task2():
|
|
220
|
+
view = tbl.view()
|
|
221
|
+
records = view.to_records()
|
|
222
|
+
view.delete()
|
|
223
|
+
tbl.delete()
|
|
224
|
+
return records
|
|
225
|
+
|
|
226
|
+
assert _tbl_task2() == data[:5]
|
|
227
|
+
|
|
228
|
+
view = tbl2.view()
|
|
229
|
+
assert view.to_records() == data[:5]
|
|
230
|
+
|
|
231
|
+
view.delete()
|
|
232
|
+
tbl2.delete()
|
|
233
|
+
|
|
234
|
+
@mark.skip(
|
|
235
|
+
reason="This test is failing because we're not calling process after each update like before"
|
|
236
|
+
)
|
|
237
|
+
def test_async_multiple_managers_delayed_process(self):
|
|
238
|
+
sentinel = {"async": 0, "sync": 0}
|
|
239
|
+
|
|
240
|
+
def _counter(key, f, *args, **kwargs):
|
|
241
|
+
sentinel[key] += 1
|
|
242
|
+
return f(*args, **kwargs)
|
|
243
|
+
|
|
244
|
+
short_delay_queue_process = partial(_counter, "sync")
|
|
245
|
+
long_delay_queue_process = partial(
|
|
246
|
+
TestAsync.loop.add_timeout, 1, _counter, "async"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
server = Server()
|
|
250
|
+
client = Client.from_server(
|
|
251
|
+
server, lambda fn, *args: short_delay_queue_process(fn, *args)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
server2 = Server()
|
|
255
|
+
client2 = Client.from_server(
|
|
256
|
+
server2, lambda fn, *args: long_delay_queue_process(fn, *args)
|
|
257
|
+
)
|
|
258
|
+
tbl = client.table({"a": "integer", "b": "float", "c": "string"})
|
|
259
|
+
tbl2 = client2.table({"a": "integer", "b": "float", "c": "string"})
|
|
260
|
+
|
|
261
|
+
@syncify
|
|
262
|
+
def _tbl_task():
|
|
263
|
+
for i in range(10):
|
|
264
|
+
tbl2.update([data[i]])
|
|
265
|
+
|
|
266
|
+
_tbl_task()
|
|
267
|
+
for i in range(10):
|
|
268
|
+
tbl.update([data[i]])
|
|
269
|
+
|
|
270
|
+
@syncify
|
|
271
|
+
def _tbl_task2():
|
|
272
|
+
size = tbl2.size()
|
|
273
|
+
tbl2.delete()
|
|
274
|
+
return size
|
|
275
|
+
|
|
276
|
+
assert _tbl_task2() == 10
|
|
277
|
+
assert tbl.size() == 10
|
|
278
|
+
assert sentinel["async"] == 1
|
|
279
|
+
assert sentinel["sync"] == 10
|
|
280
|
+
|
|
281
|
+
tbl.delete()
|
|
282
|
+
|
|
283
|
+
def test_async_single_manager_tables_chained(self):
|
|
284
|
+
def call_loop(fn, *args):
|
|
285
|
+
TestAsync.loop.add_callback(fn, *args)
|
|
286
|
+
|
|
287
|
+
server = Server()
|
|
288
|
+
client = Client.from_server(server, call_loop)
|
|
289
|
+
columns = {"index": "integer", "num1": "integer", "num2": "integer"}
|
|
290
|
+
# tbl = client.table(columns, index="index")
|
|
291
|
+
tbl = client.table(columns)
|
|
292
|
+
view = tbl.view()
|
|
293
|
+
tbl2 = client.table(view.to_arrow())
|
|
294
|
+
|
|
295
|
+
def _update(port_id, delta):
|
|
296
|
+
print("Updating tbl2", delta)
|
|
297
|
+
tbl2.update(delta)
|
|
298
|
+
|
|
299
|
+
view.on_update(_update, mode="row")
|
|
300
|
+
for i in range(1000):
|
|
301
|
+
call_loop(tbl.update, [{"index": i, "num1": i, "num2": 2 * i}])
|
|
302
|
+
i += 1
|
|
303
|
+
|
|
304
|
+
call_loop(tbl.size)
|
|
305
|
+
|
|
306
|
+
q = queue.Queue()
|
|
307
|
+
call_loop(q.put, True)
|
|
308
|
+
q.get()
|
|
309
|
+
|
|
310
|
+
@syncify
|
|
311
|
+
def _tbl_task2():
|
|
312
|
+
size = tbl2.size()
|
|
313
|
+
return size
|
|
314
|
+
|
|
315
|
+
assert _tbl_task2() == 1000
|
|
316
|
+
# assert tbl2.size() == 1000
|
|
317
|
+
view.delete()
|
|
318
|
+
tbl.delete()
|
|
319
|
+
tbl2.delete()
|
|
320
|
+
|
|
321
|
+
def test_async_queue_process_multiple_ports(self):
|
|
322
|
+
server = Server()
|
|
323
|
+
client = Client.from_server(
|
|
324
|
+
server, lambda fn, *args: TestAsync.loop.add_callback(fn, *args)
|
|
325
|
+
)
|
|
326
|
+
tbl = client.table({"a": "integer", "b": "float", "c": "string"})
|
|
327
|
+
port_ids = [0]
|
|
328
|
+
port_data = [{"a": 0, "b": 0, "c": "0"}]
|
|
329
|
+
|
|
330
|
+
for i in range(10):
|
|
331
|
+
port_id = tbl.make_port()
|
|
332
|
+
port_ids.append(port_id)
|
|
333
|
+
port_data.append({"a": port_id, "b": port_id * 1.5, "c": str(port_id)})
|
|
334
|
+
|
|
335
|
+
assert port_ids == list(range(0, 11))
|
|
336
|
+
|
|
337
|
+
assert syncify(lambda: tbl.size())() == 0
|
|
338
|
+
|
|
339
|
+
random.shuffle(port_ids)
|
|
340
|
+
|
|
341
|
+
@syncify
|
|
342
|
+
def _tbl_task():
|
|
343
|
+
for port_id in port_ids:
|
|
344
|
+
idx = port_id if port_id < len(port_ids) else len(port_ids) - 1
|
|
345
|
+
tbl.update([port_data[idx]], port_id=port_id)
|
|
346
|
+
size = tbl.size()
|
|
347
|
+
tbl.delete()
|
|
348
|
+
return size
|
|
349
|
+
|
|
350
|
+
assert len(port_ids) == 11
|
|
351
|
+
assert _tbl_task() == 11
|
|
352
|
+
|
|
353
|
+
def test_async_multiple_managers_queue_process_multiple_ports(self):
|
|
354
|
+
server = Server()
|
|
355
|
+
client = Client.from_server(
|
|
356
|
+
server, lambda fn, *args: TestAsync.loop.add_callback(fn, *args)
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
server2 = Server()
|
|
360
|
+
client2 = Client.from_server(
|
|
361
|
+
server2, lambda fn, *args: TestAsync.loop.add_callback(fn, *args)
|
|
362
|
+
)
|
|
363
|
+
tbl = client.table({"a": "integer", "b": "float", "c": "string"})
|
|
364
|
+
tbl2 = client2.table({"a": "integer", "b": "float", "c": "string"})
|
|
365
|
+
port_ids = [0]
|
|
366
|
+
port_data = [{"a": 0, "b": 0, "c": "0"}]
|
|
367
|
+
|
|
368
|
+
for i in range(10):
|
|
369
|
+
port_id = tbl.make_port()
|
|
370
|
+
port_id2 = tbl2.make_port()
|
|
371
|
+
assert port_id == port_id2
|
|
372
|
+
port_ids.append(port_id)
|
|
373
|
+
port_data.append({"a": port_id, "b": port_id * 1.5, "c": str(port_id)})
|
|
374
|
+
|
|
375
|
+
@syncify
|
|
376
|
+
def _task():
|
|
377
|
+
random.shuffle(port_ids)
|
|
378
|
+
for port_id in port_ids:
|
|
379
|
+
idx = port_id if port_id < len(port_ids) else len(port_ids) - 1
|
|
380
|
+
tbl.update([port_data[idx]], port_id=port_id)
|
|
381
|
+
tbl2.update([port_data[idx]], port_id=port_id)
|
|
382
|
+
return (tbl.size(), tbl2.size())
|
|
383
|
+
|
|
384
|
+
assert _task() == (11, 11)
|
|
385
|
+
|
|
386
|
+
@mark.skip(
|
|
387
|
+
reason="This test is failing because we're not calling process after each update like before"
|
|
388
|
+
)
|
|
389
|
+
def test_async_multiple_managers_mixed_queue_process_multiple_ports(self):
|
|
390
|
+
sentinel = {"async": 0, "sync": 0}
|
|
391
|
+
|
|
392
|
+
def _counter(key, f, *args, **kwargs):
|
|
393
|
+
sentinel[key] += 1
|
|
394
|
+
return f(*args, **kwargs)
|
|
395
|
+
|
|
396
|
+
sync_process = partial(_counter, "sync")
|
|
397
|
+
async_process = partial(TestAsync.loop.add_timeout, 1, _counter, "async")
|
|
398
|
+
server = Server()
|
|
399
|
+
sync_client = Client.from_server(server, lambda *args: sync_process(*args))
|
|
400
|
+
async_client = Client.from_server(server, lambda *args: async_process(*args))
|
|
401
|
+
tbl = async_client.table({"a": "integer", "b": "float", "c": "string"})
|
|
402
|
+
tbl2 = sync_client.table({"a": "integer", "b": "float", "c": "string"})
|
|
403
|
+
port_ids = [0]
|
|
404
|
+
port_data = [{"a": 0, "b": 0, "c": "0"}]
|
|
405
|
+
|
|
406
|
+
for i in range(10):
|
|
407
|
+
port_id = tbl.make_port()
|
|
408
|
+
port_id2 = tbl2.make_port()
|
|
409
|
+
assert port_id == port_id2
|
|
410
|
+
port_ids.append(port_id)
|
|
411
|
+
port_data.append({"a": port_id, "b": port_id * 1.5, "c": str(port_id)})
|
|
412
|
+
|
|
413
|
+
random.shuffle(port_ids)
|
|
414
|
+
|
|
415
|
+
@syncify
|
|
416
|
+
def _task():
|
|
417
|
+
for port_id in port_ids:
|
|
418
|
+
idx = port_id if port_id < len(port_ids) else len(port_ids) - 1
|
|
419
|
+
tbl.update([port_data[idx]], port_id=port_id)
|
|
420
|
+
|
|
421
|
+
_task()
|
|
422
|
+
for port_id in port_ids:
|
|
423
|
+
idx = port_id if port_id < len(port_ids) else len(port_ids) - 1
|
|
424
|
+
tbl2.update([port_data[idx]], port_id=port_id)
|
|
425
|
+
|
|
426
|
+
@syncify
|
|
427
|
+
def _get_size():
|
|
428
|
+
size = tbl.size()
|
|
429
|
+
tbl.delete()
|
|
430
|
+
return size
|
|
431
|
+
|
|
432
|
+
assert _get_size() == 11
|
|
433
|
+
assert tbl2.size() == 11
|
|
434
|
+
assert sentinel["async"] == 1
|
|
435
|
+
assert sentinel["sync"] == 11
|
|
436
|
+
tbl2.delete()
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
|
|
14
|
+
import perspective as psp
|
|
15
|
+
|
|
16
|
+
server = psp.Server()
|
|
17
|
+
client = server.new_local_client()
|
|
18
|
+
Table = client.table
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def compare_delta(received, expected):
|
|
22
|
+
"""Compare an arrow-serialized row delta by constructing a Table."""
|
|
23
|
+
tbl = Table(received)
|
|
24
|
+
assert tbl.view().to_columns() == expected
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestThreadpool(object):
|
|
28
|
+
def test_set_threadpool_size(self):
|
|
29
|
+
server.set_threadpool_size(1) # XXX: This is a no-op now...
|
|
30
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
31
|
+
tbl = Table(data)
|
|
32
|
+
view = tbl.view()
|
|
33
|
+
dims = view.dimensions()
|
|
34
|
+
assert dims["num_view_rows"] == 2
|
|
35
|
+
assert dims["num_view_columns"] == 2
|
|
36
|
+
assert view.schema() == {"a": "integer", "b": "integer"}
|
|
37
|
+
assert view.to_records() == data
|
|
38
|
+
|
|
39
|
+
def test_set_threadpool_size_max(self):
|
|
40
|
+
server.set_threadpool_size(None)
|
|
41
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
42
|
+
tbl = Table(data)
|
|
43
|
+
view = tbl.view()
|
|
44
|
+
dims = view.dimensions()
|
|
45
|
+
assert dims["num_view_rows"] == 2
|
|
46
|
+
assert dims["num_view_columns"] == 2
|
|
47
|
+
assert view.schema() == {"a": "integer", "b": "integer"}
|
|
48
|
+
assert view.to_records() == data
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|