Source code for bokeh.application.application

#-----------------------------------------------------------------------------
# Copyright (c) 2012 - 2017, Anaconda, Inc. All rights reserved.
#
# Powered by the Bokeh Development Team.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------
''' 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.

'''

#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
from __future__ import absolute_import, division, print_function, unicode_literals

import logging
log = logging.getLogger(__name__)

from bokeh.util.api import general, dev ; general, dev

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

# Standard library imports
from abc import ABCMeta, abstractmethod, abstractproperty

# External imports
from tornado import gen

# Bokeh imports
from ..document import Document
from ..settings import settings
from ..util.future import with_metaclass
from ..util.tornado import yield_for_all_futures

#-----------------------------------------------------------------------------
# Globals and constants
#-----------------------------------------------------------------------------

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

@general((1,0,0))
[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)
# Properties -------------------------------------------------------------- @property @general((1,0,0)) def handlers(self): ''' The ordered list of handlers this Application is configured with. ''' return tuple(self._handlers) @property @general((1,0,0)) def metadata(self): ''' Arbitrary user-supplied metadata to associate with this application. ''' return self._metadata @property @general((1,0,0)) def safe_to_fork(self): ''' ''' return all(handler.safe_to_fork for handler in self._handlers) @property @general((1,0,0)) def static_path(self): ''' Path to any (optional) static resources specified by handlers. ''' return self._static_path # Public methods ---------------------------------------------------------- @general((1,0,0))
[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
@general((1,0,0))
[docs] def create_document(self): ''' Creates and initializes a document using the Application's handlers. ''' doc = Document() self.initialize_document(doc) return doc
@general((1,0,0))
[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()
@dev((1,0,0))
[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)
@dev((1,0,0))
[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 @dev((1,0,0))
[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 @dev((1,0,0))
[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)
#----------------------------------------------------------------------------- # Dev API #----------------------------------------------------------------------------- @dev((1,0,0))
[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.* ''' # Properties -------------------------------------------------------------- @abstractproperty @dev((1,0,0)) def sessions(self): ''' SessionContext instances belonging to this application. *Subclasses must implement this method.* ''' pass # Public methods ---------------------------------------------------------- @abstractmethod @dev((1,0,0))
[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) ''' pass
@abstractmethod @dev((1,0,0))
[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. ''' pass
@abstractmethod @dev((1,0,0))
[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. ''' pass
@abstractmethod @dev((1,0,0))
[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 ''' pass
@abstractmethod @dev((1,0,0))
[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 ''' pass
@abstractmethod @dev((1,0,0))
[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 ''' pass
@dev((1,0,0))
[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
# Properties -------------------------------------------------------------- @abstractproperty @dev((1,0,0)) 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. ''' pass @property @dev((1,0,0)) def id(self): ''' The unique ID for the session associated with this context. ''' return self._id @property @dev((1,0,0)) def server_context(self): ''' The server context for this session context ''' return self._server_context # Public methods ---------------------------------------------------------- @abstractmethod @dev((1,0,0))
[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 ''' pass
#----------------------------------------------------------------------------- # Private API #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Code #-----------------------------------------------------------------------------