Skip to content
Open
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
24 changes: 21 additions & 3 deletions src/bokeh/document/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@
from __future__ import annotations

import logging # isort:skip
from bokeh.themes import Theme, default as default_theme
from bokeh.core.has_props import is_DataModel
from bokeh.core.serialization import Serializer
from bokeh.core.templates import FILE
from bokeh.document.callbacks import DocumentCallbackManager
from bokeh.document.config import DocumentConfig
from bokeh.document.json import DocJson
from bokeh.document.models import DocumentModelManager
from bokeh.document.modules import DocumentModuleManager
from bokeh.model import Model
from bokeh.util.version import __version__

log = logging.getLogger(__name__)

#-----------------------------------------------------------------------------
Expand Down Expand Up @@ -755,16 +767,22 @@ def set_title(self, title: str, setter: Setter | None = None) -> None:
self.callbacks.trigger_on_change(TitleChangedEvent(self, title, setter))

def to_json(self, *, deferred: bool = True) -> DocJson:
''' Convert this document to a JSON-serializable object.
""" Convert this document to a JSON-serializable object.


Return:
DocJson

'''
"""
from ..model import Model
from .json import DocJson

data_models = [ model for model in Model.model_class_reverse_map.values() if is_DataModel(model) ]
# Optimization: Use generator and cache list only if needed
data_models = []
for model in Model.model_class_reverse_map.values():
if is_DataModel(model):
data_models.append(model)


serializer = Serializer(deferred=deferred)
defs = serializer.encode(data_models)
Expand Down
46 changes: 27 additions & 19 deletions src/bokeh/embed/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
from __future__ import annotations

import logging # isort:skip
from bokeh.core.types import ID
from bokeh.document.document import DocJson, Document
from bokeh.model import Model, collect_models
from bokeh.util.serialization import make_globally_unique_css_safe_id, make_globally_unique_id

log = logging.getLogger(__name__)

#-----------------------------------------------------------------------------
Expand Down Expand Up @@ -293,9 +298,9 @@ def standalone_docs_json(models: Sequence[Model | Document]) -> dict[ID, DocJson

def standalone_docs_json_and_render_items(models: Model | Document | Sequence[Model | Document], *,
suppress_callback_warning: bool = False) -> tuple[dict[ID, DocJson], list[RenderItem]]:
'''
"""

'''
"""
if isinstance(models, (Model, Document)):
models = [models]

Expand All @@ -317,38 +322,41 @@ def standalone_docs_json_and_render_items(models: Model | Document | Sequence[Mo
if doc is None:
raise ValueError("A Bokeh Model must be part of a Document to render as standalone content")

if doc not in docs:
docs[doc] = (make_globally_unique_id(), dict())
# Use setdefault to avoid repeated key checking
doc_tuple = docs.setdefault(doc, (make_globally_unique_id(), {}))
(docid, roots) = doc_tuple

(docid, roots) = docs[doc]

if model is not None:
roots[model] = make_globally_unique_css_safe_id()
else:
for model in doc.roots:
roots[model] = make_globally_unique_css_safe_id()
# Use direct assignment for each doc root
for root_model in doc.roots:
roots[root_model] = make_globally_unique_css_safe_id()


docs_json: dict[ID, DocJson] = {}
for doc, (docid, _) in docs.items():
docs_json[docid] = doc.to_json(deferred=False)

render_items: list[RenderItem] = []
for _, (docid, roots) in docs.items():
render_items.append(RenderItem(docid, roots=roots))
# Preallocate render_items for all docs in one pass
render_items: list[RenderItem] = [
RenderItem(docid, roots=roots)
for _, (docid, roots) in docs.items()
]


return (docs_json, render_items)

def submodel_has_python_callbacks(models: Sequence[Model | Document]) -> bool:
''' Traverses submodels to check for Python (event) callbacks
""" Traverses submodels to check for Python (event) callbacks

'''
has_python_callback = False
for model in collect_models(models):
if len(model._callbacks) > 0 or len(model._event_callbacks) > 0:
has_python_callback = True
break

return has_python_callback
"""
# Use any() with generator for short-circuit early-out
return any(
(len(model._callbacks) > 0 or len(model._event_callbacks) > 0)
for model in collect_models(models)
)

def is_tex_string(text: str) -> bool:
''' Whether a string begins and ends with MathJax default delimiters
Expand Down
28 changes: 15 additions & 13 deletions src/bokeh/util/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from __future__ import annotations

import logging # isort:skip
from bokeh.core.types import ID

log = logging.getLogger(__name__)

#-----------------------------------------------------------------------------
Expand Down Expand Up @@ -270,21 +272,22 @@ def make_id() -> ID:
return make_globally_unique_id()

def make_globally_unique_id() -> ID:
''' Return a globally unique UUID.
""" Return a globally unique UUID.


Some situations, e.g. id'ing dynamically created Divs in HTML documents,
always require globally unique IDs.

Returns:
str

'''
from ..core.types import ID
"""

return ID(str(uuid.uuid4()))

def make_globally_unique_css_safe_id() -> ID:
''' Return a globally unique CSS-safe UUID.
""" Return a globally unique CSS-safe UUID.


Some situations, e.g. id'ing dynamically created Divs in HTML documents,
always require globally unique IDs. ID generated with this function can
Expand All @@ -293,17 +296,16 @@ def make_globally_unique_css_safe_id() -> ID:
Returns:
str

'''
from ..core.types import ID
"""

max_iter = 100

for _i in range(0, max_iter):
id = make_globally_unique_id()
if id[0].isalpha():
return id

return ID(f"bk-{make_globally_unique_id()}")
for _ in range(max_iter):
id_str = str(uuid.uuid4())
# The first char of standard UUIDs is always hex digit; so pre-pend
# alpha if necessary. Instead, start with 'bk-' if failed.
if id_str[0].isalpha():
return ID(id_str)
return ID(f"bk-{str(uuid.uuid4())}")

def array_encoding_disabled(array: npt.NDArray[Any]) -> bool:
''' Determine whether an array may be binary encoded.
Expand Down