handlers.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. """Tornado handlers for the sessions web service.
  2. Preliminary documentation at https://github.com/ipython/ipython/wiki/IPEP-16%3A-Notebook-multi-directory-dashboard-and-URL-mapping#sessions-api
  3. """
  4. # Copyright (c) Jupyter Development Team.
  5. # Distributed under the terms of the Modified BSD License.
  6. import json
  7. import os
  8. from tornado import gen, web
  9. from ...base.handlers import APIHandler
  10. from jupyter_client.jsonutil import date_default
  11. from notebook.utils import url_path_join
  12. from jupyter_client.kernelspec import NoSuchKernel
  13. class SessionRootHandler(APIHandler):
  14. @web.authenticated
  15. @gen.coroutine
  16. def get(self):
  17. # Return a list of running sessions
  18. sm = self.session_manager
  19. sessions = yield gen.maybe_future(sm.list_sessions())
  20. self.finish(json.dumps(sessions, default=date_default))
  21. @web.authenticated
  22. @gen.coroutine
  23. def post(self):
  24. # Creates a new session
  25. #(unless a session already exists for the named session)
  26. sm = self.session_manager
  27. model = self.get_json_body()
  28. if model is None:
  29. raise web.HTTPError(400, "No JSON data provided")
  30. if 'notebook' in model and 'path' in model['notebook']:
  31. self.log.warning('Sessions API changed, see updated swagger docs')
  32. model['path'] = model['notebook']['path']
  33. model['type'] = 'notebook'
  34. try:
  35. path = model['path']
  36. except KeyError:
  37. raise web.HTTPError(400, "Missing field in JSON data: path")
  38. try:
  39. mtype = model['type']
  40. except KeyError:
  41. raise web.HTTPError(400, "Missing field in JSON data: type")
  42. name = model.get('name', None)
  43. kernel = model.get('kernel', {})
  44. kernel_name = kernel.get('name', None)
  45. kernel_id = kernel.get('id', None)
  46. if not kernel_id and not kernel_name:
  47. self.log.debug("No kernel specified, using default kernel")
  48. kernel_name = None
  49. exists = yield gen.maybe_future(sm.session_exists(path=path))
  50. if exists:
  51. model = yield gen.maybe_future(sm.get_session(path=path))
  52. else:
  53. try:
  54. model = yield gen.maybe_future(
  55. sm.create_session(path=path, kernel_name=kernel_name,
  56. kernel_id=kernel_id, name=name,
  57. type=mtype))
  58. except NoSuchKernel:
  59. msg = ("The '%s' kernel is not available. Please pick another "
  60. "suitable kernel instead, or install that kernel." % kernel_name)
  61. status_msg = '%s not found' % kernel_name
  62. self.log.warning('Kernel not found: %s' % kernel_name)
  63. self.set_status(501)
  64. self.finish(json.dumps(dict(message=msg, short_message=status_msg)))
  65. return
  66. location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
  67. self.set_header('Location', location)
  68. self.set_status(201)
  69. self.finish(json.dumps(model, default=date_default))
  70. class SessionHandler(APIHandler):
  71. @web.authenticated
  72. @gen.coroutine
  73. def get(self, session_id):
  74. # Returns the JSON model for a single session
  75. sm = self.session_manager
  76. model = yield gen.maybe_future(sm.get_session(session_id=session_id))
  77. self.finish(json.dumps(model, default=date_default))
  78. @web.authenticated
  79. @gen.coroutine
  80. def patch(self, session_id):
  81. """Patch updates sessions:
  82. - path updates session to track renamed paths
  83. - kernel.name starts a new kernel with a given kernelspec
  84. """
  85. sm = self.session_manager
  86. km = self.kernel_manager
  87. model = self.get_json_body()
  88. if model is None:
  89. raise web.HTTPError(400, "No JSON data provided")
  90. # get the previous session model
  91. before = yield gen.maybe_future(sm.get_session(session_id=session_id))
  92. changes = {}
  93. if 'notebook' in model and 'path' in model['notebook']:
  94. self.log.warning('Sessions API changed, see updated swagger docs')
  95. model['path'] = model['notebook']['path']
  96. model['type'] = 'notebook'
  97. if 'path' in model:
  98. changes['path'] = model['path']
  99. if 'name' in model:
  100. changes['name'] = model['name']
  101. if 'type' in model:
  102. changes['type'] = model['type']
  103. if 'kernel' in model:
  104. # Kernel id takes precedence over name.
  105. if model['kernel'].get('id') is not None:
  106. kernel_id = model['kernel']['id']
  107. if kernel_id not in km:
  108. raise web.HTTPError(400, "No such kernel: %s" % kernel_id)
  109. changes['kernel_id'] = kernel_id
  110. elif model['kernel'].get('name') is not None:
  111. kernel_name = model['kernel']['name']
  112. kernel_id = yield sm.start_kernel_for_session(
  113. session_id, kernel_name=kernel_name, name=before['name'],
  114. path=before['path'], type=before['type'])
  115. changes['kernel_id'] = kernel_id
  116. yield gen.maybe_future(sm.update_session(session_id, **changes))
  117. model = yield gen.maybe_future(sm.get_session(session_id=session_id))
  118. if model['kernel']['id'] != before['kernel']['id']:
  119. # kernel_id changed because we got a new kernel
  120. # shutdown the old one
  121. yield gen.maybe_future(
  122. km.shutdown_kernel(before['kernel']['id'])
  123. )
  124. self.finish(json.dumps(model, default=date_default))
  125. @web.authenticated
  126. @gen.coroutine
  127. def delete(self, session_id):
  128. # Deletes the session with given session_id
  129. sm = self.session_manager
  130. try:
  131. yield gen.maybe_future(sm.delete_session(session_id))
  132. except KeyError:
  133. # the kernel was deleted but the session wasn't!
  134. raise web.HTTPError(410, "Kernel deleted before session")
  135. self.set_status(204)
  136. self.finish()
  137. #-----------------------------------------------------------------------------
  138. # URL to handler mappings
  139. #-----------------------------------------------------------------------------
  140. _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
  141. default_handlers = [
  142. (r"/api/sessions/%s" % _session_id_regex, SessionHandler),
  143. (r"/api/sessions", SessionRootHandler)
  144. ]