This docs on this page refers to a PREVIOUS VERSION. For the latest stable release, go to https://docs.bokeh.org/

Archived docs for versions <= 1.0.4 have had to be modified from their original published configuration, and may be missing some features (e.g. source listing)

All users are encourage to update to version 1.1 or later, as soon as they are able.

bokeh.server.application_context — Bokeh 0.12.4 documentation

Source code for bokeh.server.application_context

''' Provides the ``ApplicationContext`` class.

'''
from __future__ import absolute_import

import logging
log = logging.getLogger(__name__)

from tornado import gen

from .session import ServerSession
from .exceptions import ProtocolError

from bokeh.application.application import ServerContext, SessionContext
from bokeh.document import Document
from bokeh.util.tornado import _CallbackGroup, yield_for_all_futures

class BokehServerContext(ServerContext):
    def __init__(self, application_context):
        self.application_context = application_context
        self._callbacks = _CallbackGroup(self.application_context.io_loop)

    def _remove_all_callbacks(self):
        self._callbacks.remove_all_callbacks()

    @property
    def sessions(self):
        result = []
        for session in self.application_context.sessions:
            result.append(session.session_context)
        return result

    def add_next_tick_callback(self, callback):
        self._callbacks.add_next_tick_callback(callback)

    def remove_next_tick_callback(self, callback):
        self._callbacks.remove_next_tick_callback(callback)

    def add_timeout_callback(self, callback, timeout_milliseconds):
        self._callbacks.add_timeout_callback(callback, timeout_milliseconds)

    def remove_timeout_callback(self, callback):
        self._callbacks.remove_timeout_callback(callback)

    def add_periodic_callback(self, callback, period_milliseconds):
        self._callbacks.add_periodic_callback(callback, period_milliseconds)

    def remove_periodic_callback(self, callback):
        self._callbacks.remove_periodic_callback(callback)

class BokehSessionContext(SessionContext):
    def __init__(self, session_id, server_context, document):
        self._document = document
        self._session = None
        super(BokehSessionContext, self).__init__(server_context,
                                                  session_id)
        # request arguments used to instantiate this session
        self._request = None

    def _set_session(self, session):
        self._session = session

    @gen.coroutine
    def with_locked_document(self, func):
        if self._session is None:
            # this means we are in on_session_created, so no locking yet,
            # we have exclusive access
            yield yield_for_all_futures(func(self._document))
        else:
            self._session.with_document_locked(func, self._document)

    @property
    def destroyed(self):
        if self._session is None:
            # this means we are in on_session_created
            return False
        else:
            return self._session.destroyed

    @property
    def request(self):
        return self._request


[docs]class ApplicationContext(object): ''' Server-side holder for bokeh.application.Application plus any associated data. This holds data that's global to all sessions, while ServerSession holds data specific to an "instance" of the application. ''' def __init__(self, application, io_loop=None): self._application = application self._loop = io_loop self._sessions = dict() self._pending_sessions = dict() self._session_contexts = dict() self._server_context = None @property def io_loop(self): return self._loop @property def application(self): return self._application @property def server_context(self): if self._server_context is None: self._server_context = BokehServerContext(self) return self._server_context @property def sessions(self): return self._sessions.values() def run_load_hook(self): try: result = self._application.on_server_loaded(self.server_context) if isinstance(result, gen.Future): log.error("on_server_loaded returned a Future; this doesn't make sense " "because we run this hook before starting the IO loop.") except Exception as e: log.error("Error in server loaded hook %r", e, exc_info=True) def run_unload_hook(self): try: result = self._application.on_server_unloaded(self.server_context) if isinstance(result, gen.Future): log.error("on_server_unloaded returned a Future; this doesn't make sense " "because we stop the IO loop right away after calling on_server_unloaded.") except Exception as e: log.error("Error in server unloaded hook %r", e, exc_info=True) self.server_context._remove_all_callbacks() @gen.coroutine def create_session_if_needed(self, session_id, request=None): # this is because empty session_ids would be "falsey" and # potentially open up a way for clients to confuse us if len(session_id) == 0: raise ProtocolError("Session ID must not be empty") if session_id not in self._sessions and \ session_id not in self._pending_sessions: future = self._pending_sessions[session_id] = gen.Future() doc = Document() session_context = BokehSessionContext(session_id, self.server_context, doc) # using private attr so users only have access to a read-only property session_context._request = request # expose the session context to the document # use the _attribute to set the public property .session_context doc._session_context = session_context try: yield yield_for_all_futures(self._application.on_session_created(session_context)) except Exception as e: log.error("Failed to run session creation hooks %r", e, exc_info=True) self._application.initialize_document(doc) session = ServerSession(session_id, doc, io_loop=self._loop) del self._pending_sessions[session_id] self._sessions[session_id] = session session_context._set_session(session) self._session_contexts[session_id] = session_context # notify anyone waiting on the pending session future.set_result(session) if session_id in self._pending_sessions: # another create_session_if_needed is working on # creating this session session = yield self._pending_sessions[session_id] else: session = self._sessions[session_id] raise gen.Return(session) def get_session(self, session_id): if session_id in self._sessions: session = self._sessions[session_id] return session else: raise ProtocolError("No such session " + session_id) @gen.coroutine def _discard_session(self, session, should_discard): if session.connection_count > 0: raise RuntimeError("Should not be discarding a session with open connections") log.debug("Discarding session %r last in use %r milliseconds ago", session.id, session.milliseconds_since_last_unsubscribe) session_context = self._session_contexts[session.id] # session.destroy() wants the document lock so it can shut down the document # callbacks. def do_discard(): # while we yielded for the document lock, the discard-worthiness of the # session may have changed. # However, since we have the document lock, our own lock will cause the # block count to be 1. If there's any other block count besides our own, # we want to skip session destruction though. if should_discard(session) and session.expiration_blocked_count == 1: session.destroy() del self._sessions[session.id] del self._session_contexts[session.id] else: log.debug("Session %r was scheduled to discard but came back to life", session.id) yield session.with_document_locked(do_discard) # session lifecycle hooks are supposed to be called outside the document lock, # we only run these if we actually ended up destroying the session. if session_context.destroyed: try: result = self._application.on_session_destroyed(session_context) yield yield_for_all_futures(result) except Exception as e: log.error("Failed to run session destroy hooks %r", e, exc_info=True) raise gen.Return(None) @gen.coroutine def cleanup_sessions(self, unused_session_linger_milliseconds): def should_discard_ignoring_block(session): return session.connection_count == 0 and \ (session.milliseconds_since_last_unsubscribe > unused_session_linger_milliseconds or \ session.expiration_requested) # build a temp list to avoid trouble from self._sessions changes to_discard = [] for session in self._sessions.values(): if should_discard_ignoring_block(session) and not session.expiration_blocked: to_discard.append(session) # asynchronously reconsider each session for session in to_discard: if should_discard_ignoring_block(session) and not session.expiration_blocked: yield self._discard_session(session, should_discard_ignoring_block) raise gen.Return(None)