session manager restructuring

Redid the sessionmanager, added tests for the session manager,
and added a sqlite database to hold kernel,session,nb mapping.
Zachary Sailer 13 years ago committed by MinRK
parent b8318708eb
commit 8c2009874c

@ -46,17 +46,16 @@ class MappingKernelManager(MultiKernelManager):
self.log.warn("Kernel %s died, removing from map.", kernel_id)
self.remove_kernel(kernel_id)
def start_kernel(self, **kwargs):
def start_kernel(self, kernel_id=None, **kwargs):
"""Start a kernel for a session an return its kernel_id.
Parameters
----------
session_id : uuid
The uuid of the session to associate the new kernel with. If this
is not None, this kernel will be persistent whenever the session
requests a kernel.
kernel_id : uuid
The uuid to associate the new kernel with. If this
is not None, this kernel will be persistent whenever it is
requested.
"""
kernel_id = None
if kernel_id is None:
kwargs['extra_arguments'] = self.kernel_argv
kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)

@ -28,7 +28,6 @@ from ...base.handlers import IPythonHandler
#-----------------------------------------------------------------------------
class SessionRootHandler(IPythonHandler):
@web.authenticated
@ -45,12 +44,19 @@ class SessionRootHandler(IPythonHandler):
nbm = self.notebook_manager
km = self.kernel_manager
notebook_path = self.get_argument('notebook_path', default=None)
notebook_name, path = nbm.named_notebook_path(notebook_path)
session_id, model = sm.get_session(notebook_name, path)
if model == None:
kernel_id = km.start_kernel()
name, path = nbm.named_notebook_path(notebook_path)
if sm.session_exists(name=name, path=path):
model = sm.get_session(name=name, path=path)
kernel_id = model['kernel']['id']
km.start_kernel(kernel_id, cwd=nbm.notebook_dir)
else:
session_id = sm.get_session_id()
sm.save_session(session_id=session_id, name=name, path=path)
kernel_id = km.start_kernel(cwd=nbm.notebook_dir)
kernel = km.kernel_model(kernel_id, self.ws_url)
model = sm.session_model(session_id, notebook_name, path, kernel)
sm.update_session(session_id, kernel=kernel_id)
model = sm.get_session(id=session_id)
self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
self.finish(jsonapi.dumps(model))
class SessionHandler(IPythonHandler):
@ -60,20 +66,19 @@ class SessionHandler(IPythonHandler):
@web.authenticated
def get(self, session_id):
sm = self.session_manager
model = sm.get_session_from_id(session_id)
model = sm.get_session(id=session_id)
self.finish(jsonapi.dumps(model))
@web.authenticated
def patch(self, session_id):
# Currently, this handler is strictly for renaming notebooks
sm = self.session_manager
nbm = self.notebook_manager
km = self.kernel_manager
notebook_path = self.request.body
notebook_name, path = nbm.named_notebook_path(notebook_path)
kernel_id = sm.get_kernel_from_session(session_id)
kernel = km.kernel_model(kernel_id, self.ws_url)
sm.delete_mapping_for_session(session_id)
model = sm.session_model(session_id, notebook_name, path, kernel)
name, path = nbm.named_notebook_path(notebook_path)
sm.update_session(id=session_id, name=name)
model = sm.get_session(id=session_id)
self.finish(jsonapi.dumps(model))
@web.authenticated
@ -81,9 +86,9 @@ class SessionHandler(IPythonHandler):
sm = self.session_manager
nbm = self.notebook_manager
km = self.kernel_manager
kernel_id = sm.get_kernel_from_session(session_id)
km.shutdown_kernel(kernel_id)
sm.delete_mapping_for_session(session_id)
session = sm.get_session(id=session_id)
sm.delete_session(session_id)
km.shutdown_kernel(session['kernel']['id'])
self.set_status(204)
self.finish()
@ -99,6 +104,3 @@ default_handlers = [
(r"api/sessions", SessionRootHandler)
]

@ -18,6 +18,7 @@ Authors:
import os
import uuid
import sqlite3
from tornado import web
@ -31,67 +32,139 @@ from IPython.utils.traitlets import List, Dict, Unicode, TraitError
class SessionManager(LoggingConfigurable):
# Use session_ids to map notebook names to kernel_ids
sessions = List()
# Session database initialized below
_cursor = None
_connection = None
def get_session(self, nb_name, nb_path=None):
"""Get an existing session or create a new one"""
model = None
for session in self.sessions:
if session['name'] == nb_name and session['path'] == nb_path:
session_id = session['id']
model = session
if model != None:
return session_id, model
@property
def cursor(self):
"""Start a cursor and create a database called 'session'"""
if self._cursor is None:
self._cursor = self.connection.cursor()
self._cursor.execute("""CREATE TABLE session
(id, name, path, kernel)""")
return self._cursor
@property
def connection(self):
"""Start a database connection"""
if self._connection is None:
self._connection = sqlite3.connect(':memory:')
self._connection.row_factory = sqlite3.Row
return self._connection
def __del__(self):
"""Close connection once SessionManager closes"""
self.cursor.close()
def session_exists(self, name, path):
"""Check to see if the session for the given notebook exists"""
self.cursor.execute("SELECT * FROM session WHERE name=? AND path=?", (name,path))
reply = self.cursor.fetchone()
if reply is None:
return False
else:
session_id = unicode(uuid.uuid4())
return session_id, model
def session_model(self, session_id, notebook_name=None, notebook_path=None, kernel=None):
""" Create a session that links notebooks with kernels """
model = dict(id=session_id,
name=notebook_name,
path=notebook_path,
kernel=kernel)
if notebook_path == None:
model['path']=""
self.sessions.append(model)
return model
def list_sessions(self):
"""List all sessions and their information"""
return self.sessions
def set_kernel_for_sessions(self, session_id, kernel_id):
"""Maps the kernel_ids to the session_id in session_mapping"""
for session in self.sessions:
if session['id'] == session_id:
session['kernel']['id'] = kernel_id
return self.sessions
return True
def get_session_id(self):
"Create a uuid for a new session"
return unicode(uuid.uuid4())
def save_session(self, session_id, name=None, path=None, kernel=None):
""" Given a session_id (and any other of the arguments), this method
creates a row in the sqlite session database that holds the information
for a session.
def delete_mapping_for_session(self, session_id):
"""Delete the session from session_mapping with the given session_id"""
i = 0
for session in self.sessions:
if session['id'] == session_id:
del self.sessions[i]
i = i + 1
return self.sessions
Parameters
----------
session_id : str
uuid for the session; this method must be given a session_id
name : str
the .ipynb notebook name that started the session
path : str
the path to the named notebook
kernel : str
a uuid for the kernel associated with this session
"""
self.cursor.execute("""INSERT INTO session VALUES
(?,?,?,?)""", (session_id, name, path, kernel))
self.connection.commit()
def get_session(self, **kwargs):
""" Takes a keyword argument and searches for the value in the session
database, then returns the rest of the session's info.
Parameters
----------
**kwargs : keyword argument
must be given one of the keywords and values from the session database
(i.e. session_id, name, path, kernel)
Returns
-------
model : dict
returns a dictionary that includes all the information from the
session described by the kwarg.
"""
column = kwargs.keys()[0] # uses only the first kwarg that is entered
value = kwargs.values()[0]
try:
self.cursor.execute("SELECT * FROM session WHERE %s=?" %column, (value,))
except sqlite3.OperationalError:
raise TraitError("The session database has no column: %s" %column)
reply = self.cursor.fetchone()
if reply is not None:
model = self.reply_to_dictionary_model(reply)
else:
model = None
return model
def update_session(self, session_id, **kwargs):
"""Updates the values in the session with the given session_id
with the values from the keyword arguments.
def get_session_from_id(self, session_id):
for session in self.sessions:
if session['id'] == session_id:
return session
def get_notebook_from_session(self, session_id):
"""Returns the notebook_path for the given session_id"""
for session in self.sessions:
if session['id'] == session_id:
return session['name']
def get_kernel_from_session(self, session_id):
"""Returns the kernel_id for the given session_id"""
for session in self.sessions:
if session['id'] == session_id:
return session['kernel']['id']
Parameters
----------
session_id : str
a uuid that identifies a session in the sqlite3 database
**kwargs : str
the key must correspond to a column title in session database,
and the value replaces the current value in the session
with session_id.
"""
column = kwargs.keys()[0] # uses only the first kwarg that is entered
value = kwargs.values()[0]
try:
self.cursor.execute("UPDATE session SET %s=? WHERE id=?" %column, (value, session_id))
self.connection.commit()
except sqlite3.OperationalError:
raise TraitError("No session exists with ID: %s" %session_id)
def reply_to_dictionary_model(self, reply):
"""Takes sqlite database session row and turns it into a dictionary"""
model = {'id': reply['id'],
'name' : reply['name'],
'path' : reply['path'],
'kernel' : {'id':reply['kernel'], 'ws_url': ''}}
return model
def list_sessions(self):
"""Returns a list of dictionaries containing all the information from
the session database"""
session_list=[]
self.cursor.execute("SELECT * FROM session")
sessions = self.cursor.fetchall()
for session in sessions:
model = self.reply_to_dictionary_model(session)
session_list.append(model)
return session_list
def delete_session(self, session_id):
"""Deletes the row in the session database with given session_id"""
# Check that session exists before deleting
model = self.get_session(id=session_id)
if model is None:
raise TraitError("The session does not exist: %s" %session_id)
else:
self.cursor.execute("DELETE FROM session WHERE id=?", (session_id,))
self.connection.commit()

@ -0,0 +1,86 @@
"""Tests for the session manager."""
import os
from unittest import TestCase
from tempfile import NamedTemporaryFile
from IPython.utils.tempdir import TemporaryDirectory
from IPython.utils.traitlets import TraitError
from ..sessionmanager import SessionManager
class TestSessionManager(TestCase):
def test_get_session(self):
sm = SessionManager()
session_id = sm.get_session_id()
sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
model = sm.get_session(id=session_id)
expected = {'id':session_id, 'name':u'test.ipynb', 'path': u'/path/to/', 'kernel':{'id':u'5678', 'ws_url': u''}}
self.assertEqual(model, expected)
def test_bad_get_session(self):
# Should raise error if a bad key is passed to the database.
sm = SessionManager()
session_id = sm.get_session_id()
sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
self.assertRaises(TraitError, sm.get_session, bad_id=session_id) # Bad keyword
def test_list_sessions(self):
sm = SessionManager()
session_id1 = sm.get_session_id()
session_id2 = sm.get_session_id()
session_id3 = sm.get_session_id()
sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel='5678')
sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel='5678')
sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel='5678')
sessions = sm.list_sessions()
expected = [{'id':session_id1, 'name':u'test1.ipynb',
'path': u'/path/to/1/', 'kernel':{'id':u'5678', 'ws_url': u''}},
{'id':session_id2, 'name':u'test2.ipynb',
'path': u'/path/to/2/', 'kernel':{'id':u'5678', 'ws_url': u''}},
{'id':session_id3, 'name':u'test3.ipynb',
'path': u'/path/to/3/', 'kernel':{'id':u'5678', 'ws_url': u''}}]
self.assertEqual(sessions, expected)
def test_update_session(self):
sm = SessionManager()
session_id = sm.get_session_id()
sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel=None)
sm.update_session(session_id, kernel='5678')
sm.update_session(session_id, name='new_name.ipynb')
model = sm.get_session(id=session_id)
expected = {'id':session_id, 'name':u'new_name.ipynb', 'path': u'/path/to/', 'kernel':{'id':u'5678', 'ws_url': u''}}
self.assertEqual(model, expected)
def test_bad_update_session(self):
# try to update a session with a bad keyword ~ raise error
sm = SessionManager()
session_id = sm.get_session_id()
sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
self.assertRaises(TraitError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
def test_delete_session(self):
sm = SessionManager()
session_id1 = sm.get_session_id()
session_id2 = sm.get_session_id()
session_id3 = sm.get_session_id()
sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel='5678')
sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel='5678')
sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel='5678')
sm.delete_session(session_id2)
sessions = sm.list_sessions()
expected = [{'id':session_id1, 'name':u'test1.ipynb',
'path': u'/path/to/1/', 'kernel':{'id':u'5678', 'ws_url': u''}},
{'id':session_id3, 'name':u'test3.ipynb',
'path': u'/path/to/3/', 'kernel':{'id':u'5678', 'ws_url': u''}}]
self.assertEqual(sessions, expected)
def test_bad_delete_session(self):
# try to delete a session that doesn't exist ~ raise error
sm = SessionManager()
session_id = sm.get_session_id()
sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
self.assertRaises(TraitError, sm.delete_session, session_id='23424') # Bad keyword

@ -24,16 +24,15 @@ var IPython = (function (IPython) {
* A Kernel Class to communicate with the Python kernel
* @Class Kernel
*/
var Kernel = function (base_url, session_id) {
var Kernel = function (base_url) {
this.kernel_id = null;
this.session_id = session_id
this.shell_channel = null;
this.iopub_channel = null;
this.stdin_channel = null;
this.base_url = base_url;
this.running = false;
this.username = "username";
this.base_session_id = utils.uuid();
this.username= "username";
this.session_id = utils.uuid();
this._msg_callbacks = {};
if (typeof(WebSocket) !== 'undefined') {
@ -52,7 +51,7 @@ var IPython = (function (IPython) {
header : {
msg_id : utils.uuid(),
username : this.username,
session : this.base_session_id,
session : this.session_id,
msg_type : msg_type
},
metadata : {},
@ -76,7 +75,6 @@ var IPython = (function (IPython) {
Kernel.prototype.start = function (params) {
var that = this;
params = params || {};
params.session = this.session_id;
if (!this.running) {
var qs = $.param(params);
var url = this.base_url + '?' + qs;

@ -66,7 +66,6 @@ var IPython = (function (IPython) {
this.kernel_content = json.kernel;
var base_url = $('body').data('baseKernelUrl') + "api/kernels";
this.kernel = new IPython.Kernel(base_url, this.session_id);
// Now that the kernel has been created, tell the CodeCells about it.
this.kernel._kernel_started(this.kernel_content);
};

Loading…
Cancel
Save