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
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
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
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 = document.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 ]
def test_should_suppress_model_changed(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) # integer property changed event1 = document.ModelChangedEvent(sample, root, 'foo', root.foo, 42, 42) msg = Protocol("1.0").create("PATCH-DOC", [event1]) assert msg.should_suppress_on_change(event1) assert not msg.should_suppress_on_change(document.ModelChangedEvent(sample, root, 'foo', root.foo, 43, 43)) assert not msg.should_suppress_on_change(document.ModelChangedEvent(sample, root, 'bar', root.foo, 43, 43)) assert not msg.should_suppress_on_change(document.ModelChangedEvent(sample, other_root, 'foo', root.foo, 43, 43)) # Model property changed event2 = document.ModelChangedEvent(sample, root, 'child', root.child, new_child, new_child) msg2 = Protocol("1.0").create("PATCH-DOC", [event2]) assert msg2.should_suppress_on_change(event2) assert not msg2.should_suppress_on_change(document.ModelChangedEvent(sample, root, 'child', root.child, other_root, other_root)) assert not msg2.should_suppress_on_change(document.ModelChangedEvent(sample, root, 'blah', root.child, new_child, new_child)) assert not msg2.should_suppress_on_change(document.ModelChangedEvent(sample, other_root, 'child', other_root.child, new_child, new_child)) # Model property changed to None event3 = document.ModelChangedEvent(sample, root, 'child', root.child, None, None) msg3 = Protocol("1.0").create("PATCH-DOC", [event3]) assert msg3.should_suppress_on_change(event3) assert not msg3.should_suppress_on_change(document.ModelChangedEvent(sample, root, 'child', root.child, other_root, other_root)) assert not msg3.should_suppress_on_change(document.ModelChangedEvent(sample, root, 'blah', root.child, None, None)) assert not msg3.should_suppress_on_change(document.ModelChangedEvent(sample, other_root, 'child', other_root.child, None, None)) # Model property changed from None event4 = document.ModelChangedEvent(sample, other_root, 'child', other_root.child, None, None) msg4 = Protocol("1.0").create("PATCH-DOC", [event4]) assert msg4.should_suppress_on_change(event4) assert not msg4.should_suppress_on_change(document.ModelChangedEvent(sample, other_root, 'child', other_root.child, root, root)) assert not msg4.should_suppress_on_change(document.ModelChangedEvent(sample, other_root, 'blah', other_root.child, None, None)) assert not msg4.should_suppress_on_change(document.ModelChangedEvent(sample, root, 'child', other_root.child, None, None)) # RootAdded event5 = document.RootAddedEvent(sample, root) msg5 = Protocol("1.0").create("PATCH-DOC", [event5]) assert msg5.should_suppress_on_change(event5) assert not msg5.should_suppress_on_change(document.RootAddedEvent(sample, other_root)) assert not msg5.should_suppress_on_change(document.RootRemovedEvent(sample, root)) # RootRemoved event6 = document.RootRemovedEvent(sample, root) msg6 = Protocol("1.0").create("PATCH-DOC", [event6]) assert msg6.should_suppress_on_change(event6) assert not msg6.should_suppress_on_change(document.RootRemovedEvent(sample, other_root)) assert not msg6.should_suppress_on_change(document.RootAddedEvent(sample, root)) # ColumnsStreamed event7 = document.ModelChangedEvent(sample, root, 'data', 10, None, None, hint=document.ColumnsStreamedEvent(sample, root, {}, None)) msg7 = Protocol("1.0").create("PATCH-DOC", [event7]) assert msg7.should_suppress_on_change(event7) assert not msg7.should_suppress_on_change( document.ModelChangedEvent(sample, root, 'data', 10, None, None, hint=document.ColumnsStreamedEvent(sample, root, {}, 10)) ) assert not msg7.should_suppress_on_change( document.ModelChangedEvent(sample, root, 'data', 10, None, None, hint=document.ColumnsStreamedEvent(sample, root, {"a": [10]}, None)) ) assert not msg7.should_suppress_on_change( document.ModelChangedEvent(sample, root, 'data', 10, None, None, hint=document.ColumnsStreamedEvent(sample, other_root, {}, None)) )
def test_create_model_changed(self): sample = self._sample_doc() obj = next(iter(sample.roots)) event = document.ModelChangedEvent(sample, obj, 'foo', obj.foo, 42, 42) Protocol("1.0").create("PATCH-DOC", [event])
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 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: sent = yield message.send(self._socket) log.debug("Sent %r [%d bytes]", message, sent) 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) raise e def _tell_session_about_disconnect(self): if self._session: self._session._notify_disconnected()
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 = document.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 = document.RootAddedEvent(sample, root) msg2 = Protocol("1.0").create("PATCH-DOC", [event2]) msg2.apply_to_document(sample, mock_session) # RootRemoved event3 = document.RootRemovedEvent(sample, root) msg3 = Protocol("1.0").create("PATCH-DOC", [event3]) msg3.apply_to_document(sample, mock_session) # ColumnsStreamed event4 = document.ModelChangedEvent(sample, cds, 'data', 10, None, None, hint=document.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 = document.ModelChangedEvent(sample, cds, 'data', 10, None, None, hint=document.ColumnsPatchedEvent( sample, cds, {"a": [(0, 11)]})) msg5 = Protocol("1.0").create("PATCH-DOC", [event5]) msg5.apply_to_document(sample, mock_session)
def test_create(self): sample = self._sample_doc() Protocol("1.0").create("PUSH-DOC", sample)
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()
def test_create_reply(self): sample = self._sample_doc() Protocol("1.0").create("PULL-DOC-REPLY", 'fakereqid', sample)
def test_create_req(self): Protocol("1.0").create("PULL-DOC-REQ")
def test_should_suppress_model_changed(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) # integer property changed event1 = document.ModelChangedEvent(sample, root, "foo", root.foo, 42) msg = Protocol("1.0").create("PATCH-DOC", [event1]) assert msg.should_suppress_on_change(event1) assert not msg.should_suppress_on_change(document.ModelChangedEvent(sample, root, "foo", root.foo, 43)) assert not msg.should_suppress_on_change(document.ModelChangedEvent(sample, root, "bar", root.foo, 43)) assert not msg.should_suppress_on_change(document.ModelChangedEvent(sample, other_root, "foo", root.foo, 43)) # PlotObject property changed event2 = document.ModelChangedEvent(sample, root, "child", root.child, new_child) msg2 = Protocol("1.0").create("PATCH-DOC", [event2]) assert msg2.should_suppress_on_change(event2) assert not msg2.should_suppress_on_change( document.ModelChangedEvent(sample, root, "child", root.child, other_root) ) assert not msg2.should_suppress_on_change( document.ModelChangedEvent(sample, root, "blah", root.child, new_child) ) assert not msg2.should_suppress_on_change( document.ModelChangedEvent(sample, other_root, "child", other_root.child, new_child) ) # PlotObject property changed to None event3 = document.ModelChangedEvent(sample, root, "child", root.child, None) msg3 = Protocol("1.0").create("PATCH-DOC", [event3]) assert msg3.should_suppress_on_change(event3) assert not msg3.should_suppress_on_change( document.ModelChangedEvent(sample, root, "child", root.child, other_root) ) assert not msg3.should_suppress_on_change(document.ModelChangedEvent(sample, root, "blah", root.child, None)) assert not msg3.should_suppress_on_change( document.ModelChangedEvent(sample, other_root, "child", other_root.child, None) ) # PlotObject property changed from None event4 = document.ModelChangedEvent(sample, other_root, "child", other_root.child, None) msg4 = Protocol("1.0").create("PATCH-DOC", [event4]) assert msg4.should_suppress_on_change(event4) assert not msg4.should_suppress_on_change( document.ModelChangedEvent(sample, other_root, "child", other_root.child, root) ) assert not msg4.should_suppress_on_change( document.ModelChangedEvent(sample, other_root, "blah", other_root.child, None) ) assert not msg4.should_suppress_on_change( document.ModelChangedEvent(sample, root, "child", other_root.child, None) ) # RootAdded event5 = document.RootAddedEvent(sample, root) msg5 = Protocol("1.0").create("PATCH-DOC", [event5]) assert msg5.should_suppress_on_change(event5) assert not msg5.should_suppress_on_change(document.RootAddedEvent(sample, other_root)) assert not msg5.should_suppress_on_change(document.RootRemovedEvent(sample, root)) # RootRemoved event6 = document.RootRemovedEvent(sample, root) msg6 = Protocol("1.0").create("PATCH-DOC", [event6]) assert msg6.should_suppress_on_change(event6) assert not msg6.should_suppress_on_change(document.RootRemovedEvent(sample, other_root)) assert not msg6.should_suppress_on_change(document.RootAddedEvent(sample, root))
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)
from __future__ import absolute_import from bokeh.server.protocol import receiver, Protocol from bokeh.util.string import decode_utf8 _proto = Protocol("1.0") 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() assert partial is None partial = r.consume(decode_utf8(msg.metadata_json)).result() assert partial is None partial = r.consume(decode_utf8(msg.content_json)).result() assert partial is not None assert partial.msgtype == msg.msgtype assert partial.header == msg.header assert partial.content == msg.content assert partial.metadata == msg.metadata