Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ipywidgets for compatibility with latest bokeh #3299

Merged
merged 6 commits into from
Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions panel/io/ipywidget.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,53 @@
import logging

import param

from bokeh.document.events import MessageSentEvent
from bokeh.document.json import MessageSent, Literal, TypedDict
from bokeh.util.serialization import make_id

from ipykernel.comm import CommManager
from ipywidgets_bokeh.kernel import BokehKernel, SessionWebsocket, WebsocketStream
from tornado.ioloop import IOLoop
from ipywidgets_bokeh.kernel import BokehKernel, SessionWebsocket, WebsocketStream


class MessageSentBuffers(TypedDict):
kind: Literal["MessageSent"]
msg_type: str


class MessageSentEventPatched(MessageSentEvent):
"""
Patches MessageSentEvent with fix that ensures MessageSent event
does not define msg_data (which is an assumption in BokehJS
Document.apply_json_patch.)
"""

def generate(self, references, buffers):
if not isinstance(self.msg_data, bytes):
msg = MessageSent(
kind=self.kind,
msg_type=self.msg_type,
msg_data=self.msg_data
)
else:
msg = MessageSentBuffers(
kind=self.kind,
msg_type=self.msg_type
)
assert buffers is not None
buffer_id = make_id()
buf = (dict(id=buffer_id), self.msg_data)
buffers.append(buf)
return msg


class PanelSessionWebsocket(SessionWebsocket):

def __init__(self, *args, **kwargs):
self._queue = []
super().__init__(*args, **kwargs)
self._document = kwargs.pop('document', None)
self._queue = []
self._document.on_message("ipywidgets_bokeh", self.receive)

def send(self, stream, msg_type, content=None, parent=None, ident=None, buffers=None, track=False, header=None, metadata=None):
Expand All @@ -37,25 +73,24 @@ def send(self, stream, msg_type, content=None, parent=None, ident=None, buffers=
else:
data = packed.decode("utf-8")

doc = self._document
event = MessageSentEvent(doc, "ipywidgets_bokeh", data)
event = MessageSentEventPatched(self._document, "ipywidgets_bokeh", data)
self._queue.append(event)
doc.add_next_tick_callback(self._dispatch)
self._document.add_next_tick_callback(self._dispatch)

def _dispatch(self):
try:
for event in self._queue:
self._document.callbacks.trigger_on_change(event)
except Exception:
pass
except Exception as e:
param.main.warning(f'ipywidgets event dispatch failed with: {e}')
finally:
self._queue = []


class PanelKernel(BokehKernel):

def __init__(self, key=None, document=None):
super(BokehKernel, self).__init__()
super().__init__()

self.session = PanelSessionWebsocket(document=document, parent=self, key=key)
self.stream = self.iopub_socket = WebsocketStream(self.session)
Expand Down
6 changes: 6 additions & 0 deletions panel/pane/ipywidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ def _get_ipywidget(self, obj, doc, root, comm, **kwargs):
import ipykernel
from ipywidgets_bokeh.widget import IPyWidget
from ..io.ipywidget import PanelKernel

# Patch font-awesome CSS
IPyWidget.__css__ = [
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.css"
]

if not isinstance(ipykernel.kernelbase.Kernel._instance, PanelKernel):
kernel = PanelKernel(document=doc, key=str(id(doc)).encode('utf-8'))
for w in obj.widgets.values():
Expand Down
6 changes: 5 additions & 1 deletion panel/pane/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def __init__(self, object=None, **params):
self._managers = {}

def _get_widget(self, fig):
import matplotlib
import matplotlib.backends
old_backend = getattr(matplotlib.backends, 'backend', 'agg')

from ipympl.backend_nbagg import FigureManager, Canvas, is_interactive
Expand Down Expand Up @@ -193,6 +193,10 @@ def _get_model(self, doc, root=None, parent=None, comm=None):
props = self._process_param_change(self._init_params())
kwargs = {k: v for k, v in props.items()
if k not in self._rerender_params+['interactive']}
w, h = self.object.get_size_inches()
kwargs['width'] = self.width or int(self.dpi * w)
kwargs['height'] = self.height or int(self.dpi * h)
kwargs['sizing_mode'] = self.sizing_mode
model = self._get_ipywidget(manager.canvas, doc, root, comm,
**kwargs)
if root is None:
Expand Down