1313#-----------------------------------------------------------------------------
1414# Boilerplate
1515#-----------------------------------------------------------------------------
16+ from __future__ import annotations
1617
1718# Standard library imports
1819import json
20+ from typing import TYPE_CHECKING , Any , TypedDict
1921
2022# External imports
2123from ipywidgets import DOMWidget
2628from bokeh .document import Document
2729from bokeh .embed .elements import div_for_render_item
2830from 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
3132from 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
3436from ._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#-----------------------------------------------------------------------------
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+
5164class 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