##// END OF EJS Templates
Merge pull request #6026 from takluyver/kernelspec-rest-launching...
Min RK -
r17234:b537531d merge
parent child Browse files
Show More
@@ -658,7 +658,9 b' class NotebookApp(BaseIPythonApplication):'
658 658 kls = import_item(self.notebook_manager_class)
659 659 self.notebook_manager = kls(parent=self, log=self.log)
660 660 kls = import_item(self.session_manager_class)
661 self.session_manager = kls(parent=self, log=self.log)
661 self.session_manager = kls(parent=self, log=self.log,
662 kernel_manager=self.kernel_manager,
663 notebook_manager=self.notebook_manager)
662 664 kls = import_item(self.cluster_manager_class)
663 665 self.cluster_manager = kls(parent=self, log=self.log)
664 666 self.cluster_manager.update_profiles()
@@ -27,8 +27,16 b' class MainKernelHandler(IPythonHandler):'
27 27 @web.authenticated
28 28 @json_errors
29 29 def post(self):
30 model = self.get_json_body()
31 if model is None:
32 raise web.HTTPError(400, "No JSON data provided")
33 try:
34 name = model['name']
35 except KeyError:
36 raise web.HTTPError(400, "Missing field in JSON data: name")
37
30 38 km = self.kernel_manager
31 kernel_id = km.start_kernel()
39 kernel_id = km.start_kernel(kernel_name=name)
32 40 model = km.kernel_model(kernel_id)
33 41 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
34 42 self.set_header('Location', url_escape(location))
@@ -72,8 +72,8 b' class MappingKernelManager(MultiKernelManager):'
72 72 os_path = os.path.dirname(os_path)
73 73 return os_path
74 74
75 def start_kernel(self, kernel_id=None, path=None, **kwargs):
76 """Start a kernel for a session an return its kernel_id.
75 def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
76 """Start a kernel for a session and return its kernel_id.
77 77
78 78 Parameters
79 79 ----------
@@ -84,12 +84,16 b' class MappingKernelManager(MultiKernelManager):'
84 84 path : API path
85 85 The API path (unicode, '/' delimited) for the cwd.
86 86 Will be transformed to an OS path relative to root_dir.
87 kernel_name : str
88 The name identifying which kernel spec to launch. This is ignored if
89 an existing kernel is returned, but it may be checked in the future.
87 90 """
88 91 if kernel_id is None:
89 92 kwargs['extra_arguments'] = self.kernel_argv
90 93 if path is not None:
91 94 kwargs['cwd'] = self.cwd_for_path(path)
92 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
95 kernel_id = super(MappingKernelManager, self).start_kernel(
96 kernel_name=kernel_name, **kwargs)
93 97 self.log.info("Kernel started: %s" % kernel_id)
94 98 self.log.debug("Kernel args: %r" % kwargs)
95 99 # register callback for failed auto-restart
@@ -111,7 +115,8 b' class MappingKernelManager(MultiKernelManager):'
111 115 """Return a dictionary of kernel information described in the
112 116 JSON standard model."""
113 117 self._check_kernel_id(kernel_id)
114 model = {"id":kernel_id}
118 model = {"id":kernel_id,
119 "name": self._kernels[kernel_id].kernel_name}
115 120 return model
116 121
117 122 def list_kernels(self):
@@ -1,6 +1,6 b''
1 1 """Test the kernels service API."""
2 2
3
3 import json
4 4 import requests
5 5
6 6 from IPython.html.utils import url_path_join
@@ -30,8 +30,9 b' class KernelAPI(object):'
30 30 def get(self, id):
31 31 return self._req('GET', id)
32 32
33 def start(self):
34 return self._req('POST', '')
33 def start(self, name='python'):
34 body = json.dumps({'name': name})
35 return self._req('POST', '', body)
35 36
36 37 def shutdown(self, id):
37 38 return self._req('DELETE', id)
@@ -69,6 +70,7 b' class KernelAPITest(NotebookTestBase):'
69 70 self.assertEqual(r.status_code, 200)
70 71 assert isinstance(r.json(), list)
71 72 self.assertEqual(r.json()[0]['id'], kern1['id'])
73 self.assertEqual(r.json()[0]['name'], kern1['name'])
72 74
73 75 # create another kernel and check that they both are added to the
74 76 # list of kernels from a GET request
@@ -89,6 +91,7 b' class KernelAPITest(NotebookTestBase):'
89 91 self.assertEqual(r.headers['Location'], '/api/kernels/'+kern2['id'])
90 92 rekern = r.json()
91 93 self.assertEqual(rekern['id'], kern2['id'])
94 self.assertEqual(rekern['name'], kern2['name'])
92 95
93 96 def test_kernel_handler(self):
94 97 # GET kernel with given id
@@ -45,27 +45,28 b' class SessionRootHandler(IPythonHandler):'
45 45 # Creates a new session
46 46 #(unless a session already exists for the named nb)
47 47 sm = self.session_manager
48 nbm = self.notebook_manager
49 km = self.kernel_manager
48
50 49 model = self.get_json_body()
51 50 if model is None:
52 51 raise web.HTTPError(400, "No JSON data provided")
53 52 try:
54 53 name = model['notebook']['name']
55 54 except KeyError:
56 raise web.HTTPError(400, "Missing field in JSON data: name")
55 raise web.HTTPError(400, "Missing field in JSON data: notebook.name")
57 56 try:
58 57 path = model['notebook']['path']
59 58 except KeyError:
60 raise web.HTTPError(400, "Missing field in JSON data: path")
59 raise web.HTTPError(400, "Missing field in JSON data: notebook.path")
60 try:
61 kernel_name = model['kernel']['name']
62 except KeyError:
63 raise web.HTTPError(400, "Missing field in JSON data: kernel.name")
64
61 65 # Check to see if session exists
62 66 if sm.session_exists(name=name, path=path):
63 67 model = sm.get_session(name=name, path=path)
64 68 else:
65 # allow nbm to specify kernels cwd
66 kernel_path = nbm.get_kernel_path(name=name, path=path)
67 kernel_id = km.start_kernel(path=kernel_path)
68 model = sm.create_session(name=name, path=path, kernel_id=kernel_id)
69 model = sm.create_session(name=name, path=path, kernel_name=kernel_name)
69 70 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
70 71 self.set_header('Location', url_escape(location))
71 72 self.set_status(201)
@@ -108,10 +109,7 b' class SessionHandler(IPythonHandler):'
108 109 def delete(self, session_id):
109 110 # Deletes the session with given session_id
110 111 sm = self.session_manager
111 km = self.kernel_manager
112 session = sm.get_session(session_id=session_id)
113 112 sm.delete_session(session_id)
114 km.shutdown_kernel(session['kernel']['id'])
115 113 self.set_status(204)
116 114 self.finish()
117 115
@@ -23,12 +23,16 b' from tornado import web'
23 23
24 24 from IPython.config.configurable import LoggingConfigurable
25 25 from IPython.utils.py3compat import unicode_type
26 from IPython.utils.traitlets import Instance
26 27
27 28 #-----------------------------------------------------------------------------
28 29 # Classes
29 30 #-----------------------------------------------------------------------------
30 31
31 32 class SessionManager(LoggingConfigurable):
33
34 kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
35 notebook_manager = Instance('IPython.html.services.notebooks.nbmanager.NotebookManager', args=())
32 36
33 37 # Session database initialized below
34 38 _cursor = None
@@ -69,10 +73,15 b' class SessionManager(LoggingConfigurable):'
69 73 "Create a uuid for a new session"
70 74 return unicode_type(uuid.uuid4())
71 75
72 def create_session(self, name=None, path=None, kernel_id=None):
76 def create_session(self, name=None, path=None, kernel_name='python'):
73 77 """Creates a session and returns its model"""
74 78 session_id = self.new_session_id()
75 return self.save_session(session_id, name=name, path=path, kernel_id=kernel_id)
79 # allow nbm to specify kernels cwd
80 kernel_path = self.notebook_manager.get_kernel_path(name=name, path=path)
81 kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
82 kernel_name=kernel_name)
83 return self.save_session(session_id, name=name, path=path,
84 kernel_id=kernel_id)
76 85
77 86 def save_session(self, session_id, name=None, path=None, kernel_id=None):
78 87 """Saves the items for the session with the given session_id
@@ -170,8 +179,7 b' class SessionManager(LoggingConfigurable):'
170 179 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
171 180 self.cursor.execute(query, list(kwargs.values()) + [session_id])
172 181
173 @staticmethod
174 def row_factory(cursor, row):
182 def row_factory(self, cursor, row):
175 183 """Takes sqlite database session row and turns it into a dictionary"""
176 184 row = sqlite3.Row(cursor, row)
177 185 model = {
@@ -180,9 +188,7 b' class SessionManager(LoggingConfigurable):'
180 188 'name': row['name'],
181 189 'path': row['path']
182 190 },
183 'kernel': {
184 'id': row['kernel_id'],
185 }
191 'kernel': self.kernel_manager.kernel_model(row['kernel_id'])
186 192 }
187 193 return model
188 194
@@ -195,5 +201,6 b' class SessionManager(LoggingConfigurable):'
195 201 def delete_session(self, session_id):
196 202 """Deletes the row in the session database with given session_id"""
197 203 # Check that session exists before deleting
198 self.get_session(session_id=session_id)
204 session = self.get_session(session_id=session_id)
205 self.kernel_manager.shutdown_kernel(session['kernel']['id'])
199 206 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
@@ -5,79 +5,101 b' from unittest import TestCase'
5 5 from tornado import web
6 6
7 7 from ..sessionmanager import SessionManager
8 from IPython.html.services.kernels.kernelmanager import MappingKernelManager
9
10 class DummyKernel(object):
11 def __init__(self, kernel_name='python'):
12 self.kernel_name = kernel_name
13
14 class DummyMKM(MappingKernelManager):
15 """MappingKernelManager interface that doesn't start kernels, for testing"""
16 def __init__(self, *args, **kwargs):
17 super(DummyMKM, self).__init__(*args, **kwargs)
18 self.id_letters = iter(u'ABCDEFGHIJK')
19
20 def _new_id(self):
21 return next(self.id_letters)
22
23 def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
24 kernel_id = kernel_id or self._new_id()
25 self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name)
26 return kernel_id
27
28 def shutdown_kernel(self, kernel_id, now=False):
29 del self._kernels[kernel_id]
8 30
9 31 class TestSessionManager(TestCase):
10 32
11 33 def test_get_session(self):
12 sm = SessionManager()
13 session_id = sm.new_session_id()
14 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
34 sm = SessionManager(kernel_manager=DummyMKM())
35 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
36 kernel_name='bar')['id']
15 37 model = sm.get_session(session_id=session_id)
16 expected = {'id':session_id, 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}}
38 expected = {'id':session_id,
39 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'},
40 'kernel': {'id':u'A', 'name': 'bar'}}
17 41 self.assertEqual(model, expected)
18 42
19 43 def test_bad_get_session(self):
20 44 # Should raise error if a bad key is passed to the database.
21 sm = SessionManager()
22 session_id = sm.new_session_id()
23 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
45 sm = SessionManager(kernel_manager=DummyMKM())
46 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
47 kernel_name='foo')['id']
24 48 self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword
25 49
26 50 def test_list_sessions(self):
27 sm = SessionManager()
28 session_id1 = sm.new_session_id()
29 session_id2 = sm.new_session_id()
30 session_id3 = sm.new_session_id()
31 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678')
32 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678')
33 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678')
51 sm = SessionManager(kernel_manager=DummyMKM())
52 sessions = [
53 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
54 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
55 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
56 ]
34 57 sessions = sm.list_sessions()
35 expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
36 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}},
37 {'id':session_id2, 'notebook': {'name':u'test2.ipynb',
38 'path': u'/path/to/2/'}, 'kernel':{'id':u'5678'}},
39 {'id':session_id3, 'notebook':{'name':u'test3.ipynb',
40 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}]
58 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
59 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
60 {'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb',
61 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}},
62 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
63 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
41 64 self.assertEqual(sessions, expected)
42 65
43 66 def test_update_session(self):
44 sm = SessionManager()
45 session_id = sm.new_session_id()
46 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id=None)
47 sm.update_session(session_id, kernel_id='5678')
67 sm = SessionManager(kernel_manager=DummyMKM())
68 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
69 kernel_name='julia')['id']
48 70 sm.update_session(session_id, name='new_name.ipynb')
49 71 model = sm.get_session(session_id=session_id)
50 expected = {'id':session_id, 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}}
72 expected = {'id':session_id,
73 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'},
74 'kernel':{'id':u'A', 'name':'julia'}}
51 75 self.assertEqual(model, expected)
52 76
53 77 def test_bad_update_session(self):
54 78 # try to update a session with a bad keyword ~ raise error
55 sm = SessionManager()
56 session_id = sm.new_session_id()
57 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
79 sm = SessionManager(kernel_manager=DummyMKM())
80 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
81 kernel_name='ir')['id']
58 82 self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
59 83
60 84 def test_delete_session(self):
61 sm = SessionManager()
62 session_id1 = sm.new_session_id()
63 session_id2 = sm.new_session_id()
64 session_id3 = sm.new_session_id()
65 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678')
66 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678')
67 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678')
68 sm.delete_session(session_id2)
69 sessions = sm.list_sessions()
70 expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
71 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}},
72 {'id':session_id3, 'notebook':{'name':u'test3.ipynb',
73 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}]
74 self.assertEqual(sessions, expected)
85 sm = SessionManager(kernel_manager=DummyMKM())
86 sessions = [
87 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
88 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
89 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
90 ]
91 sm.delete_session(sessions[1]['id'])
92 new_sessions = sm.list_sessions()
93 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
94 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
95 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
96 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
97 self.assertEqual(new_sessions, expected)
75 98
76 99 def test_bad_delete_session(self):
77 100 # try to delete a session that doesn't exist ~ raise error
78 sm = SessionManager()
79 session_id = sm.new_session_id()
80 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678')
101 sm = SessionManager(kernel_manager=DummyMKM())
102 sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python')
81 103 self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
82 104 self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
83 105
@@ -37,8 +37,9 b' class SessionAPI(object):'
37 37 def get(self, id):
38 38 return self._req('GET', id)
39 39
40 def create(self, name, path):
41 body = json.dumps({'notebook': {'name':name, 'path':path}})
40 def create(self, name, path, kernel_name='python'):
41 body = json.dumps({'notebook': {'name':name, 'path':path},
42 'kernel': {'name': kernel_name}})
42 43 return self._req('POST', '', body)
43 44
44 45 def modify(self, id, name, path):
@@ -59,6 +59,9 b' define(['
59 59 this.keyboard_manager = options.keyboard_manager;
60 60 this.save_widget = options.save_widget;
61 61 this.tooltip = new tooltip.Tooltip(this.events);
62 // default_kernel_name is a temporary measure while we implement proper
63 // kernel selection and delayed start. Do not rely on it.
64 this.default_kernel_name = 'python';
62 65 // TODO: This code smells (and the other `= this` line a couple lines down)
63 66 // We need a better way to deal with circular instance references.
64 67 this.keyboard_manager.notebook = this;
@@ -1495,7 +1498,12 b' define(['
1495 1498 base_url: this.base_url,
1496 1499 notebook_path: this.notebook_path,
1497 1500 notebook_name: this.notebook_name,
1501 // For now, create all sessions with the 'python' kernel, which is the
1502 // default. Later, the user will be able to select kernels. This is
1503 // overridden if KernelManager.kernel_cmd is specified for the server.
1504 kernel_name: this.default_kernel_name,
1498 1505 notebook: this});
1506
1499 1507 this.session.start($.proxy(this._session_started, this));
1500 1508 };
1501 1509
@@ -15,13 +15,14 b' define(['
15 15 * A Kernel Class to communicate with the Python kernel
16 16 * @Class Kernel
17 17 */
18 var Kernel = function (kernel_service_url, notebook) {
18 var Kernel = function (kernel_service_url, notebook, name) {
19 19 this.events = notebook.events;
20 20 this.kernel_id = null;
21 21 this.shell_channel = null;
22 22 this.iopub_channel = null;
23 23 this.stdin_channel = null;
24 24 this.kernel_service_url = kernel_service_url;
25 this.name = name;
25 26 this.running = false;
26 27 this.username = "username";
27 28 this.session_id = utils.uuid();
@@ -15,6 +15,7 b' define(['
15 15 this.notebook = options.notebook;
16 16 this.name = options.notebook_name;
17 17 this.path = options.notebook_path;
18 this.kernel_name = options.kernel_name;
18 19 this.base_url = options.base_url;
19 20 };
20 21
@@ -24,6 +25,9 b' define(['
24 25 notebook : {
25 26 name : this.name,
26 27 path : this.path
28 },
29 kernel : {
30 name : this.kernel_name
27 31 }
28 32 };
29 33 var settings = {
@@ -87,7 +91,7 b' define(['
87 91 Session.prototype._handle_start_success = function (data, status, xhr) {
88 92 this.id = data.id;
89 93 var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
90 this.kernel = new kernel.Kernel(kernel_service_url, this.notebook);
94 this.kernel = new kernel.Kernel(kernel_service_url, this.notebook, this.kernel_name);
91 95 this.kernel._kernel_started(data.kernel);
92 96 };
93 97
@@ -80,7 +80,7 b' casper.notebook_test(function () {'
80 80 });
81 81 return return_this_thing;
82 82 }, {nbname:nbname});
83 this.test.assertEquals(notebook_url == null, false, "Escaped URL in notebook list");
83 this.test.assertNotEquals(notebook_url, null, "Escaped URL in notebook list");
84 84 // open the notebook
85 85 this.open(notebook_url);
86 86 });
@@ -63,7 +63,7 b' casper.kernel_running = function() {'
63 63 casper.shutdown_current_kernel = function () {
64 64 // Shut down the current notebook's kernel.
65 65 this.thenEvaluate(function() {
66 IPython.notebook.kernel.kill();
66 IPython.notebook.session.delete();
67 67 });
68 68 // We close the page right after this so we need to give it time to complete.
69 69 this.wait(1000);
@@ -92,7 +92,7 b' class MultiKernelManager(LoggingConfigurable):'
92 92 def __contains__(self, kernel_id):
93 93 return kernel_id in self._kernels
94 94
95 def start_kernel(self, **kwargs):
95 def start_kernel(self, kernel_name='python', **kwargs):
96 96 """Start a new kernel.
97 97
98 98 The caller can pick a kernel_id by passing one in as a keyword arg,
@@ -111,7 +111,7 b' class MultiKernelManager(LoggingConfigurable):'
111 111 # including things like its transport and ip.
112 112 km = self.kernel_manager_factory(connection_file=os.path.join(
113 113 self.connection_dir, "kernel-%s.json" % kernel_id),
114 parent=self, autorestart=True, log=self.log
114 parent=self, autorestart=True, log=self.log, kernel_name=kernel_name,
115 115 )
116 116 km.start_kernel(**kwargs)
117 117 self._kernels[kernel_id] = km
General Comments 0
You need to be logged in to leave comments. Login now