File size: 7,522 Bytes
d1ceb73 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
"""Tornado handlers for the sessions web service.
Preliminary documentation at https://github.com/ipython/ipython/wiki/IPEP-16%3A-Notebook-multi-directory-dashboard-and-URL-mapping#sessions-api
"""
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import asyncio
import json
try:
from jupyter_client.jsonutil import json_default
except ImportError:
from jupyter_client.jsonutil import date_default as json_default
from jupyter_client.kernelspec import NoSuchKernel
from jupyter_core.utils import ensure_async
from tornado import web
from jupyter_server.auth.decorator import authorized
from jupyter_server.utils import url_path_join
from ...base.handlers import APIHandler
AUTH_RESOURCE = "sessions"
class SessionsAPIHandler(APIHandler):
"""A Sessions API handler."""
auth_resource = AUTH_RESOURCE
class SessionRootHandler(SessionsAPIHandler):
"""A Session Root API handler."""
@web.authenticated
@authorized
async def get(self):
"""Get a list of running sessions."""
sm = self.session_manager
sessions = await ensure_async(sm.list_sessions())
self.finish(json.dumps(sessions, default=json_default))
@web.authenticated
@authorized
async def post(self):
"""Create a new session."""
# (unless a session already exists for the named session)
sm = self.session_manager
model = self.get_json_body()
if model is None:
raise web.HTTPError(400, "No JSON data provided")
if "notebook" in model:
self.log.warning("Sessions API changed, see updated swagger docs")
model["type"] = "notebook"
if "name" in model["notebook"]:
model["path"] = model["notebook"]["name"]
elif "path" in model["notebook"]:
model["path"] = model["notebook"]["path"]
try:
# There is a high chance here that `path` is not a path but
# a unique session id
path = model["path"]
except KeyError as e:
raise web.HTTPError(400, "Missing field in JSON data: path") from e
try:
mtype = model["type"]
except KeyError as e:
raise web.HTTPError(400, "Missing field in JSON data: type") from e
name = model.get("name", None)
kernel = model.get("kernel", {})
kernel_name = kernel.get("name", None)
kernel_id = kernel.get("id", None)
if not kernel_id and not kernel_name:
self.log.debug("No kernel specified, using default kernel")
kernel_name = None
exists = await ensure_async(sm.session_exists(path=path))
if exists:
s_model = await sm.get_session(path=path)
else:
try:
s_model = await sm.create_session(
path=path,
kernel_name=kernel_name,
kernel_id=kernel_id,
name=name,
type=mtype,
)
except NoSuchKernel:
msg = (
"The '%s' kernel is not available. Please pick another "
"suitable kernel instead, or install that kernel." % kernel_name
)
status_msg = "%s not found" % kernel_name
self.log.warning("Kernel not found: %s" % kernel_name)
self.set_status(501)
self.finish(json.dumps({"message": msg, "short_message": status_msg}))
return
except Exception as e:
raise web.HTTPError(500, str(e)) from e
location = url_path_join(self.base_url, "api", "sessions", s_model["id"])
self.set_header("Location", location)
self.set_status(201)
self.finish(json.dumps(s_model, default=json_default))
class SessionHandler(SessionsAPIHandler):
"""A handler for a single session."""
@web.authenticated
@authorized
async def get(self, session_id):
"""Get the JSON model for a single session."""
sm = self.session_manager
model = await sm.get_session(session_id=session_id)
self.finish(json.dumps(model, default=json_default))
@web.authenticated
@authorized
async def patch(self, session_id):
"""Patch updates sessions:
- path updates session to track renamed paths
- kernel.name starts a new kernel with a given kernelspec
"""
sm = self.session_manager
km = self.kernel_manager
model = self.get_json_body()
if model is None:
raise web.HTTPError(400, "No JSON data provided")
# get the previous session model
before = await sm.get_session(session_id=session_id)
changes = {}
if "notebook" in model and "path" in model["notebook"]:
self.log.warning("Sessions API changed, see updated swagger docs")
model["path"] = model["notebook"]["path"]
model["type"] = "notebook"
if "path" in model:
changes["path"] = model["path"]
if "name" in model:
changes["name"] = model["name"]
if "type" in model:
changes["type"] = model["type"]
if "kernel" in model:
# Kernel id takes precedence over name.
if model["kernel"].get("id") is not None:
kernel_id = model["kernel"]["id"]
if kernel_id not in km:
raise web.HTTPError(400, "No such kernel: %s" % kernel_id)
changes["kernel_id"] = kernel_id
elif model["kernel"].get("name") is not None:
kernel_name = model["kernel"]["name"]
kernel_id = await sm.start_kernel_for_session(
session_id,
kernel_name=kernel_name,
name=before["name"],
path=before["path"],
type=before["type"],
)
changes["kernel_id"] = kernel_id
await sm.update_session(session_id, **changes)
s_model = await sm.get_session(session_id=session_id)
if s_model["kernel"]["id"] != before["kernel"]["id"]:
# kernel_id changed because we got a new kernel
# shutdown the old one
fut = asyncio.ensure_future(ensure_async(km.shutdown_kernel(before["kernel"]["id"])))
# If we are not using pending kernels, wait for the kernel to shut down
if not getattr(km, "use_pending_kernels", None):
await fut
self.finish(json.dumps(s_model, default=json_default))
@web.authenticated
@authorized
async def delete(self, session_id):
"""Delete the session with given session_id."""
sm = self.session_manager
try:
await sm.delete_session(session_id)
except KeyError as e:
# the kernel was deleted but the session wasn't!
raise web.HTTPError(410, "Kernel deleted before session") from e
self.set_status(204)
self.finish()
# -----------------------------------------------------------------------------
# URL to handler mappings
# -----------------------------------------------------------------------------
_session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
default_handlers = [
(r"/api/sessions/%s" % _session_id_regex, SessionHandler),
(r"/api/sessions", SessionRootHandler),
]
|