#-----------------------------------------------------------------------------# Copyright (c) Anaconda, Inc., and Bokeh Contributors.# All rights reserved.## The full license is in the file LICENSE.txt, distributed with this software.#-----------------------------------------------------------------------------''' The query module provides functions for searching collections of Bokehmodels for instances that match specified criteria.'''#-----------------------------------------------------------------------------# Boilerplate#-----------------------------------------------------------------------------from__future__importannotationsimportlogging# isort:skiplog=logging.getLogger(__name__)#-----------------------------------------------------------------------------# Imports#-----------------------------------------------------------------------------# Standard library importsfromtypingimport(Any,Callable,Iterable,TypeAlias,)# Bokeh importsfrom..modelimportModel#-----------------------------------------------------------------------------# Globals and constants#-----------------------------------------------------------------------------__all__=('EQ','find','GEQ','GT','IN','LEQ','LT','NEQ','OR','find','match','is_single_string_selector',)SelectorType:TypeAlias=dict[str|type["_Operator"],Any]#-----------------------------------------------------------------------------# General API#-----------------------------------------------------------------------------
[docs]deffind(objs:Iterable[Model],selector:SelectorType)->Iterable[Model]:''' Query a collection of Bokeh models and yield any that match the a selector. Args: objs (Iterable[Model]) : model objects to test selector (JSON-like) : query selector Yields: Model : objects that match the query Queries are specified as selectors similar to MongoDB style query selectors, as described for :func:`~bokeh.core.query.match`. Examples: .. code-block:: python # find all objects with type Grid find(p.references(), {'type': Grid}) # find all objects with type Grid or Axis find(p.references(), {OR: [ {'type': Grid}, {'type': Axis} ]}) # same query, using IN operator find(p.references(), {'type': {IN: [Grid, Axis]}}) '''return(objforobjinobjsifmatch(obj,selector))
[docs]defis_single_string_selector(selector:SelectorType,field:str)->bool:''' Whether a selector is a simple single field, e.g. ``{name: "foo"}`` Args: selector (JSON-like) : query selector field (str) : field name to check for Returns bool '''returnlen(selector)==1andfieldinselectorandisinstance(selector[field],str)
[docs]defmatch(obj:Model,selector:SelectorType)->bool:''' Test whether a given Bokeh model matches a given selector. Args: obj (Model) : object to test selector (JSON-like) : query selector Returns: bool : True if the object matches, False otherwise In general, the selectors have the form: .. code-block:: python { attrname : predicate } Where a predicate is constructed from the operators ``EQ``, ``GT``, etc. and is used to compare against values of model attributes named ``attrname``. For example: .. code-block:: python >>> from bokeh.plotting import figure >>> p = figure(width=400) >>> match(p, {'width': {EQ: 400}}) True >>> match(p, {'width': {GT: 500}}) False There are two selector keys that are handled especially. The first is 'type', which will do an isinstance check: .. code-block:: python >>> from bokeh.plotting import figure >>> from bokeh.models import Axis >>> p = figure() >>> match(p.xaxis[0], {'type': Axis}) True >>> match(p.title, {'type': Axis}) False There is also a ``'tags'`` attribute that ``Model`` objects have, that is a list of user-supplied values. The ``'tags'`` selector key can be used to query against this list of tags. An object matches if any of the tags in the selector match any of the tags on the object: .. code-block:: python >>> from bokeh.plotting import figure >>> p = figure(tags = ["my plot", 10]) >>> match(p, {'tags': "my plot"}) True >>> match(p, {'tags': ["my plot", 10]}) True >>> match(p, {'tags': ["foo"]}) False '''forkey,valinselector.items():# test attributesifisinstance(key,str):# special case 'type'ifkey=="type":# type supports IN, check for that firstifisinstance(val,dict)andlist(val.keys())==[IN]:ifnotany(isinstance(obj,x)forxinval[IN]):returnFalse# otherwise just check the type of the object against valelifnotisinstance(obj,val):returnFalse# special case 'tag'elifkey=='tags':ifisinstance(val,str):ifvalnotinobj.tags:returnFalseelse:try:ifnotset(val)&set(obj.tags):returnFalseexceptTypeError:ifvalnotinobj.tags:returnFalse# if the object doesn't have the attr, it doesn't matchelifnothasattr(obj,key):returnFalse# if the value to check is a dict, recurseelse:attr=getattr(obj,key)ifisinstance(val,dict):ifnotmatch(attr,val):returnFalseelse:ifattr!=val:returnFalse# test OR conditionalselifkeyisOR:ifnot_or(obj,val):returnFalse# test operandselifkeyin_operators:ifnot_operators[key](obj,val):returnFalseelse:raiseValueError("malformed query selector")returnTrue
#-----------------------------------------------------------------------------# Dev API#-----------------------------------------------------------------------------class_Operator:pass
[docs]classOR(_Operator):''' Form disjunctions from other query predicates. Construct an ``OR`` expression by making a dict with ``OR`` as the key, and a list of other query expressions as the value: .. code-block:: python # matches any Axis subclasses or models with .name == "mycircle" { OR: [dict(type=Axis), dict(name="mycircle")] } '''pass
[docs]classIN(_Operator):''' Predicate to test if property values are in some collection. Construct and ``IN`` predicate as a dict with ``IN`` as the key, and a list of values to check against. .. code-block:: python # matches any models with .name in ['a', 'mycircle', 'myline'] dict(name={ IN: ['a', 'mycircle', 'myline'] }) '''pass
[docs]classGT(_Operator):''' Predicate to test if property values are greater than some value. Construct and ``GT`` predicate as a dict with ``GT`` as the key, and a value to compare against. .. code-block:: python # matches any models with .size > 10 dict(size={ GT: 10 }) '''pass
[docs]classLT(_Operator):''' Predicate to test if property values are less than some value. Construct and ``LT`` predicate as a dict with ``LT`` as the key, and a value to compare against. .. code-block:: python # matches any models with .size < 10 dict(size={ LT: 10 }) '''pass
[docs]classEQ(_Operator):''' Predicate to test if property values are equal to some value. Construct and ``EQ`` predicate as a dict with ``EQ`` as the key, and a value to compare against. .. code-block:: python # matches any models with .size == 10 dict(size={ EQ: 10 }) '''pass
[docs]classGEQ(_Operator):''' Predicate to test if property values are greater than or equal to some value. Construct and ``GEQ`` predicate as a dict with ``GEQ`` as the key, and a value to compare against. .. code-block:: python # matches any models with .size >= 10 dict(size={ GEQ: 10 }) '''pass
[docs]classLEQ(_Operator):''' Predicate to test if property values are less than or equal to some value. Construct and ``LEQ`` predicate as a dict with ``LEQ`` as the key, and a value to compare against. .. code-block:: python # matches any models with .size <= 10 dict(size={ LEQ: 10 }) '''pass
[docs]classNEQ(_Operator):''' Predicate to test if property values are unequal to some value. Construct and ``NEQ`` predicate as a dict with ``NEQ`` as the key, and a value to compare against. .. code-block:: python # matches any models with .size != 10 dict(size={ NEQ: 10 }) '''pass
#-----------------------------------------------------------------------------# Private API#-----------------------------------------------------------------------------# realizations of the abstract predicate operators_operators:dict[type[_Operator],Callable[[Any,Any],Any]]={IN:lambdax,y:xiny,GT:lambdax,y:x>y,LT:lambdax,y:x<y,EQ:lambdax,y:x==y,GEQ:lambdax,y:x>=y,LEQ:lambdax,y:x<=y,NEQ:lambdax,y:x!=y,}# realization of the OR operatordef_or(obj:Model,selectors:Iterable[SelectorType])->bool:returnany(match(obj,selector)forselectorinselectors)#-----------------------------------------------------------------------------# Code#-----------------------------------------------------------------------------