api_handlers.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. """API handlers for terminals."""
  2. from __future__ import annotations
  3. import json
  4. from pathlib import Path
  5. from typing import Any
  6. from jupyter_server.auth.decorator import authorized
  7. from jupyter_server.base.handlers import APIHandler
  8. from tornado import web
  9. from .base import TerminalsMixin
  10. AUTH_RESOURCE = "terminals"
  11. class TerminalAPIHandler(APIHandler):
  12. """The base terminal handler."""
  13. auth_resource = AUTH_RESOURCE
  14. class TerminalRootHandler(TerminalsMixin, TerminalAPIHandler):
  15. """The root termanal API handler."""
  16. @web.authenticated
  17. @authorized
  18. def get(self) -> None:
  19. """Get the list of terminals."""
  20. models = self.terminal_manager.list()
  21. self.finish(json.dumps(models))
  22. @web.authenticated
  23. @authorized
  24. def post(self) -> None:
  25. """POST /terminals creates a new terminal and redirects to it"""
  26. data = self.get_json_body() or {}
  27. # if cwd is a relative path, it should be relative to the root_dir,
  28. # but if we pass it as relative, it will we be considered as relative to
  29. # the path jupyter_server was started in
  30. if "cwd" in data:
  31. cwd: Path | None = Path(data["cwd"])
  32. assert cwd is not None
  33. if not cwd.resolve().exists():
  34. cwd = Path(self.settings["server_root_dir"]).expanduser() / cwd
  35. if not cwd.resolve().exists():
  36. cwd = None
  37. if cwd is None:
  38. server_root_dir = self.settings["server_root_dir"]
  39. self.log.debug(
  40. "Failed to find requested terminal cwd: %s\n"
  41. " It was not found within the server root neither: %s.",
  42. data.get("cwd"),
  43. server_root_dir,
  44. )
  45. del data["cwd"]
  46. else:
  47. self.log.debug("Opening terminal in: %s", cwd.resolve())
  48. data["cwd"] = str(cwd.resolve())
  49. model = self.terminal_manager.create(**data)
  50. self.finish(json.dumps(model))
  51. class TerminalHandler(TerminalsMixin, TerminalAPIHandler):
  52. """A handler for a specific terminal."""
  53. SUPPORTED_METHODS = ("GET", "DELETE", "OPTIONS") # type:ignore[assignment]
  54. @web.authenticated
  55. @authorized
  56. def get(self, name: str) -> None:
  57. """Get a terminal by name."""
  58. model = self.terminal_manager.get(name)
  59. self.finish(json.dumps(model))
  60. @web.authenticated
  61. @authorized
  62. async def delete(self, name: str) -> None:
  63. """Remove a terminal by name."""
  64. await self.terminal_manager.terminate(name, force=True)
  65. self.set_status(204)
  66. self.finish()
  67. default_handlers: list[tuple[str, type[Any]]] = [
  68. (r"/api/terminals", TerminalRootHandler),
  69. (r"/api/terminals/(\w+)", TerminalHandler),
  70. ]