Test whether a given Bokeh model matches a given selector.

    Args:
        obj (Model) : object to test
        selector (JSON-like) : query selector
        context (dict) : kwargs to supply callable query attributes

    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(plot_width=400)

        >>> match(p, {'plot_width': {EQ: 400}})
        True

        >>> match(p, {'plot_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

def match(obj, selector, context=None):
    ''' Test whether a given Bokeh model matches a given selector.

    Args:
        obj (Model) : object to test
        selector (JSON-like) : query selector
        context (dict) : kwargs to supply callable query attributes

    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(plot_width=400)

        >>> match(p, {'plot_width': {EQ: 400}})
        True

        >>> match(p, {'plot_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

    '''
    context = context or {}
    for key, val in selector.items():

        # test attributes
        if isinstance(key, string_types):

            # special case 'type'
            if key == "type":
                # type supports IN, check for that first
                if isinstance(val, dict) and list(val.keys()) == [IN]:
                    if not any(isinstance(obj, x) for x in val[IN]): return False
                # otherwise just check the type of the object against val
                elif not isinstance(obj, val): return False

            # special case 'tag'
            elif key == 'tags':
                if isinstance(val, string_types):
                    if val not in obj.tags: return False
                else:
                    try:
                        if not set(val) & set(obj.tags): return False
                    except TypeError:
                        if val not in obj.tags: return False

            # if the object doesn't have the attr, it doesn't match
            elif not hasattr(obj, key): return False

            # if the value to check is a dict, recurse
            else:
                attr = getattr(obj, key)
                if callable(attr):
                    try:
                        if not attr(val, **context): return False
                    except:
                        return False

                elif isinstance(val, dict):
                    if not match(attr, val, context): return False

                else:
                    if attr != val: return False

        # test OR conditionals
        elif key is OR:
            if not _or(obj, val): return False

        # test operands
        elif key in _operators:
            if not _operators[key](obj, val): return False

        else:
            raise ValueError("malformed query selector")

    return True