Show More
@@ -1,128 +1,122 b'' | |||||
1 | """Tornado handlers for the sessions web service.""" |
|
1 | """Tornado handlers for the sessions web service.""" | |
2 |
|
2 | |||
3 | # Copyright (c) IPython Development Team. |
|
3 | # Copyright (c) IPython Development Team. | |
4 | # Distributed under the terms of the Modified BSD License. |
|
4 | # Distributed under the terms of the Modified BSD License. | |
5 |
|
5 | |||
6 | import json |
|
6 | import json | |
7 |
|
7 | |||
8 | from tornado import web |
|
8 | from tornado import web | |
9 |
|
9 | |||
10 | from ...base.handlers import IPythonHandler, json_errors |
|
10 | from ...base.handlers import IPythonHandler, json_errors | |
11 | from IPython.utils.jsonutil import date_default |
|
11 | from IPython.utils.jsonutil import date_default | |
12 | from IPython.html.utils import url_path_join, url_escape |
|
12 | from IPython.html.utils import url_path_join, url_escape | |
13 | from IPython.kernel.kernelspec import NoSuchKernel |
|
13 | from IPython.kernel.kernelspec import NoSuchKernel | |
14 |
|
14 | |||
15 |
|
15 | |||
16 | class SessionRootHandler(IPythonHandler): |
|
16 | class SessionRootHandler(IPythonHandler): | |
17 |
|
17 | |||
18 | @web.authenticated |
|
18 | @web.authenticated | |
19 | @json_errors |
|
19 | @json_errors | |
20 | def get(self): |
|
20 | def get(self): | |
21 | # Return a list of running sessions |
|
21 | # Return a list of running sessions | |
22 | sm = self.session_manager |
|
22 | sm = self.session_manager | |
23 | sessions = sm.list_sessions() |
|
23 | sessions = sm.list_sessions() | |
24 | self.finish(json.dumps(sessions, default=date_default)) |
|
24 | self.finish(json.dumps(sessions, default=date_default)) | |
25 |
|
25 | |||
26 | @web.authenticated |
|
26 | @web.authenticated | |
27 | @json_errors |
|
27 | @json_errors | |
28 | def post(self): |
|
28 | def post(self): | |
29 | # Creates a new session |
|
29 | # Creates a new session | |
30 | #(unless a session already exists for the named nb) |
|
30 | #(unless a session already exists for the named nb) | |
31 | sm = self.session_manager |
|
31 | sm = self.session_manager | |
32 | cm = self.contents_manager |
|
32 | cm = self.contents_manager | |
33 | km = self.kernel_manager |
|
33 | km = self.kernel_manager | |
34 |
|
34 | |||
35 | model = self.get_json_body() |
|
35 | model = self.get_json_body() | |
36 | if model is None: |
|
36 | if model is None: | |
37 | raise web.HTTPError(400, "No JSON data provided") |
|
37 | raise web.HTTPError(400, "No JSON data provided") | |
38 | try: |
|
38 | try: | |
39 | name = model['notebook']['name'] |
|
|||
40 | except KeyError: |
|
|||
41 | raise web.HTTPError(400, "Missing field in JSON data: notebook.name") |
|
|||
42 | try: |
|
|||
43 | path = model['notebook']['path'] |
|
39 | path = model['notebook']['path'] | |
44 | except KeyError: |
|
40 | except KeyError: | |
45 | raise web.HTTPError(400, "Missing field in JSON data: notebook.path") |
|
41 | raise web.HTTPError(400, "Missing field in JSON data: notebook.path") | |
46 | try: |
|
42 | try: | |
47 | kernel_name = model['kernel']['name'] |
|
43 | kernel_name = model['kernel']['name'] | |
48 | except KeyError: |
|
44 | except KeyError: | |
49 | self.log.debug("No kernel name specified, using default kernel") |
|
45 | self.log.debug("No kernel name specified, using default kernel") | |
50 | kernel_name = None |
|
46 | kernel_name = None | |
51 |
|
47 | |||
52 | # Check to see if session exists |
|
48 | # Check to see if session exists | |
53 |
if sm.session_exists( |
|
49 | if sm.session_exists(path=path): | |
54 |
model = sm.get_session( |
|
50 | model = sm.get_session(path=path) | |
55 | else: |
|
51 | else: | |
56 | try: |
|
52 | try: | |
57 |
model = sm.create_session( |
|
53 | model = sm.create_session(path=path, kernel_name=kernel_name) | |
58 | except NoSuchKernel: |
|
54 | except NoSuchKernel: | |
59 | msg = ("The '%s' kernel is not available. Please pick another " |
|
55 | msg = ("The '%s' kernel is not available. Please pick another " | |
60 | "suitable kernel instead, or install that kernel." % kernel_name) |
|
56 | "suitable kernel instead, or install that kernel." % kernel_name) | |
61 | status_msg = '%s not found' % kernel_name |
|
57 | status_msg = '%s not found' % kernel_name | |
62 | self.log.warn('Kernel not found: %s' % kernel_name) |
|
58 | self.log.warn('Kernel not found: %s' % kernel_name) | |
63 | self.set_status(501) |
|
59 | self.set_status(501) | |
64 | self.finish(json.dumps(dict(message=msg, short_message=status_msg))) |
|
60 | self.finish(json.dumps(dict(message=msg, short_message=status_msg))) | |
65 | return |
|
61 | return | |
66 |
|
62 | |||
67 | location = url_path_join(self.base_url, 'api', 'sessions', model['id']) |
|
63 | location = url_path_join(self.base_url, 'api', 'sessions', model['id']) | |
68 | self.set_header('Location', url_escape(location)) |
|
64 | self.set_header('Location', url_escape(location)) | |
69 | self.set_status(201) |
|
65 | self.set_status(201) | |
70 | self.finish(json.dumps(model, default=date_default)) |
|
66 | self.finish(json.dumps(model, default=date_default)) | |
71 |
|
67 | |||
72 | class SessionHandler(IPythonHandler): |
|
68 | class SessionHandler(IPythonHandler): | |
73 |
|
69 | |||
74 | SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE') |
|
70 | SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE') | |
75 |
|
71 | |||
76 | @web.authenticated |
|
72 | @web.authenticated | |
77 | @json_errors |
|
73 | @json_errors | |
78 | def get(self, session_id): |
|
74 | def get(self, session_id): | |
79 | # Returns the JSON model for a single session |
|
75 | # Returns the JSON model for a single session | |
80 | sm = self.session_manager |
|
76 | sm = self.session_manager | |
81 | model = sm.get_session(session_id=session_id) |
|
77 | model = sm.get_session(session_id=session_id) | |
82 | self.finish(json.dumps(model, default=date_default)) |
|
78 | self.finish(json.dumps(model, default=date_default)) | |
83 |
|
79 | |||
84 | @web.authenticated |
|
80 | @web.authenticated | |
85 | @json_errors |
|
81 | @json_errors | |
86 | def patch(self, session_id): |
|
82 | def patch(self, session_id): | |
87 | # Currently, this handler is strictly for renaming notebooks |
|
83 | # Currently, this handler is strictly for renaming notebooks | |
88 | sm = self.session_manager |
|
84 | sm = self.session_manager | |
89 | model = self.get_json_body() |
|
85 | model = self.get_json_body() | |
90 | if model is None: |
|
86 | if model is None: | |
91 | raise web.HTTPError(400, "No JSON data provided") |
|
87 | raise web.HTTPError(400, "No JSON data provided") | |
92 | changes = {} |
|
88 | changes = {} | |
93 | if 'notebook' in model: |
|
89 | if 'notebook' in model: | |
94 | notebook = model['notebook'] |
|
90 | notebook = model['notebook'] | |
95 | if 'name' in notebook: |
|
|||
96 | changes['name'] = notebook['name'] |
|
|||
97 | if 'path' in notebook: |
|
91 | if 'path' in notebook: | |
98 | changes['path'] = notebook['path'] |
|
92 | changes['path'] = notebook['path'] | |
99 |
|
93 | |||
100 | sm.update_session(session_id, **changes) |
|
94 | sm.update_session(session_id, **changes) | |
101 | model = sm.get_session(session_id=session_id) |
|
95 | model = sm.get_session(session_id=session_id) | |
102 | self.finish(json.dumps(model, default=date_default)) |
|
96 | self.finish(json.dumps(model, default=date_default)) | |
103 |
|
97 | |||
104 | @web.authenticated |
|
98 | @web.authenticated | |
105 | @json_errors |
|
99 | @json_errors | |
106 | def delete(self, session_id): |
|
100 | def delete(self, session_id): | |
107 | # Deletes the session with given session_id |
|
101 | # Deletes the session with given session_id | |
108 | sm = self.session_manager |
|
102 | sm = self.session_manager | |
109 | try: |
|
103 | try: | |
110 | sm.delete_session(session_id) |
|
104 | sm.delete_session(session_id) | |
111 | except KeyError: |
|
105 | except KeyError: | |
112 | # the kernel was deleted but the session wasn't! |
|
106 | # the kernel was deleted but the session wasn't! | |
113 | raise web.HTTPError(410, "Kernel deleted before session") |
|
107 | raise web.HTTPError(410, "Kernel deleted before session") | |
114 | self.set_status(204) |
|
108 | self.set_status(204) | |
115 | self.finish() |
|
109 | self.finish() | |
116 |
|
110 | |||
117 |
|
111 | |||
118 | #----------------------------------------------------------------------------- |
|
112 | #----------------------------------------------------------------------------- | |
119 | # URL to handler mappings |
|
113 | # URL to handler mappings | |
120 | #----------------------------------------------------------------------------- |
|
114 | #----------------------------------------------------------------------------- | |
121 |
|
115 | |||
122 | _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)" |
|
116 | _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)" | |
123 |
|
117 | |||
124 | default_handlers = [ |
|
118 | default_handlers = [ | |
125 | (r"/api/sessions/%s" % _session_id_regex, SessionHandler), |
|
119 | (r"/api/sessions/%s" % _session_id_regex, SessionHandler), | |
126 | (r"/api/sessions", SessionRootHandler) |
|
120 | (r"/api/sessions", SessionRootHandler) | |
127 | ] |
|
121 | ] | |
128 |
|
122 |
@@ -1,211 +1,208 b'' | |||||
1 | """A base class session manager.""" |
|
1 | """A base class session manager.""" | |
2 |
|
2 | |||
3 | # Copyright (c) IPython Development Team. |
|
3 | # Copyright (c) IPython Development Team. | |
4 | # Distributed under the terms of the Modified BSD License. |
|
4 | # Distributed under the terms of the Modified BSD License. | |
5 |
|
5 | |||
6 | import uuid |
|
6 | import uuid | |
7 | import sqlite3 |
|
7 | import sqlite3 | |
8 |
|
8 | |||
9 | from tornado import web |
|
9 | from tornado import web | |
10 |
|
10 | |||
11 | from IPython.config.configurable import LoggingConfigurable |
|
11 | from IPython.config.configurable import LoggingConfigurable | |
12 | from IPython.utils.py3compat import unicode_type |
|
12 | from IPython.utils.py3compat import unicode_type | |
13 | from IPython.utils.traitlets import Instance |
|
13 | from IPython.utils.traitlets import Instance | |
14 |
|
14 | |||
15 |
|
15 | |||
16 | class SessionManager(LoggingConfigurable): |
|
16 | class SessionManager(LoggingConfigurable): | |
17 |
|
17 | |||
18 | kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager') |
|
18 | kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager') | |
19 | contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=()) |
|
19 | contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=()) | |
20 |
|
20 | |||
21 | # Session database initialized below |
|
21 | # Session database initialized below | |
22 | _cursor = None |
|
22 | _cursor = None | |
23 | _connection = None |
|
23 | _connection = None | |
24 |
_columns = {'session_id', ' |
|
24 | _columns = {'session_id', 'path', 'kernel_id'} | |
25 |
|
25 | |||
26 | @property |
|
26 | @property | |
27 | def cursor(self): |
|
27 | def cursor(self): | |
28 | """Start a cursor and create a database called 'session'""" |
|
28 | """Start a cursor and create a database called 'session'""" | |
29 | if self._cursor is None: |
|
29 | if self._cursor is None: | |
30 | self._cursor = self.connection.cursor() |
|
30 | self._cursor = self.connection.cursor() | |
31 | self._cursor.execute("""CREATE TABLE session |
|
31 | self._cursor.execute("""CREATE TABLE session | |
32 |
(session_id, |
|
32 | (session_id, path, kernel_id)""") | |
33 | return self._cursor |
|
33 | return self._cursor | |
34 |
|
34 | |||
35 | @property |
|
35 | @property | |
36 | def connection(self): |
|
36 | def connection(self): | |
37 | """Start a database connection""" |
|
37 | """Start a database connection""" | |
38 | if self._connection is None: |
|
38 | if self._connection is None: | |
39 | self._connection = sqlite3.connect(':memory:') |
|
39 | self._connection = sqlite3.connect(':memory:') | |
40 | self._connection.row_factory = sqlite3.Row |
|
40 | self._connection.row_factory = sqlite3.Row | |
41 | return self._connection |
|
41 | return self._connection | |
42 |
|
42 | |||
43 | def __del__(self): |
|
43 | def __del__(self): | |
44 | """Close connection once SessionManager closes""" |
|
44 | """Close connection once SessionManager closes""" | |
45 | self.cursor.close() |
|
45 | self.cursor.close() | |
46 |
|
46 | |||
47 |
def session_exists(self, |
|
47 | def session_exists(self, path): | |
48 | """Check to see if the session for a given notebook exists""" |
|
48 | """Check to see if the session for a given notebook exists""" | |
49 |
self.cursor.execute("SELECT * FROM session WHERE |
|
49 | self.cursor.execute("SELECT * FROM session WHERE path=?", (path,)) | |
50 | reply = self.cursor.fetchone() |
|
50 | reply = self.cursor.fetchone() | |
51 | if reply is None: |
|
51 | if reply is None: | |
52 | return False |
|
52 | return False | |
53 | else: |
|
53 | else: | |
54 | return True |
|
54 | return True | |
55 |
|
55 | |||
56 | def new_session_id(self): |
|
56 | def new_session_id(self): | |
57 | "Create a uuid for a new session" |
|
57 | "Create a uuid for a new session" | |
58 | return unicode_type(uuid.uuid4()) |
|
58 | return unicode_type(uuid.uuid4()) | |
59 |
|
59 | |||
60 |
def create_session(self |
|
60 | def create_session(self, path=None, kernel_name=None): | |
61 | """Creates a session and returns its model""" |
|
61 | """Creates a session and returns its model""" | |
62 | session_id = self.new_session_id() |
|
62 | session_id = self.new_session_id() | |
63 | # allow nbm to specify kernels cwd |
|
63 | # allow nbm to specify kernels cwd | |
64 |
kernel_path = self.contents_manager.get_kernel_path( |
|
64 | kernel_path = self.contents_manager.get_kernel_path(path=path) | |
65 | kernel_id = self.kernel_manager.start_kernel(path=kernel_path, |
|
65 | kernel_id = self.kernel_manager.start_kernel(path=kernel_path, | |
66 | kernel_name=kernel_name) |
|
66 | kernel_name=kernel_name) | |
67 |
return self.save_session(session_id, |
|
67 | return self.save_session(session_id, path=path, | |
68 | kernel_id=kernel_id) |
|
68 | kernel_id=kernel_id) | |
69 |
|
69 | |||
70 |
def save_session(self, session_id |
|
70 | def save_session(self, session_id, path=None, kernel_id=None): | |
71 | """Saves the items for the session with the given session_id |
|
71 | """Saves the items for the session with the given session_id | |
72 |
|
72 | |||
73 | Given a session_id (and any other of the arguments), this method |
|
73 | Given a session_id (and any other of the arguments), this method | |
74 | creates a row in the sqlite session database that holds the information |
|
74 | creates a row in the sqlite session database that holds the information | |
75 | for a session. |
|
75 | for a session. | |
76 |
|
76 | |||
77 | Parameters |
|
77 | Parameters | |
78 | ---------- |
|
78 | ---------- | |
79 | session_id : str |
|
79 | session_id : str | |
80 | uuid for the session; this method must be given a session_id |
|
80 | uuid for the session; this method must be given a session_id | |
81 | name : str |
|
|||
82 | the .ipynb notebook name that started the session |
|
|||
83 | path : str |
|
81 | path : str | |
84 |
the path |
|
82 | the path for the given notebook | |
85 | kernel_id : str |
|
83 | kernel_id : str | |
86 | a uuid for the kernel associated with this session |
|
84 | a uuid for the kernel associated with this session | |
87 |
|
85 | |||
88 | Returns |
|
86 | Returns | |
89 | ------- |
|
87 | ------- | |
90 | model : dict |
|
88 | model : dict | |
91 | a dictionary of the session model |
|
89 | a dictionary of the session model | |
92 | """ |
|
90 | """ | |
93 |
self.cursor.execute("INSERT INTO session VALUES (?,?,? |
|
91 | self.cursor.execute("INSERT INTO session VALUES (?,?,?)", | |
94 |
(session_id |
|
92 | (session_id, path, kernel_id) | |
95 | ) |
|
93 | ) | |
96 | return self.get_session(session_id=session_id) |
|
94 | return self.get_session(session_id=session_id) | |
97 |
|
95 | |||
98 | def get_session(self, **kwargs): |
|
96 | def get_session(self, **kwargs): | |
99 | """Returns the model for a particular session. |
|
97 | """Returns the model for a particular session. | |
100 |
|
98 | |||
101 | Takes a keyword argument and searches for the value in the session |
|
99 | Takes a keyword argument and searches for the value in the session | |
102 | database, then returns the rest of the session's info. |
|
100 | database, then returns the rest of the session's info. | |
103 |
|
101 | |||
104 | Parameters |
|
102 | Parameters | |
105 | ---------- |
|
103 | ---------- | |
106 | **kwargs : keyword argument |
|
104 | **kwargs : keyword argument | |
107 | must be given one of the keywords and values from the session database |
|
105 | must be given one of the keywords and values from the session database | |
108 |
(i.e. session_id, |
|
106 | (i.e. session_id, path, kernel_id) | |
109 |
|
107 | |||
110 | Returns |
|
108 | Returns | |
111 | ------- |
|
109 | ------- | |
112 | model : dict |
|
110 | model : dict | |
113 | returns a dictionary that includes all the information from the |
|
111 | returns a dictionary that includes all the information from the | |
114 | session described by the kwarg. |
|
112 | session described by the kwarg. | |
115 | """ |
|
113 | """ | |
116 | if not kwargs: |
|
114 | if not kwargs: | |
117 | raise TypeError("must specify a column to query") |
|
115 | raise TypeError("must specify a column to query") | |
118 |
|
116 | |||
119 | conditions = [] |
|
117 | conditions = [] | |
120 | for column in kwargs.keys(): |
|
118 | for column in kwargs.keys(): | |
121 | if column not in self._columns: |
|
119 | if column not in self._columns: | |
122 | raise TypeError("No such column: %r", column) |
|
120 | raise TypeError("No such column: %r", column) | |
123 | conditions.append("%s=?" % column) |
|
121 | conditions.append("%s=?" % column) | |
124 |
|
122 | |||
125 | query = "SELECT * FROM session WHERE %s" % (' AND '.join(conditions)) |
|
123 | query = "SELECT * FROM session WHERE %s" % (' AND '.join(conditions)) | |
126 |
|
124 | |||
127 | self.cursor.execute(query, list(kwargs.values())) |
|
125 | self.cursor.execute(query, list(kwargs.values())) | |
128 | try: |
|
126 | try: | |
129 | row = self.cursor.fetchone() |
|
127 | row = self.cursor.fetchone() | |
130 | except KeyError: |
|
128 | except KeyError: | |
131 | # The kernel is missing, so the session just got deleted. |
|
129 | # The kernel is missing, so the session just got deleted. | |
132 | row = None |
|
130 | row = None | |
133 |
|
131 | |||
134 | if row is None: |
|
132 | if row is None: | |
135 | q = [] |
|
133 | q = [] | |
136 | for key, value in kwargs.items(): |
|
134 | for key, value in kwargs.items(): | |
137 | q.append("%s=%r" % (key, value)) |
|
135 | q.append("%s=%r" % (key, value)) | |
138 |
|
136 | |||
139 | raise web.HTTPError(404, u'Session not found: %s' % (', '.join(q))) |
|
137 | raise web.HTTPError(404, u'Session not found: %s' % (', '.join(q))) | |
140 |
|
138 | |||
141 | return self.row_to_model(row) |
|
139 | return self.row_to_model(row) | |
142 |
|
140 | |||
143 | def update_session(self, session_id, **kwargs): |
|
141 | def update_session(self, session_id, **kwargs): | |
144 | """Updates the values in the session database. |
|
142 | """Updates the values in the session database. | |
145 |
|
143 | |||
146 | Changes the values of the session with the given session_id |
|
144 | Changes the values of the session with the given session_id | |
147 | with the values from the keyword arguments. |
|
145 | with the values from the keyword arguments. | |
148 |
|
146 | |||
149 | Parameters |
|
147 | Parameters | |
150 | ---------- |
|
148 | ---------- | |
151 | session_id : str |
|
149 | session_id : str | |
152 | a uuid that identifies a session in the sqlite3 database |
|
150 | a uuid that identifies a session in the sqlite3 database | |
153 | **kwargs : str |
|
151 | **kwargs : str | |
154 | the key must correspond to a column title in session database, |
|
152 | the key must correspond to a column title in session database, | |
155 | and the value replaces the current value in the session |
|
153 | and the value replaces the current value in the session | |
156 | with session_id. |
|
154 | with session_id. | |
157 | """ |
|
155 | """ | |
158 | self.get_session(session_id=session_id) |
|
156 | self.get_session(session_id=session_id) | |
159 |
|
157 | |||
160 | if not kwargs: |
|
158 | if not kwargs: | |
161 | # no changes |
|
159 | # no changes | |
162 | return |
|
160 | return | |
163 |
|
161 | |||
164 | sets = [] |
|
162 | sets = [] | |
165 | for column in kwargs.keys(): |
|
163 | for column in kwargs.keys(): | |
166 | if column not in self._columns: |
|
164 | if column not in self._columns: | |
167 | raise TypeError("No such column: %r" % column) |
|
165 | raise TypeError("No such column: %r" % column) | |
168 | sets.append("%s=?" % column) |
|
166 | sets.append("%s=?" % column) | |
169 | query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets)) |
|
167 | query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets)) | |
170 | self.cursor.execute(query, list(kwargs.values()) + [session_id]) |
|
168 | self.cursor.execute(query, list(kwargs.values()) + [session_id]) | |
171 |
|
169 | |||
172 | def row_to_model(self, row): |
|
170 | def row_to_model(self, row): | |
173 | """Takes sqlite database session row and turns it into a dictionary""" |
|
171 | """Takes sqlite database session row and turns it into a dictionary""" | |
174 | if row['kernel_id'] not in self.kernel_manager: |
|
172 | if row['kernel_id'] not in self.kernel_manager: | |
175 | # The kernel was killed or died without deleting the session. |
|
173 | # The kernel was killed or died without deleting the session. | |
176 | # We can't use delete_session here because that tries to find |
|
174 | # We can't use delete_session here because that tries to find | |
177 | # and shut down the kernel. |
|
175 | # and shut down the kernel. | |
178 | self.cursor.execute("DELETE FROM session WHERE session_id=?", |
|
176 | self.cursor.execute("DELETE FROM session WHERE session_id=?", | |
179 | (row['session_id'],)) |
|
177 | (row['session_id'],)) | |
180 | raise KeyError |
|
178 | raise KeyError | |
181 |
|
179 | |||
182 | model = { |
|
180 | model = { | |
183 | 'id': row['session_id'], |
|
181 | 'id': row['session_id'], | |
184 | 'notebook': { |
|
182 | 'notebook': { | |
185 | 'name': row['name'], |
|
|||
186 | 'path': row['path'] |
|
183 | 'path': row['path'] | |
187 | }, |
|
184 | }, | |
188 | 'kernel': self.kernel_manager.kernel_model(row['kernel_id']) |
|
185 | 'kernel': self.kernel_manager.kernel_model(row['kernel_id']) | |
189 | } |
|
186 | } | |
190 | return model |
|
187 | return model | |
191 |
|
188 | |||
192 | def list_sessions(self): |
|
189 | def list_sessions(self): | |
193 | """Returns a list of dictionaries containing all the information from |
|
190 | """Returns a list of dictionaries containing all the information from | |
194 | the session database""" |
|
191 | the session database""" | |
195 | c = self.cursor.execute("SELECT * FROM session") |
|
192 | c = self.cursor.execute("SELECT * FROM session") | |
196 | result = [] |
|
193 | result = [] | |
197 | # We need to use fetchall() here, because row_to_model can delete rows, |
|
194 | # We need to use fetchall() here, because row_to_model can delete rows, | |
198 | # which messes up the cursor if we're iterating over rows. |
|
195 | # which messes up the cursor if we're iterating over rows. | |
199 | for row in c.fetchall(): |
|
196 | for row in c.fetchall(): | |
200 | try: |
|
197 | try: | |
201 | result.append(self.row_to_model(row)) |
|
198 | result.append(self.row_to_model(row)) | |
202 | except KeyError: |
|
199 | except KeyError: | |
203 | pass |
|
200 | pass | |
204 | return result |
|
201 | return result | |
205 |
|
202 | |||
206 | def delete_session(self, session_id): |
|
203 | def delete_session(self, session_id): | |
207 | """Deletes the row in the session database with given session_id""" |
|
204 | """Deletes the row in the session database with given session_id""" | |
208 | # Check that session exists before deleting |
|
205 | # Check that session exists before deleting | |
209 | session = self.get_session(session_id=session_id) |
|
206 | session = self.get_session(session_id=session_id) | |
210 | self.kernel_manager.shutdown_kernel(session['kernel']['id']) |
|
207 | self.kernel_manager.shutdown_kernel(session['kernel']['id']) | |
211 | self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,)) |
|
208 | self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,)) |
@@ -1,140 +1,154 b'' | |||||
1 | """Tests for the session manager.""" |
|
1 | """Tests for the session manager.""" | |
2 |
|
2 | |||
3 | from unittest import TestCase |
|
3 | from unittest import TestCase | |
4 |
|
4 | |||
5 | from tornado import web |
|
5 | from tornado import web | |
6 |
|
6 | |||
7 | from ..sessionmanager import SessionManager |
|
7 | from ..sessionmanager import SessionManager | |
8 | from IPython.html.services.kernels.kernelmanager import MappingKernelManager |
|
8 | from IPython.html.services.kernels.kernelmanager import MappingKernelManager | |
9 |
|
9 | |||
10 | class DummyKernel(object): |
|
10 | class DummyKernel(object): | |
11 | def __init__(self, kernel_name='python'): |
|
11 | def __init__(self, kernel_name='python'): | |
12 | self.kernel_name = kernel_name |
|
12 | self.kernel_name = kernel_name | |
13 |
|
13 | |||
14 | class DummyMKM(MappingKernelManager): |
|
14 | class DummyMKM(MappingKernelManager): | |
15 | """MappingKernelManager interface that doesn't start kernels, for testing""" |
|
15 | """MappingKernelManager interface that doesn't start kernels, for testing""" | |
16 | def __init__(self, *args, **kwargs): |
|
16 | def __init__(self, *args, **kwargs): | |
17 | super(DummyMKM, self).__init__(*args, **kwargs) |
|
17 | super(DummyMKM, self).__init__(*args, **kwargs) | |
18 | self.id_letters = iter(u'ABCDEFGHIJK') |
|
18 | self.id_letters = iter(u'ABCDEFGHIJK') | |
19 |
|
19 | |||
20 | def _new_id(self): |
|
20 | def _new_id(self): | |
21 | return next(self.id_letters) |
|
21 | return next(self.id_letters) | |
22 |
|
22 | |||
23 | def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs): |
|
23 | def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs): | |
24 | kernel_id = kernel_id or self._new_id() |
|
24 | kernel_id = kernel_id or self._new_id() | |
25 | self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name) |
|
25 | self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name) | |
26 | return kernel_id |
|
26 | return kernel_id | |
27 |
|
27 | |||
28 | def shutdown_kernel(self, kernel_id, now=False): |
|
28 | def shutdown_kernel(self, kernel_id, now=False): | |
29 | del self._kernels[kernel_id] |
|
29 | del self._kernels[kernel_id] | |
30 |
|
30 | |||
31 | class TestSessionManager(TestCase): |
|
31 | class TestSessionManager(TestCase): | |
32 |
|
32 | |||
33 | def test_get_session(self): |
|
33 | def test_get_session(self): | |
34 | sm = SessionManager(kernel_manager=DummyMKM()) |
|
34 | sm = SessionManager(kernel_manager=DummyMKM()) | |
35 |
session_id = sm.create_session( |
|
35 | session_id = sm.create_session(path='/path/to/test.ipynb', | |
36 | kernel_name='bar')['id'] |
|
36 | kernel_name='bar')['id'] | |
37 | model = sm.get_session(session_id=session_id) |
|
37 | model = sm.get_session(session_id=session_id) | |
38 | expected = {'id':session_id, |
|
38 | expected = {'id':session_id, | |
39 |
'notebook':{' |
|
39 | 'notebook':{'path': u'/path/to/test.ipynb'}, | |
40 | 'kernel': {'id':u'A', 'name': 'bar'}} |
|
40 | 'kernel': {'id':u'A', 'name': 'bar'}} | |
41 | self.assertEqual(model, expected) |
|
41 | self.assertEqual(model, expected) | |
42 |
|
42 | |||
43 | def test_bad_get_session(self): |
|
43 | def test_bad_get_session(self): | |
44 | # Should raise error if a bad key is passed to the database. |
|
44 | # Should raise error if a bad key is passed to the database. | |
45 | sm = SessionManager(kernel_manager=DummyMKM()) |
|
45 | sm = SessionManager(kernel_manager=DummyMKM()) | |
46 |
session_id = sm.create_session( |
|
46 | session_id = sm.create_session(path='/path/to/test.ipynb', | |
47 | kernel_name='foo')['id'] |
|
47 | kernel_name='foo')['id'] | |
48 | self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword |
|
48 | self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword | |
49 |
|
49 | |||
50 | def test_get_session_dead_kernel(self): |
|
50 | def test_get_session_dead_kernel(self): | |
51 | sm = SessionManager(kernel_manager=DummyMKM()) |
|
51 | sm = SessionManager(kernel_manager=DummyMKM()) | |
52 |
session = sm.create_session( |
|
52 | session = sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python') | |
53 | # kill the kernel |
|
53 | # kill the kernel | |
54 | sm.kernel_manager.shutdown_kernel(session['kernel']['id']) |
|
54 | sm.kernel_manager.shutdown_kernel(session['kernel']['id']) | |
55 | with self.assertRaises(KeyError): |
|
55 | with self.assertRaises(KeyError): | |
56 | sm.get_session(session_id=session['id']) |
|
56 | sm.get_session(session_id=session['id']) | |
57 | # no sessions left |
|
57 | # no sessions left | |
58 | listed = sm.list_sessions() |
|
58 | listed = sm.list_sessions() | |
59 | self.assertEqual(listed, []) |
|
59 | self.assertEqual(listed, []) | |
60 |
|
60 | |||
61 | def test_list_sessions(self): |
|
61 | def test_list_sessions(self): | |
62 | sm = SessionManager(kernel_manager=DummyMKM()) |
|
62 | sm = SessionManager(kernel_manager=DummyMKM()) | |
63 | sessions = [ |
|
63 | sessions = [ | |
64 |
sm.create_session( |
|
64 | sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'), | |
65 |
sm.create_session( |
|
65 | sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'), | |
66 |
sm.create_session( |
|
66 | sm.create_session(path='/path/to/3/test3.ipynb', kernel_name='python'), | |
67 | ] |
|
67 | ] | |
68 | sessions = sm.list_sessions() |
|
68 | sessions = sm.list_sessions() | |
69 | expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb', |
|
69 | expected = [ | |
70 | 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}}, |
|
70 | { | |
71 |
|
|
71 | 'id':sessions[0]['id'], | |
72 | 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}}, |
|
72 | 'notebook':{'path': u'/path/to/1/test1.ipynb'}, | |
73 | {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb', |
|
73 | 'kernel':{'id':u'A', 'name':'python'} | |
74 | 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}] |
|
74 | }, { | |
|
75 | 'id':sessions[1]['id'], | |||
|
76 | 'notebook': {'path': u'/path/to/2/test2.ipynb'}, | |||
|
77 | 'kernel':{'id':u'B', 'name':'python'} | |||
|
78 | }, { | |||
|
79 | 'id':sessions[2]['id'], | |||
|
80 | 'notebook':{'path': u'/path/to/3/test3.ipynb'}, | |||
|
81 | 'kernel':{'id':u'C', 'name':'python'} | |||
|
82 | } | |||
|
83 | ] | |||
75 | self.assertEqual(sessions, expected) |
|
84 | self.assertEqual(sessions, expected) | |
76 |
|
85 | |||
77 | def test_list_sessions_dead_kernel(self): |
|
86 | def test_list_sessions_dead_kernel(self): | |
78 | sm = SessionManager(kernel_manager=DummyMKM()) |
|
87 | sm = SessionManager(kernel_manager=DummyMKM()) | |
79 | sessions = [ |
|
88 | sessions = [ | |
80 |
sm.create_session( |
|
89 | sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'), | |
81 |
sm.create_session( |
|
90 | sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'), | |
82 | ] |
|
91 | ] | |
83 | # kill one of the kernels |
|
92 | # kill one of the kernels | |
84 | sm.kernel_manager.shutdown_kernel(sessions[0]['kernel']['id']) |
|
93 | sm.kernel_manager.shutdown_kernel(sessions[0]['kernel']['id']) | |
85 | listed = sm.list_sessions() |
|
94 | listed = sm.list_sessions() | |
86 | expected = [ |
|
95 | expected = [ | |
87 | { |
|
96 | { | |
88 | 'id': sessions[1]['id'], |
|
97 | 'id': sessions[1]['id'], | |
89 | 'notebook': { |
|
98 | 'notebook': { | |
90 |
' |
|
99 | 'path': u'/path/to/2/test2.ipynb', | |
91 | 'path': u'/path/to/2/', |
|
|||
92 | }, |
|
100 | }, | |
93 | 'kernel': { |
|
101 | 'kernel': { | |
94 | 'id': u'B', |
|
102 | 'id': u'B', | |
95 | 'name':'python', |
|
103 | 'name':'python', | |
96 | } |
|
104 | } | |
97 | } |
|
105 | } | |
98 | ] |
|
106 | ] | |
99 | self.assertEqual(listed, expected) |
|
107 | self.assertEqual(listed, expected) | |
100 |
|
108 | |||
101 | def test_update_session(self): |
|
109 | def test_update_session(self): | |
102 | sm = SessionManager(kernel_manager=DummyMKM()) |
|
110 | sm = SessionManager(kernel_manager=DummyMKM()) | |
103 |
session_id = sm.create_session( |
|
111 | session_id = sm.create_session(path='/path/to/test.ipynb', | |
104 | kernel_name='julia')['id'] |
|
112 | kernel_name='julia')['id'] | |
105 |
sm.update_session(session_id, |
|
113 | sm.update_session(session_id, path='/path/to/new_name.ipynb') | |
106 | model = sm.get_session(session_id=session_id) |
|
114 | model = sm.get_session(session_id=session_id) | |
107 | expected = {'id':session_id, |
|
115 | expected = {'id':session_id, | |
108 |
'notebook':{' |
|
116 | 'notebook':{'path': u'/path/to/new_name.ipynb'}, | |
109 | 'kernel':{'id':u'A', 'name':'julia'}} |
|
117 | 'kernel':{'id':u'A', 'name':'julia'}} | |
110 | self.assertEqual(model, expected) |
|
118 | self.assertEqual(model, expected) | |
111 |
|
119 | |||
112 | def test_bad_update_session(self): |
|
120 | def test_bad_update_session(self): | |
113 | # try to update a session with a bad keyword ~ raise error |
|
121 | # try to update a session with a bad keyword ~ raise error | |
114 | sm = SessionManager(kernel_manager=DummyMKM()) |
|
122 | sm = SessionManager(kernel_manager=DummyMKM()) | |
115 |
session_id = sm.create_session( |
|
123 | session_id = sm.create_session(path='/path/to/test.ipynb', | |
116 | kernel_name='ir')['id'] |
|
124 | kernel_name='ir')['id'] | |
117 | self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword |
|
125 | self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword | |
118 |
|
126 | |||
119 | def test_delete_session(self): |
|
127 | def test_delete_session(self): | |
120 | sm = SessionManager(kernel_manager=DummyMKM()) |
|
128 | sm = SessionManager(kernel_manager=DummyMKM()) | |
121 | sessions = [ |
|
129 | sessions = [ | |
122 |
sm.create_session( |
|
130 | sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'), | |
123 |
sm.create_session( |
|
131 | sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'), | |
124 |
sm.create_session( |
|
132 | sm.create_session(path='/path/to/3/test3.ipynb', kernel_name='python'), | |
125 | ] |
|
133 | ] | |
126 | sm.delete_session(sessions[1]['id']) |
|
134 | sm.delete_session(sessions[1]['id']) | |
127 | new_sessions = sm.list_sessions() |
|
135 | new_sessions = sm.list_sessions() | |
128 | expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb', |
|
136 | expected = [{ | |
129 | 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}}, |
|
137 | 'id': sessions[0]['id'], | |
130 |
|
|
138 | 'notebook': {'path': u'/path/to/1/test1.ipynb'}, | |
131 |
|
|
139 | 'kernel': {'id':u'A', 'name':'python'} | |
|
140 | }, { | |||
|
141 | 'id': sessions[2]['id'], | |||
|
142 | 'notebook': {'path': u'/path/to/3/test3.ipynb'}, | |||
|
143 | 'kernel': {'id':u'C', 'name':'python'} | |||
|
144 | } | |||
|
145 | ] | |||
132 | self.assertEqual(new_sessions, expected) |
|
146 | self.assertEqual(new_sessions, expected) | |
133 |
|
147 | |||
134 | def test_bad_delete_session(self): |
|
148 | def test_bad_delete_session(self): | |
135 | # try to delete a session that doesn't exist ~ raise error |
|
149 | # try to delete a session that doesn't exist ~ raise error | |
136 | sm = SessionManager(kernel_manager=DummyMKM()) |
|
150 | sm = SessionManager(kernel_manager=DummyMKM()) | |
137 |
sm.create_session( |
|
151 | sm.create_session(path='/path/to/test.ipynb', kernel_name='python') | |
138 | self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword |
|
152 | self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword | |
139 | self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant |
|
153 | self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant | |
140 |
|
154 |
@@ -1,117 +1,115 b'' | |||||
1 | """Test the sessions web service API.""" |
|
1 | """Test the sessions web service API.""" | |
2 |
|
2 | |||
3 | import errno |
|
3 | import errno | |
4 | import io |
|
4 | import io | |
5 | import os |
|
5 | import os | |
6 | import json |
|
6 | import json | |
7 | import requests |
|
7 | import requests | |
8 | import shutil |
|
8 | import shutil | |
9 |
|
9 | |||
10 | pjoin = os.path.join |
|
10 | pjoin = os.path.join | |
11 |
|
11 | |||
12 | from IPython.html.utils import url_path_join |
|
12 | from IPython.html.utils import url_path_join | |
13 | from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error |
|
13 | from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error | |
14 | from IPython.nbformat.v4 import new_notebook |
|
14 | from IPython.nbformat.v4 import new_notebook | |
15 | from IPython.nbformat import write |
|
15 | from IPython.nbformat import write | |
16 |
|
16 | |||
17 | class SessionAPI(object): |
|
17 | class SessionAPI(object): | |
18 | """Wrapper for notebook API calls.""" |
|
18 | """Wrapper for notebook API calls.""" | |
19 | def __init__(self, base_url): |
|
19 | def __init__(self, base_url): | |
20 | self.base_url = base_url |
|
20 | self.base_url = base_url | |
21 |
|
21 | |||
22 | def _req(self, verb, path, body=None): |
|
22 | def _req(self, verb, path, body=None): | |
23 | response = requests.request(verb, |
|
23 | response = requests.request(verb, | |
24 | url_path_join(self.base_url, 'api/sessions', path), data=body) |
|
24 | url_path_join(self.base_url, 'api/sessions', path), data=body) | |
25 |
|
25 | |||
26 | if 400 <= response.status_code < 600: |
|
26 | if 400 <= response.status_code < 600: | |
27 | try: |
|
27 | try: | |
28 | response.reason = response.json()['message'] |
|
28 | response.reason = response.json()['message'] | |
29 | except: |
|
29 | except: | |
30 | pass |
|
30 | pass | |
31 | response.raise_for_status() |
|
31 | response.raise_for_status() | |
32 |
|
32 | |||
33 | return response |
|
33 | return response | |
34 |
|
34 | |||
35 | def list(self): |
|
35 | def list(self): | |
36 | return self._req('GET', '') |
|
36 | return self._req('GET', '') | |
37 |
|
37 | |||
38 | def get(self, id): |
|
38 | def get(self, id): | |
39 | return self._req('GET', id) |
|
39 | return self._req('GET', id) | |
40 |
|
40 | |||
41 |
def create(self |
|
41 | def create(self, path, kernel_name='python'): | |
42 |
body = json.dumps({'notebook': {' |
|
42 | body = json.dumps({'notebook': {'path':path}, | |
43 | 'kernel': {'name': kernel_name}}) |
|
43 | 'kernel': {'name': kernel_name}}) | |
44 | return self._req('POST', '', body) |
|
44 | return self._req('POST', '', body) | |
45 |
|
45 | |||
46 |
def modify(self, id, |
|
46 | def modify(self, id, path): | |
47 |
body = json.dumps({'notebook': {' |
|
47 | body = json.dumps({'notebook': {'path':path}}) | |
48 | return self._req('PATCH', id, body) |
|
48 | return self._req('PATCH', id, body) | |
49 |
|
49 | |||
50 | def delete(self, id): |
|
50 | def delete(self, id): | |
51 | return self._req('DELETE', id) |
|
51 | return self._req('DELETE', id) | |
52 |
|
52 | |||
53 | class SessionAPITest(NotebookTestBase): |
|
53 | class SessionAPITest(NotebookTestBase): | |
54 | """Test the sessions web service API""" |
|
54 | """Test the sessions web service API""" | |
55 | def setUp(self): |
|
55 | def setUp(self): | |
56 | nbdir = self.notebook_dir.name |
|
56 | nbdir = self.notebook_dir.name | |
57 | try: |
|
57 | try: | |
58 | os.mkdir(pjoin(nbdir, 'foo')) |
|
58 | os.mkdir(pjoin(nbdir, 'foo')) | |
59 | except OSError as e: |
|
59 | except OSError as e: | |
60 | # Deleting the folder in an earlier test may have failed |
|
60 | # Deleting the folder in an earlier test may have failed | |
61 | if e.errno != errno.EEXIST: |
|
61 | if e.errno != errno.EEXIST: | |
62 | raise |
|
62 | raise | |
63 |
|
63 | |||
64 | with io.open(pjoin(nbdir, 'foo', 'nb1.ipynb'), 'w', |
|
64 | with io.open(pjoin(nbdir, 'foo', 'nb1.ipynb'), 'w', | |
65 | encoding='utf-8') as f: |
|
65 | encoding='utf-8') as f: | |
66 | nb = new_notebook() |
|
66 | nb = new_notebook() | |
67 | write(nb, f, version=4) |
|
67 | write(nb, f, version=4) | |
68 |
|
68 | |||
69 | self.sess_api = SessionAPI(self.base_url()) |
|
69 | self.sess_api = SessionAPI(self.base_url()) | |
70 |
|
70 | |||
71 | def tearDown(self): |
|
71 | def tearDown(self): | |
72 | for session in self.sess_api.list().json(): |
|
72 | for session in self.sess_api.list().json(): | |
73 | self.sess_api.delete(session['id']) |
|
73 | self.sess_api.delete(session['id']) | |
74 | shutil.rmtree(pjoin(self.notebook_dir.name, 'foo'), |
|
74 | shutil.rmtree(pjoin(self.notebook_dir.name, 'foo'), | |
75 | ignore_errors=True) |
|
75 | ignore_errors=True) | |
76 |
|
76 | |||
77 | def test_create(self): |
|
77 | def test_create(self): | |
78 | sessions = self.sess_api.list().json() |
|
78 | sessions = self.sess_api.list().json() | |
79 | self.assertEqual(len(sessions), 0) |
|
79 | self.assertEqual(len(sessions), 0) | |
80 |
|
80 | |||
81 |
resp = self.sess_api.create('nb1.ipynb |
|
81 | resp = self.sess_api.create('foo/nb1.ipynb') | |
82 | self.assertEqual(resp.status_code, 201) |
|
82 | self.assertEqual(resp.status_code, 201) | |
83 | newsession = resp.json() |
|
83 | newsession = resp.json() | |
84 | self.assertIn('id', newsession) |
|
84 | self.assertIn('id', newsession) | |
85 |
self.assertEqual(newsession['notebook'][' |
|
85 | self.assertEqual(newsession['notebook']['path'], 'foo/nb1.ipynb') | |
86 | self.assertEqual(newsession['notebook']['path'], 'foo') |
|
|||
87 | self.assertEqual(resp.headers['Location'], '/api/sessions/{0}'.format(newsession['id'])) |
|
86 | self.assertEqual(resp.headers['Location'], '/api/sessions/{0}'.format(newsession['id'])) | |
88 |
|
87 | |||
89 | sessions = self.sess_api.list().json() |
|
88 | sessions = self.sess_api.list().json() | |
90 | self.assertEqual(sessions, [newsession]) |
|
89 | self.assertEqual(sessions, [newsession]) | |
91 |
|
90 | |||
92 | # Retrieve it |
|
91 | # Retrieve it | |
93 | sid = newsession['id'] |
|
92 | sid = newsession['id'] | |
94 | got = self.sess_api.get(sid).json() |
|
93 | got = self.sess_api.get(sid).json() | |
95 | self.assertEqual(got, newsession) |
|
94 | self.assertEqual(got, newsession) | |
96 |
|
95 | |||
97 | def test_delete(self): |
|
96 | def test_delete(self): | |
98 |
newsession = self.sess_api.create('nb1.ipynb |
|
97 | newsession = self.sess_api.create('foo/nb1.ipynb').json() | |
99 | sid = newsession['id'] |
|
98 | sid = newsession['id'] | |
100 |
|
99 | |||
101 | resp = self.sess_api.delete(sid) |
|
100 | resp = self.sess_api.delete(sid) | |
102 | self.assertEqual(resp.status_code, 204) |
|
101 | self.assertEqual(resp.status_code, 204) | |
103 |
|
102 | |||
104 | sessions = self.sess_api.list().json() |
|
103 | sessions = self.sess_api.list().json() | |
105 | self.assertEqual(sessions, []) |
|
104 | self.assertEqual(sessions, []) | |
106 |
|
105 | |||
107 | with assert_http_error(404): |
|
106 | with assert_http_error(404): | |
108 | self.sess_api.get(sid) |
|
107 | self.sess_api.get(sid) | |
109 |
|
108 | |||
110 | def test_modify(self): |
|
109 | def test_modify(self): | |
111 |
newsession = self.sess_api.create('nb1.ipynb |
|
110 | newsession = self.sess_api.create('foo/nb1.ipynb').json() | |
112 | sid = newsession['id'] |
|
111 | sid = newsession['id'] | |
113 |
|
112 | |||
114 |
changed = self.sess_api.modify(sid, 'nb2.ipynb' |
|
113 | changed = self.sess_api.modify(sid, 'nb2.ipynb').json() | |
115 | self.assertEqual(changed['id'], sid) |
|
114 | self.assertEqual(changed['id'], sid) | |
116 |
self.assertEqual(changed['notebook'][' |
|
115 | self.assertEqual(changed['notebook']['path'], 'nb2.ipynb') | |
117 | self.assertEqual(changed['notebook']['path'], '') |
|
General Comments 0
You need to be logged in to leave comments.
Login now