Source code for bokeh.application.application

''' Provide the ``Application`` class.

Application instances are factories for creating new Bokeh Documents.

When a Bokeh server session is initiated, the Bokeh server asks the Application
for a new Document to service the session. To do this, the Application first
creates a new empty Document, then it passes this new Document to the
``modify_document`` method of each of its handlers. When all handlers have
updated the Document, it is used to service the user session.

'''
from __future__ import absolute_import

import logging
log = logging.getLogger(__name__)

from tornado import gen

from abc import ABCMeta, abstractmethod

from ..util.future import with_metaclass
from ..util.tornado import yield_for_all_futures
from ..document import Document
from ..settings import settings

[docs]class ServerContext(with_metaclass(ABCMeta)): ''' A harness for server-specific information and tasks related to collections of Bokeh sessions. *This base class is probably not of interest to general users.* ''' @property @abstractmethod def sessions(self): ''' SessionContext instances belonging to this application. *Subclasses must implement this method.* ''' raise NotImplementedError("sessions property, should return SessionContext") @abstractmethod
[docs] def add_next_tick_callback(self, callback): ''' Add a callback to be run on the next tick of the event loop. *Subclasses must implement this method.* Args: callback (callable) : a callback to add The callback will execute on the next tick of the event loop, and should have the form ``def callback()`` (i.e. it should not accept any arguments) ''' raise NotImplementedError("implement add_next_tick_callback")
@abstractmethod
[docs] def remove_next_tick_callback(self, callback): ''' Remove a callback added with ``add_next_tick_callback``, before it runs. *Subclasses must implement this method.* Args: callback (callable) : the callback to remove ''' raise NotImplementedError("implement remove_next_tick_callback")
@abstractmethod
[docs] def add_timeout_callback(self, callback, timeout_milliseconds): ''' Add a callback to be run once after timeout_milliseconds. *Subclasses must implement this method.* Args: callback (callable) : a callback to add The callback will execute once on the event loop after the timeout has passed, and should have the form ``def callback()`` (i.e. it should not accept any arguments) timeout_milliseconds (int) : number of milliseconds to wait before executing the callback. ''' raise NotImplementedError("implement add_timeout_callback")
@abstractmethod
[docs] def remove_timeout_callback(self, callback): ''' Remove a callback added with ``add_timeout_callback``, before it runs. *Subclasses must implement this method.* Args: callback (callable) : the callback to remove ''' raise NotImplementedError("implement remove_timeout_callback")
@abstractmethod
[docs] def add_periodic_callback(self, callback, period_milliseconds): ''' Add a callback to be run periodically until it is removed. *Subclasses must implement this method.* Args: callback (callable) : a callback to add The callback will execute periodically on the event loop as specified, and should have the form ``def callback()`` (i.e. it should not accept any arguments) period_milliseconds (int) : number of milliseconds to wait between executing the callback. ''' raise NotImplementedError("implement add_periodic_callback")
@abstractmethod
[docs] def remove_periodic_callback(self, callback): ''' Removes a callback added with ``add_periodic_callback``. *Subclasses must implement this method.* Args: callback (callable) : the callback to remove ''' raise NotImplementedError("implement remove_periodic_callback")
[docs]class SessionContext(with_metaclass(ABCMeta)): ''' A harness for server-specific information and tasks related to Bokeh sessions. *This base class is probably not of interest to general users.* '''
[docs] def __init__(self, server_context, session_id): ''' ''' self._server_context = server_context self._id = session_id
@property def server_context(self): ''' The server context for this session context ''' return self._server_context @property def id(self): ''' The unique ID for the session associated with this context. ''' return self._id @property @abstractmethod def destroyed(self): ''' If ``True``, the session has been discarded and cannot be used. A new session with the same ID could be created later but this instance will not come back to life. ''' raise NotImplementedError("destroyed") @abstractmethod
[docs] def with_locked_document(self, func): ''' Runs a function with the document lock held, passing the document to the function. *Subclasses must implement this method.* Args: func (callable): function that takes a single parameter (the Document) and returns ``None`` or a ``Future`` Returns: a ``Future`` containing the result of the function ''' raise NotImplementedError("locked_document")
[docs]class Application(object): ''' An Application is a factory for Document instances. ''' # This is so that bokeh.io.show can check if a passed in object is an # Application without having to import Application directly. This module # depends on tornado and we have made a commitment that "basic" modules # will function without bringing in tornado. _is_a_bokeh_application_class = True
[docs] def __init__(self, *handlers, **kwargs): ''' Application factory. Args: handlers (seq[Handler]): List of handlers to call. The URL is taken from the first one only. Keyword Args: metadata (dict): abitrary user-supplied JSON data to make available with the application. The server will provide a URL ``http://applicationurl/metadata`` which returns a JSON blob of the form: .. code-block:: json { "data": { "hi": "hi", "there": "there" }, "url": "/myapp" } The user-supplied metadata is returned as-is under the ``"data"`` key in the blob. ''' metadata = kwargs.pop('metadata', None) if kwargs: raise TypeError("Invalid keyword argument: %s" % kwargs.keys()[0]) self._static_path = None self._handlers = [] self._metadata = metadata for h in handlers: self.add(h)
[docs] def create_document(self): ''' Creates and initializes a document using the Application's handlers. ''' doc = Document() self.initialize_document(doc) return doc
[docs] def initialize_document(self, doc): ''' Fills in a new document using the Application's handlers. ''' for h in self._handlers: # TODO (havocp) we need to check the 'failed' flag on each handler # and build a composite error display. In develop mode, we want to # somehow get these errors to the client. h.modify_document(doc) if h.failed: log.error("Error running application handler %r: %s %s ", h, h.error, h.error_detail) if settings.perform_document_validation(): doc.validate()
[docs] def add(self, handler): ''' Add a handler to the pipeline used to initialize new documents. Args: handler (Handler) : a handler for this Application to use to process Documents ''' self._handlers.append(handler) # make sure there is at most one static path static_paths = set(h.static_path() for h in self.handlers) static_paths.discard(None) if len(static_paths) > 1: raise RuntimeError("More than one static path requested for app: %r" % list(static_paths)) elif len(static_paths) == 1: self._static_path = static_paths.pop() else: self._static_path = None
@property def handlers(self): ''' The ordered list of handlers this Application is configured with. ''' return tuple(self._handlers) @property def safe_to_fork(self): ''' ''' return all(handler.safe_to_fork for handler in self._handlers) @property def metadata(self): ''' Arbitrary user-supplied metadata to associate with this application. ''' return self._metadata @property def static_path(self): ''' Path to any (optional) static resources specified by handlers. ''' return self._static_path
[docs] def on_server_loaded(self, server_context): ''' Invoked to execute code when a new session is created. This method calls ``on_server_loaded`` on each handler, in order, with the server context passed as the only argument. ''' for h in self._handlers: h.on_server_loaded(server_context)
[docs] def on_server_unloaded(self, server_context): ''' Invoked to execute code when the server cleanly exits. (Before stopping the server's ``IOLoop``.) This method calls ``on_server_unloaded`` on each handler, in order, with the server context passed as the only argument. .. warning:: In practice this code may not run, since servers are often killed by a signal. ''' for h in self._handlers: h.on_server_unloaded(server_context)
@gen.coroutine
[docs] def on_session_created(self, session_context): ''' Invoked to execute code when a new session is created. This method calls ``on_session_created`` on each handler, in order, with the session context passed as the only argument. May return a ``Future`` which will delay session creation until the ``Future`` completes. ''' for h in self._handlers: result = h.on_session_created(session_context) yield yield_for_all_futures(result) raise gen.Return(None)
@gen.coroutine
[docs] def on_session_destroyed(self, session_context): ''' Invoked to execute code when a session is destroyed. This method calls ``on_session_destroyed`` on each handler, in order, with the session context passed as the only argument. Afterwards, ``session_context.destroyed`` will be ``True``. ''' for h in self._handlers: result = h.on_session_destroyed(session_context) yield yield_for_all_futures(result) raise gen.Return(None)