perspective-python 3.0.0__cp39-abi3-macosx_13_0_arm64.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 (71) hide show
  1. perspective/__init__.py +81 -0
  2. perspective/darwin-arm64-libpsp.so +0 -0
  3. perspective/extension/finos-perspective-nbextension.json +5 -0
  4. perspective/handlers/__init__.py +26 -0
  5. perspective/handlers/aiohttp.py +54 -0
  6. perspective/handlers/starlette.py +51 -0
  7. perspective/handlers/tornado.py +61 -0
  8. perspective/perspective.abi3.so +0 -0
  9. perspective/psp_cffi.py +127 -0
  10. perspective/templates/exported_widget.html.jinja +35 -0
  11. perspective/tests/__init__.py +11 -0
  12. perspective/tests/conftest.py +272 -0
  13. perspective/tests/core/__init__.py +11 -0
  14. perspective/tests/core/test_async.py +413 -0
  15. perspective/tests/core/test_threadpool.py +48 -0
  16. perspective/tests/server/__init__.py +11 -0
  17. perspective/tests/server/test_server.py +1058 -0
  18. perspective/tests/server/test_session.py +55 -0
  19. perspective/tests/single_threaded/test_single_threaded.py +61 -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_delete.py +124 -0
  30. perspective/tests/table/test_exception.py +53 -0
  31. perspective/tests/table/test_leaks.py +54 -0
  32. perspective/tests/table/test_ports.py +178 -0
  33. perspective/tests/table/test_remove.py +102 -0
  34. perspective/tests/table/test_table.py +612 -0
  35. perspective/tests/table/test_table_arrow.py +452 -0
  36. perspective/tests/table/test_table_datetime.py +2409 -0
  37. perspective/tests/table/test_table_infer.py +201 -0
  38. perspective/tests/table/test_table_limit.py +41 -0
  39. perspective/tests/table/test_table_numpy.py +1022 -0
  40. perspective/tests/table/test_table_pandas.py +1018 -0
  41. perspective/tests/table/test_to_arrow.py +417 -0
  42. perspective/tests/table/test_to_arrow_lz4.py +32 -0
  43. perspective/tests/table/test_to_format.py +1024 -0
  44. perspective/tests/table/test_update.py +545 -0
  45. perspective/tests/table/test_update_arrow.py +980 -0
  46. perspective/tests/table/test_update_pandas.py +211 -0
  47. perspective/tests/table/test_view.py +2234 -0
  48. perspective/tests/table/test_view_expression.py +1940 -0
  49. perspective/tests/viewer/__init__.py +11 -0
  50. perspective/tests/viewer/test_validate.py +69 -0
  51. perspective/tests/viewer/test_viewer.py +245 -0
  52. perspective/tests/widget/__init__.py +11 -0
  53. perspective/tests/widget/test_widget.py +278 -0
  54. perspective/tests/widget/test_widget_pandas.py +453 -0
  55. perspective/viewer/__init__.py +15 -0
  56. perspective/viewer/validate.py +22 -0
  57. perspective/viewer/viewer.py +331 -0
  58. perspective/viewer/viewer_traitlets.py +101 -0
  59. perspective/widget/__init__.py +16 -0
  60. perspective/widget/widget.py +269 -0
  61. perspective_python-3.0.0.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/install.json +5 -0
  62. perspective_python-3.0.0.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/package.json +80 -0
  63. perspective_python-3.0.0.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/253.3b3f5e7f7f7cce48f967.js +18 -0
  64. perspective_python-3.0.0.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/253.3b3f5e7f7f7cce48f967.js.LICENSE.txt +59 -0
  65. perspective_python-3.0.0.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/905.bda9d13f81bc1d4190ff.js +1 -0
  66. perspective_python-3.0.0.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/remoteEntry.1f19f47ae899be5b3e51.js +1 -0
  67. perspective_python-3.0.0.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/style.js +4 -0
  68. perspective_python-3.0.0.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/third-party-licenses.json +16 -0
  69. perspective_python-3.0.0.dist-info/METADATA +30 -0
  70. perspective_python-3.0.0.dist-info/RECORD +71 -0
  71. perspective_python-3.0.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,81 @@
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
+ __version__ = "3.0.0"
14
+ __all__ = [
15
+ "_jupyter_labextension_paths",
16
+ "PerspectiveError",
17
+ "PerspectiveWidget",
18
+ "PerspectiveViewer",
19
+ "PerspectiveTornadoHandler",
20
+ "ProxySession",
21
+ "Table",
22
+ "View",
23
+ "Server",
24
+ "Client",
25
+ ]
26
+
27
+ from .perspective import (
28
+ Client as PySyncClient,
29
+ PerspectiveError,
30
+ Table,
31
+ View,
32
+ ProxySession,
33
+ )
34
+
35
+ from .widget import PerspectiveWidget
36
+ from .viewer import PerspectiveViewer
37
+
38
+ from .psp_cffi import ServerBase
39
+
40
+ try:
41
+ from .handlers import PerspectiveTornadoHandler
42
+ except ImportError:
43
+ ...
44
+
45
+
46
+ def default_loop_cb(fn, *args, **kwargs):
47
+ return fn(*args, **kwargs)
48
+
49
+
50
+ class Server(ServerBase):
51
+ def set_threadpool_size(self, n_cpus):
52
+ pass
53
+
54
+ def new_local_client(self, loop_callback=default_loop_cb):
55
+ """Create a new `Client` instance bound to this in-process `Server`."""
56
+ return Client.from_server(self, loop_callback)
57
+
58
+
59
+ class Client(PySyncClient):
60
+ def from_server(
61
+ server: Server,
62
+ loop_callback=default_loop_cb,
63
+ ):
64
+ """Create a new `Client` instance bound synchronously to an Python
65
+ instance of `PerspectiveServer`."""
66
+
67
+ def handle_request(bytes):
68
+ session.handle_request(bytes)
69
+ loop_callback(lambda: session.poll())
70
+
71
+ def handle_response(bytes):
72
+ client.handle_response(bytes)
73
+
74
+ session = server.new_session(handle_response)
75
+ client = Client(handle_request)
76
+ return client
77
+
78
+
79
+ # Read by `jupyter labextension develop`
80
+ def _jupyter_labextension_paths():
81
+ return [{"src": "labextension", "dest": "@finos/perspective-jupyterlab"}]
Binary file
@@ -0,0 +1,5 @@
1
+ {
2
+ "load_extensions": {
3
+ "@finos/perspective-jupyterlab/extension": true
4
+ }
5
+ }
@@ -0,0 +1,26 @@
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
+ try:
14
+ from .aiohttp import PerspectiveAIOHTTPHandler # noqa: F401
15
+ except ImportError:
16
+ ...
17
+
18
+ try:
19
+ from .starlette import PerspectiveStarletteHandler # noqa: F401
20
+ except ImportError:
21
+ ...
22
+
23
+ try:
24
+ from .tornado import PerspectiveTornadoHandler # noqa: F401
25
+ except ImportError:
26
+ ...
@@ -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
+ from aiohttp import web, WSMsgType
14
+ import asyncio
15
+
16
+
17
+ class PerspectiveAIOHTTPHandler(object):
18
+ """PerspectiveAIOHTTPHandler is a drop-in implementation of Perspective.
19
+
20
+ Use it inside AIOHTTP routing to create a server-side Perspective that is
21
+ ready to receive websocket messages from the front-end `perspective-viewer`.
22
+
23
+ The Perspective client and server will automatically keep the Websocket
24
+ alive without timing out.
25
+
26
+ Examples:
27
+ >>> server = Server()
28
+ >>> async def websocket_handler(request):
29
+ ... handler = PerspectiveAIOHTTPHandler(perspective_server=server, request=request)
30
+ ... await handler.run()
31
+
32
+ >>> app = web.Application()
33
+ >>> app.router.add_get("/websocket", websocket_handler)
34
+ """
35
+
36
+ def __init__(self, **kwargs):
37
+ self.server = kwargs.pop("perspective_server")
38
+ self._request = kwargs.pop("request")
39
+ super().__init__(**kwargs)
40
+
41
+ async def run(self) -> None:
42
+ def inner(msg):
43
+ asyncio.get_running_loop().create_task(self._ws.send_bytes(msg))
44
+
45
+ self.session = self.server.new_session(inner)
46
+ try:
47
+ self._ws = web.WebSocketResponse()
48
+ await self._ws.prepare(self._request)
49
+ async for msg in self._ws:
50
+ if msg.type == WSMsgType.BINARY:
51
+ self.session.handle_request(msg.data)
52
+
53
+ finally:
54
+ self.session.close()
@@ -0,0 +1,51 @@
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 asyncio
14
+
15
+
16
+ class PerspectiveStarletteHandler(object):
17
+ """PerspectiveStarletteHandler is a drop-in implementation of Perspective.
18
+
19
+ The Perspective client and server will automatically keep the Websocket
20
+ alive without timing out.
21
+
22
+ Examples:
23
+ >>> server = Server()
24
+ >>> client = server.client()
25
+ >>> client.table(pd.read_csv("superstore.csv"), name="data_source_one")
26
+ >>> app = FastAPI()
27
+ >>> async def endpoint(websocket: Websocket):
28
+ ... handler = PerspectiveStarletteHandler(server, websocket)
29
+ ... await handler.run()
30
+ ... app.add_api_websocket_route('/websocket', endpoint)
31
+ """
32
+
33
+ def __init__(self, **kwargs):
34
+ self._server = kwargs.pop("perspective_server")
35
+ self._websocket = kwargs.pop("websocket")
36
+ super().__init__(**kwargs)
37
+
38
+ async def run(self) -> None:
39
+ def inner(msg):
40
+ asyncio.get_running_loop().create_task(self._websocket.send_bytes(msg))
41
+
42
+ self.session = self._server.new_session(inner)
43
+
44
+ try:
45
+ await self._websocket.accept()
46
+ while True:
47
+ message = await self._websocket.receive()
48
+ self._websocket._raise_on_disconnect(message)
49
+ self.session.handle_request(message["bytes"])
50
+ finally:
51
+ self.session.close()
@@ -0,0 +1,61 @@
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 tornado.websocket import WebSocketHandler
14
+
15
+
16
+ class PerspectiveTornadoHandler(WebSocketHandler):
17
+ """PerspectiveTornadoHandler is a drop-in implementation of Perspective.
18
+
19
+ Use it inside Tornado routing to create a server-side Perspective that is
20
+ ready to receive websocket messages from the front-end `perspective-viewer`.
21
+ Because Tornado implements an event loop, this handler links Perspective
22
+ with `IOLoop.current()` in order to defer expensive operations until the
23
+ next free iteration of the event loop.
24
+
25
+ The Perspective client and server will automatically keep the Websocket
26
+ alive without timing out.
27
+
28
+ Examples:
29
+ >>> server = psp.Server()
30
+ >>> client = server.new_local_client()
31
+ >>> client.table(pd.read_csv("superstore.csv"), name="data_source_one")
32
+ >>> app = tornado.web.Application([
33
+ ... (r"/", MainHandler),
34
+ ... (r"/websocket", PerspectiveTornadoHandler, {
35
+ ... "perspective_server": server,
36
+ ... "check_origin": True
37
+ ... })
38
+ ... ])
39
+ """
40
+
41
+ def initialize(self, perspective_server):
42
+ self.server = perspective_server
43
+
44
+ def open(self):
45
+ def inner(msg):
46
+ self.write_message(msg, binary=True)
47
+
48
+ self.session = self.server.new_session(inner)
49
+
50
+ def on_close(self) -> None:
51
+ self.session.close()
52
+ del self.session
53
+
54
+ def on_message(self, msg: bytes):
55
+ if not isinstance(msg, bytes):
56
+ return
57
+
58
+ self.session.handle_request(msg)
59
+
60
+ # TODO schedule me
61
+ self.session.poll()
Binary file
@@ -0,0 +1,127 @@
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 ctypes
14
+ import os
15
+ import platform
16
+ from typing import Callable
17
+
18
+ if "PSP_CXX_SO_PATH" in os.environ:
19
+ lib = ctypes.CDLL(os.environ["PSP_CXX_SO_PATH"])
20
+ else:
21
+ ext = "so"
22
+ if platform.system().lower() == "windows":
23
+ ext = "dll"
24
+ arch = platform.machine().lower()
25
+ if arch == "amd64":
26
+ arch = "x86_64"
27
+ dylib_name = f"{platform.system().lower()}-{arch}-libpsp.{ext}"
28
+ lib = ctypes.CDLL(os.path.join(os.path.dirname(__file__), dylib_name))
29
+
30
+
31
+ class EncodedApiResp(ctypes.Structure):
32
+ _pack_ = 1
33
+ _fields_ = [
34
+ ("data", ctypes.POINTER(ctypes.c_char)),
35
+ ("size", ctypes.c_uint32),
36
+ ("client_id", ctypes.c_uint32),
37
+ ]
38
+
39
+
40
+ class EncodedApiEntries(ctypes.Structure):
41
+ _pack_ = 1
42
+ _fields_ = [
43
+ ("size", ctypes.c_uint32),
44
+ ("entries", ctypes.POINTER(EncodedApiResp)),
45
+ ]
46
+
47
+
48
+ ProtoApiServerPtr = ctypes.c_void_p
49
+ lib.psp_new_server.restype = ProtoApiServerPtr
50
+
51
+ lib.psp_new_session.argtypes = [ProtoApiServerPtr]
52
+ lib.psp_new_session.restype = ctypes.c_uint32
53
+
54
+ lib.psp_handle_request.argtypes = [
55
+ ProtoApiServerPtr,
56
+ ctypes.c_uint32,
57
+ ctypes.c_char_p,
58
+ ctypes.c_size_t,
59
+ ]
60
+ lib.psp_handle_request.restype = ctypes.POINTER(EncodedApiEntries)
61
+
62
+ lib.psp_poll.argtypes = [ProtoApiServerPtr]
63
+ lib.psp_poll.restype = ctypes.POINTER(EncodedApiEntries)
64
+
65
+ lib.psp_new_session.argtypes = [ProtoApiServerPtr]
66
+ lib.psp_new_session.restype = ctypes.c_uint32
67
+
68
+ lib.psp_close_session.argtypes = [ProtoApiServerPtr, ctypes.c_uint32]
69
+ lib.psp_close_session.restype = None
70
+
71
+ lib.psp_delete_server.argtypes = [ProtoApiServerPtr]
72
+ lib.psp_delete_server.restype = None
73
+
74
+
75
+ class Session:
76
+ def __init__(self, server: "ServerBase"):
77
+ self._server: "ServerBase" = server
78
+ self._session_id = lib.psp_new_session(server._server)
79
+
80
+ def __del__(self):
81
+ lib.psp_close_session(self._server._server, self._session_id)
82
+
83
+ def handle_request(self, bytes_msg):
84
+ resps = lib.psp_handle_request(
85
+ self._server._server, self._session_id, bytes_msg, len(bytes_msg)
86
+ )
87
+ try:
88
+ size = resps.contents.size
89
+ for i in range(size):
90
+ resp = resps.contents.entries[i]
91
+ self._server._client_cbs[resp.client_id](resp.data[: resp.size])
92
+ finally:
93
+ for i in range(size):
94
+ lib.psp_free(resps.contents.entries[i].data)
95
+ lib.psp_free(resps.contents.entries)
96
+ lib.psp_free(resps)
97
+
98
+ def poll(self):
99
+ resps = lib.psp_poll(self._server._server)
100
+ try:
101
+ size = resps.contents.size
102
+ for i in range(size):
103
+ resp = resps.contents.entries[i]
104
+ self._server._client_cbs[resp.client_id](resp.data[: resp.size])
105
+ finally:
106
+ for i in range(size):
107
+ lib.psp_free(resps.contents.entries[i].data)
108
+ lib.psp_free(resps.contents.entries)
109
+ lib.psp_free(resps)
110
+
111
+
112
+ class ServerBase:
113
+ def __init__(self):
114
+ self._client_cbs: dict[str, Callable[[bytes], None]] = {}
115
+ self._sessions: list[Session] = []
116
+ self._server: int = lib.psp_new_server()
117
+
118
+ def __del__(self):
119
+ for session in self._sessions:
120
+ del session
121
+ lib.psp_delete_server(self._server)
122
+
123
+ def new_session(self, cb: Callable[[bytes], None]):
124
+ session = Session(self)
125
+ self._client_cbs[session._session_id] = cb
126
+ self._sessions.append(session)
127
+ return session
@@ -0,0 +1,35 @@
1
+ <script type="module" src="{{ psp_cdn('perspective') }}"></script>
2
+ <script type="module" src="{{ psp_cdn('perspective-viewer') }}"></script>
3
+ <script type="module" src="{{ psp_cdn('perspective-viewer-datagrid') }}"></script>
4
+ <script type="module" src="{{ psp_cdn('perspective-viewer-d3fc') }}"></script>
5
+ <link rel="stylesheet" crossorigin="anonymous" href="{{ psp_cdn('perspective-viewer', 'css/pro.css') }}" />
6
+
7
+ <div class="perspective-envelope" id="perspective-envelope-{{viewer_id}}">
8
+ <script type="application/vnd.apache.arrow.file">
9
+ {{ b64_data }}
10
+ </script>
11
+ <perspective-viewer style="height: 690px;"></perspective-viewer>
12
+ <script type="module">
13
+ // from MDN
14
+ function base64ToBytes(base64) {
15
+ const binString = atob(base64);
16
+ return Uint8Array.from(binString, (m) => m.codePointAt(0));
17
+ }
18
+
19
+ import * as perspective from "{{ psp_cdn('perspective') }}";
20
+ const viewerId = {{ viewer_id | tojson }};
21
+ const currentScript = document.scripts[document.scripts.length - 1];
22
+ const envelope = document.getElementById(`perspective-envelope-${viewerId}`);
23
+ const dataScript = envelope.querySelector('script[type="application/vnd.apache.arrow.file"]');;
24
+ if (!dataScript)
25
+ throw new Error('data script missing for viewer', viewerId);
26
+ const data = base64ToBytes(dataScript.textContent);
27
+ const viewerAttrs = {{ viewer_attrs | tojson }};
28
+
29
+ // Create a new worker, then a new table promise on that worker.
30
+ const table = await perspective.worker().table(data.buffer);
31
+ const viewer = envelope.querySelector('perspective-viewer');
32
+ viewer.load(table);
33
+ viewer.restore(viewerAttrs);
34
+ </script>
35
+ </div>
@@ -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
+ # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛