#-----------------------------------------------------------------------------# Copyright (c) Anaconda, Inc., and Bokeh Contributors.# All rights reserved.## The full license is in the file LICENSE.txt, distributed with this software.#-----------------------------------------------------------------------------''' Provide events that represent various changes to Bokeh Documents.These events are used internally to signal changes to Documents. Forinformation about user-facing (e.g. UI or tool) events, see the referencefor :ref:`bokeh.events`.These events are employed for incoming and outgoing websocket messages andinternally for triggering callbacks. For example, the sequence of events thathappens when a user calls a Document API or sets a property resulting in a"patch event" to the Document:.. code-block:: user invokes Document API -> Document API triggers event objects -> registered callbacks are executed -> Session callback generates JSON message from event object -> Session sends JSON message over websocketBut events may also be triggered from the client, and arrive as JSON messagesover the transport layer, which is why the JSON handling and Document API mustbe separated. Consider the alternative sequence of events:.. code-block:: Session receives JSON message over websocket -> Document calls event.handle_json -> handle_json invokes appropriate Document API -> Document API triggers event objects -> registered callbacks are executed -> Session callback suppresses outgoing eventAs a final note, message "ping-pong" is avoided by recording a "setter" whenevents objects are created. If the session callback notes the event setter isitself, then no further action (e.g. sending an outgoing change event identicalto the incoming event it just processed) is taken.'''#-----------------------------------------------------------------------------# Boilerplate#-----------------------------------------------------------------------------from__future__importannotationsimportlogging# isort:skiplog=logging.getLogger(__name__)#-----------------------------------------------------------------------------# Imports#-----------------------------------------------------------------------------# Standard library importsfromtypingimport(TYPE_CHECKING,Any,Callable,ClassVar,TypeAlias,cast,)# Bokeh importsfrom..core.serializationimportSerializable,Serializerfrom.jsonimport(ColumnDataChanged,ColumnsPatched,ColumnsStreamed,DocumentPatched,MessageSent,ModelChanged,RootAdded,RootRemoved,TitleChanged,)ifTYPE_CHECKING:importpandasaspdfrom..core.has_propsimportSetterfrom..modelimportModelfrom..models.sourcesimportDataDictfrom..protocol.messageimportBufferReffrom..server.callbacksimportSessionCallbackfrom.documentimportDocumentfrom.jsonimportPatches#-----------------------------------------------------------------------------# Globals and constants#-----------------------------------------------------------------------------__all__=('ColumnDataChangedEvent','ColumnsStreamedEvent','ColumnsPatchedEvent','DocumentChangedEvent','DocumentPatchedEvent','ModelChangedEvent','RootAddedEvent','RootRemovedEvent','SessionCallbackAdded','SessionCallbackRemoved','TitleChangedEvent','MessageSentEvent',)#-----------------------------------------------------------------------------# General API#-----------------------------------------------------------------------------#-----------------------------------------------------------------------------# Dev API#-----------------------------------------------------------------------------ifTYPE_CHECKING:Buffers:TypeAlias=list[BufferRef]|NoneInvoker:TypeAlias=Callable[...,Any]# TODOclassDocumentChangedMixin:def_document_changed(self,event:DocumentChangedEvent)->None:...classDocumentPatchedMixin:def_document_patched(self,event:DocumentPatchedEvent)->None:...classDocumentMessageSentMixin:def_document_message_sent(self,event:MessageSentEvent)->None:...classDocumentModelChangedMixin:def_document_model_changed(self,event:ModelChangedEvent)->None:...classColumnDataChangedMixin:def_column_data_changed(self,event:ColumnDataChangedEvent)->None:...classColumnsStreamedMixin:def_columns_streamed(self,event:ColumnsStreamedEvent)->None:...classColumnsPatchedMixin:def_columns_patched(self,event:ColumnsPatchedEvent)->None:...classSessionCallbackAddedMixin:def_session_callback_added(self,event:SessionCallbackAdded)->None:...classSessionCallbackRemovedMixin:def_session_callback_removed(self,event:SessionCallbackRemoved)->None:...
[docs]classDocumentChangedEvent:''' Base class for all internal events representing a change to a Bokeh Document. '''document:Documentsetter:Setter|Nonecallback_invoker:Invoker|None
[docs]def__init__(self,document:Document,setter:Setter|None=None,callback_invoker:Invoker|None=None)->None:''' Args: document (Document) : A Bokeh document that is to be updated. setter (ClientSession or ServerSession or None, optional) : This is used to prevent "boomerang" updates to Bokeh apps. (default: None) In the context of a Bokeh server application, incoming updates to properties will be annotated with the session that is doing the updating. This value is propagated through any subsequent change notifications that the update triggers. The session can compare the event setter to itself, and suppress any updates that originate from itself. callback_invoker (callable, optional) : A callable that will invoke any Model callbacks that should be executed in response to the change that triggered this event. (default: None) '''self.document=documentself.setter=setterself.callback_invoker=callback_invoker
[docs]defdispatch(self,receiver:Any)->None:''' Dispatch handling of this event to a receiver. This method will invoke ``receiver._document_changed`` if it exists. '''ifhasattr(receiver,'_document_changed'):cast(DocumentChangedMixin,receiver)._document_changed(self)
[docs]classDocumentPatchedEvent(DocumentChangedEvent,Serializable):''' A Base class for events that represent updating Bokeh Models and their properties. '''kind:ClassVar[str]_handlers:ClassVar[dict[str,type[DocumentPatchedEvent]]]={}def__init_subclass__(cls):cls._handlers[cls.kind]=cls
[docs]defdispatch(self,receiver:Any)->None:''' Dispatch handling of this event to a receiver. This method will invoke ``receiver._document_patched`` if it exists. '''super().dispatch(receiver)ifhasattr(receiver,'_document_patched'):cast(DocumentPatchedMixin,receiver)._document_patched(self)
[docs]defto_serializable(self,serializer:Serializer)->DocumentPatched:''' Create a JSON representation of this event suitable for sending to clients. *Sub-classes must implement this method.* Args: serializer (Serializer): '''raiseNotImplementedError()
[docs]@staticmethoddefhandle_event(doc:Document,event_rep:DocumentPatched,setter:Setter|None)->None:''' '''event_kind=event_rep.pop("kind")event_cls=DocumentPatchedEvent._handlers.get(event_kind,None)ifevent_clsisNone:raiseRuntimeError(f"unknown patch event type '{event_kind!r}'")event=event_cls(document=doc,setter=setter,**event_rep)event_cls._handle_event(doc,event)
[docs]classModelChangedEvent(DocumentPatchedEvent):''' A concrete event representing updating an attribute and value of a specific Bokeh Model. '''kind="ModelChanged"
[docs]def__init__(self,document:Document,model:Model,attr:str,new:Any,setter:Setter|None=None,callback_invoker:Invoker|None=None):''' Args: document (Document) : A Bokeh document that is to be updated. model (Model) : A Model to update attr (str) : The name of the attribute to update on the model. new (object) : The new value of the attribute setter (ClientSession or ServerSession or None, optional) : This is used to prevent "boomerang" updates to Bokeh apps. (default: None) See :class:`~bokeh.document.events.DocumentChangedEvent` for more details. callback_invoker (callable, optional) : A callable that will invoke any Model callbacks that should be executed in response to the change that triggered this event. (default: None) '''super().__init__(document,setter,callback_invoker)self.model=modelself.attr=attrself.new=new
[docs]defcombine(self,event:DocumentChangedEvent)->bool:''' '''ifnotisinstance(event,ModelChangedEvent):returnFalse# If these are not true something weird is going on, maybe updates from# Python bokeh.client, don't try to combineifself.setter!=event.setter:returnFalseifself.document!=event.document:returnFalseif(self.model==event.model)and(self.attr==event.attr):self.new=event.newself.callback_invoker=event.callback_invokerreturnTruereturnFalse
[docs]defdispatch(self,receiver:Any)->None:''' Dispatch handling of this event to a receiver. This method will invoke ``receiver._document_model_changed`` if it exists. '''super().dispatch(receiver)ifhasattr(receiver,'_document_model_changed'):cast(DocumentModelChangedMixin,receiver)._document_model_changed(self)
[docs]defto_serializable(self,serializer:Serializer)->ModelChanged:''' Create a JSON representation of this event suitable for sending to clients. Args: serializer (Serializer): '''returnModelChanged(kind=self.kind,model=self.model.ref,attr=self.attr,new=serializer.encode(self.new),)
[docs]classColumnDataChangedEvent(DocumentPatchedEvent):''' A concrete event representing efficiently replacing *all* existing data for a :class:`~bokeh.models.sources.ColumnDataSource` '''kind="ColumnDataChanged"
[docs]def__init__(self,document:Document,model:Model,attr:str,data:DataDict|None=None,cols:list[str]|None=None,setter:Setter|None=None,callback_invoker:Invoker|None=None):''' Args: document (Document) : A Bokeh document that is to be updated. column_source (ColumnDataSource) : cols (list[str]) : optional explicit list of column names to update. If None, all columns will be updated (default: None) setter (ClientSession or ServerSession or None, optional) : This is used to prevent "boomerang" updates to Bokeh apps. (default: None) See :class:`~bokeh.document.events.DocumentChangedEvent` for more details. callback_invoker (callable, optional) : A callable that will invoke any Model callbacks that should be executed in response to the change that triggered this event. (default: None) '''super().__init__(document,setter,callback_invoker)self.model=modelself.attr=attrself.data=dataself.cols=cols
[docs]defdispatch(self,receiver:Any)->None:''' Dispatch handling of this event to a receiver. This method will invoke ``receiver._column_data_changed`` if it exists. '''super().dispatch(receiver)ifhasattr(receiver,'_column_data_changed'):cast(ColumnDataChangedMixin,receiver)._column_data_changed(self)
[docs]defto_serializable(self,serializer:Serializer)->ColumnDataChanged:''' Create a JSON representation of this event suitable for sending to clients. .. code-block:: python { 'kind' : 'ColumnDataChanged' 'column_source' : <reference to a CDS> 'data' : <new data to steam to column_source> 'cols' : <specific columns to update> } Args: serializer (Serializer): '''data=self.dataifself.dataisnotNoneelsegetattr(self.model,self.attr)cols=self.colsifcolsisnotNone:data={col:valueforcolincolsif(value:=data.get(col))isnotNone}returnColumnDataChanged(kind=self.kind,model=self.model.ref,attr=self.attr,data=serializer.encode(data),cols=serializer.encode(cols),)
[docs]classColumnsStreamedEvent(DocumentPatchedEvent):''' A concrete event representing efficiently streaming new data to a :class:`~bokeh.models.sources.ColumnDataSource` '''kind="ColumnsStreamed"data:DataDict
[docs]def__init__(self,document:Document,model:Model,attr:str,data:DataDict|pd.DataFrame,rollover:int|None=None,setter:Setter|None=None,callback_invoker:Invoker|None=None):''' Args: document (Document) : A Bokeh document that is to be updated. column_source (ColumnDataSource) : The data source to stream new data to. data (dict or DataFrame) : New data to stream. If a DataFrame, will be stored as ``{c: df[c] for c in df.columns}`` rollover (int, optional) : A rollover limit. If the data source columns exceed this limit, earlier values will be discarded to maintain the column length under the limit. setter (ClientSession or ServerSession or None, optional) : This is used to prevent "boomerang" updates to Bokeh apps. (default: None) See :class:`~bokeh.document.events.DocumentChangedEvent` for more details. callback_invoker (callable, optional) : A callable that will invoke any Model callbacks that should be executed in response to the change that triggered this event. (default: None) '''super().__init__(document,setter,callback_invoker)self.model=modelself.attr=attrimportpandasaspdifisinstance(data,pd.DataFrame):data={c:data[c]forcindata.columns}self.data=dataself.rollover=rollover
[docs]defdispatch(self,receiver:Any)->None:''' Dispatch handling of this event to a receiver. This method will invoke ``receiver._columns_streamed`` if it exists. '''super().dispatch(receiver)ifhasattr(receiver,'_columns_streamed'):cast(ColumnsStreamedMixin,receiver)._columns_streamed(self)
[docs]defto_serializable(self,serializer:Serializer)->ColumnsStreamed:''' Create a JSON representation of this event suitable for sending to clients. .. code-block:: python { 'kind' : 'ColumnsStreamed' 'column_source' : <reference to a CDS> 'data' : <new data to steam to column_source> 'rollover' : <rollover limit> } Args: serializer (Serializer): '''returnColumnsStreamed(kind=self.kind,model=self.model.ref,attr=self.attr,data=serializer.encode(self.data),rollover=self.rollover,)
[docs]classColumnsPatchedEvent(DocumentPatchedEvent):''' A concrete event representing efficiently applying data patches to a :class:`~bokeh.models.sources.ColumnDataSource` '''kind="ColumnsPatched"
[docs]def__init__(self,document:Document,model:Model,attr:str,patches:Patches,setter:Setter|None=None,callback_invoker:Invoker|None=None):''' Args: document (Document) : A Bokeh document that is to be updated. column_source (ColumnDataSource) : The data source to apply patches to. patches (list) : setter (ClientSession or ServerSession or None, optional) : This is used to prevent "boomerang" updates to Bokeh apps. (default: None) See :class:`~bokeh.document.events.DocumentChangedEvent` for more details. callback_invoker (callable, optional) : A callable that will invoke any Model callbacks that should be executed in response to the change that triggered this event. (default: None) '''super().__init__(document,setter,callback_invoker)self.model=modelself.attr=attrself.patches=patches
[docs]defdispatch(self,receiver:Any)->None:''' Dispatch handling of this event to a receiver. This method will invoke ``receiver._columns_patched`` if it exists. '''super().dispatch(receiver)ifhasattr(receiver,'_columns_patched'):cast(ColumnsPatchedMixin,receiver)._columns_patched(self)
[docs]defto_serializable(self,serializer:Serializer)->ColumnsPatched:''' Create a JSON representation of this event suitable for sending to clients. .. code-block:: python { 'kind' : 'ColumnsPatched' 'column_source' : <reference to a CDS> 'patches' : <patches to apply to column_source> } Args: serializer (Serializer): '''returnColumnsPatched(kind=self.kind,model=self.model.ref,attr=self.attr,patches=serializer.encode(self.patches),)
[docs]classTitleChangedEvent(DocumentPatchedEvent):''' A concrete event representing a change to the title of a Bokeh Document. '''kind="TitleChanged"
[docs]def__init__(self,document:Document,title:str,setter:Setter|None=None,callback_invoker:Invoker|None=None):''' Args: document (Document) : A Bokeh document that is to be updated. title (str) : The new title to set on the Document setter (ClientSession or ServerSession or None, optional) : This is used to prevent "boomerang" updates to Bokeh apps. (default: None) See :class:`~bokeh.document.events.DocumentChangedEvent` for more details. callback_invoker (callable, optional) : A callable that will invoke any Model callbacks that should be executed in response to the change that triggered this event. (default: None) '''super().__init__(document,setter,callback_invoker)self.title=title
[docs]defcombine(self,event:DocumentChangedEvent)->bool:''' '''ifnotisinstance(event,TitleChangedEvent):returnFalse# If these are not true something weird is going on, maybe updates from# Python bokeh.client, don't try to combineifself.setter!=event.setter:returnFalseifself.document!=event.document:returnFalseself.title=event.titleself.callback_invoker=event.callback_invokerreturnTrue
[docs]defto_serializable(self,serializer:Serializer)->TitleChanged:''' Create a JSON representation of this event suitable for sending to clients. .. code-block:: python { 'kind' : 'TitleChanged' 'title' : <new title to set> } Args: serializer (Serializer): '''returnTitleChanged(kind=self.kind,title=self.title,)
[docs]classRootAddedEvent(DocumentPatchedEvent):''' A concrete event representing a change to add a new Model to a Document's collection of "root" models. '''kind="RootAdded"
[docs]def__init__(self,document:Document,model:Model,setter:Setter|None=None,callback_invoker:Invoker|None=None)->None:''' Args: document (Document) : A Bokeh document that is to be updated. model (Model) : The Bokeh Model to add as a Document root. setter (ClientSession or ServerSession or None, optional) : This is used to prevent "boomerang" updates to Bokeh apps. (default: None) See :class:`~bokeh.document.events.DocumentChangedEvent` for more details. callback_invoker (callable, optional) : A callable that will invoke any Model callbacks that should be executed in response to the change that triggered this event. (default: None) '''super().__init__(document,setter,callback_invoker)self.model=model
[docs]defto_serializable(self,serializer:Serializer)->RootAdded:''' Create a JSON representation of this event suitable for sending to clients. .. code-block:: python { 'kind' : 'RootAdded' 'title' : <reference to a Model> } Args: serializer (Serializer): '''returnRootAdded(kind=self.kind,model=serializer.encode(self.model),)
[docs]classRootRemovedEvent(DocumentPatchedEvent):''' A concrete event representing a change to remove an existing Model from a Document's collection of "root" models. '''kind="RootRemoved"
[docs]def__init__(self,document:Document,model:Model,setter:Setter|None=None,callback_invoker:Invoker|None=None)->None:''' Args: document (Document) : A Bokeh document that is to be updated. model (Model) : The Bokeh Model to remove as a Document root. setter (ClientSession or ServerSession or None, optional) : This is used to prevent "boomerang" updates to Bokeh apps. (default: None) See :class:`~bokeh.document.events.DocumentChangedEvent` for more details. callback_invoker (callable, optional) : A callable that will invoke any Model callbacks that should be executed in response to the change that triggered this event. (default: None) '''super().__init__(document,setter,callback_invoker)self.model=model
[docs]defto_serializable(self,serializer:Serializer)->RootRemoved:''' Create a JSON representation of this event suitable for sending to clients. .. code-block:: python { 'kind' : 'RootRemoved' 'title' : <reference to a Model> } Args: serializer (Serializer): '''returnRootRemoved(kind=self.kind,model=self.model.ref,)
[docs]classSessionCallbackAdded(DocumentChangedEvent):''' A concrete event representing a change to add a new callback (e.g. periodic, timeout, or "next tick") to a Document. '''
[docs]def__init__(self,document:Document,callback:SessionCallback)->None:''' Args: document (Document) : A Bokeh document that is to be updated. callback (SessionCallback) : The callback to add '''super().__init__(document)self.callback=callback
[docs]defdispatch(self,receiver:Any)->None:''' Dispatch handling of this event to a receiver. This method will invoke ``receiver._session_callback_added`` if it exists. '''super().dispatch(receiver)ifhasattr(receiver,'_session_callback_added'):cast(SessionCallbackAddedMixin,receiver)._session_callback_added(self)
[docs]classSessionCallbackRemoved(DocumentChangedEvent):''' A concrete event representing a change to remove an existing callback (e.g. periodic, timeout, or "next tick") from a Document. '''
[docs]def__init__(self,document:Document,callback:SessionCallback)->None:''' Args: document (Document) : A Bokeh document that is to be updated. callback (SessionCallback) : The callback to remove '''super().__init__(document)self.callback=callback
[docs]defdispatch(self,receiver:Any)->None:''' Dispatch handling of this event to a receiver. This method will invoke ``receiver._session_callback_removed`` if it exists. '''super().dispatch(receiver)ifhasattr(receiver,'_session_callback_removed'):cast(SessionCallbackRemovedMixin,receiver)._session_callback_removed(self)