13
13
#-----------------------------------------------------------------------------
14
14
# Boilerplate
15
15
#-----------------------------------------------------------------------------
16
+ from __future__ import annotations
16
17
17
18
# Standard library imports
18
19
import json
20
+ from typing import TYPE_CHECKING , Any , TypedDict
19
21
20
22
# External imports
21
23
from ipywidgets import DOMWidget
26
28
from bokeh .document import Document
27
29
from bokeh .embed .elements import div_for_render_item
28
30
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
31
32
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
33
35
34
36
from ._version import __version__
35
37
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
+
36
43
#-----------------------------------------------------------------------------
37
44
# Globals and constants
38
45
#-----------------------------------------------------------------------------
48
55
# General API
49
56
#-----------------------------------------------------------------------------
50
57
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
+
51
64
class BokehModel (DOMWidget ):
52
65
53
66
_model_name = Unicode ("BokehModel" ).tag (sync = True )
@@ -61,79 +74,100 @@ class BokehModel(DOMWidget):
61
74
combine_events = Bool (False ).tag (sync = True )
62
75
render_bundle = Dict ().tag (sync = True , to_json = lambda obj , _ : serialize_json (obj ))
63
76
77
+ _model : Model
78
+
64
79
@property
65
- def _document (self ):
80
+ def _document (self ) -> Document | None :
66
81
return self ._model .document
67
82
68
- def __init__ (self , model , ** kwargs ) :
83
+ def __init__ (self , model : LayoutDOM , ** kwargs : Any ) -> None :
69
84
assert isinstance (model , LayoutDOM )
70
85
self .update_from_model (model )
71
86
super (BokehModel , self ).__init__ (** kwargs )
72
87
self .on_msg (self ._sync_model )
73
88
74
- def close (self ):
89
+ def close (self ) -> None :
75
90
super ().close ()
76
91
if self ._document is not None :
77
92
self ._document .remove_on_change (self )
78
93
79
94
@classmethod
80
- def _model_to_traits (cls , model ) :
95
+ def _model_to_traits (cls , model : Model ) -> RenderBundle :
81
96
if model .document is None :
82
97
document = Document ()
83
98
document .add_root (model )
84
99
(docs_json , [render_item ]) = standalone_docs_json_and_render_items ([model ], suppress_callback_warning = True )
85
- render_bundle = dict (
100
+ render_bundle = RenderBundle (
86
101
docs_json = docs_json ,
87
102
render_items = [render_item .to_json ()],
88
103
div = div_for_render_item (render_item ),
89
104
)
90
105
return render_bundle
91
106
92
- def update_from_model (self , model ) :
107
+ def update_from_model (self , model : Model ) -> None :
93
108
self ._model = model
94
109
self .render_bundle = self ._model_to_traits (model )
95
110
self ._document .on_change_dispatch_to (self )
96
111
97
- def _document_patched (self , event ) :
112
+ def _document_patched (self , event : DocumentPatchedEvent ) -> None :
98
113
if event .setter is self :
99
114
return
100
115
msg = Protocol ().create ("PATCH-DOC" , [event ])
101
116
102
117
self .send ({"msg" : "patch" , "payload" : msg .header_json })
103
118
self .send ({"msg" : "patch" , "payload" : msg .metadata_json })
104
119
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 ])
108
125
109
- def _sync_model (self , _ , content , _buffers ) :
126
+ def _sync_model (self , _model : BokehModel , content : dict [ str , Any ], _buffers : list [ Any ]) -> None :
110
127
if content .get ("event" , "" ) != "jsevent" :
111
128
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 , []):
134
152
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 )
137
171
138
172
#-----------------------------------------------------------------------------
139
173
# Dev API
0 commit comments