Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion notebook/terminal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ipython_genutils.py3compat import which
from notebook.utils import url_path_join as ujoin
from .terminalmanager import TerminalManager
from .handlers import TerminalHandler, TermSocket
from .handlers import TerminalHandler, TermSocket, NewTerminalHandler
from . import api_handlers


Expand Down Expand Up @@ -45,6 +45,7 @@ def initialize(nb_app):
(ujoin(base_url, r"/terminals/(\w+)"), TerminalHandler),
(ujoin(base_url, r"/terminals/websocket/(\w+)"), TermSocket,
{'term_manager': terminal_manager}),
(ujoin(base_url, r"/terminals/new/(\w+)"), NewTerminalHandler),
(ujoin(base_url, r"/api/terminals"), api_handlers.TerminalRootHandler),
(ujoin(base_url, r"/api/terminals/(\w+)"), api_handlers.TerminalHandler),
]
Expand Down
16 changes: 16 additions & 0 deletions notebook/terminal/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

import json
from tornado import web
import terminado
from notebook._tz import utcnow
Expand All @@ -18,6 +19,21 @@ def get(self, term_name):
ws_path="terminals/websocket/%s" % term_name))


class NewTerminalHandler(IPythonHandler):
"""Renders a new terminal interface using the named argument."""
@web.authenticated
def get(self, term_name):
new_path = self.request.path.replace("new/{}".format(term_name), term_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the term_name isn't set (i.e. they visit /new), can we have it create a new named one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I added a commit supporting this. This does preclude someone from creating their own named terminal of 'new' (via GET /terminals/new/new), but that's probably okay.

if term_name in self.terminal_manager.terminals:
self.set_header('Location', new_path)
self.set_status(302)
self.finish(json.dumps(self.terminal_manager.get_terminal_model(term_name)))
return

self.terminal_manager.create_with_name(term_name)
self.redirect(new_path)


class TermSocket(WebSocketMixin, IPythonHandler, terminado.TermSocket):

def origin_check(self):
Expand Down
10 changes: 10 additions & 0 deletions notebook/terminal/terminalmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ def __init__(self, *args, **kwargs):
def create(self):
"""Create a new terminal."""
name, term = self.new_named_terminal()
return self._finish_create(name, term)

def create_with_name(self, name):
"""Create a new terminal."""
if name in self.terminals:
raise web.HTTPError(409, "A terminal with name '{}' already exists.".format(name))
term = self.get_terminal(name)
return self._finish_create(name, term)

def _finish_create(self, name, term):
# Monkey-patch last-activity, similar to kernels. Should we need
# more functionality per terminal, we can look into possible sub-
# classing or containment then.
Expand Down
31 changes: 30 additions & 1 deletion notebook/terminal/tests/test_terminals_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def tearDown(self):
self.term_api.shutdown(k['name'])

def test_no_terminals(self):
# Make sure there are no terminals running at the start
# Make sure there are no terminals are running at the start
terminals = self.term_api.list().json()
self.assertEqual(terminals, [])

Expand All @@ -65,6 +65,35 @@ def test_create_terminal(self):
self.assertEqual(r.status_code, 200)
self.assertIsInstance(term1, dict)

def test_create_terminal_via_get(self):
# Test creation of terminal via GET against terminals/new/<name>
r = self.term_api._req('GET', 'terminals/new/foo')
self.assertEqual(r.status_code, 200)

r = self.term_api.get('foo')
foo_term = r.json()
self.assertEqual(r.status_code, 200)
self.assertIsInstance(foo_term, dict)
self.assertEqual(foo_term['name'], 'foo')

# hit the same endpoint a second time and ensure 302 with Location is returned
r = self.term_api._req('GET', 'terminals/new/foo')
# Access the "interesting" response from the history
self.assertEqual(len(r.history), 1)
r = r.history[0]
foo_term = r.json()
self.assertEqual(r.status_code, 302)
self.assertEqual(r.headers['Location'], self.url_prefix + "terminals/foo")
self.assertIsInstance(foo_term, dict)
self.assertEqual(foo_term['name'], 'foo')

r = self.term_api.shutdown('foo')
self.assertEqual(r.status_code, 204)

# Make sure there are no terminals are running
terminals = self.term_api.list().json()
self.assertEqual(terminals, [])

def test_terminal_root_handler(self):
# POST request
r = self.term_api.start()
Expand Down