File size: 4,952 Bytes
d1ceb73 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
"""An extension handler."""
from __future__ import annotations
from logging import Logger
from typing import TYPE_CHECKING, Any, cast
from jinja2.exceptions import TemplateNotFound
from jupyter_server.base.handlers import FileFindHandler
if TYPE_CHECKING:
from traitlets.config import Config
from jupyter_server.extension.application import ExtensionApp
from jupyter_server.serverapp import ServerApp
class ExtensionHandlerJinjaMixin:
"""Mixin class for ExtensionApp handlers that use jinja templating for
template rendering.
"""
def get_template(self, name: str) -> str:
"""Return the jinja template object for a given name"""
try:
env = f"{self.name}_jinja2_env" # type:ignore[attr-defined]
return cast(str, self.settings[env].get_template(name)) # type:ignore[attr-defined]
except TemplateNotFound:
return cast(str, super().get_template(name)) # type:ignore[misc]
class ExtensionHandlerMixin:
"""Base class for Jupyter server extension handlers.
Subclasses can serve static files behind a namespaced
endpoint: "<base_url>/static/<name>/"
This allows multiple extensions to serve static files under
their own namespace and avoid intercepting requests for
other extensions.
"""
settings: dict[str, Any]
def initialize(self, name: str, *args: Any, **kwargs: Any) -> None:
self.name = name
try:
super().initialize(*args, **kwargs) # type:ignore[misc]
except TypeError:
pass
@property
def extensionapp(self) -> ExtensionApp:
return cast("ExtensionApp", self.settings[self.name])
@property
def serverapp(self) -> ServerApp:
key = "serverapp"
return cast("ServerApp", self.settings[key])
@property
def log(self) -> Logger:
if not hasattr(self, "name"):
return cast(Logger, super().log) # type:ignore[misc]
# Attempt to pull the ExtensionApp's log, otherwise fall back to ServerApp.
try:
return cast(Logger, self.extensionapp.log)
except AttributeError:
return cast(Logger, self.serverapp.log)
@property
def config(self) -> Config:
return cast("Config", self.settings[f"{self.name}_config"])
@property
def server_config(self) -> Config:
return cast("Config", self.settings["config"])
@property
def base_url(self) -> str:
return cast(str, self.settings.get("base_url", "/"))
@property
def static_url_prefix(self) -> str:
return self.extensionapp.static_url_prefix
@property
def static_path(self) -> str:
return cast(str, self.settings[f"{self.name}_static_paths"])
def static_url(self, path: str, include_host: bool | None = None, **kwargs: Any) -> str:
"""Returns a static URL for the given relative static file path.
This method requires you set the ``{name}_static_path``
setting in your extension (which specifies the root directory
of your static files).
This method returns a versioned url (by default appending
``?v=<signature>``), which allows the static files to be
cached indefinitely. This can be disabled by passing
``include_version=False`` (in the default implementation;
other static file implementations are not required to support
this, but they may support other options).
By default this method returns URLs relative to the current
host, but if ``include_host`` is true the URL returned will be
absolute. If this handler has an ``include_host`` attribute,
that value will be used as the default for all `static_url`
calls that do not pass ``include_host`` as a keyword argument.
"""
key = f"{self.name}_static_paths"
try:
self.require_setting(key, "static_url") # type:ignore[attr-defined]
except Exception as e:
if key in self.settings:
msg = (
"This extension doesn't have any static paths listed. Check that the "
"extension's `static_paths` trait is set."
)
raise Exception(msg) from None
else:
raise e
get_url = self.settings.get("static_handler_class", FileFindHandler).make_static_url
if include_host is None:
include_host = getattr(self, "include_host", False)
base = ""
if include_host:
base = self.request.protocol + "://" + self.request.host # type:ignore[attr-defined]
# Hijack settings dict to send extension templates to extension
# static directory.
settings = {
"static_path": self.static_path,
"static_url_prefix": self.static_url_prefix,
}
return base + cast(str, get_url(settings, path, **kwargs))
|