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,269 @@
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 base64
14
+ import jinja2
15
+ import json
16
+ import logging
17
+ import os
18
+ from datetime import date, datetime
19
+ from functools import partial
20
+
21
+ from ipywidgets import DOMWidget
22
+ from traitlets import Unicode, observe
23
+ from ..viewer import PerspectiveViewer
24
+ import importlib
25
+
26
+ __version__ = importlib.metadata.version("perspective-python")
27
+
28
+
29
+ class PerspectiveWidget(DOMWidget, PerspectiveViewer):
30
+ """:class`~perspective.PerspectiveWidget` allows for Perspective to be used
31
+ in the form of a Jupyter IPython widget.
32
+
33
+ Using `perspective.Table`, you can create a widget that extends the full
34
+ functionality of `perspective-viewer`. Changes on the viewer can be
35
+ programatically set on the :class`~perspective.PerspectiveWidget` instance,
36
+ and state is maintained across page refreshes.
37
+
38
+ Examples:
39
+ >>> from perspective import Table, PerspectiveWidget
40
+ >>> data = {
41
+ ... "a": [1, 2, 3],
42
+ ... "b": [
43
+ ... "2019/07/11 7:30PM",
44
+ ... "2019/07/11 8:30PM",
45
+ ... "2019/07/11 9:30PM"
46
+ ... ]
47
+ ... }
48
+ >>> tbl = Table(data, index="a")
49
+ >>> widget = PerspectiveWidget(
50
+ ... tbl,
51
+ ... group_by=["a"],
52
+ ... sort=[["b", "desc"]],
53
+ ... filter=[["a", ">", 1]]
54
+ ... )
55
+ >>> widget.sort
56
+ [["b", "desc"]]
57
+ >>> widget.sort.append(["a", "asc"])
58
+ >>> widget.sort
59
+ [["b", "desc"], ["a", "asc"]]
60
+ >>> widget.update({"a": [4, 5]}) # Browser UI updates
61
+ """
62
+
63
+ # Required by ipywidgets for proper registration of the backend
64
+ _model_name = Unicode("PerspectiveModel").tag(sync=True)
65
+ _model_module = Unicode("@finos/perspective-jupyterlab").tag(sync=True)
66
+ _model_module_version = Unicode("~{}".format(__version__)).tag(sync=True)
67
+ _view_name = Unicode("PerspectiveView").tag(sync=True)
68
+ _view_module = Unicode("@finos/perspective-jupyterlab").tag(sync=True)
69
+ _view_module_version = Unicode("~{}".format(__version__)).tag(sync=True)
70
+
71
+ def __init__(
72
+ self,
73
+ data,
74
+ index=None,
75
+ limit=None,
76
+ binding_mode="server",
77
+ **kwargs,
78
+ ):
79
+ """Initialize an instance of :class`~perspective.PerspectiveWidget`
80
+ with the given table/data and viewer configuration.
81
+
82
+ If a pivoted DataFrame or MultiIndex table is passed in, the widget
83
+ preserves pivots and applies them. See `PerspectiveViewer.__init__` for
84
+ arguments that transform the view shown in the widget.
85
+
86
+ Args:
87
+ data (:obj:`Table`|:obj:`View`|:obj:`dict`|:obj:`list`|:obj:`pandas.DataFrame`|:obj:`bytes`|:obj:`str`): a
88
+ `perspective.Table` instance, a `perspective.View` instance, or
89
+ a dataset to be loaded in the widget.
90
+
91
+ Keyword Arguments:
92
+ index (:obj:`str`): A column name to be used as the primary key.
93
+ Ignored if `server` is True.
94
+
95
+ limit (:obj:`int`): A upper limit on the number of rows in the Table.
96
+ Cannot be set at the same time as `index`, ignored if `server`
97
+ is True.
98
+
99
+ binding_mode (:obj:`str`): "client-server" or "server"
100
+
101
+ kwargs (:obj:`dict`): configuration options for the `PerspectiveViewer`,
102
+ and `Table` constructor if `data` is a dataset.
103
+
104
+ Examples:
105
+ >>> widget = PerspectiveWidget(
106
+ ... {"a": [1, 2, 3]},
107
+ ... aggregates={"a": "avg"},
108
+ ... group_by=["a"],
109
+ ... sort=[["b", "desc"]],
110
+ ... filter=[["a", ">", 1]],
111
+ ... expressions=["\"a\" + 100"])
112
+ """
113
+
114
+ self.binding_mode = binding_mode
115
+
116
+ # Pass table load options to the front-end, unless in server mode
117
+ self._options = {}
118
+
119
+ if index is not None and limit is not None:
120
+ raise TypeError("Index and Limit cannot be set at the same time!")
121
+
122
+ # Parse the dataset we pass in - if it's Pandas, preserve pivots
123
+ # if isinstance(data, pandas.DataFrame) or isinstance(data, pandas.Series):
124
+ # data, config = deconstruct_pandas(data)
125
+
126
+ # if config.get("group_by", None) and "group_by" not in kwargs:
127
+ # kwargs.update({"group_by": config["group_by"]})
128
+
129
+ # if config.get("split_by", None) and "split_by" not in kwargs:
130
+ # kwargs.update({"split_by": config["split_by"]})
131
+
132
+ # if config.get("columns", None) and "columns" not in kwargs:
133
+ # kwargs.update({"columns": config["columns"]})
134
+
135
+ # Initialize the viewer
136
+ super(PerspectiveWidget, self).__init__(**kwargs)
137
+
138
+ # Handle messages from the the front end
139
+ self.on_msg(self.handle_message)
140
+ self._sessions = {}
141
+
142
+ # If an empty dataset is provided, don't call `load()` and wait
143
+ # for the user to call `load()`.
144
+ if data is None:
145
+ if index is not None or limit is not None:
146
+ raise TypeError(
147
+ "Cannot initialize PerspectiveWidget `index` or `limit` without a Table, data, or schema!"
148
+ )
149
+ else:
150
+ if index is not None:
151
+ self._options.update({"index": index})
152
+
153
+ if limit is not None:
154
+ self._options.update({"limit": limit})
155
+
156
+ self.load(data, **self._options)
157
+
158
+ def load(self, data, **options):
159
+ """Load the widget with data."""
160
+ # Viewer will ignore **options if `data` is a Table or View.
161
+ super(PerspectiveWidget, self).load(data, **options)
162
+
163
+ def update(self, data):
164
+ """Update the widget with new data."""
165
+ super(PerspectiveWidget, self).update(data)
166
+
167
+ def clear(self):
168
+ """Clears the widget's underlying `Table`."""
169
+ super(PerspectiveWidget, self).clear()
170
+
171
+ def replace(self, data):
172
+ """Replaces the widget's `Table` with new data conforming to the same
173
+ schema. Does not clear user-set state. If in client mode, serializes
174
+ the data and sends it to the browser.
175
+ """
176
+ super(PerspectiveWidget, self).replace(data)
177
+
178
+ def delete(self, delete_table=True):
179
+ """Delete the Widget's data and clears its internal state.
180
+
181
+ Args:
182
+ delete_table (`bool`): whether the underlying `Table` will be
183
+ deleted. Defaults to True.
184
+ """
185
+ super(PerspectiveWidget, self).delete(delete_table)
186
+
187
+ # Close the underlying comm and remove widget from the front-end
188
+ self.close()
189
+
190
+ @observe("value")
191
+ def handle_message(self, widget, content, buffers):
192
+ """Given a message from `PerspectiveJupyterClient.send()`, process the
193
+ message and return the result to `self.post`.
194
+
195
+ Args:
196
+ widget: a reference to the `Widget` instance that received the
197
+ message.
198
+ content (dict): the message from the front-end. Automatically
199
+ de-serialized by ipywidgets.
200
+ buffers : optional arraybuffers from the front-end, if any.
201
+ """
202
+ if content["type"] == "connect":
203
+ client_id = content["client_id"]
204
+ logging.debug("view {} connected", client_id)
205
+ self._sessions[client_id] = self.new_proxy_session(
206
+ lambda msg: self.send(
207
+ {"type": "binary_msg", "client_id": client_id}, [msg]
208
+ )
209
+ )
210
+ elif content["type"] == "binary_msg":
211
+ [binary_msg] = buffers
212
+ client_id = content["client_id"]
213
+ session = self._sessions[client_id]
214
+ logging.debug("view {} message {}", client_id, len(binary_msg))
215
+ if session is not None:
216
+ session.handle_request(binary_msg)
217
+ else:
218
+ logging.error("No session for client_id {}".format(client_id))
219
+ elif content["type"] == "hangup":
220
+ # XXX(tom): client won't reliably send this so shouldn't rely on it to clean up; does jupyter notify us
221
+ # when the client on the websocket, i.e. the view, disconnects?
222
+ client_id = content["client_id"]
223
+ logging.debug("view {} hangup", client_id)
224
+ session = self._sessions.pop(client_id, None)
225
+ if session:
226
+ session.close()
227
+
228
+ def _repr_mimebundle_(self, **kwargs):
229
+ super_bundle = super(DOMWidget, self)._repr_mimebundle_(**kwargs)
230
+ if not _jupyter_html_export_enabled():
231
+ return super_bundle
232
+ # Serialize viewer attrs + view data to be rendered in the template
233
+ viewer_attrs = self.save()
234
+ data = self.table.view().to_arrow()
235
+ b64_data = base64.encodebytes(data)
236
+
237
+ jinja_env = jinja2.Environment(
238
+ loader=jinja2.PackageLoader("perspective"),
239
+ autoescape=jinja2.select_autoescape(),
240
+ )
241
+ template = jinja_env.get_template("exported_widget.html.jinja")
242
+
243
+ def psp_cdn(module, path=None):
244
+ if path is None:
245
+ path = f"cdn/{module}.js"
246
+ # perspective developer affordance: works with your local `pnpm run start blocks`
247
+ # return f"http://localhost:8080/node_modules/@finos/{module}/dist/{path}"
248
+ return f"https://cdn.jsdelivr.net/npm/@finos/{module}@{__version__}/dist/{path}"
249
+
250
+ return super(DOMWidget, self)._repr_mimebundle_(**kwargs) | {
251
+ "text/html": template.render(
252
+ psp_cdn=psp_cdn,
253
+ viewer_id=self.model_id,
254
+ viewer_attrs=viewer_attrs,
255
+ b64_data=b64_data.decode("utf-8"),
256
+ )
257
+ }
258
+
259
+
260
+ def _jupyter_html_export_enabled():
261
+ return os.environ.get("PSP_JUPYTER_HTML_EXPORT", None) == "1"
262
+
263
+
264
+ def set_jupyter_html_export(val):
265
+ """Enables HTML export for Jupyter widgets, when set to True.
266
+ HTML export can also be enabled by setting the environment variable
267
+ `PSP_JUPYTER_HTML_EXPORT` to the string `1`.
268
+ """
269
+ os.environ["PSP_JUPYTER_HTML_EXPORT"] = "1" if val else "0"
@@ -0,0 +1,5 @@
1
+ {
2
+ "packageManager": "python",
3
+ "packageName": "perspective-python",
4
+ "uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package perspective-python"
5
+ }
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@finos/perspective-jupyterlab",
3
+ "version": "3.0.0-rc.1",
4
+ "description": "A Jupyterlab extension for the Perspective library, designed to be used with perspective-python.",
5
+ "files": [
6
+ "dist/**/*",
7
+ "src/**/*"
8
+ ],
9
+ "type": "commonjs",
10
+ "main": "dist/esm/perspective-jupyterlab.js",
11
+ "style": "dist/css/perspective-jupyterlab.css",
12
+ "directories": {
13
+ "dist": "dist/"
14
+ },
15
+ "license": "Apache-2.0",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "scripts": {
20
+ "bench": "npm-run-all bench:build bench:run",
21
+ "bench:build": "echo \"No Benchmarks\"",
22
+ "bench:run": "echo \"No Benchmarks\"",
23
+ "build": "npm-run-all build:js build:labextension",
24
+ "build:js": "node build.mjs",
25
+ "build:labextension": "jupyter labextension build .",
26
+ "clean": "npm-run-all clean:*",
27
+ "clean:dist": "rimraf dist",
28
+ "clean:lib": "rimraf lib",
29
+ "clean:labextension": "rimraf ../../rust/perspective-python/perspective/labextension",
30
+ "clean:nbextension": "rimraf ../../rust/perspective-python/perspective/nbextension/static",
31
+ "test:build": "node build.mjs --test",
32
+ "test:jupyter:build": "cpy \"test/arrow/*\" dist/esm",
33
+ "test:jupyter": "__JUPYTERLAB_PORT__=6538 npx playwright test --config ../../tools/perspective-test/playwright.config.ts -- --jupyter",
34
+ "test": "npm-run-all test:build",
35
+ "version": "yarn build"
36
+ },
37
+ "dependencies": {
38
+ "@finos/perspective": "workspace:^",
39
+ "@finos/perspective-viewer": "workspace:^",
40
+ "@finos/perspective-viewer-d3fc": "workspace:^",
41
+ "@finos/perspective-viewer-datagrid": "workspace:^",
42
+ "@finos/perspective-viewer-openlayers": "workspace:^",
43
+ "@jupyter-widgets/base": ">2 <5",
44
+ "@jupyterlab/application": ">2 <5",
45
+ "@lumino/application": "<3",
46
+ "@lumino/widgets": "<3"
47
+ },
48
+ "devDependencies": {
49
+ "@finos/perspective-esbuild-plugin": "workspace:^",
50
+ "@finos/perspective-test": "workspace:^",
51
+ "@jupyterlab/builder": "^4",
52
+ "@prospective.co/procss": "^0.1.15",
53
+ "cpy": "^9.0.1"
54
+ },
55
+ "jupyterlab": {
56
+ "webpackConfig": "./webpack.config.js",
57
+ "extension": true,
58
+ "outputDir": "../../rust/perspective-python/perspective.data/data/share/jupyter/labextensions/@finos/perspective-jupyterlab",
59
+ "sharedPackages": {
60
+ "@jupyter-widgets/base": {
61
+ "bundled": false,
62
+ "singleton": true
63
+ }
64
+ },
65
+ "discovery": {
66
+ "server": {
67
+ "base": {
68
+ "name": "perspective-python"
69
+ },
70
+ "managers": [
71
+ "pip"
72
+ ]
73
+ }
74
+ },
75
+ "_build": {
76
+ "load": "static\\remoteEntry.7044010cbbf2a7208035.js",
77
+ "extension": "./extension",
78
+ "style": "./style"
79
+ }
80
+ }
81
+ }