sessionmanager.py
211 lines
| 7.6 KiB
| text/x-python
|
PythonLexer
MinRK
|
r18022 | """A base class session manager.""" | ||
Zachary Sailer
|
r12985 | |||
MinRK
|
r18022 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
Zachary Sailer
|
r12985 | |||
import uuid | ||||
Zachary Sailer
|
r13035 | import sqlite3 | ||
Zachary Sailer
|
r12985 | |||
from tornado import web | ||||
from IPython.config.configurable import LoggingConfigurable | ||||
Thomas Kluyver
|
r13353 | from IPython.utils.py3compat import unicode_type | ||
Thomas Kluyver
|
r17222 | from IPython.utils.traitlets import Instance | ||
Zachary Sailer
|
r12985 | |||
class SessionManager(LoggingConfigurable): | ||||
Thomas Kluyver
|
r17222 | |||
kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager') | ||||
MinRK
|
r17524 | contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=()) | ||
Zachary Sailer
|
r12985 | |||
Zachary Sailer
|
r13035 | # Session database initialized below | ||
_cursor = None | ||||
_connection = None | ||||
MinRK
|
r15400 | _columns = {'session_id', 'name', 'path', 'kernel_id'} | ||
Zachary Sailer
|
r12985 | |||
Zachary Sailer
|
r13035 | @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 | ||||
MinRK
|
r15400 | (session_id, name, path, kernel_id)""") | ||
Zachary Sailer
|
r13035 | return self._cursor | ||
@property | ||||
def connection(self): | ||||
"""Start a database connection""" | ||||
if self._connection is None: | ||||
self._connection = sqlite3.connect(':memory:') | ||||
Thomas Kluyver
|
r17775 | self._connection.row_factory = sqlite3.Row | ||
Zachary Sailer
|
r13035 | return self._connection | ||
def __del__(self): | ||||
"""Close connection once SessionManager closes""" | ||||
self.cursor.close() | ||||
def session_exists(self, name, path): | ||||
MinRK
|
r13101 | """Check to see if the session for a given notebook exists""" | ||
self.cursor.execute("SELECT * FROM session WHERE name=? AND path=?", (name, path)) | ||||
Zachary Sailer
|
r13035 | reply = self.cursor.fetchone() | ||
if reply is None: | ||||
return False | ||||
Zachary Sailer
|
r12985 | else: | ||
Zachary Sailer
|
r13035 | return True | ||
MinRK
|
r13101 | def new_session_id(self): | ||
Zachary Sailer
|
r13035 | "Create a uuid for a new session" | ||
Thomas Kluyver
|
r13353 | return unicode_type(uuid.uuid4()) | ||
Zachary Sailer
|
r13035 | |||
MinRK
|
r18022 | def create_session(self, name=None, path=None, kernel_name=None): | ||
Zachary Sailer
|
r13057 | """Creates a session and returns its model""" | ||
MinRK
|
r13101 | session_id = self.new_session_id() | ||
Thomas Kluyver
|
r17222 | # allow nbm to specify kernels cwd | ||
MinRK
|
r17524 | kernel_path = self.contents_manager.get_kernel_path(name=name, path=path) | ||
Thomas Kluyver
|
r17222 | kernel_id = self.kernel_manager.start_kernel(path=kernel_path, | ||
kernel_name=kernel_name) | ||||
return self.save_session(session_id, name=name, path=path, | ||||
kernel_id=kernel_id) | ||||
Zachary Sailer
|
r13057 | |||
MinRK
|
r15400 | def save_session(self, session_id, name=None, path=None, kernel_id=None): | ||
Zachary Sailer
|
r13057 | """Saves the items for the session with the given session_id | ||
Given a session_id (and any other of the arguments), this method | ||||
Zachary Sailer
|
r13035 | creates a row in the sqlite session database that holds the information | ||
for a session. | ||||
Zachary Sailer
|
r12985 | |||
Zachary Sailer
|
r13035 | 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 | ||||
Zachary Sailer
|
r13057 | kernel_id : str | ||
Zachary Sailer
|
r13035 | a uuid for the kernel associated with this session | ||
MinRK
|
r15400 | |||
Zachary Sailer
|
r13057 | Returns | ||
------- | ||||
model : dict | ||||
a dictionary of the session model | ||||
Zachary Sailer
|
r13035 | """ | ||
MinRK
|
r15400 | self.cursor.execute("INSERT INTO session VALUES (?,?,?,?)", | ||
(session_id, name, path, kernel_id) | ||||
MinRK
|
r13101 | ) | ||
return self.get_session(session_id=session_id) | ||||
Zachary Sailer
|
r13035 | |||
def get_session(self, **kwargs): | ||||
Zachary Sailer
|
r13057 | """Returns the model for a particular session. | ||
Takes a keyword argument and searches for the value in the session | ||||
Zachary Sailer
|
r13035 | 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 | ||||
MinRK
|
r15400 | (i.e. session_id, name, path, kernel_id) | ||
Zachary Sailer
|
r13035 | |||
Returns | ||||
------- | ||||
model : dict | ||||
returns a dictionary that includes all the information from the | ||||
session described by the kwarg. | ||||
""" | ||||
MinRK
|
r13101 | if not kwargs: | ||
raise TypeError("must specify a column to query") | ||||
conditions = [] | ||||
for column in kwargs.keys(): | ||||
if column not in self._columns: | ||||
raise TypeError("No such column: %r", column) | ||||
conditions.append("%s=?" % column) | ||||
query = "SELECT * FROM session WHERE %s" % (' AND '.join(conditions)) | ||||
Thomas Kluyver
|
r13377 | self.cursor.execute(query, list(kwargs.values())) | ||
Thomas Kluyver
|
r17773 | try: | ||
Thomas Kluyver
|
r17775 | row = self.cursor.fetchone() | ||
Thomas Kluyver
|
r17773 | except KeyError: | ||
# The kernel is missing, so the session just got deleted. | ||||
Thomas Kluyver
|
r17775 | row = None | ||
Thomas Kluyver
|
r17773 | |||
Thomas Kluyver
|
r17775 | if row is None: | ||
MinRK
|
r13101 | q = [] | ||
for key, value in kwargs.items(): | ||||
q.append("%s=%r" % (key, value)) | ||||
raise web.HTTPError(404, u'Session not found: %s' % (', '.join(q))) | ||||
Thomas Kluyver
|
r17775 | |||
return self.row_to_model(row) | ||||
Zachary Sailer
|
r13035 | |||
def update_session(self, session_id, **kwargs): | ||||
Zachary Sailer
|
r13057 | """Updates the values in the session database. | ||
Changes the values of the session with the given session_id | ||||
Zachary Sailer
|
r13035 | with the values from the keyword arguments. | ||
Zachary Sailer
|
r12985 | |||
Zachary Sailer
|
r13035 | 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. | ||||
""" | ||||
MinRK
|
r13101 | self.get_session(session_id=session_id) | ||
if not kwargs: | ||||
# no changes | ||||
return | ||||
sets = [] | ||||
for column in kwargs.keys(): | ||||
if column not in self._columns: | ||||
raise TypeError("No such column: %r" % column) | ||||
sets.append("%s=?" % column) | ||||
query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets)) | ||||
Thomas Kluyver
|
r13377 | self.cursor.execute(query, list(kwargs.values()) + [session_id]) | ||
MinRK
|
r13101 | |||
Thomas Kluyver
|
r17775 | def row_to_model(self, row): | ||
Zachary Sailer
|
r13035 | """Takes sqlite database session row and turns it into a dictionary""" | ||
Thomas Kluyver
|
r17773 | if row['kernel_id'] not in self.kernel_manager: | ||
Thomas Kluyver
|
r17781 | # The kernel was killed or died without deleting the session. | ||
Thomas Kluyver
|
r17782 | # We can't use delete_session here because that tries to find | ||
# and shut down the kernel. | ||||
self.cursor.execute("DELETE FROM session WHERE session_id=?", | ||||
(row['session_id'],)) | ||||
Thomas Kluyver
|
r17773 | raise KeyError | ||
MinRK
|
r13101 | model = { | ||
'id': row['session_id'], | ||||
'notebook': { | ||||
'name': row['name'], | ||||
'path': row['path'] | ||||
}, | ||||
Thomas Kluyver
|
r17222 | 'kernel': self.kernel_manager.kernel_model(row['kernel_id']) | ||
MinRK
|
r13101 | } | ||
Zachary Sailer
|
r13035 | return model | ||
MinRK
|
r13101 | |||
Zachary Sailer
|
r13035 | def list_sessions(self): | ||
"""Returns a list of dictionaries containing all the information from | ||||
the session database""" | ||||
MinRK
|
r13101 | c = self.cursor.execute("SELECT * FROM session") | ||
Thomas Kluyver
|
r17773 | result = [] | ||
Thomas Kluyver
|
r17782 | # We need to use fetchall() here, because row_to_model can delete rows, | ||
# which messes up the cursor if we're iterating over rows. | ||||
for row in c.fetchall(): | ||||
Thomas Kluyver
|
r17773 | try: | ||
Thomas Kluyver
|
r17775 | result.append(self.row_to_model(row)) | ||
Thomas Kluyver
|
r17773 | except KeyError: | ||
pass | ||||
return result | ||||
Zachary Sailer
|
r13035 | |||
def delete_session(self, session_id): | ||||
"""Deletes the row in the session database with given session_id""" | ||||
# Check that session exists before deleting | ||||
Thomas Kluyver
|
r17222 | session = self.get_session(session_id=session_id) | ||
self.kernel_manager.shutdown_kernel(session['kernel']['id']) | ||||
MinRK
|
r13101 | self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,)) | ||