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))