handlers.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. """Tornado handlers for kernel specifications.
  2. Preliminary documentation at https://github.com/ipython/ipython/wiki/IPEP-25%3A-Registry-of-installed-kernels#rest-api
  3. """
  4. # Copyright (c) Jupyter Development Team.
  5. # Distributed under the terms of the Modified BSD License.
  6. from __future__ import annotations
  7. import glob
  8. import json
  9. import os
  10. from typing import Any
  11. pjoin = os.path.join
  12. from jupyter_core.utils import ensure_async
  13. from tornado import web
  14. from jupyter_server.auth.decorator import authorized
  15. from ...base.handlers import APIHandler
  16. from ...utils import url_path_join, url_unescape
  17. AUTH_RESOURCE = "kernelspecs"
  18. def kernelspec_model(handler, name, spec_dict, resource_dir):
  19. """Load a KernelSpec by name and return the REST API model"""
  20. d = {"name": name, "spec": spec_dict, "resources": {}}
  21. # Add resource files if they exist
  22. for resource in ["kernel.js", "kernel.css"]:
  23. if os.path.exists(pjoin(resource_dir, resource)):
  24. d["resources"][resource] = url_path_join(
  25. handler.base_url, "kernelspecs", name, resource
  26. )
  27. for logo_file in glob.glob(pjoin(resource_dir, "logo-*")):
  28. fname = os.path.basename(logo_file)
  29. no_ext, _ = os.path.splitext(fname)
  30. d["resources"][no_ext] = url_path_join(handler.base_url, "kernelspecs", name, fname)
  31. return d
  32. def is_kernelspec_model(spec_dict):
  33. """Returns True if spec_dict is already in proper form. This will occur when using a gateway."""
  34. return (
  35. isinstance(spec_dict, dict)
  36. and "name" in spec_dict
  37. and "spec" in spec_dict
  38. and "resources" in spec_dict
  39. )
  40. class KernelSpecsAPIHandler(APIHandler):
  41. """A kernel spec API handler."""
  42. auth_resource = AUTH_RESOURCE
  43. class MainKernelSpecHandler(KernelSpecsAPIHandler):
  44. """The root kernel spec handler."""
  45. @web.authenticated
  46. @authorized
  47. async def get(self):
  48. """Get the list of kernel specs."""
  49. ksm = self.kernel_spec_manager
  50. km = self.kernel_manager
  51. model: dict[str, Any] = {}
  52. model["default"] = km.default_kernel_name
  53. model["kernelspecs"] = specs = {}
  54. kspecs = await ensure_async(ksm.get_all_specs())
  55. for kernel_name, kernel_info in kspecs.items():
  56. try:
  57. if is_kernelspec_model(kernel_info):
  58. d = kernel_info
  59. else:
  60. d = kernelspec_model(
  61. self,
  62. kernel_name,
  63. kernel_info["spec"],
  64. kernel_info["resource_dir"],
  65. )
  66. except Exception:
  67. self.log.error("Failed to load kernel spec: '%s'", kernel_name, exc_info=True)
  68. continue
  69. specs[kernel_name] = d
  70. self.set_header("Content-Type", "application/json")
  71. self.finish(json.dumps(model))
  72. class KernelSpecHandler(KernelSpecsAPIHandler):
  73. """A handler for an individual kernel spec."""
  74. @web.authenticated
  75. @authorized
  76. async def get(self, kernel_name):
  77. """Get a kernel spec model."""
  78. ksm = self.kernel_spec_manager
  79. kernel_name = url_unescape(kernel_name)
  80. try:
  81. spec = await ensure_async(ksm.get_kernel_spec(kernel_name))
  82. except KeyError as e:
  83. raise web.HTTPError(404, "Kernel spec %s not found" % kernel_name) from e
  84. if is_kernelspec_model(spec):
  85. model = spec
  86. else:
  87. model = kernelspec_model(self, kernel_name, spec.to_dict(), spec.resource_dir)
  88. self.set_header("Content-Type", "application/json")
  89. self.finish(json.dumps(model))
  90. # URL to handler mappings
  91. kernel_name_regex = r"(?P<kernel_name>[\w\.\-%]+)"
  92. default_handlers = [
  93. (r"/api/kernelspecs", MainKernelSpecHandler),
  94. (r"/api/kernelspecs/%s" % kernel_name_regex, KernelSpecHandler),
  95. ]