Skip to content

Commit 4f7f661

Browse files
mattpapaxilLev Maximov
authored
jupyter_bokeh 3.0.6 (bokeh 3 and ipywidgets 8) (#178)
* v3.0.5, added support for ipywidgets>=8.0.0 (#169) * v3.0.5, added support for ipywidgets>=8.0.0 * Update nodejs in CI Co-authored-by: Lev Maximov <[email protected]> Co-authored-by: Mateusz Paprocki <[email protected]> * Update yarn.lock * npm audit fix * Upgrade webpack and webpack-cli * Migrate the build to webpack 5 * Add index.js.LICENSE.txt * Update and unify dependencies * Update yarn.lock * Bump version to 3.0.6 * Upgrade actions and nodejs in CI * Adjust version spec scheme * Upgrade jupyter-packaging * Add types to jupyter_bokeh/widgets.py * Remove unused imports * Rename trigger_{json_->}event() * Update event handling to Bokeh 3.0 * Update .gitignore --------- Co-authored-by: Lev Maximov <[email protected]> Co-authored-by: Lev Maximov <[email protected]>
1 parent f343b90 commit 4f7f661

File tree

10 files changed

+7757
-11626
lines changed

10 files changed

+7757
-11626
lines changed

.github/workflows/build.yml

+10-10
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,30 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- name: Checkout
14-
uses: actions/checkout@v2
14+
uses: actions/checkout@v3
1515
- name: Install node
16-
uses: actions/setup-node@v1
16+
uses: actions/setup-node@v3
1717
with:
18-
node-version: '16.x'
18+
node-version: '18.x'
1919
- name: Install Python
20-
uses: actions/setup-python@v2
20+
uses: actions/setup-python@v4
2121
with:
22-
python-version: '3.7'
22+
python-version: '3.8'
2323
architecture: 'x64'
2424
- name: Setup pip cache
25-
uses: actions/cache@v2
25+
uses: actions/cache@v3
2626
with:
2727
path: ~/.cache/pip
28-
key: pip-3.7-${{ hashFiles('package.json') }}
28+
key: pip-3.8-${{ hashFiles('package.json') }}
2929
restore-keys: |
30-
pip-3.7-
30+
pip-3.8-
3131
pip-
3232
3333
- name: Get yarn cache directory path
3434
id: yarn-cache-dir-path
3535
run: echo "::set-output name=dir::$(yarn cache dir)"
3636
- name: Setup yarn cache
37-
uses: actions/cache@v2
37+
uses: actions/cache@v3
3838
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
3939
with:
4040
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@@ -43,7 +43,7 @@ jobs:
4343
yarn-
4444
4545
- name: Install dependencies
46-
run: python -m pip install -U jupyterlab~=3.0 jupyter_packaging~=0.7.9
46+
run: python -m pip install -U jupyterlab~=3.0 jupyter_packaging~=0.12.3
4747
- name: Build the extension
4848
run: |
4949
jlpm

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
/jupyter_bokeh/labextension/
1010
/jupyter_bokeh/nbextension/index.js
1111
/jupyter_bokeh/nbextension/index.js.map
12+
__pycache__/

conda.recipe/meta.yaml

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ requirements:
2424
- notebook
2525
- python
2626
- setuptools
27-
- nodejs >=10.13.0
27+
- nodejs >=18.0
2828
run:
2929
- python
30-
- bokeh >=2.0.0
31-
- ipywidgets >=7.5.0
30+
- bokeh 2.4.*,3.*
31+
- ipywidgets 8.*
3232
run_constrained:
33-
- jupyterlab >=3.0.0,<4
33+
- jupyterlab 3.*
3434

3535
test:
3636
imports:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*!
2+
* Sizzle CSS Selector Engine v2.3.6
3+
* https://sizzlejs.com/
4+
*
5+
* Copyright JS Foundation and other contributors
6+
* Released under the MIT license
7+
* https://js.foundation/
8+
*
9+
* Date: 2021-02-16
10+
*/
11+
12+
/*!
13+
* jQuery JavaScript Library v3.6.0
14+
* https://jquery.com/
15+
*
16+
* Includes Sizzle.js
17+
* https://sizzlejs.com/
18+
*
19+
* Copyright OpenJS Foundation and other contributors
20+
* Released under the MIT license
21+
* https://jquery.org/license
22+
*
23+
* Date: 2021-03-02T17:08Z
24+
*/
25+
26+
/*! *****************************************************************************
27+
Copyright (c) Microsoft Corporation.
28+
29+
Permission to use, copy, modify, and/or distribute this software for any
30+
purpose with or without fee is hereby granted.
31+
32+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
33+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
34+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
35+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
36+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
37+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
38+
PERFORMANCE OF THIS SOFTWARE.
39+
***************************************************************************** */

jupyter_bokeh/widgets.py

+72-38
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
#-----------------------------------------------------------------------------
1414
# Boilerplate
1515
#-----------------------------------------------------------------------------
16+
from __future__ import annotations
1617

1718
# Standard library imports
1819
import json
20+
from typing import TYPE_CHECKING, Any, TypedDict
1921

2022
# External imports
2123
from ipywidgets import DOMWidget
@@ -26,13 +28,18 @@
2628
from bokeh.document import Document
2729
from bokeh.embed.elements import div_for_render_item
2830
from bokeh.embed.util import standalone_docs_json_and_render_items
29-
from bokeh.events import Event
30-
from bokeh.models import LayoutDOM
31+
from bokeh.models import ColumnDataSource, LayoutDOM
3132
from bokeh.protocol import Protocol
32-
from bokeh.util.dependencies import import_optional
33+
from bokeh.core.serialization import Deserializer, Serialized
34+
from bokeh.model import Model
3335

3436
from ._version import __version__
3537

38+
if TYPE_CHECKING:
39+
from bokeh.core.types import ID
40+
from bokeh.document.events import DocumentPatchedEvent
41+
from bokeh.document.json import DocJson
42+
3643
#-----------------------------------------------------------------------------
3744
# Globals and constants
3845
#-----------------------------------------------------------------------------
@@ -48,6 +55,12 @@
4855
# General API
4956
#-----------------------------------------------------------------------------
5057

58+
class RenderBundle(TypedDict):
59+
60+
docs_json: dict[ID, DocJson]
61+
render_items: list[dict[str, Any]] # TODO: list[RenderItemJson]
62+
div: str
63+
5164
class BokehModel(DOMWidget):
5265

5366
_model_name = Unicode("BokehModel").tag(sync=True)
@@ -61,79 +74,100 @@ class BokehModel(DOMWidget):
6174
combine_events = Bool(False).tag(sync=True)
6275
render_bundle = Dict().tag(sync=True, to_json=lambda obj, _: serialize_json(obj))
6376

77+
_model: Model
78+
6479
@property
65-
def _document(self):
80+
def _document(self) -> Document | None:
6681
return self._model.document
6782

68-
def __init__(self, model, **kwargs):
83+
def __init__(self, model: LayoutDOM, **kwargs: Any) -> None:
6984
assert isinstance(model, LayoutDOM)
7085
self.update_from_model(model)
7186
super(BokehModel, self).__init__(**kwargs)
7287
self.on_msg(self._sync_model)
7388

74-
def close(self):
89+
def close(self) -> None:
7590
super().close()
7691
if self._document is not None:
7792
self._document.remove_on_change(self)
7893

7994
@classmethod
80-
def _model_to_traits(cls, model):
95+
def _model_to_traits(cls, model: Model) -> RenderBundle:
8196
if model.document is None:
8297
document = Document()
8398
document.add_root(model)
8499
(docs_json, [render_item]) = standalone_docs_json_and_render_items([model], suppress_callback_warning=True)
85-
render_bundle = dict(
100+
render_bundle = RenderBundle(
86101
docs_json=docs_json,
87102
render_items=[render_item.to_json()],
88103
div=div_for_render_item(render_item),
89104
)
90105
return render_bundle
91106

92-
def update_from_model(self, model):
107+
def update_from_model(self, model: Model) -> None:
93108
self._model = model
94109
self.render_bundle = self._model_to_traits(model)
95110
self._document.on_change_dispatch_to(self)
96111

97-
def _document_patched(self, event):
112+
def _document_patched(self, event: DocumentPatchedEvent) -> None:
98113
if event.setter is self:
99114
return
100115
msg = Protocol().create("PATCH-DOC", [event])
101116

102117
self.send({"msg": "patch", "payload": msg.header_json})
103118
self.send({"msg": "patch", "payload": msg.metadata_json})
104119
self.send({"msg": "patch", "payload": msg.content_json})
105-
for header, buffer in msg.buffers:
106-
self.send({"msg": "patch", "payload": json.dumps(header)})
107-
self.send({"msg": "patch"}, [buffer])
120+
for buffer in msg.buffers:
121+
header = json.dumps(buffer.ref)
122+
payload = buffer.to_bytes()
123+
self.send({"msg": "patch", "payload": header})
124+
self.send({"msg": "patch"}, [payload])
108125

109-
def _sync_model(self, _, content, _buffers):
126+
def _sync_model(self, _model: BokehModel, content: dict[str, Any], _buffers: list[Any]) -> None:
110127
if content.get("event", "") != "jsevent":
111128
return
112-
kind = content.get("kind")
113-
if kind == 'ModelChanged':
114-
hint = content.get("hint")
115-
if hint:
116-
cds = self._model.select_one({"id": hint["column_source"]["id"]})
117-
if "patches" in hint:
118-
# Handle ColumnsPatchedEvent
119-
cds.patch(hint["patches"], setter=self)
120-
elif "data" in hint:
121-
# Handle ColumnsStreamedEvent
122-
cds._stream(hint["data"], rollover=hint["rollover"], setter=self)
123-
return
124-
125-
# Handle ModelChangedEvent
126-
new, old, attr = content["new"], content["old"], content["attr"]
127-
submodel = self._model.select_one({"id": content["id"]})
128-
descriptor = submodel.lookup(content['attr'])
129-
try:
130-
descriptor._set(submodel, old, new, hint=hint, setter=self)
131-
except Exception:
132-
return
133-
for cb in submodel._callbacks.get(attr, []):
129+
del content["event"]
130+
131+
setter: Any = self
132+
133+
assert self._document is not None
134+
deserializer = Deserializer(list(self._document.models), setter=setter)
135+
event = deserializer.deserialize(Serialized(content=content, buffers=[]))
136+
137+
kind = event["kind"]
138+
if kind == "ModelChanged":
139+
attr = event["attr"]
140+
model = event["model"]
141+
new = event["new"]
142+
143+
assert isinstance(model, Model)
144+
descriptor = model.lookup(attr)
145+
146+
# descriptor.set_from_json()
147+
new = descriptor.property.prepare_value(model, descriptor.name, new)
148+
old = descriptor._get(model)
149+
descriptor._set(model, old, new, setter=setter)
150+
151+
for cb in model._callbacks.get(attr, []):
134152
cb(attr, old, new)
135-
elif kind == 'MessageSent':
136-
self._document.callbacks.trigger_json_event(content["msg_data"])
153+
elif kind == "ColumnsStreamed":
154+
model = content["model"]
155+
data = content["data"]
156+
rollover = content["rollover"]
157+
158+
assert isinstance(model, ColumnDataSource)
159+
model._stream(data, rollover, setter=setter)
160+
elif kind == "ColumnsPatched":
161+
model = content["model"]
162+
patches = content["data"]
163+
164+
assert isinstance(model, ColumnDataSource)
165+
model.patch(patches, setter=setter)
166+
elif kind == "MessageSent":
167+
msg_type = event["msg_type"]
168+
msg_data = event["msg_data"]
169+
if msg_type == "bokeh_event":
170+
self._document.callbacks.trigger_event(msg_data)
137171

138172
#-----------------------------------------------------------------------------
139173
# Dev API

0 commit comments

Comments
 (0)