Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring back support for jupyter-server 1 #1498

Merged
merged 4 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions .github/workflows/ui-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8]
node-version: [18.x]
jupyter-server-version: ["1.24.0", "2.14.2"]
fail-fast: false

steps:
Expand All @@ -26,6 +25,7 @@ jobs:
python -m pip install -r requirements-visual-test.txt
python -m pip install jupyterlab_miami_nights --no-deps
python -m pip install ".[test]"
python -m pip install jupyter-server==${{ matrix.jupyter-server-version }}
jlpm
jlpm build
jupyter labextension develop . --overwrite
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ classifiers = [
dependencies = [
"jupyter_client>=7.4.4,<9",
"jupyter_core>=4.11.0",
"jupyter_server>=2.0.0,<3",
"jupyter_server>=1.18,<3",
"jupyterlab_server>=2.3.0,<3",
"nbclient>=0.4.0",
"nbconvert>=6.4.5,<8",
Expand Down
1 change: 1 addition & 0 deletions requirements-visual-test.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
bokeh
bokeh_sampledata
bqplot
ipympl==0.9.2
jupyterlab~=4.0
Expand Down
216 changes: 123 additions & 93 deletions voila/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@

from .tornado.contentshandler import VoilaContentsHandler

from .voila_identity_provider import VoilaLoginHandler

from urllib.parse import urljoin
from urllib.request import pathname2url

Expand All @@ -33,23 +31,38 @@
from jupyter_core.paths import jupyter_config_path, jupyter_path
from jupyter_server.base.handlers import FileFindHandler, path_regex
from jupyter_server.config_manager import recursive_update
from jupyter_server.services.config.manager import ConfigManager
from jupyter_server.services.contents.largefilemanager import LargeFileManager
from jupyter_server.services.kernels.handlers import KernelHandler
from jupyter_server.services.kernels.websocket import KernelWebsocketHandler
from jupyter_server.auth.authorizer import AllowAllAuthorizer, Authorizer
from jupyter_server.auth.identity import PasswordIdentityProvider
from jupyter_server import DEFAULT_TEMPLATE_PATH_LIST, DEFAULT_STATIC_FILES_PATH
from jupyter_server.services.kernels.connection.base import (
BaseKernelWebsocketConnection,
)
from jupyter_server.services.kernels.connection.channels import (
ZMQChannelsWebsocketConnection,
)
from jupyter_server.auth.identity import (
IdentityProvider,
)
from jupyter_server.utils import url_path_join

try:
JUPYTER_SERVER_2 = True

from jupyter_server.services.kernels.websocket import KernelWebsocketHandler
from jupyter_server.auth.authorizer import AllowAllAuthorizer, Authorizer
from jupyter_server.auth.identity import PasswordIdentityProvider
from jupyter_server import DEFAULT_TEMPLATE_PATH_LIST, DEFAULT_STATIC_FILES_PATH
from jupyter_server.services.kernels.connection.base import (
BaseKernelWebsocketConnection,
)
from jupyter_server.services.kernels.connection.channels import (
ZMQChannelsWebsocketConnection,
)
from jupyter_server.auth.identity import (
IdentityProvider,
)
from jupyter_server.utils import url_path_join
from jupyter_core.utils import run_sync
from jupyter_server.services.config.manager import ConfigManager

from jupyterlab_server.themes_handler import ThemesHandler
from .voila_identity_provider import VoilaLoginHandler
except ImportError:
JUPYTER_SERVER_2 = False

from jupyter_server.services.kernels.handlers import ZMQChannelsHandler
from jupyter_server.utils import url_path_join, run_sync
from jupyter_server.services.config import ConfigManager

from jupyter_core.utils import run_sync

from jupyterlab_server.themes_handler import ThemesHandler
Expand Down Expand Up @@ -122,10 +135,6 @@ class Voila(Application):
'Show left and right margins for the "lab" template, this gives a "classic" template look'
),
),
"token": (
{"Voila": {"auto_token": True}},
_(""),
),
"classic-tree": (
{
"VoilaConfiguration": {"classic_tree": True},
Expand All @@ -134,6 +143,15 @@ class Voila(Application):
),
}

if JUPYTER_SERVER_2:
flags = {
**flags,
"token": (
{"Voila": {"auto_token": True}},
_(""),
),
}

description = Unicode(
"""voila [OPTIONS] NOTEBOOK_FILENAME

Expand Down Expand Up @@ -167,7 +185,6 @@ class Voila(Application):
"port": "Voila.port",
"static": "Voila.static_root",
"server_url": "Voila.server_url",
"token": "Voila.token",
"pool_size": "VoilaConfiguration.default_pool_size",
"show_tracebacks": "VoilaConfiguration.show_tracebacks",
"preheat_kernel": "VoilaConfiguration.preheat_kernel",
Expand All @@ -177,6 +194,8 @@ class Voila(Application):
"classic_tree": "VoilaConfiguration.classic_tree",
"kernel_spec_manager_class": "VoilaConfiguration.kernel_spec_manager_class",
}
if JUPYTER_SERVER_2:
aliases = {**aliases, "token": "Voila.token"}
classes = [VoilaConfiguration, VoilaExecutor, VoilaExporter]
connection_dir_root = Unicode(
config=True,
Expand Down Expand Up @@ -330,50 +349,51 @@ def hook(req: tornado.web.RequestHandler,
),
)

cookie_secret = Bytes(
b"",
config=True,
help="""The random bytes used to secure cookies.
By default this is a new random number every time you start the server.
Set it to a value in a config file to enable logins to persist across server sessions.

Note: Cookie secrets should be kept private, do not share config files with
cookie_secret stored in plaintext (you can read the value from a file).
""",
)
if JUPYTER_SERVER_2:
cookie_secret = Bytes(
b"",
config=True,
help="""The random bytes used to secure cookies.
By default this is a new random number every time you start the server.
Set it to a value in a config file to enable logins to persist across server sessions.

Note: Cookie secrets should be kept private, do not share config files with
cookie_secret stored in plaintext (you can read the value from a file).
""",
)

token = Unicode(None, help="""Token for identity provider """, allow_none=True).tag(
config=True
)
token = Unicode(
None, help="""Token for identity provider """, allow_none=True
).tag(config=True)

auto_token = Bool(
False, help="""Generate token automatically """, allow_none=True
).tag(config=True)
auto_token = Bool(
False, help="""Generate token automatically """, allow_none=True
).tag(config=True)

@default("cookie_secret")
def _default_cookie_secret(self):
return os.urandom(32)
@default("cookie_secret")
def _default_cookie_secret(self):
return os.urandom(32)

authorizer_class = Type(
default_value=AllowAllAuthorizer,
klass=Authorizer,
config=True,
help=_("The authorizer class to use."),
)
authorizer_class = Type(
default_value=AllowAllAuthorizer,
klass=Authorizer,
config=True,
help=_("The authorizer class to use."),
)

identity_provider_class = Type(
default_value=PasswordIdentityProvider,
klass=IdentityProvider,
config=True,
help=_("The identity provider class to use."),
)
identity_provider_class = Type(
default_value=PasswordIdentityProvider,
klass=IdentityProvider,
config=True,
help=_("The identity provider class to use."),
)

kernel_websocket_connection_class = Type(
default_value=ZMQChannelsWebsocketConnection,
klass=BaseKernelWebsocketConnection,
config=True,
help=_("The kernel websocket connection class to use."),
)
kernel_websocket_connection_class = Type(
default_value=ZMQChannelsWebsocketConnection,
klass=BaseKernelWebsocketConnection,
config=True,
help=_("The kernel websocket connection class to use."),
)

@property
def display_url(self):
Expand All @@ -385,7 +405,7 @@ def display_url(self):
ip = "%s" % socket.gethostname() if self.ip in ("", "0.0.0.0") else self.ip
url = self._url(ip)
# TODO: do we want to have the token?
if self.identity_provider.token:
if JUPYTER_SERVER_2 and self.identity_provider.token:
# Don't log full token if it came from config
token = (
self.identity_provider.token
Expand Down Expand Up @@ -542,7 +562,8 @@ def setup_template_dirs(self):
self.static_paths = collect_static_paths(
["voila", "nbconvert"], template_name
)
self.static_paths.append(DEFAULT_STATIC_FILES_PATH)
if JUPYTER_SERVER_2:
self.static_paths.append(DEFAULT_STATIC_FILES_PATH)
conf_paths = [os.path.join(d, "conf.json") for d in self.template_paths]
for p in conf_paths:
# see if config file exists
Expand Down Expand Up @@ -617,35 +638,36 @@ def init_settings(self) -> Dict:
extensions=["jinja2.ext.i18n"],
**jenv_opt,
)
server_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(DEFAULT_TEMPLATE_PATH_LIST),
extensions=["jinja2.ext.i18n"],
**jenv_opt,
)

nbui = gettext.translation(
"nbui", localedir=os.path.join(ROOT, "i18n"), fallback=True
)
env.install_gettext_translations(nbui, newstyle=False)
server_env.install_gettext_translations(nbui, newstyle=False)

identity_provider_kwargs = {
"parent": self,
"log": self.log,
"login_handler_class": VoilaLoginHandler,
}
if self.token is None and not self.auto_token:
identity_provider_kwargs["token"] = ""
elif self.token is not None:
identity_provider_kwargs["token"] = self.token

self.identity_provider = self.identity_provider_class(
**identity_provider_kwargs
)
if JUPYTER_SERVER_2:
server_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(DEFAULT_TEMPLATE_PATH_LIST),
extensions=["jinja2.ext.i18n"],
**jenv_opt,
)
server_env.install_gettext_translations(nbui, newstyle=False)

identity_provider_kwargs = {
"parent": self,
"log": self.log,
"login_handler_class": VoilaLoginHandler,
}
if self.token is None and not self.auto_token:
identity_provider_kwargs["token"] = ""
elif self.token is not None:
identity_provider_kwargs["token"] = self.token

self.identity_provider = self.identity_provider_class(
**identity_provider_kwargs
)

self.authorizer = self.authorizer_class(
parent=self, log=self.log, identity_provider=self.identity_provider
)
self.authorizer = self.authorizer_class(
parent=self, log=self.log, identity_provider=self.identity_provider
)

settings = dict(
base_url=self.base_url,
Expand All @@ -655,20 +677,25 @@ def init_settings(self) -> Dict:
allow_remote_access=True,
autoreload=self.autoreload,
voila_jinja2_env=env,
jinja2_env=server_env,
jinja2_env=server_env if JUPYTER_SERVER_2 else env,
server_root_dir="/",
contents_manager=self.contents_manager,
config_manager=self.config_manager,
cookie_secret=self.cookie_secret,
authorizer=self.authorizer,
identity_provider=self.identity_provider,
kernel_websocket_connection_class=self.kernel_websocket_connection_class,
login_url=url_path_join(self.base_url, "/login"),
mathjax_config=self.mathjax_config,
mathjax_url=self.mathjax_url,
)
settings[self.name] = self # Why???

if JUPYTER_SERVER_2:
settings = {
**settings,
"cookie_secret": self.cookie_secret,
"authorizer": self.authorizer,
"identity_provider": self.identity_provider,
"kernel_websocket_connection_class": self.kernel_websocket_connection_class,
"login_url": url_path_join(self.base_url, "/login"),
}

return settings

def init_handlers(self) -> List:
Expand All @@ -687,7 +714,7 @@ def init_handlers(self) -> List:
url_path_join(
self.server_url, r"/api/kernels/%s/channels" % _kernel_id_regex
),
KernelWebsocketHandler,
KernelWebsocketHandler if JUPYTER_SERVER_2 else ZMQChannelsHandler,
),
(
url_path_join(self.server_url, r"/voila/templates/(.*)"),
Expand All @@ -714,7 +741,10 @@ def init_handlers(self) -> List:
),
]
)
handlers.extend(self.identity_provider.get_handlers())

if JUPYTER_SERVER_2:
handlers.extend(self.identity_provider.get_handlers())

if self.voila_configuration.preheat_kernel:
handlers.append(
(
Expand Down
Loading