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,124 @@
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 TestDelete(object):
20
+ # delete
21
+
22
+ def test_table_delete(self):
23
+ data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
24
+ tbl = Table(data)
25
+ tbl.delete()
26
+ # don't segfault
27
+
28
+ def test_table_delete_callback(self, sentinel):
29
+ s = sentinel(False)
30
+
31
+ def callback():
32
+ s.set(True)
33
+
34
+ data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
35
+ tbl = Table(data)
36
+ tbl.on_delete(callback)
37
+ tbl.delete()
38
+ assert s.get() is True
39
+
40
+ def test_table_delete_with_view(self, sentinel):
41
+ s = sentinel(False)
42
+
43
+ def callback():
44
+ s.set(True)
45
+
46
+ data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
47
+ tbl = Table(data)
48
+ tbl.on_delete(callback)
49
+ view = tbl.view()
50
+ view.delete()
51
+ tbl.delete()
52
+ assert s.get() is True
53
+
54
+ def test_table_delete_multiple_callback(self, sentinel):
55
+ s1 = sentinel(False)
56
+ s2 = sentinel(False)
57
+
58
+ def callback1():
59
+ s1.set(True)
60
+
61
+ def callback2():
62
+ s2.set(True)
63
+
64
+ tbl = Table([{"a": 1}])
65
+ tbl.on_delete(callback1)
66
+ tbl.on_delete(callback2)
67
+
68
+ tbl.delete()
69
+
70
+ assert s1.get() is True
71
+ assert s2.get() is True
72
+
73
+ def test_table_remove_delete_callback(self, sentinel):
74
+ s = sentinel(False)
75
+
76
+ def callback():
77
+ s.set(True)
78
+
79
+ tbl = Table([{"a": 1}])
80
+ callback_id = tbl.on_delete(callback)
81
+ tbl.remove_delete(callback_id)
82
+
83
+ tbl.delete()
84
+
85
+ assert s.get() is False
86
+
87
+ def test_view_delete_multiple_callback(self, sentinel):
88
+ s1 = sentinel(False)
89
+ s2 = sentinel(False)
90
+
91
+ def callback1():
92
+ s1.set(True)
93
+
94
+ def callback2():
95
+ s2.set(True)
96
+
97
+ tbl = Table([{"a": 1}])
98
+ view = tbl.view()
99
+
100
+ view.on_delete(callback1)
101
+ view.on_delete(callback2)
102
+
103
+ view.delete()
104
+ tbl.delete()
105
+
106
+ assert s1.get() is True
107
+ assert s2.get() is True
108
+
109
+ def test_view_remove_delete_callback(self, sentinel):
110
+ s = sentinel(False)
111
+
112
+ def callback():
113
+ s.set(True)
114
+
115
+ tbl = Table([{"a": 1}])
116
+ view = tbl.view()
117
+
118
+ callback_id = view.on_delete(callback)
119
+ view.remove_delete(callback_id)
120
+
121
+ view.delete()
122
+ tbl.delete()
123
+
124
+ assert s.get() is False
@@ -0,0 +1,65 @@
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
+ from pytest import raises
14
+ from perspective import PerspectiveError
15
+
16
+ import perspective as psp
17
+
18
+ client = psp.Server().new_local_client()
19
+ Table = client.table
20
+
21
+
22
+ class TestException(object):
23
+ def test_instantiation_exception_table(self):
24
+ from perspective import Table
25
+ with raises(TypeError) as ex:
26
+ Table()
27
+ assert "Do not call Table's constructor directly" in str(ex.value)
28
+
29
+ def test_instantiation_exception_view(self):
30
+ from perspective import View
31
+ with raises(TypeError) as ex:
32
+ View()
33
+ assert "Do not call View's constructor directly" in str(ex.value)
34
+
35
+ def test_exception_from_core(self):
36
+ tbl = Table({"a": [1, 2, 3]})
37
+
38
+ with raises(PerspectiveError) as ex:
39
+ # creating view with unknown column should throw
40
+ tbl.view(group_by=["b"])
41
+
42
+ assert str(ex.value) == "Abort(): Invalid column 'b' found in View group_by.\n"
43
+
44
+ def test_exception_from_core_catch_generic(self):
45
+ tbl = Table({"a": [1, 2, 3]})
46
+ # `PerspectiveCppError` should inherit from `Exception`
47
+ with raises(Exception) as ex:
48
+ tbl.view(group_by=["b"])
49
+
50
+ assert str(ex.value) == "Abort(): Invalid column 'b' found in View group_by.\n"
51
+
52
+ def test_exception_from_core_correct_types(self):
53
+ tbl = Table({"a": [1, 2, 3]})
54
+
55
+ # `PerspectiveError` should be raised from the Python layer
56
+ with raises(PerspectiveError) as ex:
57
+ tbl.view()
58
+ tbl.delete()
59
+
60
+ assert str(ex.value) == "Abort(): Cannot delete table with views"
61
+
62
+ with raises(PerspectiveError) as ex:
63
+ tbl.view(group_by=["b"])
64
+
65
+ assert str(ex.value) == "Abort(): Invalid column 'b' found in View group_by.\n"
@@ -0,0 +1,54 @@
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 psutil
15
+ import os
16
+ import perspective as psp
17
+
18
+ client = psp.Server().new_local_client()
19
+ Table = client.table
20
+
21
+
22
+ class TestDelete(object):
23
+ # delete
24
+
25
+ def test_table_delete(self):
26
+ process = psutil.Process(os.getpid())
27
+ data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
28
+ tbl = Table(data)
29
+ tbl.delete()
30
+ mem = process.memory_info().rss
31
+
32
+ for x in range(10000):
33
+ data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
34
+ tbl = Table(data)
35
+ tbl.delete()
36
+
37
+ mem2 = process.memory_info().rss
38
+
39
+ # assert 1 < (max2 / max) < 1.01
40
+ assert (mem2 - mem) < 2000000
41
+
42
+ def test_table_delete_with_view(self, sentinel):
43
+ data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
44
+ tbl = Table(data)
45
+
46
+ process = psutil.Process(os.getpid())
47
+ mem = process.memory_info().rss
48
+ for x in range(10000):
49
+ view = tbl.view()
50
+ view.delete()
51
+
52
+ tbl.delete()
53
+ mem2 = process.memory_info().rss
54
+ assert (mem2 - mem) < 2000000
@@ -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
+ ]