示例#1
0
def bkapp_command():
    with pull_session(session_id='1234567890',
                      url="http://localhost:5006/myapp") as session:
        session.request_server_info()
        cur_doc = session.document
        button = cur_doc.get_model_by_name('button_text')
        cur_doc.remove_root(button)
        import json
        from bokeh.events import Event
        data = '{"event_name": "document_update", "event_values" : {"x": 10, "y": 20, "sx": 200, "sy": 37}}'
        json.loads(data)
        event = json.loads(data, object_hook=Event.decode_json)
        event = json.loads(data)
        cur_doc.apply_json_event(event)
        from bokeh.protocol import Protocol
        # from bokeh.events import DocumentUpdate
        # document_update_event = DocumentUpdate()
        # protocol = Protocol()
        # message = protocol.create("PATCH-DOC", [document_update_event])
        # message.apply_to_document(cur_doc)
        #session._connection.send_message(message)

        from bokeh.document.events import MessageSentEvent
        document_patched_event = MessageSentEvent(
            document=cur_doc,
            msg_type='append_dataset',
            msg_data='append_dataset_data')
        protocol = Protocol()
        message = protocol.create("PATCH-DOC", [document_patched_event])
        # message.apply_to_document(cur_doc)
        session._connection.send_message(message)

    return ''
示例#2
0
 def test_create_then_parse(self):
     sample = self._sample_doc()
     msg = Protocol("1.0").create("PUSH-DOC", sample)
     copy = document.Document()
     msg.push_to_document(copy)
     assert len(sample.roots) == 2
     assert len(copy.roots) == 2
示例#3
0
def patch_event(image):
    """
    Generates a bokeh patch event message given an InteractiveImage
    instance. Uses the bokeh messaging protocol for bokeh>=0.12.10
    and a custom patch for previous versions.

    Parameters
    ----------
    image: InteractiveImage
        InteractiveImage instance with a plot

    Returns
    -------
    msg: str
        JSON message containing patch events to update the plot
    """
    if bokeh_version > '0.12.9':
        event_obj = image.doc.callbacks if bokeh_version >= '2.4' else image.doc
        events = list(event_obj._held_events)
        if not events:
            return None
        if bokeh_version > '2.0.0':
            protocol = Protocol()
        else:
            protocol = Protocol("1.0")
        msg = protocol.create("PATCH-DOC", events)
        event_obj._held_events = []
        return msg
    data = dict(image.ds.data)
    data['image'] = [data['image'][0].tolist()]
    return json.dumps({'events': [{'attr': u'data',
                                   'kind': 'ModelChanged',
                                   'model': image.ds.ref,
                                   'new': data}],
                       'references': []})
 def test_create_then_parse(self):
     sample = self._sample_doc()
     msg = Protocol("1.0").create("PUSH-DOC", sample)
     copy = document.Document()
     msg.push_to_document(copy)
     assert len(sample.roots) == 2
     assert len(copy.roots) == 2
示例#5
0
 def test_create_reply_then_parse(self):
     sample = self._sample_doc()
     msg = Protocol("1.0").create("PULL-DOC-REPLY", 'fakereqid', sample)
     copy = document.Document()
     msg.push_to_document(copy)
     assert len(sample.roots) == 2
     assert len(copy.roots) == 2
示例#6
0
    async def open(self, path, *args, **kwargs):
        _, context = _APPS[path]

        token = self._token
        if self.selected_subprotocol != 'bokeh':
            self.close()
            raise ProtocolError("Subprotocol header is not 'bokeh'")
        elif token is None:
            self.close()
            raise ProtocolError("No token received in subprotocol header")

        session_id = get_session_id(token)

        await context.create_session_if_needed(session_id, self.request, token)
        session = context.get_session(session_id)

        try:
            protocol = Protocol()
            self.receiver = Receiver(protocol)
            self.handler = ProtocolHandler()
            self.connection = self.new_connection(protocol, context, session)
        except ProtocolError as e:
            self.close()
            raise e

        msg = self.connection.protocol.create('ACK')
        await self.send_message(msg)
示例#7
0
文件: consumers.py 项目: afcarl/bokeh
    async def _async_open(self, session_id: str) -> None:
        try:
            await self.application_context.create_session_if_needed(
                session_id, self.request)
            session = self.application_context.get_session(session_id)

            protocol = Protocol()
            self.receiver = Receiver(protocol)
            log.debug("Receiver created for %r", protocol)

            self.handler = ProtocolHandler()
            log.debug("ProtocolHandler created for %r", protocol)

            self.connection = self._new_connection(protocol, self,
                                                   self.application_context,
                                                   session)
            log.info("ServerConnection created")

        except Exception as e:
            log.error("Could not create new server session, reason: %s", e)
            self.close()
            raise e

        msg = self.connection.protocol.create('ACK')
        await self._send_bokeh_message(msg)
示例#8
0
    def __init__(self, session, websocket_url, io_loop=None):
        ''' Opens a websocket connection to the server.

        '''
        self._url = websocket_url
        self._session = session
        self._protocol = Protocol("1.0")
        self._receiver = Receiver(self._protocol)
        self._socket = None
        self._state = NOT_YET_CONNECTED()
        if io_loop is None:
            # We can't use IOLoop.current because then we break
            # when running inside a notebook since ipython also uses it
            io_loop = IOLoop()
        self._loop = io_loop
        self._until_predicate = None
        self._protocol = Protocol("1.0")
        self._server_info = None
示例#9
0
 def __init__(self, session, websocket_url, io_loop=None):
     '''
       Opens a websocket connection to the server.
     '''
     self._url = websocket_url
     self._session = session
     self._protocol = Protocol("1.0")
     self._receiver = Receiver(self._protocol)
     self._socket = None
     self._state = self.NOT_YET_CONNECTED()
     if io_loop is None:
         # We can't use IOLoop.current because then we break
         # when running inside a notebook since ipython also uses it
         io_loop = IOLoop()
     self._loop = io_loop
     self._until_predicate = None
     self._protocol = Protocol("1.0")
     self._server_info = None
示例#10
0
    def test_create_multiple_docs(self):
        sample1 = self._sample_doc()
        obj1 = next(iter(sample1.roots))
        event1 = ModelChangedEvent(sample1, obj1, 'foo', obj1.foo, 42, 42)

        sample2 = self._sample_doc()
        obj2 = next(iter(sample2.roots))
        event2 = ModelChangedEvent(sample2, obj2, 'foo', obj2.foo, 42, 42)
        with pytest.raises(ValueError):
            Protocol("1.0").create("PATCH-DOC", [event1, event2])
示例#11
0
def diff(doc, binary=True, events=None):
    """
    Returns a json diff required to update an existing plot with
    the latest plot data.
    """
    events = list(doc._held_events) if events is None else events
    if not events:
        return None
    msg = Protocol("1.0").create("PATCH-DOC", events, use_buffers=binary)
    doc._held_events = [e for e in doc._held_events if e not in events]
    return msg
示例#12
0
    def _document_patched(self, event):
        if event.setter is self:
            return
        msg = Protocol().create("PATCH-DOC", [event])

        self.send({"msg": "patch", "payload": msg.header_json})
        self.send({"msg": "patch", "payload": msg.metadata_json})
        self.send({"msg": "patch", "payload": msg.content_json})
        for header, buffer in msg.buffers:
            self.send({"msg": "patch", "payload": json.dumps(header)})
            self.send({"msg": "patch"}, [buffer])
示例#13
0
 def diff(self, plot, binary=True):
     """
     Returns a json diff required to update an existing plot with
     the latest plot data.
     """
     events = list(plot.document._held_events)
     if not events:
         return None
     msg = Protocol("1.0").create("PATCH-DOC", events, use_buffers=binary)
     plot.document._held_events = []
     return msg
示例#14
0
    def test_create_then_apply_model_changed(self):
        sample = self._sample_doc()

        foos = []
        for r in sample.roots:
            foos.append(r.foo)
        assert foos == [ 2, 2 ]

        obj = next(iter(sample.roots))
        assert obj.foo == 2
        event = ModelChangedEvent(sample, obj, 'foo', obj.foo, 42, 42)
        msg = Protocol("1.0").create("PATCH-DOC", [event])

        copy = document.Document.from_json_string(sample.to_json_string())
        msg.apply_to_document(copy)

        foos = []
        for r in copy.roots:
            foos.append(r.foo)
        foos.sort()
        assert foos == [ 2, 42 ]
示例#15
0
 def _send_notebook_diff(self):
     events = list(self.document._held_events)
     msg = Protocol("1.0").create("PATCH-DOC", events, use_buffers=True)
     self.document._held_events = []
     if msg is None:
         return
     self.server_comm.send(msg.header_json)
     self.server_comm.send(msg.metadata_json)
     self.server_comm.send(msg.content_json)
     for header, payload in msg.buffers:
         self.server_comm.send(json.dumps(header))
         self.server_comm.send(buffers=[payload])
示例#16
0
    def diff(self, plot, binary=True, individual=False):
        """
        Returns a json diff required to update an existing plot with
        the latest plot data.
        """
        events = list(plot.document._held_events)
        if not events:
            return None

        if individual:
            msgs = []
            for event in events:
                msg = Protocol("1.0").create("PATCH-DOC", [event],
                                             use_buffers=binary)
                msgs.append(msg)
        else:
            msgs = Protocol("1.0").create("PATCH-DOC",
                                          events,
                                          use_buffers=binary)
        plot.document._held_events = []
        return msgs
示例#17
0
    def test_create_then_apply_model_changed(self):
        sample = self._sample_doc()

        foos = []
        for r in sample.roots:
            foos.append(r.foo)
        assert foos == [2, 2]

        obj = next(iter(sample.roots))
        assert obj.foo == 2
        event = ModelChangedEvent(sample, obj, 'foo', obj.foo, 42, 42)
        msg = Protocol("1.0").create("PATCH-DOC", [event])

        copy = document.Document.from_json_string(sample.to_json_string())
        msg.apply_to_document(copy)

        foos = []
        for r in copy.roots:
            foos.append(r.foo)
        foos.sort()
        assert foos == [2, 42]
示例#18
0
文件: model.py 项目: syamajala/panel
def diff(doc, binary=True, events=None):
    """
    Returns a json diff required to update an existing plot with
    the latest plot data.
    """
    events = list(doc._held_events) if events is None else events
    if not events or state._hold:
        return None

    # Patch ColumnDataChangedEvents which reference non-existing columns
    for e in events:
        if (hasattr(e, 'hint') and isinstance(e.hint, ColumnDataChangedEvent)
            and e.hint.cols is not None):
            e.hint.cols = None
    msg = Protocol().create("PATCH-DOC", events, use_buffers=binary)
    doc._held_events = [e for e in doc._held_events if e not in events]
    return msg
示例#19
0
 def diff(self, plot, serialize=True, binary=False):
     """
     Returns a json diff required to update an existing plot with
     the latest plot data.
     """
     if binary:
         events = list(plot.document._held_events)
         if not events:
             return None
         msg = Protocol("1.0").create("PATCH-DOC", events)
         plot.document._held_events = []
         return msg
     else:
         plotobjects = [h for handles in plot.traverse(lambda x: x.current_handles, [lambda x: x._updated])
                        for h in handles]
         plot.traverse(lambda x: setattr(x, '_updated', False))
         patch = compute_static_patch(plot.document, plotobjects)
     processed = self._apply_post_render_hooks(patch, plot, 'json')
     return serialize_json(processed) if serialize else processed
示例#20
0
文件: model.py 项目: xtaje/panel
def diff(doc, binary=True, events=None):
    """
    Returns a json diff required to update an existing plot with
    the latest plot data.
    """
    events = list(doc._held_events) if events is None else events
    if not events or state._hold:
        return None

    # Filter ColumnDataChangedEvents which reference non-existing
    # columns, later event will include the changes
    fixed_events = []
    for e in events:
        if isinstance(e.hint,
                      ColumnDataChangedEvent) and e.hint.cols is not None:
            e.hint.cols = None
        fixed_events.append(e)
    msg = Protocol("1.0").create("PATCH-DOC", events, use_buffers=binary)
    doc._held_events = [e for e in doc._held_events if e not in events]
    return msg
示例#21
0
    def test_patch_event_contains_setter(self):
        sample = self._sample_doc()
        root = None
        other_root = None
        for r in sample.roots:
            if r.child is not None:
                root = r
            else:
                other_root = r
        assert root is not None
        assert other_root is not None
        new_child = AnotherModelInTestPatchDoc(bar=56)

        cds = ColumnDataSource(data={'a': [0, 1, 2]})
        sample.add_root(cds)

        mock_session = object()

        def sample_document_callback_assert(event):
            """Asserts that setter is correctly set on event"""
            assert event.setter is mock_session

        sample.on_change(sample_document_callback_assert)

        # Model property changed
        event = ModelChangedEvent(sample, root, 'child', root.child, new_child,
                                  new_child)
        msg = Protocol("1.0").create("PATCH-DOC", [event])
        msg.apply_to_document(sample, mock_session)

        # RootAdded
        event2 = RootAddedEvent(sample, root)
        msg2 = Protocol("1.0").create("PATCH-DOC", [event2])
        msg2.apply_to_document(sample, mock_session)

        # RootRemoved
        event3 = RootRemovedEvent(sample, root)
        msg3 = Protocol("1.0").create("PATCH-DOC", [event3])
        msg3.apply_to_document(sample, mock_session)

        # ColumnsStreamed
        event4 = ModelChangedEvent(sample,
                                   cds,
                                   'data',
                                   10,
                                   None,
                                   None,
                                   hint=ColumnsStreamedEvent(
                                       sample, cds, {"a": [3]}, None,
                                       mock_session))
        msg4 = Protocol("1.0").create("PATCH-DOC", [event4])
        msg4.apply_to_document(sample, mock_session)

        # ColumnsPatched
        event5 = ModelChangedEvent(sample,
                                   cds,
                                   'data',
                                   10,
                                   None,
                                   None,
                                   hint=ColumnsPatchedEvent(
                                       sample, cds, {"a": [(0, 11)]}))
        msg5 = Protocol("1.0").create("PATCH-DOC", [event5])
        msg5.apply_to_document(sample, mock_session)
示例#22
0
 def test_create_reply(self):
     sample = self._sample_doc()
     Protocol("1.0").create("PULL-DOC-REPLY", 'fakereqid', sample)
示例#23
0
 def test_create_req(self):
     Protocol("1.0").create("PULL-DOC-REQ")
 def test_create(self):
     sample = self._sample_doc()
     Protocol("1.0").create("PUSH-DOC", sample)
示例#25
0
    def test_patch_event_contains_setter(self):
        sample = self._sample_doc()
        root = None
        other_root = None
        for r in sample.roots:
            if r.child is not None:
                root = r
            else:
                other_root = r
        assert root is not None
        assert other_root is not None
        new_child = AnotherModelInTestPatchDoc(bar=56)

        cds = ColumnDataSource(data={'a': np.array([0., 1., 2.])})
        sample.add_root(cds)

        mock_session = object()
        def sample_document_callback_assert(event):
            """Asserts that setter is correctly set on event"""
            assert event.setter is mock_session
        sample.on_change(sample_document_callback_assert)

        # Model property changed
        event = ModelChangedEvent(sample, root, 'child', root.child, new_child, new_child)
        msg = Protocol("1.0").create("PATCH-DOC", [event])
        msg.apply_to_document(sample, mock_session)
        assert msg.buffers == []

        # RootAdded
        event2 = RootAddedEvent(sample, root)
        msg2 = Protocol("1.0").create("PATCH-DOC", [event2])
        msg2.apply_to_document(sample, mock_session)
        assert msg2.buffers == []

        # RootRemoved
        event3 = RootRemovedEvent(sample, root)
        msg3 = Protocol("1.0").create("PATCH-DOC", [event3])
        msg3.apply_to_document(sample, mock_session)
        assert msg3.buffers == []

        # ColumnsStreamed
        event4 = ModelChangedEvent(sample, cds, 'data', 10, None, None,
                                   hint=ColumnsStreamedEvent(sample, cds, {"a": [3]}, None, mock_session))
        msg4 = Protocol("1.0").create("PATCH-DOC", [event4])
        msg4.apply_to_document(sample, mock_session)
        assert msg4.buffers == []

        # ColumnsPatched
        event5 = ModelChangedEvent(sample, cds, 'data', 10, None, None,
                                   hint=ColumnsPatchedEvent(sample, cds, {"a": [(0, 11)]}))
        msg5 = Protocol("1.0").create("PATCH-DOC", [event5])
        msg5.apply_to_document(sample, mock_session)
        assert msg5.buffers == []

        # ColumnDataChanged, use_buffers=False
        event6 = ModelChangedEvent(sample, cds, 'data', {'a': np.array([0., 1.])}, None, None,
                                   hint=ColumnDataChangedEvent(sample, cds))
        msg6 = Protocol("1.0").create("PATCH-DOC", [event6], use_buffers=False)
        msg6.apply_to_document(sample, mock_session)
        assert msg6.buffers == []

        print(cds.data)
        # ColumnDataChanged, use_buffers=True
        event7 = ModelChangedEvent(sample, cds, 'data', {'a': np.array([0., 1.])}, None, None,
                                   hint=ColumnDataChangedEvent(sample, cds))
        msg7 = Protocol("1.0").create("PATCH-DOC", [event7])
        # can't test apply, doc not set up to *receive* binary buffers
        # msg7.apply_to_document(sample, mock_session)
        assert len(msg7.buffers) == 1
        buf = msg7.buffers.pop()
        assert len(buf) == 2
        assert isinstance(buf[0], dict)
        assert list(buf[0]) == ['id']

        # reports CDS buffer *as it is* Normally events called by setter and
        # value in local object would have been already mutated.
        assert buf[1] == np.array([11., 1., 2., 3]).tobytes()
 def __init__(self, **properties):
     super().__init__(**properties)
     self._protocol = Protocol()
示例#27
0
# External imports

# Bokeh imports
from bokeh.protocol import Protocol
from bokeh.protocol.exceptions import ValidationError
from bokeh.util.string import decode_utf8

# Module under test
from bokeh.protocol import receiver

#-----------------------------------------------------------------------------
# Setup
#-----------------------------------------------------------------------------

_proto = Protocol("1.0")

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


def test_creation():
    receiver.Receiver(None)


def test_validation_success():
    msg = _proto.create('ACK')
    r = receiver.Receiver(_proto)

    partial = r.consume(decode_utf8(msg.header_json)).result()
示例#28
0
    def test_patch_event_contains_setter(self):
        sample = self._sample_doc()
        root = None
        other_root = None
        for r in sample.roots:
            if r.child is not None:
                root = r
            else:
                other_root = r
        assert root is not None
        assert other_root is not None
        new_child = AnotherModelInTestPatchDoc(bar=56)

        cds = ColumnDataSource(data={'a': np.array([0., 1., 2.])})
        sample.add_root(cds)

        mock_session = object()

        def sample_document_callback_assert(event):
            """Asserts that setter is correctly set on event"""
            assert event.setter is mock_session

        sample.on_change(sample_document_callback_assert)

        # Model property changed
        event = ModelChangedEvent(sample, root, 'child', root.child, new_child,
                                  new_child)
        msg = Protocol("1.0").create("PATCH-DOC", [event])
        msg.apply_to_document(sample, mock_session)
        assert msg.buffers == []

        # RootAdded
        event2 = RootAddedEvent(sample, root)
        msg2 = Protocol("1.0").create("PATCH-DOC", [event2])
        msg2.apply_to_document(sample, mock_session)
        assert msg2.buffers == []

        # RootRemoved
        event3 = RootRemovedEvent(sample, root)
        msg3 = Protocol("1.0").create("PATCH-DOC", [event3])
        msg3.apply_to_document(sample, mock_session)
        assert msg3.buffers == []

        # ColumnsStreamed
        event4 = ModelChangedEvent(sample,
                                   cds,
                                   'data',
                                   10,
                                   None,
                                   None,
                                   hint=ColumnsStreamedEvent(
                                       sample, cds, {"a": [3]}, None,
                                       mock_session))
        msg4 = Protocol("1.0").create("PATCH-DOC", [event4])
        msg4.apply_to_document(sample, mock_session)
        assert msg4.buffers == []

        # ColumnsPatched
        event5 = ModelChangedEvent(sample,
                                   cds,
                                   'data',
                                   10,
                                   None,
                                   None,
                                   hint=ColumnsPatchedEvent(
                                       sample, cds, {"a": [(0, 11)]}))
        msg5 = Protocol("1.0").create("PATCH-DOC", [event5])
        msg5.apply_to_document(sample, mock_session)
        assert msg5.buffers == []

        # ColumnDataChanged, use_buffers=False
        event6 = ModelChangedEvent(sample,
                                   cds,
                                   'data', {'a': np.array([0., 1.])},
                                   None,
                                   None,
                                   hint=ColumnDataChangedEvent(sample, cds))
        msg6 = Protocol("1.0").create("PATCH-DOC", [event6], use_buffers=False)
        msg6.apply_to_document(sample, mock_session)
        assert msg6.buffers == []

        print(cds.data)
        # ColumnDataChanged, use_buffers=True
        event7 = ModelChangedEvent(sample,
                                   cds,
                                   'data', {'a': np.array([0., 1.])},
                                   None,
                                   None,
                                   hint=ColumnDataChangedEvent(sample, cds))
        msg7 = Protocol("1.0").create("PATCH-DOC", [event7])
        # can't test apply, doc not set up to *receive* binary buffers
        # msg7.apply_to_document(sample, mock_session)
        assert len(msg7.buffers) == 1
        buf = msg7.buffers.pop()
        assert len(buf) == 2
        assert isinstance(buf[0], dict)
        assert list(buf[0]) == ['id']

        # reports CDS buffer *as it is* Normally events called by setter and
        # value in local object would have been already mutated.
        assert buf[1] == np.array([11., 1., 2., 3]).tobytes()
示例#29
0
class ClientConnection(object):
    ''' A Bokeh low-level class used to implement ClientSession; use ClientSession to connect to the server.

    '''
    def __init__(self, session, websocket_url, io_loop=None):
        ''' Opens a websocket connection to the server.

        '''
        self._url = websocket_url
        self._session = session
        self._protocol = Protocol("1.0")
        self._receiver = Receiver(self._protocol)
        self._socket = None
        self._state = NOT_YET_CONNECTED()
        if io_loop is None:
            # We can't use IOLoop.current because then we break
            # when running inside a notebook since ipython also uses it
            io_loop = IOLoop()
        self._loop = io_loop
        self._until_predicate = None
        self._protocol = Protocol("1.0")
        self._server_info = None

    @property
    def url(self):
        ''' The URL of the websocket this Connection is to. '''
        return self._url

    @property
    def io_loop(self):
        ''' The Tornado ``IOLoop`` this connection is using. '''
        return self._loop

    @property
    def connected(self):
        ''' Whether we've connected the Websocket and have exchanged initial
        handshake messages.

        '''
        return isinstance(self._state, CONNECTED_AFTER_ACK)

    def connect(self):
        def connected_or_closed():
            # we should be looking at the same state here as the 'connected' property above, so connected
            # means both connected and that we did our initial message exchange
            return isinstance(self._state, CONNECTED_AFTER_ACK) or isinstance(
                self._state, DISCONNECTED)

        self._loop_until(connected_or_closed)

    def close(self, why="closed"):
        ''' Close the Websocket connection.

        '''
        if self._socket is not None:
            self._socket.close(1000, why)

    def loop_until_closed(self):
        ''' Execute a blocking loop that runs and exectutes event callbacks
        until the connection is closed (e.g. by hitting Ctrl-C).

        While this method can be used to run Bokeh application code "outside"
        the Bokeh server, this practice is HIGHLY DISCOURAGED for any real
        use case.

        '''
        if isinstance(self._state, NOT_YET_CONNECTED):
            # we don't use self._transition_to_disconnected here
            # because _transition is a coroutine
            self._tell_session_about_disconnect()
            self._state = DISCONNECTED()
        else:

            def closed():
                return isinstance(self._state, DISCONNECTED)

            self._loop_until(closed)

    @gen.coroutine
    def send_message(self, message):
        if self._socket is None:
            log.info("We're disconnected, so not sending message %r", message)
        else:
            try:
                sent = yield message.send(self._socket)
                log.debug("Sent %r [%d bytes]", message, sent)
            except WebSocketError as e:
                # A thing that happens is that we detect the
                # socket closing by getting a None from
                # read_message, but the network socket can be down
                # with many messages still in the read buffer, so
                # we'll process all those incoming messages and
                # get write errors trying to send change
                # notifications during that processing.

                # this is just debug level because it's completely normal
                # for it to happen when the socket shuts down.
                log.debug("Error sending message to server: %r", e)

                # error is almost certainly because
                # socket is already closed, but be sure,
                # because once we fail to send a message
                # we can't recover
                self.close(why="received error while sending")

                # don't re-throw the error - there's nothing to
                # do about it.

        raise gen.Return(None)

    def _send_patch_document(self, session_id, event):
        # XXX This will cause the client to always send all columns when a CDS
        # is mutated in place. Additionally we set use_buffers=False below as
        # well, to suppress using the binary array transport. Real Bokeh server
        # apps running inside a server can handle these updates much more
        # efficiently
        from bokeh.document.events import ColumnDataChangedEvent
        if hasattr(event, 'hint') and isinstance(event.hint,
                                                 ColumnDataChangedEvent):
            event.hint.cols = None
        msg = self._protocol.create('PATCH-DOC', [event], use_buffers=False)
        self.send_message(msg)

    def _send_message_wait_for_reply(self, message):
        waiter = WAITING_FOR_REPLY(message.header['msgid'])
        self._state = waiter

        send_result = []

        def message_sent(future):
            send_result.append(future)

        self._loop.add_future(self.send_message(message), message_sent)

        def have_send_result_or_disconnected():
            return len(send_result) > 0 or self._state != waiter

        self._loop_until(have_send_result_or_disconnected)

        def have_reply_or_disconnected():
            return self._state != waiter or waiter.reply is not None

        self._loop_until(have_reply_or_disconnected)

        return waiter.reply

    def push_doc(self, document):
        ''' Push a document to the server, overwriting any existing server-side doc.

        Args:
            document : (Document)
                A Document to push to the server

        Returns:
            The server reply

        '''
        msg = self._protocol.create('PUSH-DOC', document)
        reply = self._send_message_wait_for_reply(msg)
        if reply is None:
            raise RuntimeError("Connection to server was lost")
        elif reply.header['msgtype'] == 'ERROR':
            raise RuntimeError("Failed to push document: " +
                               reply.content['text'])
        else:
            return reply

    def pull_doc(self, document):
        ''' Pull a document from the server, overwriting the passed-in document

        Args:
            document : (Document)
              The document to overwrite with server content.

        Returns:
            None

        '''
        msg = self._protocol.create('PULL-DOC-REQ')
        reply = self._send_message_wait_for_reply(msg)
        if reply is None:
            raise RuntimeError("Connection to server was lost")
        elif reply.header['msgtype'] == 'ERROR':
            raise RuntimeError("Failed to pull document: " +
                               reply.content['text'])
        else:
            reply.push_to_document(document)

    def _send_request_server_info(self):
        msg = self._protocol.create('SERVER-INFO-REQ')
        reply = self._send_message_wait_for_reply(msg)
        if reply is None:
            raise RuntimeError(
                "Did not get a reply to server info request before disconnect")
        return reply.content

    def request_server_info(self):
        ''' Ask for information about the server.

        Returns:
            A dictionary of server attributes.

        '''
        if self._server_info is None:
            self._server_info = self._send_request_server_info()
        return self._server_info

    def force_roundtrip(self):
        ''' Force a round-trip request/reply to the server, sometimes needed to
        avoid race conditions. Mostly useful for testing.

        Outside of test suites, this method hurts performance and should not be
        needed.

        Returns:
           None

        '''
        self._send_request_server_info()

    def _loop_until(self, predicate):
        self._until_predicate = predicate
        try:
            # this runs self._next ONE time, but
            # self._next re-runs itself until
            # the predicate says to quit.
            self._loop.add_callback(self._next)
            self._loop.start()
        except KeyboardInterrupt:
            self.close("user interruption")

    @gen.coroutine
    def _next(self):
        if self._until_predicate is not None and self._until_predicate():
            log.debug("Stopping client loop in state %s due to True from %s",
                      self._state.__class__.__name__,
                      self._until_predicate.__name__)
            self._until_predicate = None
            self._loop.stop()
            raise gen.Return(None)
        else:
            log.debug("Running state " + self._state.__class__.__name__)
            yield self._state.run(self)

    @gen.coroutine
    def _transition(self, new_state):
        log.debug("transitioning to state " + new_state.__class__.__name__)
        self._state = new_state
        yield self._next()

    @gen.coroutine
    def _transition_to_disconnected(self):
        self._tell_session_about_disconnect()
        yield self._transition(DISCONNECTED())

    @gen.coroutine
    def _connect_async(self):
        versioned_url = "%s?bokeh-protocol-version=1.0&bokeh-session-id=%s" % (
            self._url, self._session.id)
        request = HTTPRequest(versioned_url)
        try:
            socket = yield websocket_connect(request)
            self._socket = WebSocketClientConnectionWrapper(socket)
        except Exception as e:
            log.info("Failed to connect to server: %r", e)

        if self._socket is None:
            yield self._transition_to_disconnected()
        else:
            yield self._transition(CONNECTED_BEFORE_ACK())

    @gen.coroutine
    def _wait_for_ack(self):
        message = yield self._pop_message()
        if message and message.msgtype == 'ACK':
            log.debug("Received %r", message)
            yield self._transition(CONNECTED_AFTER_ACK())
        elif message is None:
            yield self._transition_to_disconnected()
        else:
            raise ProtocolError("Received %r instead of ACK" % message)

    @gen.coroutine
    def _handle_messages(self):
        message = yield self._pop_message()
        if message is None:
            yield self._transition_to_disconnected()
        else:
            if message.msgtype == 'PATCH-DOC':
                log.debug("Got PATCH-DOC, applying to session")
                self._session._handle_patch(message)
            else:
                log.debug("Ignoring %r", message)
            # we don't know about whatever message we got, ignore it.
            yield self._next()

    @gen.coroutine
    def _pop_message(self):
        while True:
            if self._socket is None:
                raise gen.Return(None)

            # log.debug("Waiting for fragment...")
            fragment = None
            try:
                fragment = yield self._socket.read_message()
            except Exception as e:
                # this happens on close, so debug level since it's "normal"
                log.debug("Error reading from socket %r", e)
            # log.debug("... got fragment %r", fragment)
            if fragment is None:
                # XXX Tornado doesn't give us the code and reason
                log.info("Connection closed by server")
                raise gen.Return(None)
            try:
                message = yield self._receiver.consume(fragment)
                if message is not None:
                    log.debug("Received message %r" % message)
                    raise gen.Return(message)
            except (MessageError, ProtocolError, ValidationError) as e:
                log.error("%r", e, exc_info=True)
                self.close(why="error parsing message from server")

    def _tell_session_about_disconnect(self):
        if self._session:
            self._session._notify_disconnected()
示例#30
0
# Imports
#-----------------------------------------------------------------------------

# Bokeh imports
import bokeh.document as document
from bokeh.core.properties import Instance, Int
from bokeh.model import Model

# Module under test
from bokeh.protocol import Protocol  # isort:skip

#-----------------------------------------------------------------------------
# Setup
#-----------------------------------------------------------------------------

proto = Protocol()

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


class AnotherModelInTestPullDoc(Model):
    bar = Int(1)


class SomeModelInTestPullDoc(Model):
    foo = Int(2)
    child = Instance(Model)

示例#31
0
class ClientConnection(object):
    """ A Bokeh-private class used to implement ClientSession; use ClientSession to connect to the server."""

    class NOT_YET_CONNECTED(object):
        @gen.coroutine
        def run(self, connection):
            yield connection._connect_async()

    class CONNECTED_BEFORE_ACK(object):
        @gen.coroutine
        def run(self, connection):
            yield connection._wait_for_ack()

    class CONNECTED_AFTER_ACK(object):
        @gen.coroutine
        def run(self, connection):
            yield connection._handle_messages()

    class DISCONNECTED(object):
        @gen.coroutine
        def run(self, connection):
            raise gen.Return(None)

    class WAITING_FOR_REPLY(object):
        def __init__(self, reqid):
            self._reqid = reqid
            self._reply = None

        @property
        def reply(self):
            return self._reply

        @gen.coroutine
        def run(self, connection):
            message = yield connection._pop_message()
            if message is None:
                yield connection._transition_to_disconnected()
            elif 'reqid' in message.header and message.header['reqid'] == self._reqid:
                self._reply = message
                yield connection._transition(connection.CONNECTED_AFTER_ACK())
            else:
                yield connection._next()

    def __init__(self, session, websocket_url, io_loop=None):
        '''
          Opens a websocket connection to the server.
        '''
        self._url = websocket_url
        self._session = session
        self._protocol = Protocol("1.0")
        self._receiver = Receiver(self._protocol)
        self._socket = None
        self._state = self.NOT_YET_CONNECTED()
        if io_loop is None:
            # We can't use IOLoop.current because then we break
            # when running inside a notebook since ipython also uses it
            io_loop = IOLoop()
        self._loop = io_loop
        self._until_predicate = None
        self._protocol = Protocol("1.0")
        self._server_info = None

    @property
    def url(self):
        return self._url

    @property
    def io_loop(self):
        return self._loop

    @property
    def connected(self):
        """True if we've connected the websocket and exchanged initial handshake messages."""
        return isinstance(self._state, self.CONNECTED_AFTER_ACK)

    def connect(self):
        def connected_or_closed():
            # we should be looking at the same state here as the 'connected' property above, so connected
            # means both connected and that we did our initial message exchange
            return isinstance(self._state, self.CONNECTED_AFTER_ACK) or isinstance(self._state, self.DISCONNECTED)
        self._loop_until(connected_or_closed)

    def close(self, why="closed"):
        if self._socket is not None:
            self._socket.close(1000, why)

    def loop_until_closed(self):
        if isinstance(self._state, self.NOT_YET_CONNECTED):
            # we don't use self._transition_to_disconnected here
            # because _transition is a coroutine
            self._tell_session_about_disconnect()
            self._state = self.DISCONNECTED()
        else:
            def closed():
                return isinstance(self._state, self.DISCONNECTED)
            self._loop_until(closed)

    @gen.coroutine
    def send_message(self, message):
        if self._socket is None:
            log.info("We're disconnected, so not sending message %r", message)
        else:
            try:
                sent = yield message.send(self._socket)
                log.debug("Sent %r [%d bytes]", message, sent)
            except WebSocketError as e:
                # A thing that happens is that we detect the
                # socket closing by getting a None from
                # read_message, but the network socket can be down
                # with many messages still in the read buffer, so
                # we'll process all those incoming messages and
                # get write errors trying to send change
                # notifications during that processing.

                # this is just debug level because it's completely normal
                # for it to happen when the socket shuts down.
                log.debug("Error sending message to server: %r", e)

                # error is almost certainly because
                # socket is already closed, but be sure,
                # because once we fail to send a message
                # we can't recover
                self.close(why="received error while sending")

                # don't re-throw the error - there's nothing to
                # do about it.

        raise gen.Return(None)

    def _send_patch_document(self, session_id, event):
        msg = self._protocol.create('PATCH-DOC', [event])
        self.send_message(msg)

    def _send_message_wait_for_reply(self, message):
        waiter = self.WAITING_FOR_REPLY(message.header['msgid'])
        self._state = waiter

        send_result = []
        def message_sent(future):
            send_result.append(future)
        self._loop.add_future(self.send_message(message), message_sent)

        def have_send_result_or_disconnected():
            return len(send_result) > 0 or self._state != waiter
        self._loop_until(have_send_result_or_disconnected)

        def have_reply_or_disconnected():
            return self._state != waiter or waiter.reply is not None
        self._loop_until(have_reply_or_disconnected)

        return waiter.reply

    def push_doc(self, document):
        ''' Push a document to the server, overwriting any existing server-side doc.

        Args:
            document : bokeh.document.Document
              the Document to push to the server
        Returns:
             The server reply
        '''
        msg = self._protocol.create('PUSH-DOC', document)
        reply = self._send_message_wait_for_reply(msg)
        if reply is None:
            raise RuntimeError("Connection to server was lost")
        elif reply.header['msgtype'] == 'ERROR':
            raise RuntimeError("Failed to push document: " + reply.content['text'])
        else:
            return reply

    def pull_doc(self, document):
        ''' Pull a document from the server, overwriting the passed-in document
        Args:
            document : bokeh.document.Document
              The document to overwrite with server content.
        Returns:
            None
        '''
        msg = self._protocol.create('PULL-DOC-REQ')
        reply = self._send_message_wait_for_reply(msg)
        if reply is None:
            raise RuntimeError("Connection to server was lost")
        elif reply.header['msgtype'] == 'ERROR':
            raise RuntimeError("Failed to pull document: " + reply.content['text'])
        else:
            reply.push_to_document(document)

    def _send_request_server_info(self):
        msg = self._protocol.create('SERVER-INFO-REQ')
        reply = self._send_message_wait_for_reply(msg)
        if reply is None:
            raise RuntimeError("Did not get a reply to server info request before disconnect")
        return reply.content

    def request_server_info(self):
        '''
        Ask for information about the server.

        Returns:
            A dictionary of server attributes.
        '''
        if self._server_info is None:
            self._server_info = self._send_request_server_info()
        return self._server_info

    def force_roundtrip(self):
        '''
        Force a round-trip request/reply to the server, sometimes needed to avoid race conditions.

        Outside of test suites, this method probably hurts performance and shouldn't be needed.

        Returns:
           None
        '''
        self._send_request_server_info()

    def _loop_until(self, predicate):
        self._until_predicate = predicate
        try:
            # this runs self._next ONE time, but
            # self._next re-runs itself until
            # the predicate says to quit.
            self._loop.add_callback(self._next)
            self._loop.start()
        except KeyboardInterrupt:
            self.close("user interruption")

    @gen.coroutine
    def _next(self):
        if self._until_predicate is not None and self._until_predicate():
            log.debug("Stopping client loop in state %s due to True from %s",
                      self._state.__class__.__name__, self._until_predicate.__name__)
            self._until_predicate = None
            self._loop.stop()
            raise gen.Return(None)
        else:
            log.debug("Running state " + self._state.__class__.__name__)
            yield self._state.run(self)


    @gen.coroutine
    def _transition(self, new_state):
        log.debug("transitioning to state " + new_state.__class__.__name__)
        self._state = new_state
        yield self._next()

    @gen.coroutine
    def _transition_to_disconnected(self):
        self._tell_session_about_disconnect()
        yield self._transition(self.DISCONNECTED())

    @gen.coroutine
    def _connect_async(self):
        versioned_url = "%s?bokeh-protocol-version=1.0&bokeh-session-id=%s" % (self._url, self._session.id)
        request = HTTPRequest(versioned_url)
        try:
            socket = yield websocket_connect(request)
            self._socket = _WebSocketClientConnectionWrapper(socket)
        except Exception as e:
            log.info("Failed to connect to server: %r", e)

        if self._socket is None:
            yield self._transition_to_disconnected()
        else:
            yield self._transition(self.CONNECTED_BEFORE_ACK())


    @gen.coroutine
    def _wait_for_ack(self):
        message = yield self._pop_message()
        if message and message.msgtype == 'ACK':
            log.debug("Received %r", message)
            yield self._transition(self.CONNECTED_AFTER_ACK())
        elif message is None:
            yield self._transition_to_disconnected()
        else:
            raise ProtocolError("Received %r instead of ACK" % message)

    @gen.coroutine
    def _handle_messages(self):
        message = yield self._pop_message()
        if message is None:
            yield self._transition_to_disconnected()
        else:
            if message.msgtype == 'PATCH-DOC':
                log.debug("Got PATCH-DOC, applying to session")
                self._session._handle_patch(message)
            else:
                log.debug("Ignoring %r", message)
            # we don't know about whatever message we got, ignore it.
            yield self._next()

    @gen.coroutine
    def _pop_message(self):
        while True:
            if self._socket is None:
                raise gen.Return(None)

            # log.debug("Waiting for fragment...")
            fragment = None
            try:
                fragment = yield self._socket.read_message()
            except Exception as e:
                # this happens on close, so debug level since it's "normal"
                log.debug("Error reading from socket %r", e)
            # log.debug("... got fragment %r", fragment)
            if fragment is None:
                # XXX Tornado doesn't give us the code and reason
                log.info("Connection closed by server")
                raise gen.Return(None)
            try:
                message = yield self._receiver.consume(fragment)
                if message is not None:
                    log.debug("Received message %r" % message)
                    raise gen.Return(message)
            except (MessageError, ProtocolError, ValidationError) as e:
                log.error("%r", e, exc_info=True)
                self.close(why="error parsing message from server")

    def _tell_session_about_disconnect(self):
        if self._session:
            self._session._notify_disconnected()
示例#32
0
 def test_create_model_changed(self):
     sample = self._sample_doc()
     obj = next(iter(sample.roots))
     event = ModelChangedEvent(sample, obj, 'foo', obj.foo, 42, 42)
     Protocol("1.0").create("PATCH-DOC", [event])
示例#33
0
 def test_create_no_events(self):
     with pytest.raises(ValueError):
         Protocol("1.0").create("PATCH-DOC", [])