''' ''' 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 class ServerContext(with_metaclass(ABCMeta)): @property @abstractmethod def sessions(self): """ SessionContext instances belonging to this application.""" raise NotImplementedError("sessions property, should return SessionContext") @property @abstractmethod def develop_mode(self): """ True if we are in develop mode.""" raise NotImplementedError("develop_mode") @abstractmethod def add_next_tick_callback(self, callback): """ Adds a callback to be run on the next tick of the event loop.""" raise NotImplementedError("add_next_tick_callback") @abstractmethod def remove_next_tick_callback(self, callback): """ Removes a callback added with add_next_tick_callback, before it runs.""" raise NotImplementedError("remove_next_tick_callback") @abstractmethod def add_timeout_callback(self, callback, timeout_milliseconds): """ Adds a callback to be run once after timeout_milliseconds.""" raise NotImplementedError("add_timeout_callback") @abstractmethod def remove_timeout_callback(self, callback): """ Removes a callback added with add_timeout_callback, before it runs.""" raise NotImplementedError("remove_timeout_callback") @abstractmethod def add_periodic_callback(self, callback, period_milliseconds): """ Adds a callback to be run every period_milliseconds until it is removed.""" raise NotImplementedError("add_periodic_callback") @abstractmethod def remove_periodic_callback(self, callback): """ Removes a callback added with add_periodic_callback.""" raise NotImplementedError("remove_periodic_callback") class SessionContext(with_metaclass(ABCMeta)): def __init__(self, server_context, session_id): self._server_context = server_context self._id = session_id @property def server_context(self): return self._server_context @property def id(self): 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 object instance will not come back to life. """ raise NotImplementedError("destroyed") @abstractmethod def with_locked_document(self, func): """ Runs a function with the document lock held, passing the document to the function. The function may return a future. Args: func: 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") class Application(object): ''' An Application is a factory for Document instances. ''' def __init__(self, *handlers): self._handlers = list(handlers) def create_document(self): ''' Creates and initializes a document using the Application's handlers.''' doc = Document() self.initialize_document(doc) return doc 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) # A future server setting could make it configurable whether to do this, # since it has some performance impact probably. Let's see if we need to. doc.validate() def add(self, handler): ''' Add a handler to the pipeline used to initialize new documents. Args: handler (Handler) : a handler to process this Application ''' self._handlers.append(handler) @property def handlers(self): return tuple(self._handlers) def on_server_loaded(self, server_context): """ Invoked after server startup but before any sessions are created.""" for h in self._handlers: h.on_server_loaded(server_context) def on_server_unloaded(self, server_context): """ Invoked in theory if the server shuts down cleanly, probably not invoked most of the time in practice since servers tend to be killed by a signal. Invoked before stopping the server's IOLoop.""" for h in self._handlers: h.on_server_unloaded(server_context) @gen.coroutine def on_session_created(self, session_context): """ Invoked when we create a new session, with a blank Document that hasn't been filled in yet. 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 def on_session_destroyed(self, session_context): """ Invoked when we have destroyed a session. ``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)