test_sessions_api.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. """Test the sessions web service API."""
  2. import errno
  3. from functools import partial
  4. import io
  5. import os
  6. import json
  7. import requests
  8. import shutil
  9. import time
  10. pjoin = os.path.join
  11. from notebook.utils import url_path_join
  12. from notebook.tests.launchnotebook import NotebookTestBase, assert_http_error
  13. from nbformat.v4 import new_notebook
  14. from nbformat import write
  15. class SessionAPI(object):
  16. """Wrapper for notebook API calls."""
  17. def __init__(self, request):
  18. self.request = request
  19. def _req(self, verb, path, body=None):
  20. response = self.request(verb,
  21. url_path_join('api/sessions', path), data=body)
  22. if 400 <= response.status_code < 600:
  23. try:
  24. response.reason = response.json()['message']
  25. except:
  26. pass
  27. response.raise_for_status()
  28. return response
  29. def list(self):
  30. return self._req('GET', '')
  31. def get(self, id):
  32. return self._req('GET', id)
  33. def create(self, path, type='notebook', kernel_name='python', kernel_id=None):
  34. body = json.dumps({'path': path,
  35. 'type': type,
  36. 'kernel': {'name': kernel_name,
  37. 'id': kernel_id}})
  38. return self._req('POST', '', body)
  39. def create_deprecated(self, path):
  40. body = json.dumps({'notebook': {'path': path},
  41. 'kernel': {'name': 'python',
  42. 'id': 'foo'}})
  43. return self._req('POST', '', body)
  44. def modify_path(self, id, path):
  45. body = json.dumps({'path': path})
  46. return self._req('PATCH', id, body)
  47. def modify_path_deprecated(self, id, path):
  48. body = json.dumps({'notebook': {'path': path}})
  49. return self._req('PATCH', id, body)
  50. def modify_type(self, id, type):
  51. body = json.dumps({'type': type})
  52. return self._req('PATCH', id, body)
  53. def modify_kernel_name(self, id, kernel_name):
  54. body = json.dumps({'kernel': {'name': kernel_name}})
  55. return self._req('PATCH', id, body)
  56. def modify_kernel_id(self, id, kernel_id):
  57. # Also send a dummy name to show that id takes precedence.
  58. body = json.dumps({'kernel': {'id': kernel_id, 'name': 'foo'}})
  59. return self._req('PATCH', id, body)
  60. def delete(self, id):
  61. return self._req('DELETE', id)
  62. class SessionAPITest(NotebookTestBase):
  63. """Test the sessions web service API"""
  64. def setUp(self):
  65. nbdir = self.notebook_dir
  66. subdir = pjoin(nbdir, 'foo')
  67. try:
  68. os.mkdir(subdir)
  69. except OSError as e:
  70. # Deleting the folder in an earlier test may have failed
  71. if e.errno != errno.EEXIST:
  72. raise
  73. self.addCleanup(partial(shutil.rmtree, subdir, ignore_errors=True))
  74. with io.open(pjoin(subdir, 'nb1.ipynb'), 'w', encoding='utf-8') as f:
  75. nb = new_notebook()
  76. write(nb, f, version=4)
  77. self.sess_api = SessionAPI(self.request)
  78. @self.addCleanup
  79. def cleanup_sessions():
  80. for session in self.sess_api.list().json():
  81. self.sess_api.delete(session['id'])
  82. # This is necessary in some situations on Windows: without it, it
  83. # fails to delete the directory because something is still using
  84. # it. I think there is a brief period after the kernel terminates
  85. # where Windows still treats its working directory as in use. On my
  86. # Windows VM, 0.01s is not long enough, but 0.1s appears to work
  87. # reliably. -- TK, 15 December 2014
  88. time.sleep(0.1)
  89. def test_create(self):
  90. sessions = self.sess_api.list().json()
  91. self.assertEqual(len(sessions), 0)
  92. resp = self.sess_api.create('foo/nb1.ipynb')
  93. self.assertEqual(resp.status_code, 201)
  94. newsession = resp.json()
  95. self.assertIn('id', newsession)
  96. self.assertEqual(newsession['path'], 'foo/nb1.ipynb')
  97. self.assertEqual(newsession['type'], 'notebook')
  98. self.assertEqual(resp.headers['Location'], self.url_prefix + 'api/sessions/{0}'.format(newsession['id']))
  99. sessions = self.sess_api.list().json()
  100. self.assertEqual(sessions, [newsession])
  101. # Retrieve it
  102. sid = newsession['id']
  103. got = self.sess_api.get(sid).json()
  104. self.assertEqual(got, newsession)
  105. def test_create_file_session(self):
  106. resp = self.sess_api.create('foo/nb1.py', type='file')
  107. self.assertEqual(resp.status_code, 201)
  108. newsession = resp.json()
  109. self.assertEqual(newsession['path'], 'foo/nb1.py')
  110. self.assertEqual(newsession['type'], 'file')
  111. def test_create_console_session(self):
  112. resp = self.sess_api.create('foo/abc123', type='console')
  113. self.assertEqual(resp.status_code, 201)
  114. newsession = resp.json()
  115. self.assertEqual(newsession['path'], 'foo/abc123')
  116. self.assertEqual(newsession['type'], 'console')
  117. def test_create_deprecated(self):
  118. resp = self.sess_api.create_deprecated('foo/nb1.ipynb')
  119. self.assertEqual(resp.status_code, 201)
  120. newsession = resp.json()
  121. self.assertEqual(newsession['path'], 'foo/nb1.ipynb')
  122. self.assertEqual(newsession['type'], 'notebook')
  123. self.assertEqual(newsession['notebook']['path'], 'foo/nb1.ipynb')
  124. def test_create_with_kernel_id(self):
  125. # create a new kernel
  126. r = self.request('POST', 'api/kernels')
  127. r.raise_for_status()
  128. kernel = r.json()
  129. resp = self.sess_api.create('foo/nb1.ipynb', kernel_id=kernel['id'])
  130. self.assertEqual(resp.status_code, 201)
  131. newsession = resp.json()
  132. self.assertIn('id', newsession)
  133. self.assertEqual(newsession['path'], 'foo/nb1.ipynb')
  134. self.assertEqual(newsession['kernel']['id'], kernel['id'])
  135. self.assertEqual(resp.headers['Location'], self.url_prefix + 'api/sessions/{0}'.format(newsession['id']))
  136. sessions = self.sess_api.list().json()
  137. self.assertEqual(sessions, [newsession])
  138. # Retrieve it
  139. sid = newsession['id']
  140. got = self.sess_api.get(sid).json()
  141. self.assertEqual(got, newsession)
  142. def test_delete(self):
  143. newsession = self.sess_api.create('foo/nb1.ipynb').json()
  144. sid = newsession['id']
  145. resp = self.sess_api.delete(sid)
  146. self.assertEqual(resp.status_code, 204)
  147. sessions = self.sess_api.list().json()
  148. self.assertEqual(sessions, [])
  149. with assert_http_error(404):
  150. self.sess_api.get(sid)
  151. def test_modify_path(self):
  152. newsession = self.sess_api.create('foo/nb1.ipynb').json()
  153. sid = newsession['id']
  154. changed = self.sess_api.modify_path(sid, 'nb2.ipynb').json()
  155. self.assertEqual(changed['id'], sid)
  156. self.assertEqual(changed['path'], 'nb2.ipynb')
  157. def test_modify_path_deprecated(self):
  158. newsession = self.sess_api.create('foo/nb1.ipynb').json()
  159. sid = newsession['id']
  160. changed = self.sess_api.modify_path_deprecated(sid, 'nb2.ipynb').json()
  161. self.assertEqual(changed['id'], sid)
  162. self.assertEqual(changed['notebook']['path'], 'nb2.ipynb')
  163. def test_modify_type(self):
  164. newsession = self.sess_api.create('foo/nb1.ipynb').json()
  165. sid = newsession['id']
  166. changed = self.sess_api.modify_type(sid, 'console').json()
  167. self.assertEqual(changed['id'], sid)
  168. self.assertEqual(changed['type'], 'console')
  169. def test_modify_kernel_name(self):
  170. before = self.sess_api.create('foo/nb1.ipynb').json()
  171. sid = before['id']
  172. after = self.sess_api.modify_kernel_name(sid, before['kernel']['name']).json()
  173. self.assertEqual(after['id'], sid)
  174. self.assertEqual(after['path'], before['path'])
  175. self.assertEqual(after['type'], before['type'])
  176. self.assertNotEqual(after['kernel']['id'], before['kernel']['id'])
  177. # check kernel list, to be sure previous kernel was cleaned up
  178. r = self.request('GET', 'api/kernels')
  179. r.raise_for_status()
  180. kernel_list = r.json()
  181. after['kernel'].pop('last_activity')
  182. [ k.pop('last_activity') for k in kernel_list ]
  183. self.assertEqual(kernel_list, [after['kernel']])
  184. def test_modify_kernel_id(self):
  185. before = self.sess_api.create('foo/nb1.ipynb').json()
  186. sid = before['id']
  187. # create a new kernel
  188. r = self.request('POST', 'api/kernels')
  189. r.raise_for_status()
  190. kernel = r.json()
  191. # Attach our session to the existing kernel
  192. after = self.sess_api.modify_kernel_id(sid, kernel['id']).json()
  193. self.assertEqual(after['id'], sid)
  194. self.assertEqual(after['path'], before['path'])
  195. self.assertEqual(after['type'], before['type'])
  196. self.assertNotEqual(after['kernel']['id'], before['kernel']['id'])
  197. self.assertEqual(after['kernel']['id'], kernel['id'])
  198. # check kernel list, to be sure previous kernel was cleaned up
  199. r = self.request('GET', 'api/kernels')
  200. r.raise_for_status()
  201. kernel_list = r.json()
  202. kernel.pop('last_activity')
  203. [ k.pop('last_activity') for k in kernel_list ]
  204. self.assertEqual(kernel_list, [kernel])