diff --git a/notebook/services/sessions/handlers.py b/notebook/services/sessions/handlers.py index 4c46c45df..b7a7f11de 100644 --- a/notebook/services/sessions/handlers.py +++ b/notebook/services/sessions/handlers.py @@ -8,7 +8,7 @@ Preliminary documentation at https://github.com/ipython/ipython/wiki/IPEP-16%3A- import json -from tornado import web +from tornado import gen, web from ...base.handlers import APIHandler, json_errors from jupyter_client.jsonutil import date_default @@ -20,20 +20,20 @@ class SessionRootHandler(APIHandler): @web.authenticated @json_errors + @gen.coroutine def get(self): # Return a list of running sessions sm = self.session_manager - sessions = sm.list_sessions() + sessions = yield gen.maybe_future(sm.list_sessions()) self.finish(json.dumps(sessions, default=date_default)) @web.authenticated @json_errors + @gen.coroutine def post(self): # Creates a new session #(unless a session already exists for the named nb) sm = self.session_manager - cm = self.contents_manager - km = self.kernel_manager model = self.get_json_body() if model is None: @@ -49,11 +49,13 @@ class SessionRootHandler(APIHandler): kernel_name = None # Check to see if session exists - if sm.session_exists(path=path): - model = sm.get_session(path=path) + exists = yield gen.maybe_future(sm.session_exists(path=path)) + if exists: + model = yield gen.maybe_future(sm.get_session(path=path)) else: try: - model = sm.create_session(path=path, kernel_name=kernel_name) + model = yield gen.maybe_future( + sm.create_session(path=path, kernel_name=kernel_name)) except NoSuchKernel: msg = ("The '%s' kernel is not available. Please pick another " "suitable kernel instead, or install that kernel." % kernel_name) @@ -73,14 +75,16 @@ class SessionHandler(APIHandler): @web.authenticated @json_errors + @gen.coroutine def get(self, session_id): # Returns the JSON model for a single session sm = self.session_manager - model = sm.get_session(session_id=session_id) + model = yield gen.maybe_future(sm.get_session(session_id=session_id)) self.finish(json.dumps(model, default=date_default)) @web.authenticated @json_errors + @gen.coroutine def patch(self, session_id): # Currently, this handler is strictly for renaming notebooks sm = self.session_manager @@ -93,17 +97,18 @@ class SessionHandler(APIHandler): if 'path' in notebook: changes['path'] = notebook['path'] - sm.update_session(session_id, **changes) - model = sm.get_session(session_id=session_id) + yield gen.maybe_future(sm.update_session(session_id, **changes)) + model = yield gen.maybe_future(sm.get_session(session_id=session_id)) self.finish(json.dumps(model, default=date_default)) @web.authenticated @json_errors + @gen.coroutine def delete(self, session_id): # Deletes the session with given session_id sm = self.session_manager try: - sm.delete_session(session_id) + yield gen.maybe_future(sm.delete_session(session_id)) except KeyError: # the kernel was deleted but the session wasn't! raise web.HTTPError(410, "Kernel deleted before session") diff --git a/notebook/services/sessions/sessionmanager.py b/notebook/services/sessions/sessionmanager.py index b75732087..ffd0cc28a 100644 --- a/notebook/services/sessions/sessionmanager.py +++ b/notebook/services/sessions/sessionmanager.py @@ -6,7 +6,7 @@ import uuid import sqlite3 -from tornado import web +from tornado import gen, web from traitlets.config.configurable import LoggingConfigurable from ipython_genutils.py3compat import unicode_type @@ -56,17 +56,22 @@ class SessionManager(LoggingConfigurable): def new_session_id(self): "Create a uuid for a new session" return unicode_type(uuid.uuid4()) - + + @gen.coroutine def create_session(self, path=None, kernel_name=None): """Creates a session and returns its model""" session_id = self.new_session_id() # allow nbm to specify kernels cwd kernel_path = self.contents_manager.get_kernel_path(path=path) - kernel_id = self.kernel_manager.start_kernel(path=kernel_path, - kernel_name=kernel_name) - return self.save_session(session_id, path=path, - kernel_id=kernel_id) - + kernel_id = yield gen.maybe_future( + self.kernel_manager.start_kernel(path=kernel_path, kernel_name=kernel_name) + ) + result = yield gen.maybe_future( + self.save_session(session_id, path=path, kernel_id=kernel_id) + ) + # py2-compat + raise gen.Return(result) + def save_session(self, session_id, path=None, kernel_id=None): """Saves the items for the session with the given session_id diff --git a/notebook/services/sessions/tests/test_sessionmanager.py b/notebook/services/sessions/tests/test_sessionmanager.py index d1382ed1d..dbb4feb5e 100644 --- a/notebook/services/sessions/tests/test_sessionmanager.py +++ b/notebook/services/sessions/tests/test_sessionmanager.py @@ -2,7 +2,8 @@ from unittest import TestCase -from tornado import web +from tornado import gen, web +from tornado.ioloop import IOLoop from ..sessionmanager import SessionManager from notebook.services.kernels.kernelmanager import MappingKernelManager @@ -37,12 +38,31 @@ class TestSessionManager(TestCase): kernel_manager=DummyMKM(), contents_manager=ContentsManager(), ) - + self.loop = IOLoop() + + def tearDown(self): + self.loop.close(all_fds=True) + + def create_sessions(self, *kwarg_list): + @gen.coroutine + def co_add(): + sessions = [] + for kwargs in kwarg_list: + session = yield self.sm.create_session(**kwargs) + sessions.append(session) + raise gen.Return(sessions) + return self.loop.run_sync(co_add) + + def create_session(self, **kwargs): + return self.create_sessions(kwargs)[0] + + def get_session(self, **kwargs): + return self.loop.run_sync(lambda : self.sm.get_session(**kwargs)) + def test_get_session(self): sm = self.sm - session_id = sm.create_session(path='/path/to/test.ipynb', - kernel_name='bar')['id'] - model = sm.get_session(session_id=session_id) + session_id = self.create_session(path='/path/to/test.ipynb', kernel_name='bar')['id'] + model = self.get_session(session_id=session_id) expected = {'id':session_id, 'notebook':{'path': u'/path/to/test.ipynb'}, 'kernel': {'id':u'A', 'name': 'bar'}} @@ -51,13 +71,13 @@ class TestSessionManager(TestCase): def test_bad_get_session(self): # Should raise error if a bad key is passed to the database. sm = self.sm - session_id = sm.create_session(path='/path/to/test.ipynb', + session_id = self.create_session(path='/path/to/test.ipynb', kernel_name='foo')['id'] self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword def test_get_session_dead_kernel(self): sm = self.sm - session = sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python') + session = self.create_session(path='/path/to/1/test1.ipynb', kernel_name='python') # kill the kernel sm.kernel_manager.shutdown_kernel(session['kernel']['id']) with self.assertRaises(KeyError): @@ -68,11 +88,12 @@ class TestSessionManager(TestCase): def test_list_sessions(self): sm = self.sm - sessions = [ - sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'), - sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'), - sm.create_session(path='/path/to/3/test3.ipynb', kernel_name='python'), - ] + sessions = self.create_sessions( + dict(path='/path/to/1/test1.ipynb', kernel_name='python'), + dict(path='/path/to/2/test2.ipynb', kernel_name='python'), + dict(path='/path/to/3/test3.ipynb', kernel_name='python'), + ) + sessions = sm.list_sessions() expected = [ { @@ -93,10 +114,10 @@ class TestSessionManager(TestCase): def test_list_sessions_dead_kernel(self): sm = self.sm - sessions = [ - sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'), - sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'), - ] + sessions = self.create_sessions( + dict(path='/path/to/1/test1.ipynb', kernel_name='python'), + dict(path='/path/to/2/test2.ipynb', kernel_name='python'), + ) # kill one of the kernels sm.kernel_manager.shutdown_kernel(sessions[0]['kernel']['id']) listed = sm.list_sessions() @@ -116,10 +137,10 @@ class TestSessionManager(TestCase): def test_update_session(self): sm = self.sm - session_id = sm.create_session(path='/path/to/test.ipynb', + session_id = self.create_session(path='/path/to/test.ipynb', kernel_name='julia')['id'] sm.update_session(session_id, path='/path/to/new_name.ipynb') - model = sm.get_session(session_id=session_id) + model = self.loop.run_sync(lambda : sm.get_session(session_id=session_id)) expected = {'id':session_id, 'notebook':{'path': u'/path/to/new_name.ipynb'}, 'kernel':{'id':u'A', 'name':'julia'}} @@ -128,17 +149,17 @@ class TestSessionManager(TestCase): def test_bad_update_session(self): # try to update a session with a bad keyword ~ raise error sm = self.sm - session_id = sm.create_session(path='/path/to/test.ipynb', + session_id = self.create_session(path='/path/to/test.ipynb', kernel_name='ir')['id'] self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword def test_delete_session(self): sm = self.sm - sessions = [ - sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'), - sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'), - sm.create_session(path='/path/to/3/test3.ipynb', kernel_name='python'), - ] + sessions = self.create_sessions( + dict(path='/path/to/1/test1.ipynb', kernel_name='python'), + dict(path='/path/to/2/test2.ipynb', kernel_name='python'), + dict(path='/path/to/3/test3.ipynb', kernel_name='python'), + ) sm.delete_session(sessions[1]['id']) new_sessions = sm.list_sessions() expected = [{ @@ -156,7 +177,7 @@ class TestSessionManager(TestCase): def test_bad_delete_session(self): # try to delete a session that doesn't exist ~ raise error sm = self.sm - sm.create_session(path='/path/to/test.ipynb', kernel_name='python') + self.create_session(path='/path/to/test.ipynb', kernel_name='python') self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant