diff --git a/IPython/html/services/contents/tests/__init__.py b/IPython/html/services/contents/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/IPython/html/services/contents/tests/__init__.py
diff --git a/IPython/html/services/contents/tests/test_contentmanger.py b/IPython/html/services/contents/tests/test_contentmanger.py
new file mode 100644
index 0000000..d92bc25
--- /dev/null
+++ b/IPython/html/services/contents/tests/test_contentmanger.py
@@ -0,0 +1,12 @@
+"""Tests for the content manager."""
+
+import os
+from unittest import TestCase
+from tempfile import NamedTemporaryFile
+
+from IPython.utils.tempdir import TemporaryDirectory
+from IPython.utils.traitlets import TraitError
+
+from ..contentmanager import ContentManager
+
+#class TestContentManager(TestCase):
diff --git a/IPython/html/services/kernels/kernelmanager.py b/IPython/html/services/kernels/kernelmanager.py
index aa668bd..dbb3453 100644
--- a/IPython/html/services/kernels/kernelmanager.py
+++ b/IPython/html/services/kernels/kernelmanager.py
@@ -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)
diff --git a/IPython/html/services/sessions/handlers.py b/IPython/html/services/sessions/handlers.py
index c1e925d..006166b 100644
--- a/IPython/html/services/sessions/handlers.py
+++ b/IPython/html/services/sessions/handlers.py
@@ -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)
]
-
-
-
diff --git a/IPython/html/services/sessions/sessionmanager.py b/IPython/html/services/sessions/sessionmanager.py
index 21cad3e..2f7a3e1 100644
--- a/IPython/html/services/sessions/sessionmanager.py
+++ b/IPython/html/services/sessions/sessionmanager.py
@@ -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()
\ No newline at end of file
diff --git a/IPython/html/services/sessions/tests/__init__.py b/IPython/html/services/sessions/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/IPython/html/services/sessions/tests/__init__.py
diff --git a/IPython/html/services/sessions/tests/test_sessionmanager.py b/IPython/html/services/sessions/tests/test_sessionmanager.py
new file mode 100644
index 0000000..005d26a
--- /dev/null
+++ b/IPython/html/services/sessions/tests/test_sessionmanager.py
@@ -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
+
diff --git a/IPython/html/static/services/kernels/js/kernel.js b/IPython/html/static/services/kernels/js/kernel.js
index 16b8344..82d5f45 100644
--- a/IPython/html/static/services/kernels/js/kernel.js
+++ b/IPython/html/static/services/kernels/js/kernel.js
@@ -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;
diff --git a/IPython/html/static/services/sessions/js/session.js b/IPython/html/static/services/sessions/js/session.js
index 0635fc5..50fffe2 100644
--- a/IPython/html/static/services/sessions/js/session.js
+++ b/IPython/html/static/services/sessions/js/session.js
@@ -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);
};