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.
- perspective/__init__.py +396 -0
- perspective/extension/finos-perspective-nbextension.json +5 -0
- perspective/handlers/__init__.py +11 -0
- perspective/handlers/aiohttp.py +61 -0
- perspective/handlers/starlette.py +55 -0
- perspective/handlers/tornado.py +184 -0
- perspective/perspective.pyd +0 -0
- perspective/templates/exported_widget.html.template +35 -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/conftest.py +272 -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_multi_threaded.py +201 -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 +89 -0
- perspective/tests/table/test_delete.py +124 -0
- perspective/tests/table/test_exception.py +65 -0
- perspective/tests/table/test_leaks.py +54 -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 +503 -0
- perspective/tests/table/test_table_datetime.py +2409 -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_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 +417 -0
- perspective/tests/table/test_to_arrow_lz4.py +32 -0
- perspective/tests/table/test_to_format.py +1024 -0
- perspective/tests/table/test_to_polars.py +26 -0
- perspective/tests/table/test_update.py +545 -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 +2261 -0
- perspective/tests/table/test_view_expression.py +1940 -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/widget/__init__.py +11 -0
- perspective/tests/widget/test_widget.py +278 -0
- perspective/tests/widget/test_widget_pandas.py +453 -0
- perspective/virtual_servers/__init__.py +134 -0
- perspective/virtual_servers/clickhouse.py +245 -0
- perspective/virtual_servers/duckdb.py +236 -0
- perspective/widget/__init__.py +349 -0
- perspective/widget/viewer/__init__.py +15 -0
- perspective/widget/viewer/validate.py +22 -0
- perspective/widget/viewer/viewer.py +343 -0
- perspective/widget/viewer/viewer_traitlets.py +101 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/install.json +5 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/package.json +71 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/253.5f5c9e80605aa4106a28.js +2 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/253.5f5c9e80605aa4106a28.js.LICENSE.txt +25 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/523.c030af5d3c4f67ff83f6.js +1 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/remoteEntry.95a8ea1b44d96032833f.js +1 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/style.js +4 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/third-party-licenses.json +16 -0
- perspective_python-4.2.0.dist-info/METADATA +27 -0
- perspective_python-4.2.0.dist-info/RECORD +79 -0
- perspective_python-4.2.0.dist-info/WHEEL +4 -0
- perspective_python-4.2.0.dist-info/licenses/LICENSE.md +193 -0
- perspective_python-4.2.0.dist-info/licenses/LICENSE_THIRDPARTY_cargo.yml +17395 -0
|
@@ -0,0 +1,349 @@
|
|
|
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 logging
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import importlib.metadata
|
|
18
|
+
import inspect
|
|
19
|
+
|
|
20
|
+
from string import Template
|
|
21
|
+
from ipywidgets import DOMWidget
|
|
22
|
+
from traitlets import Unicode, observe
|
|
23
|
+
from .viewer import PerspectiveViewer
|
|
24
|
+
|
|
25
|
+
__version__ = re.sub(".dev[0-9]+", "", importlib.metadata.version("perspective-python"))
|
|
26
|
+
|
|
27
|
+
__all__ = ["PerspectiveWidget"]
|
|
28
|
+
|
|
29
|
+
__doc__ = """
|
|
30
|
+
`PerspectiveWidget` is a JupyterLab widget that implements the same API as
|
|
31
|
+
`<perspective-viewer>`, allowing for fast, intuitive
|
|
32
|
+
transformations/visualizations of various data formats within JupyterLab.
|
|
33
|
+
|
|
34
|
+
`PerspectiveWidget` is compatible with Jupyterlab 3 and Jupyter Notebook 6 via a
|
|
35
|
+
[prebuilt extension](https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#prebuilt-extensions).
|
|
36
|
+
To use it, simply install `perspective-python` and the extensions should be
|
|
37
|
+
available.
|
|
38
|
+
|
|
39
|
+
`perspective-python`'s JupyterLab extension also provides convenient builtin
|
|
40
|
+
viewers for `csv`, `json`, or `arrow` files. Simply right-click on a file with
|
|
41
|
+
this extension and choose the appropriate `Perpective` option from the context
|
|
42
|
+
menu.
|
|
43
|
+
|
|
44
|
+
## `PerspectiveWidget`
|
|
45
|
+
|
|
46
|
+
Building on top of the API provided by `perspective.Table`, the
|
|
47
|
+
`PerspectiveWidget` is a JupyterLab plugin that offers the entire functionality
|
|
48
|
+
of Perspective within the Jupyter environment. It supports the same API
|
|
49
|
+
semantics of `<perspective-viewer>`, along with the additional data types
|
|
50
|
+
supported by `perspective.Table`. `PerspectiveWidget` takes keyword arguments
|
|
51
|
+
for the managed `View`:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from perspective.widget import PerspectiveWidget
|
|
55
|
+
w = perspective.PerspectiveWidget(
|
|
56
|
+
data,
|
|
57
|
+
plugin="X Bar",
|
|
58
|
+
aggregates={"datetime": "any"},
|
|
59
|
+
sort=[["date", "desc"]]
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Creating a widget
|
|
64
|
+
|
|
65
|
+
A widget is created through the `PerspectiveWidget` constructor, which takes as
|
|
66
|
+
its first, required parameter a `perspective.Table`, a dataset, a schema, or
|
|
67
|
+
`None`, which serves as a special value that tells the Widget to defer loading
|
|
68
|
+
any data until later. In maintaining consistency with the Javascript API,
|
|
69
|
+
Widgets cannot be created with empty dictionaries or lists—`None` should be used
|
|
70
|
+
if the intention is to await data for loading later on. A widget can be
|
|
71
|
+
constructed from a dataset:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from perspective.widget import PerspectiveWidget
|
|
75
|
+
PerspectiveWidget(data, group_by=["date"])
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
.. or a schema:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
PerspectiveWidget({"a": int, "b": str})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
.. or an instance of a `perspective.Table`:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
table = perspective.table(data)
|
|
88
|
+
PerspectiveWidget(table)
|
|
89
|
+
```
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class PerspectiveWidget(DOMWidget, PerspectiveViewer):
|
|
94
|
+
"""`PerspectiveWidget` allows for Perspective to be used as a Jupyter
|
|
95
|
+
widget.
|
|
96
|
+
|
|
97
|
+
Using `perspective.Table`, you can create a widget that extends the full
|
|
98
|
+
functionality of `perspective-viewer`. Changes on the viewer can be
|
|
99
|
+
programatically set on the `PerspectiveWidget` instance.
|
|
100
|
+
|
|
101
|
+
# Examples
|
|
102
|
+
|
|
103
|
+
>>> from perspective.widget import PerspectiveWidget
|
|
104
|
+
>>> data = {
|
|
105
|
+
... "a": [1, 2, 3],
|
|
106
|
+
... "b": [
|
|
107
|
+
... "2019/07/11 7:30PM",
|
|
108
|
+
... "2019/07/11 8:30PM",
|
|
109
|
+
... "2019/07/11 9:30PM"
|
|
110
|
+
... ]
|
|
111
|
+
... }
|
|
112
|
+
>>> widget = PerspectiveWidget(
|
|
113
|
+
... data,
|
|
114
|
+
... group_by=["a"],
|
|
115
|
+
... sort=[["b", "desc"]],
|
|
116
|
+
... filter=[["a", ">", 1]]
|
|
117
|
+
... )
|
|
118
|
+
>>> widget.sort
|
|
119
|
+
[["b", "desc"]]
|
|
120
|
+
>>> widget.sort.append(["a", "asc"])
|
|
121
|
+
>>> widget.sort
|
|
122
|
+
[["b", "desc"], ["a", "asc"]]
|
|
123
|
+
>>> widget.table.update({"a": [4, 5]}) # Browser UI updates
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
# Required by ipywidgets for proper registration of the backend
|
|
127
|
+
_model_name = Unicode("PerspectiveModel").tag(sync=True)
|
|
128
|
+
_model_module = Unicode("@perspective-dev/jupyterlab").tag(sync=True)
|
|
129
|
+
_model_module_version = Unicode("~{}".format(__version__)).tag(sync=True)
|
|
130
|
+
_view_name = Unicode("PerspectiveView").tag(sync=True)
|
|
131
|
+
_view_module = Unicode("@perspective-dev/jupyterlab").tag(sync=True)
|
|
132
|
+
_view_module_version = Unicode("~{}".format(__version__)).tag(sync=True)
|
|
133
|
+
|
|
134
|
+
def __init__(
|
|
135
|
+
self,
|
|
136
|
+
data,
|
|
137
|
+
index=None,
|
|
138
|
+
limit=None,
|
|
139
|
+
binding_mode="server",
|
|
140
|
+
**kwargs,
|
|
141
|
+
):
|
|
142
|
+
"""Initialize an instance of `PerspectiveWidget`
|
|
143
|
+
with the given table/data and viewer configuration.
|
|
144
|
+
|
|
145
|
+
If an `AsyncTable` is passed in, then certain widget methods like
|
|
146
|
+
`update()` and `delete()` return coroutines which must be awaited.
|
|
147
|
+
|
|
148
|
+
# Arguments
|
|
149
|
+
|
|
150
|
+
- `data` (`Table`|`AsyncTable`|`dict`|`list`|`pandas.DataFrame`|`bytes`|`str`): a
|
|
151
|
+
`perspective.Table` instance, a `perspective.AsyncTable` instance, or
|
|
152
|
+
a dataset to be loaded in the widget.
|
|
153
|
+
|
|
154
|
+
# Keyword Arguments
|
|
155
|
+
|
|
156
|
+
- `index` (`str`): A column name to be used as the primary key.
|
|
157
|
+
Ignored if `server` is True.
|
|
158
|
+
- `binding_mode` (`str`): "client-server" or "server"
|
|
159
|
+
- `limit` (`int`): A upper limit on the number of rows in the Table.
|
|
160
|
+
Cannot be set at the same time as `index`, ignored if `server`
|
|
161
|
+
is True.
|
|
162
|
+
- `kwargs` (`dict`): configuration options for the `PerspectiveViewer`,
|
|
163
|
+
and `Table` constructor if `data` is a dataset.
|
|
164
|
+
|
|
165
|
+
# Examples
|
|
166
|
+
|
|
167
|
+
>>> widget = PerspectiveWidget(
|
|
168
|
+
... {"a": [1, 2, 3]},
|
|
169
|
+
... aggregates={"a": "avg"},
|
|
170
|
+
... group_by=["a"],
|
|
171
|
+
... sort=[["b", "desc"]],
|
|
172
|
+
... filter=[["a", ">", 1]],
|
|
173
|
+
... expressions=["\"a\" + 100"])
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
self.binding_mode = binding_mode
|
|
177
|
+
|
|
178
|
+
# Pass table load options to the front-end, unless in server mode
|
|
179
|
+
self._options = {}
|
|
180
|
+
|
|
181
|
+
if index is not None and limit is not None:
|
|
182
|
+
raise TypeError("Index and Limit cannot be set at the same time!")
|
|
183
|
+
|
|
184
|
+
# Parse the dataset we pass in - if it's Pandas, preserve pivots
|
|
185
|
+
# if isinstance(data, pandas.DataFrame) or isinstance(data, pandas.Series):
|
|
186
|
+
# data, config = deconstruct_pandas(data)
|
|
187
|
+
|
|
188
|
+
# if config.get("group_by", None) and "group_by" not in kwargs:
|
|
189
|
+
# kwargs.update({"group_by": config["group_by"]})
|
|
190
|
+
|
|
191
|
+
# if config.get("split_by", None) and "split_by" not in kwargs:
|
|
192
|
+
# kwargs.update({"split_by": config["split_by"]})
|
|
193
|
+
|
|
194
|
+
# if config.get("columns", None) and "columns" not in kwargs:
|
|
195
|
+
# kwargs.update({"columns": config["columns"]})
|
|
196
|
+
|
|
197
|
+
# Initialize the viewer
|
|
198
|
+
super(PerspectiveWidget, self).__init__(**kwargs)
|
|
199
|
+
|
|
200
|
+
# Handle messages from the the front end
|
|
201
|
+
self.on_msg(self.handle_message)
|
|
202
|
+
self._sessions = {}
|
|
203
|
+
|
|
204
|
+
# If an empty dataset is provided, don't call `load()` and wait
|
|
205
|
+
# for the user to call `load()`.
|
|
206
|
+
if data is None:
|
|
207
|
+
if index is not None or limit is not None:
|
|
208
|
+
raise TypeError(
|
|
209
|
+
"Cannot initialize PerspectiveWidget `index` or `limit` without a Table, data, or schema!"
|
|
210
|
+
)
|
|
211
|
+
else:
|
|
212
|
+
if index is not None:
|
|
213
|
+
self._options.update({"index": index})
|
|
214
|
+
|
|
215
|
+
if limit is not None:
|
|
216
|
+
self._options.update({"limit": limit})
|
|
217
|
+
|
|
218
|
+
loading = self.load(data, **self._options)
|
|
219
|
+
if inspect.isawaitable(loading):
|
|
220
|
+
import asyncio
|
|
221
|
+
|
|
222
|
+
asyncio.create_task(loading)
|
|
223
|
+
|
|
224
|
+
def load(self, data, **options):
|
|
225
|
+
"""Load the widget with data."""
|
|
226
|
+
# Viewer will ignore **options if `data` is a Table or View.
|
|
227
|
+
return super(PerspectiveWidget, self).load(data, **options)
|
|
228
|
+
|
|
229
|
+
def update(self, data):
|
|
230
|
+
"""Update the widget with new data."""
|
|
231
|
+
return super(PerspectiveWidget, self).update(data)
|
|
232
|
+
|
|
233
|
+
def clear(self):
|
|
234
|
+
"""Clears the widget's underlying `Table`."""
|
|
235
|
+
return super(PerspectiveWidget, self).clear()
|
|
236
|
+
|
|
237
|
+
def replace(self, data):
|
|
238
|
+
"""Replaces the widget's `Table` with new data conforming to the same
|
|
239
|
+
schema. Does not clear user-set state. If in client mode, serializes
|
|
240
|
+
the data and sends it to the browser.
|
|
241
|
+
"""
|
|
242
|
+
return super(PerspectiveWidget, self).replace(data)
|
|
243
|
+
|
|
244
|
+
def delete(self, delete_table=True):
|
|
245
|
+
"""Delete the Widget's data and clears its internal state.
|
|
246
|
+
|
|
247
|
+
# Arguments
|
|
248
|
+
|
|
249
|
+
- `delete_table` (`bool`): whether the underlying `Table` will be
|
|
250
|
+
deleted. Defaults to True.
|
|
251
|
+
"""
|
|
252
|
+
ret = super(PerspectiveWidget, self).delete(delete_table)
|
|
253
|
+
|
|
254
|
+
# Close the underlying comm and remove widget from the front-end
|
|
255
|
+
self.close()
|
|
256
|
+
return ret
|
|
257
|
+
|
|
258
|
+
@observe("value")
|
|
259
|
+
def handle_message(self, widget, content, buffers):
|
|
260
|
+
"""Given a message from `PerspectiveJupyterClient.send`, process the
|
|
261
|
+
message and return the result to `self.post`.
|
|
262
|
+
|
|
263
|
+
# Arguments
|
|
264
|
+
|
|
265
|
+
- `widget`: a reference to the `Widget` instance that received the
|
|
266
|
+
message.
|
|
267
|
+
- `content` (dict): - the message from the front-end. Automatically
|
|
268
|
+
de-serialized by ipywidgets.
|
|
269
|
+
- `buffers`: optional arraybuffers from the front-end, if any.
|
|
270
|
+
"""
|
|
271
|
+
if content["type"] == "connect":
|
|
272
|
+
client_id = content["client_id"]
|
|
273
|
+
logging.debug("view {} connected", client_id)
|
|
274
|
+
|
|
275
|
+
def send_response(msg):
|
|
276
|
+
self.send({"type": "binary_msg", "client_id": client_id}, [msg])
|
|
277
|
+
|
|
278
|
+
self._sessions[client_id] = self.new_proxy_session(send_response)
|
|
279
|
+
elif content["type"] == "binary_msg":
|
|
280
|
+
[binary_msg] = buffers
|
|
281
|
+
client_id = content["client_id"]
|
|
282
|
+
session = self._sessions[client_id]
|
|
283
|
+
if session is not None:
|
|
284
|
+
import asyncio
|
|
285
|
+
|
|
286
|
+
asyncio.create_task(session.handle_request_async(binary_msg))
|
|
287
|
+
else:
|
|
288
|
+
logging.error("No session for client_id {}".format(client_id))
|
|
289
|
+
elif content["type"] == "hangup":
|
|
290
|
+
# XXX(tom): client won't reliably send this so shouldn't rely on it
|
|
291
|
+
# to clean up; does jupyter notify us when the client on the
|
|
292
|
+
# websocket, i.e. the view, disconnects?
|
|
293
|
+
client_id = content["client_id"]
|
|
294
|
+
logging.debug("view {} hangup", client_id)
|
|
295
|
+
session = self._sessions.pop(client_id, None)
|
|
296
|
+
if session:
|
|
297
|
+
session.close()
|
|
298
|
+
|
|
299
|
+
def _repr_mimebundle_(self, **kwargs):
|
|
300
|
+
super_bundle = super(DOMWidget, self)._repr_mimebundle_(**kwargs)
|
|
301
|
+
if not _jupyter_html_export_enabled():
|
|
302
|
+
return super_bundle
|
|
303
|
+
|
|
304
|
+
# Serialize viewer attrs + view data to be rendered in the template
|
|
305
|
+
viewer_attrs = self.save()
|
|
306
|
+
data = self.table.view().to_arrow()
|
|
307
|
+
b64_data = base64.encodebytes(data)
|
|
308
|
+
template_path = os.path.join(
|
|
309
|
+
os.path.dirname(__file__), "../templates/exported_widget.html.template"
|
|
310
|
+
)
|
|
311
|
+
with open(template_path, "r") as template_data:
|
|
312
|
+
template = Template(template_data.read())
|
|
313
|
+
|
|
314
|
+
def psp_cdn(module, path=None):
|
|
315
|
+
if path is None:
|
|
316
|
+
path = f"cdn/{module}.js"
|
|
317
|
+
|
|
318
|
+
# perspective developer affordance: works with your local `pnpm run start blocks`
|
|
319
|
+
# return f"http://localhost:8080/node_modules/@perspective-dev/{module}/dist/{path}"
|
|
320
|
+
return f"https://cdn.jsdelivr.net/npm/@perspective-dev/{module}@{__version__}/dist/{path}"
|
|
321
|
+
|
|
322
|
+
return super(DOMWidget, self)._repr_mimebundle_(**kwargs) | {
|
|
323
|
+
"text/html": template.substitute(
|
|
324
|
+
psp_cdn_perspective=psp_cdn("perspective"),
|
|
325
|
+
psp_cdn_perspective_viewer=psp_cdn("perspective-viewer"),
|
|
326
|
+
psp_cdn_perspective_viewer_datagrid=psp_cdn(
|
|
327
|
+
"perspective-viewer-datagrid"
|
|
328
|
+
),
|
|
329
|
+
psp_cdn_perspective_viewer_d3fc=psp_cdn("perspective-viewer-d3fc"),
|
|
330
|
+
psp_cdn_perspective_viewer_themes=psp_cdn(
|
|
331
|
+
"perspective-viewer-themes", "css/themes.css"
|
|
332
|
+
),
|
|
333
|
+
viewer_id=self.model_id,
|
|
334
|
+
viewer_attrs=viewer_attrs,
|
|
335
|
+
b64_data=b64_data.decode("utf-8"),
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _jupyter_html_export_enabled():
|
|
341
|
+
return os.environ.get("PSP_JUPYTER_HTML_EXPORT", None) == "1"
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def set_jupyter_html_export(val):
|
|
345
|
+
"""Enables HTML export for Jupyter widgets, when set to True.
|
|
346
|
+
HTML export can also be enabled by setting the environment variable
|
|
347
|
+
`PSP_JUPYTER_HTML_EXPORT` to the string `1`.
|
|
348
|
+
"""
|
|
349
|
+
os.environ["PSP_JUPYTER_HTML_EXPORT"] = "1" if val else "0"
|
|
@@ -0,0 +1,15 @@
|
|
|
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 .viewer import PerspectiveViewer
|
|
14
|
+
|
|
15
|
+
__all__ = ["PerspectiveViewer"]
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
def validate_version(version):
|
|
15
|
+
# basic semver of form \d+\.\d+\.\d+(\+.+)?
|
|
16
|
+
spl = version.split(".", 2)
|
|
17
|
+
return (
|
|
18
|
+
len(spl) == 3
|
|
19
|
+
and spl[0].isdigit()
|
|
20
|
+
and spl[1].isdigit()
|
|
21
|
+
and (spl[2].split("+")[0]).isdigit()
|
|
22
|
+
)
|