Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 74321ff

Browse files
committed
add idom widget implementation
1 parent d39f331 commit 74321ff

File tree

17 files changed

+561
-107
lines changed

17 files changed

+561
-107
lines changed

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,6 @@ idom_jupyter/labextension/
1111

1212
# OS X
1313
.DS_Store
14+
15+
# PyEnv
16+
.python-version

‎idom_jupyter/__init__.py‎

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
1-
from ._version import version_info, __version__
1+
from ._version import version_info # noqa
2+
from ._version import __version__ # noqa
23

3-
from .example import *
4+
from .widget import LayoutWidget, widgetize, run, set_jupyter_server_base_url
5+
from .ipython_extension import load_ipython_extension, unload_ipython_extension
6+
7+
8+
__all__ = [
9+
"LayoutWidget",
10+
"widgetize",
11+
"run",
12+
"load_ipython_extension",
13+
"unload_ipython_extension",
14+
"set_jupyter_server_base_url",
15+
]
416

517

618
def _jupyter_labextension_paths():
719
"""Called by Jupyter Lab Server to detect if it is a valid labextension and
820
to install the widget
9-
1021
Returns
1122
=======
1223
src: Source directory name to copy files from. Webpack outputs generated files
@@ -16,10 +27,12 @@ def _jupyter_labextension_paths():
1627
from `src` directory into <jupyter path>/labextensions/<dest> directory
1728
during widget installation
1829
"""
19-
return [{
20-
'src': 'labextension',
21-
'dest': 'idom-client-jupyter',
22-
}]
30+
return [
31+
{
32+
"src": "labextension",
33+
"dest": "idom-client-jupyter",
34+
}
35+
]
2336

2437

2538
def _jupyter_nbextension_paths():
@@ -39,9 +52,15 @@ def _jupyter_nbextension_paths():
3952
require: Path to importable AMD Javascript module inside the
4053
<jupyter path>/nbextensions/<dest> directory
4154
"""
42-
return [{
43-
'section': 'notebook',
44-
'src': 'nbextension',
45-
'dest': 'idom-client-jupyter',
46-
'require': 'idom-client-jupyter/extension'
47-
}]
55+
return [
56+
{
57+
"section": "notebook",
58+
"src": "nbextension",
59+
"dest": "idom-client-jupyter",
60+
"require": "idom-client-jupyter/extension",
61+
}
62+
]
63+
64+
65+
def _jupyter_server_extension_paths():
66+
return [{"module": "idom_jupyter.jupyter_server_extension"}]

‎idom_jupyter/_version.py‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
# Module version
2-
version_info = (0, 1, 0, 'alpha', 0)
2+
version_info = (0, 3, 1, "final", 0)
33

44
# Module version stage suffix map
5-
_specifier_ = {'alpha': 'a', 'beta': 'b', 'candidate': 'rc', 'final': ''}
5+
_specifier_ = {"alpha": "a", "beta": "b", "candidate": "rc", "final": ""}
66

77
# Module version accessible using idom_jupyter.__version__
8-
__version__ = '%s.%s.%s%s'%(version_info[0], version_info[1], version_info[2],
9-
'' if version_info[3]=='final' else _specifier_[version_info[3]]+str(version_info[4]))
8+
__version__ = "%s.%s.%s%s" % (
9+
version_info[0],
10+
version_info[1],
11+
version_info[2],
12+
""
13+
if version_info[3] == "final"
14+
else _specifier_[version_info[3]] + str(version_info[4]),
15+
)

‎idom_jupyter/example.py‎

Lines changed: 0 additions & 31 deletions
This file was deleted.

‎idom_jupyter/ipython_extension.py‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from functools import partial
2+
3+
from idom import Element as _Element
4+
from IPython import get_ipython
5+
from IPython.display import display
6+
7+
from .widget import LayoutWidget
8+
9+
10+
_EXTENSION_LOADED = False
11+
_POST_RUN_CELL_HOOK = None
12+
13+
14+
def load_ipython_extension(ipython):
15+
global _POST_RUN_CELL_HOOK, _EXTENSION_LOADED
16+
if not _EXTENSION_LOADED:
17+
_POST_RUN_CELL_HOOK = partial(_post_run_cell, ipython)
18+
ipython.events.register("post_run_cell", _POST_RUN_CELL_HOOK)
19+
ipython.display_formatter.ipython_display_formatter.for_type(
20+
_Element, lambda element: ({}, {})
21+
)
22+
_EXTENSION_LOADED = True
23+
24+
25+
def unload_ipython_extension(ipython):
26+
global _POST_RUN_CELL_HOOK, _EXTENSION_LOADED
27+
ipython.events.unregister("post_run_cell", _POST_RUN_CELL_HOOK)
28+
_POST_RUN_CELL_HOOK = None
29+
_EXTENSION_LOADED = False
30+
31+
32+
def _post_run_cell(ipython, result):
33+
if isinstance(result.result, _Element):
34+
display(LayoutWidget(result.result))
35+
36+
37+
if get_ipython() is not None:
38+
load_ipython_extension(get_ipython())
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from urllib.parse import urljoin
2+
3+
from notebook.notebookapp import NotebookApp
4+
from idom.client.manage import WEB_MODULES_DIR
5+
from tornado.web import StaticFileHandler
6+
from tornado.web import Application
7+
8+
9+
def load_jupyter_server_extension(notebook_app: NotebookApp):
10+
web_app: Application = notebook_app.web_app
11+
route_pattern = urljoin(web_app.settings["base_url"], "_idom_web_modules/(.*)")
12+
web_app.add_handlers(
13+
host_pattern=".*$",
14+
host_handlers=[
15+
(
16+
route_pattern,
17+
StaticFileHandler,
18+
{"path": str(WEB_MODULES_DIR.absolute())},
19+
),
20+
],
21+
)

‎idom_jupyter/widget.py‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import asyncio
2+
from functools import wraps
3+
from threading import Thread
4+
from queue import Queue as SyncQueue
5+
6+
import ipywidgets as widgets
7+
from IPython.display import display as ipython_display
8+
from traitlets import Unicode
9+
from idom.core.layout import Layout, LayoutEvent, LayoutUpdate
10+
11+
12+
_JUPYTER_SERVER_BASE_URL = ""
13+
14+
15+
def set_jupyter_server_base_url(base_url):
16+
global _JUPYTER_SERVER_BASE_URL
17+
_JUPYTER_SERVER_BASE_URL = base_url
18+
19+
20+
def run(constructor):
21+
"""Run the given IDOM elemen definition as a Jupyter Widget.
22+
23+
This function is meant to be similarly to ``idom.run``.
24+
"""
25+
return ipython_display(LayoutWidget(constructor()))
26+
27+
28+
def widgetize(constructor):
29+
"""A decorator that turns an IDOM element into a Jupyter Widget constructor"""
30+
31+
@wraps(constructor)
32+
def wrapper(*args, **kwargs):
33+
return LayoutWidget(constructor(*args, **kwargs))
34+
35+
return wrapper
36+
37+
38+
@widgets.register
39+
class LayoutWidget(widgets.DOMWidget):
40+
"""A widget for displaying IDOM elements"""
41+
42+
# Name of the widget view class in front-end
43+
_view_name = Unicode("IdomView").tag(sync=True)
44+
45+
# Name of the widget model class in front-end
46+
_model_name = Unicode("IdomModel").tag(sync=True)
47+
48+
# Name of the front-end module containing widget view
49+
_view_module = Unicode("idom-client-jupyter").tag(sync=True)
50+
51+
# Name of the front-end module containing widget model
52+
_model_module = Unicode("idom-client-jupyter").tag(sync=True)
53+
54+
# Version of the front-end module containing widget view
55+
_view_module_version = Unicode("^0.1.0").tag(sync=True)
56+
# Version of the front-end module containing widget model
57+
_model_module_version = Unicode("^0.1.0").tag(sync=True)
58+
59+
_jupyter_server_base_url = Unicode().tag(sync=True)
60+
61+
def __init__(self, element):
62+
super().__init__(_jupyter_server_base_url=_JUPYTER_SERVER_BASE_URL)
63+
self._idom_model = {}
64+
self._idom_views = set()
65+
self._idom_layout = Layout(element)
66+
self._idom_loop = _spawn_threaded_event_loop(self._idom_layout_render_loop())
67+
self.on_msg(self._idom_on_msg)
68+
69+
@staticmethod
70+
def _idom_on_msg(self, message, buffers):
71+
m_type = message.get("type")
72+
if m_type == "client-ready":
73+
v_id = message["viewID"]
74+
self._idom_views.add(v_id)
75+
update = LayoutUpdate.create_from({}, self._idom_model)
76+
self.send({"viewID": v_id, "data": update})
77+
elif m_type == "dom-event":
78+
asyncio.run_coroutine_threadsafe(
79+
self._idom_layout.dispatch(LayoutEvent(**message["data"])),
80+
loop=self._idom_loop,
81+
)
82+
elif m_type == "client-removed":
83+
v_id = message["viewID"]
84+
if v_id in self._idom_views:
85+
self._idom_views.remove(message["viewID"])
86+
87+
async def _idom_layout_render_loop(self):
88+
async with self._idom_layout:
89+
while True:
90+
update = await self._idom_layout.render()
91+
92+
self._idom_model = update.apply_to(self._idom_model)
93+
for v_id in self._idom_views:
94+
self.send({"viewID": v_id, "data": update})
95+
96+
97+
def _spawn_threaded_event_loop(coro):
98+
loop_q = SyncQueue()
99+
100+
def run_in_thread() -> None:
101+
loop = asyncio.new_event_loop()
102+
asyncio.set_event_loop(loop)
103+
loop_q.put(loop)
104+
loop.run_until_complete(coro)
105+
106+
thread = Thread(target=run_in_thread, daemon=True)
107+
thread.start()
108+
109+
return loop_q.get()

‎js/lib/embed.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
// already be loaded by the notebook otherwise.
66

77
// Export widget models and views, and the npm package version number.
8-
module.exports = require('./example.js');
8+
module.exports = require('./widget.js');
99
module.exports['version'] = require('../package.json').version;

‎js/lib/example.js‎

Lines changed: 0 additions & 55 deletions
This file was deleted.

‎js/lib/index.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
// Export widget models and views, and the npm package version number.
2-
module.exports = require('./example.js');
2+
module.exports = require('./widget.js');
33
module.exports['version'] = require('../package.json').version;

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /