Source code for bokeh.model.util

#-----------------------------------------------------------------------------
# Copyright (c) 2012 - 2023, Anaconda, Inc., and Bokeh Contributors.
# All rights reserved.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------
''' Provide a base class for all objects (called Bokeh Models) that can go in
a Bokeh |Document|.

'''
#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
from __future__ import annotations

import logging # isort:skip
log = logging.getLogger(__name__)

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

# Standard library imports
from typing import TYPE_CHECKING, Any, Callable

# Bokeh imports
from ..core.has_props import HasProps, Qualified

if TYPE_CHECKING:
    from ..core.types import ID
    from ..document import Document
    from .model import Model

#-----------------------------------------------------------------------------
# Globals and constants
#-----------------------------------------------------------------------------

__all__ = (
    'HasDocumentRef',
    'Qualified', # XXX: for backwards compatibility
    'collect_filtered_models',
    'collect_models',
    'get_class',
    'visit_immediate_value_references',
    'visit_value_and_its_immediate_references',
)

#-----------------------------------------------------------------------------
# General API
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Dev API
#-----------------------------------------------------------------------------

class HasDocumentRef:

    _document: Document | None
    _temp_document: Document | None

    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self._document = None
        self._temp_document = None

    @property
    def document(self) -> Document | None:
        ''' The |Document| this model is attached to (can be ``None``)

        '''
        if self._temp_document is not None:
            return self._temp_document
        return self._document

    @document.setter
    def document(self, doc: Document) -> None:
        self._document = doc

def collect_filtered_models(discard: Callable[[Model], bool] | None, *input_values: Any) -> list[Model]:
    ''' Collect a duplicate-free list of all other Bokeh models referred to by
    this model, or by any of its references, etc, unless filtered-out by the
    provided callable.

    Iterate over ``input_values`` and descend through their structure
    collecting all nested ``Models`` on the go.

    Args:
        *discard (Callable[[Model], bool])
            a callable which accepts a *Model* instance as its single argument
            and returns a boolean stating whether to discard the instance. The
            latter means that the instance will not be added to collected
            models nor will its references be explored.

        *input_values (Model)
            Bokeh models to collect other models from

    Returns:
        list(Model)

    '''

    ids: set[ID] = set()
    collected: list[Model] = []
    queued: list[Model] = []

    def queue_one(obj: Model) -> None:
        if obj.id not in ids and not (callable(discard) and discard(obj)):
            queued.append(obj)

    for value in input_values:
        visit_value_and_its_immediate_references(value, queue_one)

    while queued:
        obj = queued.pop(0)
        if obj.id not in ids:
            ids.add(obj.id)
            collected.append(obj)
            visit_immediate_value_references(obj, queue_one)

    return collected

[docs]def collect_models(*input_values: Any) -> list[Model]: ''' Collect a duplicate-free list of all other Bokeh models referred to by this model, or by any of its references, etc. Iterate over ``input_values`` and descend through their structure collecting all nested ``Models`` on the go. The resulting list is duplicate-free based on objects' identifiers. Args: *input_values (Model) Bokeh models to collect other models from Returns: list[Model] : all models reachable from this one. ''' return collect_filtered_models(None, *input_values)
[docs]def get_class(view_model_name: str) -> type[Model]: ''' Look up a Bokeh model class, given its view model name. Args: view_model_name (str) : A view model name for a Bokeh model to look up Returns: Model: the model class corresponding to ``view_model_name`` Raises: KeyError, if the model cannot be found Example: .. code-block:: python >>> from bokeh.model import get_class >>> get_class("Range1d") <class 'bokeh.models.ranges.Range1d'> ''' # In order to look up from the model catalog that Model maintains, it # has to be created first. These imports ensure that all built-in Bokeh # models are represented in the catalog. from .. import models # noqa: F401 from .. import plotting # noqa: F401 from .model import Model known_models = Model.model_class_reverse_map if view_model_name in known_models: return known_models[view_model_name] else: raise KeyError(f"View model name '{view_model_name}' not found")
def visit_immediate_value_references(value: Any, visitor: Callable[[Model], None]) -> None: ''' Visit all references to another Model without recursing into any of the child Model; may visit the same Model more than once if it's referenced more than once. Does not visit the passed-in value. ''' if isinstance(value, HasProps): for attr in value.properties_with_refs(): child = getattr(value, attr) visit_value_and_its_immediate_references(child, visitor) else: visit_value_and_its_immediate_references(value, visitor) def visit_value_and_its_immediate_references(obj: Any, visitor: Callable[[Model], None]) -> None: ''' Visit Models, HasProps, and Python containers. Recurses down HasProps references and Python containers (does not recurse down Model subclasses). The ordering in this function is to optimize performance. We check the most comomn types (int, float, str) first so that we can quickly return in the common case. We avoid isinstance and issubclass checks in a couple places with `type` checks because isinstance checks can be slow. ''' from .model import Model typ = type(obj) if typ in {int, float, str}: # short circuit on common scalar types return if typ is list or issubclass(typ, (list, tuple)): # check common containers for item in obj: visit_value_and_its_immediate_references(item, visitor) elif issubclass(typ, dict): for key, value in obj.items(): visit_value_and_its_immediate_references(key, visitor) visit_value_and_its_immediate_references(value, visitor) elif issubclass(typ, HasProps): if issubclass(typ, Model): visitor(obj) else: # this isn't a Model, so recurse into it visit_immediate_value_references(obj, visitor) #----------------------------------------------------------------------------- # Private API #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Code #-----------------------------------------------------------------------------