Skip to content

Commit

Permalink
Merge pull request #2418 from tomjakubowski/feature/export
Browse files Browse the repository at this point in the history
`PerspectiveWidget` HTML export support in Jupyter
  • Loading branch information
texodus authored Nov 17, 2023
2 parents 4b1f190 + f61e47e commit cedadb8
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 362 deletions.
6 changes: 6 additions & 0 deletions docs/docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ To install the Jupyterlab/Jupyter Notebook plugins from your local working
directory, simply install `python/perspective` with `pip` as you might normally
do.

```bash
(cd packages/perspective-jupyterlab && yarn run build)
pip install -e python/perspective
jupyter labextension develop python/perspective
```

Afterwards, you should see it listed as a "local extension" when you run
`jupyter labextension list` and as a normal extension when you run
`jupyter nbextension list`.
Expand Down
4 changes: 4 additions & 0 deletions python/perspective/perspective/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@
from .handlers import *
from .nbextension import _jupyter_nbextension_paths
from .widget import *


def _jupyter_labextension_paths():
return [{"src": "labextension", "dest": "@finos/perspective-jupyterlab"}]
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script type="module"
src="https://cdn.jsdelivr.net/npm/@finos/perspective@{{psp_version}}/dist/cdn/perspective.js"></script>
<script type="module"
src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer@{{psp_version}}/dist/cdn/perspective-viewer.js"></script>
<script type="module"
src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-datagrid@{{psp_version}}/dist/cdn/perspective-viewer-datagrid.js"></script>
<script type="module"
src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-d3fc@{{psp_version}}/dist/cdn/perspective-viewer-d3fc.js"></script>

<link rel="stylesheet" crossorigin="anonymous"
href="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer@{{psp_version}}/dist/css/pro.css" />

<div class="perspective-envelope" id="perspective-envelope-{{viewer_id}}">
<script type="application/vnd.apache.arrow.file">
{{ b64_data }}
</script>
<perspective-viewer style="height: 690px;"></perspective-viewer>
<script type="module">
// from MDN
function base64ToBytes(base64) {
const binString = atob(base64);
return Uint8Array.from(binString, (m) => m.codePointAt(0));
}
import * as perspective from "https://cdn.jsdelivr.net/npm/@finos/perspective@{{psp_version}}/dist/cdn/perspective.js";
const viewerId = {{ viewer_id | tojson }};
const currentScript = document.scripts[document.scripts.length - 1];
const envelope = document.getElementById(`perspective-envelope-${viewerId}`);
const dataScript = envelope.querySelector('script[type="application/vnd.apache.arrow.file"]');;
if (!dataScript)
throw new Error('data script missing for viewer', viewerId);
const data = base64ToBytes(dataScript.textContent);
const viewerAttrs = {{ viewer_attrs | tojson }};
// Create a new worker, then a new table promise on that worker.
const table = await perspective.shared_worker().table(data.buffer);
const viewer = envelope.querySelector('perspective-viewer');
viewer.load(table);
viewer.restore(viewerAttrs);
</script>
</div>
5 changes: 3 additions & 2 deletions python/perspective/perspective/widget/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

from .widget import PerspectiveWidget
from .widget import PerspectiveWidget, set_jupyter_html_export

__all__ = ["PerspectiveWidget"]

__all__ = ["PerspectiveWidget", "set_jupyter_html_export"]
39 changes: 39 additions & 0 deletions python/perspective/perspective/widget/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

import base64
import jinja2
import json
import os
from datetime import date, datetime
from functools import partial

Expand Down Expand Up @@ -515,3 +518,39 @@ def _make_load_message(self):
return _PerspectiveWidgetMessage(-2, "table", msg_data)
else:
raise PerspectiveError("Widget does not have any data loaded - use the `load()` method to provide it with data.")

def _repr_mimebundle_(self, **kwargs):
super_bundle = super(DOMWidget, self)._repr_mimebundle_(**kwargs)
if not _jupyter_html_export_enabled():
return super_bundle
# Serialize viewer attrs + view data to be rendered in the template
viewer_attrs = self.save()
data = self.table.view().to_arrow()
b64_data = base64.encodebytes(data)

jinja_env = jinja2.Environment(
loader=jinja2.PackageLoader("perspective"),
autoescape=jinja2.select_autoescape(),
)
template = jinja_env.get_template("exported_widget.html.jinja")

return super(DOMWidget, self)._repr_mimebundle_(**kwargs) | {
"text/html": template.render(
psp_version=__version__,
viewer_id=self.model_id,
viewer_attrs=viewer_attrs,
b64_data=b64_data.decode("utf-8"),
)
}


def _jupyter_html_export_enabled():
return os.environ.get("PSP_JUPYTER_HTML_EXPORT", None) == "1"


def set_jupyter_html_export(val):
"""Enables HTML export for Jupyter widgets, when set to True.
HTML export can also be enabled by setting the environment variable
`PSP_JUPYTER_HTML_EXPORT` to the string `1`.
"""
os.environ["PSP_JUPYTER_HTML_EXPORT"] = "1" if val else "0"
170 changes: 84 additions & 86 deletions python/perspective/requirements/requirements-310.txt
Original file line number Diff line number Diff line change
@@ -1,161 +1,159 @@
Babel==2.13.1
Faker==20.0.3
Jinja2==3.1.2
MarkupSafe==2.1.3
PyYAML==6.0.1
Pygments==2.16.1
Send2Trash==1.8.2
Sphinx==7.2.6
aiofiles==22.1.0
aiohttp==3.8.5
aiohttp==3.8.6
aiosignal==1.3.1
aiosqlite==0.19.0
alabaster==0.7.13
annotated-types==0.5.0
annotated-types==0.6.0
anyio==3.7.1
appnope==0.1.3
argon2-cffi==21.3.0
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
arrow==1.2.3
asttokens==2.2.1
async-timeout==4.0.2
arrow==1.3.0
asttokens==2.4.1
async-timeout==4.0.3
attrs==23.1.0
Babel==2.12.1
backcall==0.2.0
beautifulsoup4==4.12.2
black==23.1.0
bleach==6.0.0
bleach==6.1.0
certifi==2023.7.22
cffi==1.15.1
charset-normalizer==3.2.0
click==8.1.6
comm==0.1.4
coverage==7.2.7
debugpy==1.6.7
cffi==1.16.0
charset-normalizer==3.3.2
click==8.1.7
comm==0.2.0
coverage==7.3.2
debugpy==1.8.0
decorator==5.1.1
defusedxml==0.7.1
deprecation==2.1.0
docutils==0.20.1
entrypoints==0.4
exceptiongroup==1.1.2
executing==1.2.0
Faker==19.2.0
fastapi==0.100.1
fastjsonschema==2.18.0
executing==2.0.1
fastapi==0.104.1
fastjsonschema==2.19.0
flake8==6.1.0
flake8-black==0.3.6
fqdn==1.5.1
frozenlist==1.4.0
future==0.18.3
h11==0.14.0
html5lib==1.1
httpcore==0.17.3
httpx==0.24.1
httpcore==1.0.2
httpx==0.25.1
idna==3.4
imagesize==1.4.1
importlib-metadata==6.8.0
iniconfig==2.0.0
ipykernel==6.25.0
ipython==8.14.0
ipykernel==6.26.0
ipython==8.17.2
ipython-genutils==0.2.0
ipywidgets==8.1.0
ipywidgets==8.1.1
isoduration==20.11.0
jedi==0.19.0
Jinja2==3.1.2
jedi==0.19.1
json5==0.9.14
jsonpointer==2.4
jsonschema==4.18.6
jsonschema-specifications==2023.7.1
jupyter-events==0.7.0
jsonschema==4.19.2
jsonschema-specifications==2023.11.1
jupyter-events==0.9.0
jupyter-ydoc==0.2.5
jupyter_client==7.4.9
jupyter_core==5.3.1
jupyter_core==5.5.0
jupyter_packaging==0.12.3
jupyter_server==2.7.0
jupyter_server==2.10.1
jupyter_server_fileid==0.9.0
jupyter_server_terminals==0.4.4
jupyter_server_ydoc==0.8.0
jupyterlab==3.6.5
jupyterlab==3.6.6
jupyterlab-pygments==0.2.2
jupyterlab-widgets==3.0.8
jupyterlab_server==2.24.0
MarkupSafe==2.1.3
jupyterlab-widgets==3.0.9
jupyterlab_server==2.25.1
matplotlib-inline==0.1.6
mccabe==0.7.0
mistune==3.0.1
mistune==3.0.2
multidict==6.0.4
mypy-extensions==1.0.0
nbclassic==1.0.0
nbclient==0.8.0
nbconvert==7.7.3
nbclient==0.9.0
nbconvert==7.11.0
nbformat==5.9.2
nest-asyncio==1.5.7
notebook==6.5.5
nest-asyncio==1.5.8
notebook==6.5.6
notebook_shim==0.2.3
numpy==1.25.2
overrides==7.3.1
packaging==23.1
pandas==2.0.3
numpy==1.26.2
overrides==7.4.0
packaging==23.2
pandas==2.1.3
pandocfilters==1.5.0
parso==0.8.3
pathspec==0.11.2
perspective-python==2.6.1
pexpect==4.8.0
pickleshare==0.7.5
platformdirs==3.10.0
pluggy==1.2.0
prometheus-client==0.17.1
prompt-toolkit==3.0.39
psutil==5.9.5
pip==23.3.1
platformdirs==4.0.0
pluggy==1.3.0
prometheus-client==0.18.0
prompt-toolkit==3.0.41
psutil==5.9.6
ptyprocess==0.7.0
pure-eval==0.2.2
pyarrow==12.0.1
pyarrow==14.0.1
pybind11==2.11.1
pycodestyle==2.11.0
pycodestyle==2.11.1
pycparser==2.21
pydantic==2.1.1
pydantic_core==2.4.0
pydantic==2.5.1
pydantic_core==2.14.3
pyflakes==3.1.0
Pygments==2.15.1
pytest==7.4.0
pytest-aiohttp==1.0.4
pytest==7.4.3
pytest-aiohttp==1.0.5
pytest-asyncio==0.21.1
pytest-cov==4.1.0
pytest-tornado==0.8.1
pytest_check_links==0.9.0
python-dateutil==2.8.2
python-json-logger==2.0.7
pytz==2023.3
PyYAML==6.0.1
pytz==2023.3.post1
pyzmq==24.0.1
referencing==0.30.1
referencing==0.31.0
requests==2.31.0
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rpds-py==0.9.2
Send2Trash==1.8.2
rpds-py==0.12.0
setuptools==68.2.2
six==1.16.0
sniffio==1.3.0
snowballstemmer==2.2.0
soupsieve==2.4.1
Sphinx==7.1.2
sphinx-markdown-builder==0.6.4
sphinxcontrib-applehelp==1.0.4
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.1
soupsieve==2.5
sphinx-markdown-builder==0.6.5
sphinxcontrib-applehelp==1.0.7
sphinxcontrib-devhelp==1.0.5
sphinxcontrib-htmlhelp==2.0.4
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
stack-data==0.6.2
sphinxcontrib-qthelp==1.0.6
sphinxcontrib-serializinghtml==1.1.9
stack-data==0.6.3
starlette==0.27.0
tabulate==0.9.0
terminado==0.17.1
terminado==0.18.0
tinycss2==1.2.1
tomli==2.0.1
tomlkit==0.12.1
tornado==6.3.2
traitlets==5.9.0
typing_extensions==4.7.1
tomlkit==0.12.3
tornado==6.3.3
traitlets==5.13.0
types-python-dateutil==2.8.19.14
typing_extensions==4.8.0
tzdata==2023.3
uri-template==1.3.0
urllib3==2.0.4
wcwidth==0.2.6
urllib3==2.1.0
wcwidth==0.2.10
webcolors==1.13
webencodings==0.5.1
websocket-client==1.6.1
widgetsnbextension==4.0.8
y-py==0.6.0
websocket-client==1.6.4
wheel==0.41.3
widgetsnbextension==4.0.9
y-py==0.6.2
yarl==1.9.2
ypy-websocket==0.8.4
zipp==3.16.2
Loading

0 comments on commit cedadb8

Please sign in to comment.