perspective-python 5.6.0__cp311-abi3-pyemscripten_2026_0_wasm32.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 +403 -0
- perspective/extension/finos-perspective-nbextension.json +5 -0
- perspective/handlers/__init__.py +11 -0
- perspective/handlers/aiohttp.py +69 -0
- perspective/handlers/starlette.py +63 -0
- perspective/handlers/tornado.py +192 -0
- perspective/perspective.abi3.so +0 -0
- perspective/templates/exported_widget.html.template +39 -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/column_paths.py +33 -0
- perspective/tests/conftest.py +276 -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_derived_threaded.py +103 -0
- perspective/tests/multi_threaded/test_multi_threaded.py +293 -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 +88 -0
- perspective/tests/table/test_delete.py +124 -0
- perspective/tests/table/test_derived.py +145 -0
- perspective/tests/table/test_exception.py +73 -0
- perspective/tests/table/test_join.py +115 -0
- perspective/tests/table/test_leaks.py +105 -0
- perspective/tests/table/test_on_remove.py +140 -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 +500 -0
- perspective/tests/table/test_table_arrow_nested.py +535 -0
- perspective/tests/table/test_table_datetime.py +2544 -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_page_to_disk.py +304 -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 +416 -0
- perspective/tests/table/test_to_arrow_lz4.py +32 -0
- perspective/tests/table/test_to_arrow_zstd.py +41 -0
- perspective/tests/table/test_to_format.py +1030 -0
- perspective/tests/table/test_to_polars.py +26 -0
- perspective/tests/table/test_update.py +561 -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 +2503 -0
- perspective/tests/table/test_view_expression.py +2256 -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/virtual_servers/__init__.py +12 -0
- perspective/tests/virtual_servers/test_coerce_types.py +464 -0
- perspective/tests/virtual_servers/test_duckdb.py +1280 -0
- perspective/tests/virtual_servers/test_polars.py +1075 -0
- perspective/tests/virtual_servers/test_postgres.py +964 -0
- perspective/tests/widget/__init__.py +11 -0
- perspective/tests/widget/test_anywidget_smoke.py +109 -0
- perspective/tests/widget/test_widget.py +278 -0
- perspective/tests/widget/test_widget_html_export.py +113 -0
- perspective/tests/widget/test_widget_pandas.py +453 -0
- perspective/virtual_servers/__init__.py +206 -0
- perspective/virtual_servers/clickhouse.py +296 -0
- perspective/virtual_servers/duckdb.py +367 -0
- perspective/virtual_servers/polars.py +750 -0
- perspective/virtual_servers/postgres.py +448 -0
- perspective/widget/__init__.py +351 -0
- perspective/widget/static/perspective-anywidget.css +1 -0
- perspective/widget/static/perspective-anywidget.js +31 -0
- perspective/widget/viewer/__init__.py +15 -0
- perspective/widget/viewer/viewer.py +355 -0
- perspective/widget/viewer/viewer_traitlets.py +98 -0
- perspective_python-5.6.0.dist-info/METADATA +127 -0
- perspective_python-5.6.0.dist-info/RECORD +90 -0
- perspective_python-5.6.0.dist-info/WHEEL +4 -0
- perspective_python-5.6.0.dist-info/licenses/LICENSE.md +193 -0
- perspective_python-5.6.0.dist-info/licenses/LICENSE_THIRDPARTY_cargo.yml +16075 -0
- perspective_python-5.6.0.dist-info/sboms/perspective-python.cyclonedx.json +5334 -0
|
@@ -0,0 +1,140 @@
|
|
|
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 perspective as psp
|
|
14
|
+
|
|
15
|
+
client = psp.Server().new_local_client()
|
|
16
|
+
Table = client.table
|
|
17
|
+
|
|
18
|
+
data = [{"x": 1, "y": "a"}, {"x": 2, "y": "b"}, {"x": 3, "y": "c"}]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def removed_indices(indices):
|
|
22
|
+
return Table(indices).view().to_records()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestOnRemove(object):
|
|
26
|
+
def test_on_remove_fires_with_removed_index_values(self):
|
|
27
|
+
tbl = Table(data, index="x")
|
|
28
|
+
view = tbl.view()
|
|
29
|
+
calls = []
|
|
30
|
+
cid = view.on_remove(lambda port_id, indices: calls.append((port_id, indices)))
|
|
31
|
+
tbl.remove([2])
|
|
32
|
+
assert len(calls) == 1
|
|
33
|
+
assert calls[0][0] == 0
|
|
34
|
+
assert removed_indices(calls[0][1]) == [{"x": 2}]
|
|
35
|
+
view.remove_remove(cid)
|
|
36
|
+
|
|
37
|
+
def test_on_remove_string_index(self):
|
|
38
|
+
tbl = Table(data, index="y")
|
|
39
|
+
view = tbl.view()
|
|
40
|
+
calls = []
|
|
41
|
+
cid = view.on_remove(lambda port_id, indices: calls.append(indices))
|
|
42
|
+
tbl.remove(["b", "c"])
|
|
43
|
+
assert len(calls) == 1
|
|
44
|
+
assert removed_indices(calls[0]) == [{"y": "b"}, {"y": "c"}]
|
|
45
|
+
view.remove_remove(cid)
|
|
46
|
+
|
|
47
|
+
def test_on_remove_ignores_unknown_and_update(self):
|
|
48
|
+
tbl = Table(data, index="x")
|
|
49
|
+
view = tbl.view()
|
|
50
|
+
calls = []
|
|
51
|
+
cid = view.on_remove(lambda port_id, indices: calls.append(indices))
|
|
52
|
+
tbl.remove([9])
|
|
53
|
+
tbl.update([{"x": 1, "y": "aa"}, {"x": 4, "y": "d"}])
|
|
54
|
+
assert tbl.size() == 4
|
|
55
|
+
assert calls == []
|
|
56
|
+
tbl.remove([3])
|
|
57
|
+
assert len(calls) == 1
|
|
58
|
+
assert removed_indices(calls[0]) == [{"x": 3}]
|
|
59
|
+
view.remove_remove(cid)
|
|
60
|
+
|
|
61
|
+
def test_on_remove_replace_reports_keys_not_resupplied(self):
|
|
62
|
+
tbl = Table(data, index="x")
|
|
63
|
+
view = tbl.view()
|
|
64
|
+
calls = []
|
|
65
|
+
cid = view.on_remove(lambda port_id, indices: calls.append(indices))
|
|
66
|
+
tbl.replace([{"x": 2, "y": "bb"}, {"x": 4, "y": "d"}])
|
|
67
|
+
assert len(calls) == 1
|
|
68
|
+
assert removed_indices(calls[0]) == [{"x": 1}, {"x": 3}]
|
|
69
|
+
assert view.to_records() == [{"x": 2, "y": "bb"}, {"x": 4, "y": "d"}]
|
|
70
|
+
view.remove_remove(cid)
|
|
71
|
+
|
|
72
|
+
def test_on_remove_clear_reports_every_key(self):
|
|
73
|
+
tbl = Table(data, index="x")
|
|
74
|
+
view = tbl.view()
|
|
75
|
+
calls = []
|
|
76
|
+
updates = []
|
|
77
|
+
cid = view.on_remove(lambda port_id, indices: calls.append(indices))
|
|
78
|
+
view.on_update(lambda port_id: updates.append(port_id))
|
|
79
|
+
tbl.clear()
|
|
80
|
+
assert len(calls) == 1
|
|
81
|
+
assert removed_indices(calls[0]) == [{"x": 1}, {"x": 2}, {"x": 3}]
|
|
82
|
+
assert view.to_records() == []
|
|
83
|
+
assert updates == [0]
|
|
84
|
+
view.remove_remove(cid)
|
|
85
|
+
|
|
86
|
+
def test_remove_remove_stops_delivery(self):
|
|
87
|
+
tbl = Table(data, index="x")
|
|
88
|
+
view = tbl.view()
|
|
89
|
+
first = []
|
|
90
|
+
second = []
|
|
91
|
+
cid = view.on_remove(lambda port_id, indices: first.append(indices))
|
|
92
|
+
view.remove_remove(cid)
|
|
93
|
+
cid2 = view.on_remove(lambda port_id, indices: second.append(indices))
|
|
94
|
+
tbl.remove([1])
|
|
95
|
+
assert first == []
|
|
96
|
+
assert len(second) == 1
|
|
97
|
+
view.remove_remove(cid2)
|
|
98
|
+
|
|
99
|
+
def test_remove_accepts_arrow(self):
|
|
100
|
+
tbl = Table(data, index="x")
|
|
101
|
+
arrow = Table({"x": [2, 3]}).view().to_arrow()
|
|
102
|
+
tbl.remove(arrow)
|
|
103
|
+
assert tbl.view().to_records() == [{"x": 1, "y": "a"}]
|
|
104
|
+
|
|
105
|
+
def test_table_from_view_inherits_index_and_replicates_removes(self):
|
|
106
|
+
tbl = Table(data, index="x")
|
|
107
|
+
view = tbl.view()
|
|
108
|
+
tbl2 = Table(view)
|
|
109
|
+
assert tbl2.get_index() == "x"
|
|
110
|
+
view2 = tbl2.view()
|
|
111
|
+
|
|
112
|
+
tbl.update([{"x": 2, "y": "bb"}])
|
|
113
|
+
assert tbl2.size() == 3
|
|
114
|
+
assert view2.to_records() == view.to_records()
|
|
115
|
+
|
|
116
|
+
tbl.remove([1])
|
|
117
|
+
assert tbl2.size() == 2
|
|
118
|
+
assert view2.to_records() == [{"x": 2, "y": "bb"}, {"x": 3, "y": "c"}]
|
|
119
|
+
assert view2.to_records() == view.to_records()
|
|
120
|
+
|
|
121
|
+
def test_table_from_view_mirrors_replace_and_clear(self):
|
|
122
|
+
tbl = Table(data, index="x")
|
|
123
|
+
view = tbl.view()
|
|
124
|
+
tbl2 = Table(view)
|
|
125
|
+
view2 = tbl2.view()
|
|
126
|
+
|
|
127
|
+
tbl.replace([{"x": 2, "y": "bb"}, {"x": 4, "y": "d"}])
|
|
128
|
+
assert view2.to_records() == [{"x": 2, "y": "bb"}, {"x": 4, "y": "d"}]
|
|
129
|
+
|
|
130
|
+
tbl.clear()
|
|
131
|
+
assert view2.to_records() == []
|
|
132
|
+
|
|
133
|
+
tbl.update([{"x": 5, "y": "e"}])
|
|
134
|
+
assert view2.to_records() == [{"x": 5, "y": "e"}]
|
|
135
|
+
|
|
136
|
+
def test_table_from_pivoted_view_stays_unindexed(self):
|
|
137
|
+
tbl = Table(data, index="x")
|
|
138
|
+
view = tbl.view(group_by=["y"])
|
|
139
|
+
tbl2 = Table(view)
|
|
140
|
+
assert tbl2.get_index() is None
|
|
@@ -0,0 +1,178 @@
|
|
|
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 random
|
|
14
|
+
import perspective as psp
|
|
15
|
+
|
|
16
|
+
client = psp.Server().new_local_client()
|
|
17
|
+
Table = client.table
|
|
18
|
+
|
|
19
|
+
data = {"a": [1, 2, 3, 4], "b": ["a", "b", "c", "d"], "c": [True, False, True, False]}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestPorts(object):
|
|
23
|
+
def test_make_port_sequential(self):
|
|
24
|
+
table = Table(data)
|
|
25
|
+
port_ids = []
|
|
26
|
+
|
|
27
|
+
for i in range(10):
|
|
28
|
+
port_ids.append(table.make_port())
|
|
29
|
+
|
|
30
|
+
assert port_ids == list(range(1, 11))
|
|
31
|
+
|
|
32
|
+
def test_make_port_sequential_and_update(self):
|
|
33
|
+
table = Table(data)
|
|
34
|
+
port_ids = []
|
|
35
|
+
|
|
36
|
+
for i in range(10):
|
|
37
|
+
port_ids.append(table.make_port())
|
|
38
|
+
|
|
39
|
+
assert port_ids == list(range(1, 11))
|
|
40
|
+
|
|
41
|
+
for i in range(1, 11):
|
|
42
|
+
table.update({"a": [i], "b": ["a"], "c": [True]}, port_id=i)
|
|
43
|
+
|
|
44
|
+
view = table.view()
|
|
45
|
+
result = view.to_columns()
|
|
46
|
+
|
|
47
|
+
assert result == {
|
|
48
|
+
"a": [1, 2, 3, 4] + [i for i in range(1, 11)],
|
|
49
|
+
"b": ["a", "b", "c", "d"] + ["a" for i in range(10)],
|
|
50
|
+
"c": [True, False, True, False] + [True for i in range(10)],
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def test_arbitary_port_updates(self):
|
|
54
|
+
table = Table(data)
|
|
55
|
+
port_ids = []
|
|
56
|
+
|
|
57
|
+
for i in range(10):
|
|
58
|
+
port_ids.append(table.make_port())
|
|
59
|
+
|
|
60
|
+
assert port_ids == list(range(1, 11))
|
|
61
|
+
|
|
62
|
+
port = random.randint(0, 10)
|
|
63
|
+
|
|
64
|
+
table.update(data, port_id=port)
|
|
65
|
+
|
|
66
|
+
assert table.size() == 8
|
|
67
|
+
|
|
68
|
+
assert table.view().to_columns() == {
|
|
69
|
+
"a": [1, 2, 3, 4] * 2,
|
|
70
|
+
"b": ["a", "b", "c", "d"] * 2,
|
|
71
|
+
"c": [True, False, True, False] * 2,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def test_ports_should_only_notify_if_they_have_a_queued_update(self):
|
|
75
|
+
table = Table(data)
|
|
76
|
+
port_ids = []
|
|
77
|
+
|
|
78
|
+
for i in range(10):
|
|
79
|
+
port_ids.append(table.make_port())
|
|
80
|
+
|
|
81
|
+
assert port_ids == list(range(1, 11))
|
|
82
|
+
|
|
83
|
+
view = table.view()
|
|
84
|
+
ports_to_update = [random.randint(0, 10) for i in range(5)]
|
|
85
|
+
|
|
86
|
+
def callback(port_id):
|
|
87
|
+
assert port_id in ports_to_update
|
|
88
|
+
|
|
89
|
+
view.on_update(callback)
|
|
90
|
+
|
|
91
|
+
for port in ports_to_update:
|
|
92
|
+
table.update(data, port_id=port)
|
|
93
|
+
|
|
94
|
+
def test_ports_should_have_unique_deltas(self):
|
|
95
|
+
table = Table(data)
|
|
96
|
+
port_ids = []
|
|
97
|
+
|
|
98
|
+
for i in range(10):
|
|
99
|
+
port_ids.append(table.make_port())
|
|
100
|
+
|
|
101
|
+
assert port_ids == list(range(1, 11))
|
|
102
|
+
|
|
103
|
+
view = table.view()
|
|
104
|
+
ports_to_update = [random.randint(0, 10) for i in range(5)]
|
|
105
|
+
unique_data = {
|
|
106
|
+
port: [{"a": port, "b": str(port), "c": True}] for port in ports_to_update
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def callback(port_id, delta):
|
|
110
|
+
assert port_id in ports_to_update
|
|
111
|
+
_t = Table(delta)
|
|
112
|
+
_v = _t.view()
|
|
113
|
+
assert _v.to_records() == unique_data[port]
|
|
114
|
+
_v.delete()
|
|
115
|
+
_t.delete()
|
|
116
|
+
|
|
117
|
+
view.on_update(callback, mode="row")
|
|
118
|
+
|
|
119
|
+
for port in ports_to_update:
|
|
120
|
+
table.update(unique_data[port], port_id=port)
|
|
121
|
+
|
|
122
|
+
def test_ports_should_queue_updates_properly(self):
|
|
123
|
+
table = Table(data)
|
|
124
|
+
port_ids = []
|
|
125
|
+
|
|
126
|
+
for i in range(10):
|
|
127
|
+
port_ids.append(table.make_port())
|
|
128
|
+
|
|
129
|
+
assert port_ids == list(range(1, 11))
|
|
130
|
+
|
|
131
|
+
view = table.view()
|
|
132
|
+
ports_to_update = [random.randint(0, 10) for i in range(5)]
|
|
133
|
+
|
|
134
|
+
def callback(port_id):
|
|
135
|
+
assert port_id in ports_to_update
|
|
136
|
+
|
|
137
|
+
view.on_update(callback)
|
|
138
|
+
|
|
139
|
+
for port in ports_to_update:
|
|
140
|
+
table.update(data, port_id=port)
|
|
141
|
+
|
|
142
|
+
def test_ports_multiple_tables_with_different_ports(self):
|
|
143
|
+
server = Table(data)
|
|
144
|
+
client = Table(data)
|
|
145
|
+
|
|
146
|
+
for i in range(random.randint(5, 15)):
|
|
147
|
+
# reserve an arbitary number of ports
|
|
148
|
+
server.make_port()
|
|
149
|
+
|
|
150
|
+
# port for client is now far above the ports "ON" the client, as the
|
|
151
|
+
# client ports will begin creation at 1.
|
|
152
|
+
server_port_for_client = server.make_port()
|
|
153
|
+
client_port = client.make_port()
|
|
154
|
+
|
|
155
|
+
server_view = server.view()
|
|
156
|
+
client_view = client.view()
|
|
157
|
+
|
|
158
|
+
# when the client updates, check whether the port id matches that
|
|
159
|
+
# of the server, and complete the test.
|
|
160
|
+
def client_callback(port_id, delta):
|
|
161
|
+
if port_id == client_port:
|
|
162
|
+
print("UPDATING SERVER")
|
|
163
|
+
server.update(delta, port_id=server_port_for_client)
|
|
164
|
+
|
|
165
|
+
# when the server updates, pass the update back to the client
|
|
166
|
+
def server_callback(port_id, delta):
|
|
167
|
+
print("UPDATING CLIENT")
|
|
168
|
+
assert port_id == server_port_for_client
|
|
169
|
+
assert server.size() == 8
|
|
170
|
+
server_view.delete()
|
|
171
|
+
server.delete()
|
|
172
|
+
client_view.delete()
|
|
173
|
+
client.delete()
|
|
174
|
+
|
|
175
|
+
client_view.on_update(client_callback, mode="row")
|
|
176
|
+
server_view.on_update(server_callback, mode="row")
|
|
177
|
+
|
|
178
|
+
client.update(data, port_id=client_port)
|
|
@@ -0,0 +1,102 @@
|
|
|
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 perspective as psp
|
|
14
|
+
|
|
15
|
+
client = psp.Server().new_local_client()
|
|
16
|
+
Table = client.table
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestRemove(object):
|
|
20
|
+
def test_remove_all(self):
|
|
21
|
+
tbl = Table([{"a": "abc", "b": 123}], index="a")
|
|
22
|
+
tbl.remove(["abc"])
|
|
23
|
+
assert tbl.view().to_records() == []
|
|
24
|
+
# assert tbl.size() == 0
|
|
25
|
+
|
|
26
|
+
def test_remove_nonsequential(self):
|
|
27
|
+
tbl = Table(
|
|
28
|
+
[{"a": "abc", "b": 123}, {"a": "def", "b": 456}, {"a": "efg", "b": 789}],
|
|
29
|
+
index="a",
|
|
30
|
+
)
|
|
31
|
+
tbl.remove(["abc", "efg"])
|
|
32
|
+
assert tbl.view().to_records() == [{"a": "def", "b": 456}]
|
|
33
|
+
# assert tbl.size() == 1
|
|
34
|
+
|
|
35
|
+
def test_remove_multiple_single(self):
|
|
36
|
+
tbl = Table({"a": "integer", "b": "string"}, index="a")
|
|
37
|
+
for i in range(0, 10):
|
|
38
|
+
tbl.update([{"a": i, "b": str(i)}])
|
|
39
|
+
for i in range(1, 10):
|
|
40
|
+
tbl.remove([i])
|
|
41
|
+
assert tbl.view().to_records() == [{"a": 0, "b": "0"}]
|
|
42
|
+
# assert tbl.size() == 0
|
|
43
|
+
|
|
44
|
+
def test_remove_expressions(self):
|
|
45
|
+
schema = {"key": "string", "delta$": "float", "business_line": "string"}
|
|
46
|
+
data = [
|
|
47
|
+
{
|
|
48
|
+
"key": "A",
|
|
49
|
+
"delta$": 46412.3804275,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"key": "B",
|
|
53
|
+
"delta$": 2317615.875,
|
|
54
|
+
},
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
table = Table(schema, index="key")
|
|
58
|
+
table.update(data)
|
|
59
|
+
table.remove(["A"])
|
|
60
|
+
view = table.view(
|
|
61
|
+
group_by=["business_line"],
|
|
62
|
+
columns=["delta$", "alias"],
|
|
63
|
+
expressions={
|
|
64
|
+
"alias": '"delta$"',
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
records = view.to_records()
|
|
69
|
+
assert records == [
|
|
70
|
+
{"__ROW_PATH__": [], "delta$": 2317615.875, "alias": 2317615.875},
|
|
71
|
+
{"__ROW_PATH__": [None], "delta$": 2317615.875, "alias": 2317615.875},
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
def test_remove_expressions_after_view(self):
|
|
75
|
+
schema = {"key": "string", "delta$": "float", "business_line": "string"}
|
|
76
|
+
data = [
|
|
77
|
+
{
|
|
78
|
+
"key": "A",
|
|
79
|
+
"delta$": 46412.3804275,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"key": "B",
|
|
83
|
+
"delta$": 2317615.875,
|
|
84
|
+
},
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
table = Table(schema, index="key")
|
|
88
|
+
table.update(data)
|
|
89
|
+
view = table.view(
|
|
90
|
+
group_by=["business_line"],
|
|
91
|
+
columns=["delta$", "alias"],
|
|
92
|
+
expressions={
|
|
93
|
+
"alias": '"delta$"',
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
table.remove(["A"])
|
|
98
|
+
records = view.to_records()
|
|
99
|
+
assert records == [
|
|
100
|
+
{"__ROW_PATH__": [], "delta$": 2317615.875, "alias": 2317615.875},
|
|
101
|
+
{"__ROW_PATH__": [None], "delta$": 2317615.875, "alias": 2317615.875},
|
|
102
|
+
]
|