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.
Files changed (70) hide show
  1. perspective/__init__.py +78 -0
  2. perspective/extension/finos-perspective-nbextension.json +5 -0
  3. perspective/handlers/__init__.py +26 -0
  4. perspective/handlers/aiohttp.py +54 -0
  5. perspective/handlers/starlette.py +51 -0
  6. perspective/handlers/tornado.py +61 -0
  7. perspective/perspective.pyd +0 -0
  8. perspective/templates/exported_widget.html.jinja +35 -0
  9. perspective/tests/__init__.py +11 -0
  10. perspective/tests/conftest.py +268 -0
  11. perspective/tests/core/__init__.py +11 -0
  12. perspective/tests/core/test_async.py +436 -0
  13. perspective/tests/core/test_threadpool.py +48 -0
  14. perspective/tests/server/__init__.py +11 -0
  15. perspective/tests/server/test_server.py +1062 -0
  16. perspective/tests/server/test_session.py +55 -0
  17. perspective/tests/single_threaded/test_single_threaded.py +61 -0
  18. perspective/tests/table/__init__.py +11 -0
  19. perspective/tests/table/arrow/date32.arrow +0 -0
  20. perspective/tests/table/arrow/date64.arrow +0 -0
  21. perspective/tests/table/arrow/dict.arrow +0 -0
  22. perspective/tests/table/arrow/dict_update.arrow +0 -0
  23. perspective/tests/table/arrow/int_float_str.arrow +0 -0
  24. perspective/tests/table/arrow/int_float_str_file.arrow +0 -0
  25. perspective/tests/table/arrow/int_float_str_update.arrow +0 -0
  26. perspective/tests/table/object_sequence.py +402 -0
  27. perspective/tests/table/test_delete.py +124 -0
  28. perspective/tests/table/test_exception.py +53 -0
  29. perspective/tests/table/test_leaks.py +54 -0
  30. perspective/tests/table/test_ports.py +178 -0
  31. perspective/tests/table/test_remove.py +102 -0
  32. perspective/tests/table/test_table.py +610 -0
  33. perspective/tests/table/test_table_arrow.py +452 -0
  34. perspective/tests/table/test_table_datetime.py +2409 -0
  35. perspective/tests/table/test_table_infer.py +201 -0
  36. perspective/tests/table/test_table_limit.py +43 -0
  37. perspective/tests/table/test_table_numpy.py +1022 -0
  38. perspective/tests/table/test_table_pandas.py +1018 -0
  39. perspective/tests/table/test_to_arrow.py +414 -0
  40. perspective/tests/table/test_to_arrow_lz4.py +33 -0
  41. perspective/tests/table/test_to_format.py +1024 -0
  42. perspective/tests/table/test_update.py +545 -0
  43. perspective/tests/table/test_update_arrow.py +980 -0
  44. perspective/tests/table/test_update_numpy.py +252 -0
  45. perspective/tests/table/test_update_pandas.py +211 -0
  46. perspective/tests/table/test_view.py +2235 -0
  47. perspective/tests/table/test_view_expression.py +1940 -0
  48. perspective/tests/viewer/__init__.py +11 -0
  49. perspective/tests/viewer/test_validate.py +70 -0
  50. perspective/tests/viewer/test_viewer.py +245 -0
  51. perspective/tests/widget/__init__.py +11 -0
  52. perspective/tests/widget/test_widget.py +278 -0
  53. perspective/tests/widget/test_widget_pandas.py +453 -0
  54. perspective/viewer/__init__.py +15 -0
  55. perspective/viewer/validate.py +22 -0
  56. perspective/viewer/viewer.py +331 -0
  57. perspective/viewer/viewer_traitlets.py +101 -0
  58. perspective/widget/__init__.py +16 -0
  59. perspective/widget/widget.py +269 -0
  60. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/install.json +5 -0
  61. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/package.json +81 -0
  62. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/253.6f17b87bb4eb1e656365.js +18 -0
  63. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/253.6f17b87bb4eb1e656365.js.LICENSE.txt +59 -0
  64. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/905.d3bbc3d5954582d507bb.js +1 -0
  65. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/remoteEntry.7044010cbbf2a7208035.js +1 -0
  66. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/style.js +4 -0
  67. perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab/static/third-party-licenses.json +16 -0
  68. perspective_python-3.0.0rc1.dist-info/METADATA +13 -0
  69. perspective_python-3.0.0rc1.dist-info/RECORD +70 -0
  70. perspective_python-3.0.0rc1.dist-info/WHEEL +4 -0
@@ -0,0 +1,78 @@
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-rc.1"
14
+ __all__ = [
15
+ "_jupyter_labextension_paths",
16
+ "PerspectiveError",
17
+ "PerspectiveWidget",
18
+ "PerspectiveViewer",
19
+ "PerspectiveTornadoHandler",
20
+ "Table",
21
+ "View",
22
+ ]
23
+
24
+ from .perspective import (
25
+ PySyncClient,
26
+ PerspectiveError,
27
+ PySyncServer,
28
+ Table,
29
+ View,
30
+ PySyncProxySession as ProxySession,
31
+ )
32
+
33
+ from .widget import PerspectiveWidget
34
+ from .viewer import PerspectiveViewer
35
+
36
+
37
+ try:
38
+ from .handlers import PerspectiveTornadoHandler
39
+ except ImportError:
40
+ ...
41
+
42
+
43
+ def default_loop_cb(fn, *args, **kwargs):
44
+ return fn(*args, **kwargs)
45
+
46
+
47
+ class Server(PySyncServer):
48
+ def set_threadpool_size(self, n_cpus):
49
+ pass
50
+
51
+ def new_local_client(self, loop_callback=default_loop_cb):
52
+ """Create a new `Client` instance bound to this in-process `Server`."""
53
+ return Client.from_server(self, loop_callback)
54
+
55
+
56
+ class Client(PySyncClient):
57
+ def from_server(
58
+ server: Server,
59
+ loop_callback=default_loop_cb,
60
+ ):
61
+ """Create a new `Client` instance bound synchronously to an Python
62
+ instance of `PerspectiveServer`."""
63
+
64
+ def handle_request(bytes):
65
+ session.handle_request(bytes)
66
+ loop_callback(lambda: session.poll())
67
+
68
+ def handle_response(bytes):
69
+ client.handle_response(bytes)
70
+
71
+ session = server.new_session(handle_response)
72
+ client = Client(handle_request)
73
+ return client
74
+
75
+
76
+ # read by `jupyter labextension develop`
77
+ def _jupyter_labextension_paths():
78
+ return [{"src": "labextension", "dest": "@finos/perspective-jupyterlab"}]
@@ -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
15
+ except ImportError:
16
+ ...
17
+
18
+ try:
19
+ from .starlette import PerspectiveStarletteHandler
20
+ except ImportError:
21
+ ...
22
+
23
+ try:
24
+ from .tornado import PerspectiveTornadoHandler
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,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
+ # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
@@ -0,0 +1,268 @@
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 datetime import datetime, date
14
+
15
+ import numpy as np
16
+ import pandas as pd
17
+ import pyarrow as pa
18
+ from pytest import fixture
19
+ from random import random, randint, choice
20
+ from faker import Faker
21
+
22
+
23
+ fake = Faker()
24
+
25
+ # Our tests construct naive datetimes everywhere
26
+ # so setting it here is an easy way to fix it globally.
27
+ import os
28
+ os.environ["TZ"] = "UTC"
29
+
30
+ def _make_date_time_index(size, time_unit):
31
+ return pd.date_range("2000-01-01", periods=size, freq=time_unit)
32
+
33
+
34
+ def _make_period_index(size, time_unit):
35
+ return pd.period_range(start="2000", periods=size, freq=time_unit)
36
+
37
+
38
+ def _make_dataframe(index, size=10):
39
+ """Create a new random dataframe of `size` and with a DateTimeIndex of
40
+ frequency `time_unit`.
41
+ """
42
+ return pd.DataFrame(
43
+ index=index,
44
+ data={
45
+ "a": np.random.rand(size),
46
+ "b": np.random.rand(size),
47
+ "c": np.random.rand(size),
48
+ "d": np.random.rand(size),
49
+ },
50
+ )
51
+
52
+
53
+ class Util:
54
+ @staticmethod
55
+ def make_arrow(names, data, types=None, legacy=False):
56
+ """Create an arrow binary that can be loaded and manipulated from memory.
57
+
58
+ Args:
59
+ names (list): a list of str column names
60
+ data (list): a list of lists containing data for each column
61
+ types (list): an optional list of `pyarrow.type` function references.
62
+ Types will be inferred if not provided.
63
+ legacy (bool): if True, use legacy IPC format (pre-pyarrow 0.15). Defaults to False.
64
+
65
+ Returns:
66
+ bytes : a bytes object containing the arrow-serialized output.
67
+ """
68
+ stream = pa.BufferOutputStream()
69
+ arrays = []
70
+
71
+ for idx, column in enumerate(data):
72
+ # only apply types if array is present
73
+ kwargs = {}
74
+ if types:
75
+ kwargs["type"] = types[idx]
76
+ arrays.append(pa.array(column, **kwargs))
77
+
78
+ batch = pa.RecordBatch.from_arrays(arrays, names)
79
+ table = pa.Table.from_batches([batch])
80
+ writer = pa.RecordBatchStreamWriter(
81
+ stream, table.schema, use_legacy_format=legacy
82
+ )
83
+
84
+ writer.write_table(table)
85
+ writer.close()
86
+ return stream.getvalue().to_pybytes()
87
+
88
+ @staticmethod
89
+ def make_arrow_from_pandas(df, schema=None, legacy=False):
90
+ """Create an arrow binary from a Pandas dataframe.
91
+
92
+ Args:
93
+ df (:obj:`pandas.DataFrame`)
94
+ schema (:obj:`pyarrow.Schema`)
95
+ legacy (bool): if True, use legacy IPC format (pre-pyarrow 0.15). Defaults to False.
96
+
97
+ Returns:
98
+ bytes : a bytes object containing the arrow-serialized output.
99
+ """
100
+ stream = pa.BufferOutputStream()
101
+ table = pa.Table.from_pandas(df, schema=schema)
102
+
103
+ writer = pa.RecordBatchStreamWriter(
104
+ stream, table.schema, use_legacy_format=legacy
105
+ )
106
+
107
+ writer.write_table(table)
108
+ writer.close()
109
+ return stream.getvalue().to_pybytes()
110
+
111
+ @staticmethod
112
+ def make_dictionary_arrow(names, data, types=None, legacy=False):
113
+ """Create an arrow binary that can be loaded and manipulated from memory, with
114
+ each column being a dictionary array of `str` values and `int` indices.
115
+
116
+ Args:
117
+ names (list): a list of str column names
118
+ data (list:tuple): a list of tuples, the first value being a list of indices,
119
+ and the second value being a list of values.
120
+ types (list:list:pyarrow.func): a list of lists, containing the indices type and
121
+ dictionary value type for each array.
122
+ legacy (bool): if True, use legacy IPC format (pre-pyarrow 0.15). Defaults to False.
123
+
124
+ Returns:
125
+ bytes : a bytes object containing the arrow-serialized output.
126
+ """
127
+ stream = pa.BufferOutputStream()
128
+
129
+ arrays = []
130
+ for idx, column in enumerate(data):
131
+ indice_type = pa.int64()
132
+ value_type = pa.string()
133
+
134
+ if types is not None:
135
+ indice_type = types[idx][0]
136
+ value_type = types[idx][1]
137
+
138
+ indices = pa.array(column[0], type=indice_type)
139
+ values = pa.array(column[1], type=value_type)
140
+ parray = pa.DictionaryArray.from_arrays(indices, values)
141
+ arrays.append(parray)
142
+
143
+ batch = pa.RecordBatch.from_arrays(arrays, names)
144
+ table = pa.Table.from_batches([batch])
145
+ writer = pa.RecordBatchStreamWriter(
146
+ stream, table.schema, use_legacy_format=legacy
147
+ )
148
+
149
+ writer.write_table(table)
150
+ writer.close()
151
+ return stream.getvalue().to_pybytes()
152
+
153
+ @staticmethod
154
+ def to_timestamp(obj):
155
+ """Return an integer timestamp based on a date/datetime object."""
156
+ classname = obj.__class__.__name__
157
+ if classname == "date":
158
+ return int(datetime(obj.year, obj.month, obj.day).timestamp() * 1000)
159
+ elif classname == "datetime":
160
+ return int(obj.timestamp() * 1000)
161
+ else:
162
+ return -1
163
+
164
+ @staticmethod
165
+ def make_dataframe(size=10, freq="D"):
166
+ index = _make_date_time_index(size, freq)
167
+ return _make_dataframe(index, size)
168
+
169
+ @staticmethod
170
+ def make_period_dataframe(size=10):
171
+ index = _make_period_index(size, "M")
172
+ return _make_dataframe(index, size)
173
+
174
+ @staticmethod
175
+ def make_series(size=10, freq="D"):
176
+ index = _make_date_time_index(size, freq)
177
+ return pd.Series(data=np.random.rand(size), index=index)
178
+
179
+
180
+ class Sentinel(object):
181
+ """Generic sentinel class for testing side-effectful code in Python 2 and
182
+ 3.
183
+ """
184
+
185
+ def __init__(self, value):
186
+ self.value = value
187
+
188
+ def get(self):
189
+ return self.value
190
+
191
+ def set(self, new_value):
192
+ self.value = new_value
193
+
194
+
195
+ @fixture()
196
+ def sentinel():
197
+ """Pass `sentinel` into a test and call it with `value` to create a new
198
+ instance of the Sentinel class.
199
+
200
+ Example:
201
+ >>> def test_with_sentinel(self, sentinel):
202
+ >>> s = sentinel(True)
203
+ >>> s.set(False)
204
+ >>> s.get() # returns False
205
+ """
206
+
207
+ def _sentinel(value):
208
+ return Sentinel(value)
209
+
210
+ return _sentinel
211
+
212
+
213
+ @fixture
214
+ def util():
215
+ """Pass the `Util` class in to a test."""
216
+ return Util
217
+
218
+
219
+ @fixture
220
+ def superstore(count=100):
221
+ data = []
222
+ for id in range(count):
223
+ dat = {}
224
+ dat["Row ID"] = id
225
+ dat["Order ID"] = "{}-{}".format(fake.ein(), fake.zipcode())
226
+ dat["Order Date"] = fake.date_this_year()
227
+ dat["Ship Date"] = fake.date_between_dates(dat["Order Date"]).strftime(
228
+ "%Y-%m-%d"
229
+ )
230
+ dat["Order Date"] = dat["Order Date"].strftime("%Y-%m-%d")
231
+ dat["Ship Mode"] = choice(["First Class", "Standard Class", "Second Class"])
232
+ dat["Ship Mode"] = choice(["First Class", "Standard Class", "Second Class"])
233
+ dat["Customer ID"] = fake.zipcode()
234
+ dat["Segment"] = choice(["A", "B", "C", "D"])
235
+ dat["Country"] = "US"
236
+ dat["City"] = fake.city()
237
+ dat["State"] = fake.state()
238
+ dat["Postal Code"] = fake.zipcode()
239
+ dat["Region"] = choice(["Region %d" % i for i in range(5)])
240
+ dat["Product ID"] = fake.bban()
241
+ sector = choice(["Industrials", "Technology", "Financials"])
242
+ industry = choice(["A", "B", "C"])
243
+ dat["Category"] = sector
244
+ dat["Sub-Category"] = industry
245
+ dat["Sales"] = randint(1, 100) * 100
246
+ dat["Quantity"] = randint(1, 100) * 10
247
+ dat["Discount"] = round(random() * 100, 2)
248
+ dat["Profit"] = round(random() * 1000, 2)
249
+ data.append(dat)
250
+ return pd.DataFrame(data)
251
+
252
+
253
+ # Perspective used to support datetime.date and datetime.datetime
254
+ # as Table() constructor arguments, but now we forward the parameters
255
+ # directly to JSON.loads. So to make sure the tests dont need to be
256
+ # so utterly transmogrified, we have this little hack :)
257
+ import json
258
+ old = json.JSONEncoder.default
259
+
260
+ def new_encoder(self, obj):
261
+ if isinstance(obj, datetime):
262
+ return str(obj)
263
+ elif isinstance(obj, date):
264
+ return str(obj)
265
+ else:
266
+ return old(self, obj)
267
+
268
+ json.JSONEncoder.default = new_encoder
@@ -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
+ # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛